Welcome to Reg’s documentation¶
Reg is a Python library that provides generic function support to Python. It help you build powerful registration and configuration APIs for your application, library or framework.
Using Reg¶
Introduction¶
Reg implements predicate dispatch and multiple registries:
Predicate dispatch
We all know about dynamic dispatch: when you call a method on an instance it is dispatched to the implementation in its class, and the class is determined from the first argument (
self
). This is known as single dispatch.Reg implements multiple dispatch. This is a generalization of single dispatch: multiple dispatch allows you to dispatch on the class of other arguments besides the first one.
Reg actually implements predicate dispatch, which is a further generalization that allows dispatch on arbitrary properties of arguments, instead of just their class.
The Morepath web framework is built with Reg. It uses Reg’s predicate dispatch system. Its full power can be seen in its view lookup system.
This document explains how to use Reg. Various specific patterns are documented in Patterns.
Multiple registries
Reg supports an advanced application architecture pattern where you have multiple predicate dispatch registries in the same runtime. This means that dispatch can behave differently depending on runtime context. You do this by using dispatch methods that you associate with a class that represents the application context. When you switch the context class, you switch the behavior.
Morepath uses context-based dispatch to support its application composition system, where one application can be mounted into another.
See Context-based dispatch for this advanced application pattern.
Reg is designed with a caching layer that allows it to support these features efficiently.
Example¶
Let’s examine a short example. First we use the reg.dispatch()
decorator to define a function that dispatches based on the
class of its obj
argument:
import reg
@reg.dispatch('obj')
def title(obj):
return "we don't know the title"
We want this function to return the title of its obj
argument.
Now we create a few example classes for which we want to be able to use
the title
fuction we defined above.
class TitledReport(object):
def __init__(self, title):
self.title = title
class LabeledReport(object):
def __init__(self, label):
self.label = label
If we call title
with a TitledReport
instance, we want it to return
its title
attribute:
@title.register(obj=TitledReport)
def titled_report_title(obj):
return obj.title
The title.register
decorator registers the function
titled_report_title
as an implementation of title
when obj
is an instance of TitleReport
.
There is also a more programmatic way to register implementations.
Take for example, the implementation of title
with a LabeledReport
instance, where we want it to return its label
attribute:
def labeled_report_title(obj):
return obj.label
We can register it by explicitely invoking title.register
:
title.register(labeled_report_title, obj=LabeledReport)
Now the generic title
function works on both titled and labeled
objects:
>>> titled = TitledReport('This is a report')
>>> labeled = LabeledReport('This is also a report')
>>> title(titled)
'This is a report'
>>> title(labeled)
'This is also a report'
What is going on and why is this useful at all? We present a worked out example next.
Dispatch functions¶
A Hypothetical CMS¶
Let’s look at how Reg works in the context of a hypothetical content management system (CMS).
This hypothetical CMS has two kinds of content item (we’ll add more later):
- a
Document
which contains some text. - a
Folder
which contains a bunch of content entries, for instanceDocument
instances.
This is the implementation of our CMS:
class Document(object):
def __init__(self, text):
self.text = text
class Folder(object):
def __init__(self, entries):
self.entries = entries
size
methods¶
Now we want to add a feature to our CMS: we want the ability to calculate the size (in bytes) of any content item. The size of the document is defined as the length of its text, and the size of the folder is defined as the sum of the size of everything in it.
If we have control over the implementation of Document
and
Folder
we can implement this feature easily by adding a size
method to both classes:
class Document(object):
def __init__(self, text):
self.text = text
def size(self):
return len(self.text)
class Folder(object):
def __init__(self, entries):
self.entries = entries
def size(self):
return sum([entry.size() for entry in self.entries])
And then we can simply call the .size()
method to get the size:
>>> doc = Document('Hello world!')
>>> doc.size()
12
>>> doc2 = Document('Bye world!')
>>> doc2.size()
10
>>> folder = Folder([doc, doc2])
>>> folder.size()
22
The Folder
size code is generic; it doesn’t care what the entries
inside it are; if they have a size
method that gives the right
result, it will work. If a new content item Image
is defined and
we provide a size
method for this, a Folder
instance that
contains Image
instances will still be able to calculate its
size. Let’s try this:
class Image(object):
def __init__(self, bytes):
self.bytes = bytes
def size(self):
return len(self.bytes)
When we add an Image
instance to the folder, the size of the folder
can still be calculated:
>>> image = Image('abc')
>>> folder.entries.append(image)
>>> folder.size()
25
Cool! So we’re done, right?
Adding size
from outside¶
So far we didn’t need Reg at all. But in a real world CMS we aren’t
always in the position to change the content classes themselves. We
may be dealing with a content management system core where we cannot
control the implementation of Document
and Folder
. Or perhaps
we can, but we want to keep our code modular, in independent
packages. So how would we add a size calculation feature in an
extension package?
We can fall back on good-old Python functions instead. We separate out the size logic from our classes:
def document_size(item):
return len(item.text)
def folder_size(item):
return sum([document_size(entry) for entry in item.entries])
Generic size¶
There is a problem with the above function-based implementation
however: folder_size
is not generic anymore, but now depends on
document_size
. It fails when presented with a folder with an
Image
in it:
>>> folder_size(folder)
Traceback (most recent call last):
...
AttributeError: ...
To support Image
we first need an image_size
function:
def image_size(item):
return len(item.bytes)
We can now write a generic size
function to get the size for any
item we give it:
def size(item):
if isinstance(item, Document):
return document_size(item)
elif isinstance(item, Image):
return image_size(item)
elif isinstance(item, Folder):
return folder_size(item)
assert False, "Unknown item: %s" % item
With this, we can rewrite folder_size
to use the generic size
:
def folder_size(item):
return sum([size(entry) for entry in item.entries])
Now our generic size
function works:
>>> size(doc)
12
>>> size(image)
3
>>> size(folder)
25
All a bit complicated and hard-coded, but it works!
New File
content¶
What if we want to write a new extension to our CMS that adds a new
kind of folder item, the File
, with a file_size
function?
class File(object):
def __init__(self, bytes):
self.bytes = bytes
def file_size(item):
return len(item.bytes)
We need to remember to adjust the generic size
function so we can
teach it about file_size
as well. Annoying, tightly coupled, but
sometimes doable.
But what if we are actually another party, and we have control of
neither the basic CMS nor its size extension? We cannot adjust
generic_size
to teach it about File
now! Uh oh!
Perhaps the implementers of the size extension anticipated this use
case. They could have implemented size
like this:
size_function_registry = {
Document: document_size,
Image: image_size,
Folder: folder_size
}
def register_size(class_, function):
size_function_registry[class_] = function
def size(item):
return size_function_registry[item.__class__](item)
We can now use register_size
to teach size
how to get
the size of a File
instance:
register_size(File, file_size)
And it works:
>>> size(File('xyz'))
3
But this is quite a bit of custom work that the implementers need to
do, and it involves a new API (register_size
) to manipulate the
size_function_registry
. But it can be done.
New HtmlDocument
content¶
What if we introduce a new HtmlDocument
item that is a subclass of
Document
?
class HtmlDocument(Document):
pass # imagine new html functionality here
Let’s try to get its size:
>>> htmldoc = HtmlDocument('<p>Hello world!</p>')
>>> size(htmldoc)
Traceback (most recent call last):
...
KeyError: ...
That doesn’t work! There’s nothing registered for the HtmlDocument
class.
We need to remember to also call register_size
for
HtmlDocument
. We can reuse document_size
:
>>> register_size(HtmlDocument, document_size)
Now size
will work:
>>> size(htmldoc)
19
This is getting rather complicated, requiring not only foresight and
extra implementation work for the developers of size
but also
extra work for the person who wants to subclass a content item.
Hey, we should write a system that automates a lot of this, and gives us a universal registration API, making our life easier! And what if we want to switch behavior based on more than just one argument? Maybe you even want different dispatch behavior depending on application context? This is what Reg is for.
Doing this with Reg¶
Let’s see how we can implement size
using Reg:
First we need our generic size
function:
def size(item):
raise NotImplementedError
This function raises NotImplementedError
as we don’t know how to
get the size for an arbitrary Python object. Not very useful yet. We need
to be able to hook the actual implementations into it. To do this, we first
need to transform the size
function to a generic one:
import reg
size = reg.dispatch('item')(size)
We can actually spell these two steps in a single step, as
reg.dispatch()
can be used as decorator:
@reg.dispatch('item')
def size(item):
raise NotImplementedError
What this says that when we call size
, we want to dispatch based
on the class of its item
argument.
We can now register the various size functions for the various content
items as implementations of size
:
size.register(document_size, item=Document)
size.register(folder_size, item=Folder)
size.register(image_size, item=Image)
size.register(file_size, item=File)
size
now works:
>>> size(doc)
12
It works for folder too:
>>> size(folder)
25
It works for subclasses too:
>>> size(htmldoc)
19
Reg knows that HtmlDocument
is a subclass of Document
and will
find document_size
automatically for you. We only have to register
something for HtmlDocument
if we want to use a special, different
size function for HtmlDocument
.
Multiple and predicate dispatch¶
Let’s look at an example where dispatching on multiple arguments is
useful: a web view lookup system. Given a request object that
represents a HTTP request, and a model instance ( document, icon,
etc), we want to find a view function that knows how to make a
representation of the model given the request. Information in the
request can influence the representation. In this example we use a
request_method
attribute, which can be GET
, POST
, PUT
,
etc.
Let’s first define a Request
class with a request_method
attribute:
class Request(object):
def __init__(self, request_method, body=''):
self.request_method = request_method
self.body = body
We’ve also defined a body
attribute which contains text in case
the request is a POST
request.
We use the previously defined Document
as the model class.
Now we define a view function that dispatches on the class of the
model instance, and the request_method
attribute of the request:
@reg.dispatch(
reg.match_instance('obj'),
reg.match_key('request_method',
lambda obj, request: request.request_method))
def view(obj, request):
raise NotImplementedError
As you can see here we use match_instance
and match_key
instead of strings to specify how to dispatch.
If you use a string argument, this string names an argument and
dispatch is based on the class of the instance you pass in. Here we
use match_instance
, which is equivalent to this: we have a obj
predicate which uses the class of the obj
argument for dispatch.
We also use match_key
, which dispatches on the request_method
attribute of the request; this attribute is a string, so dispatch is
on string matching, not isinstance
as with match_instance
. You
can use any Python immutable with match_key
, not just strings.
We now define concrete views for Document
and Image
:
@view.register(request_method='GET', obj=Document)
def document_get(obj, request):
return "Document text is: " + obj.text
@view.register(request_method='POST', obj=Document)
def document_post(obj, request):
obj.text = request.body
return "We changed the document"
Let’s also define them for Image
:
@view.register(request_method='GET', obj=Image)
def image_get(obj, request):
return obj.bytes
@view.register(request_method='POST', obj=Image)
def image_post(obj, request):
obj.bytes = request.body
return "We changed the image"
Let’s try it out:
>>> view(doc, Request('GET'))
'Document text is: Hello world!'
>>> view(doc, Request('POST', 'New content'))
'We changed the document'
>>> doc.text
'New content'
>>> view(image, Request('GET'))
'abc'
>>> view(image, Request('POST', "new data"))
'We changed the image'
>>> image.bytes
'new data'
Dispatch methods¶
Rather than having a size
function and a view
function, we can
also have a context class with size
and view
as methods. We
need to use reg.dispatch_method
instead of
reg.dispatch
to do this.
class CMS(object):
@reg.dispatch_method('item')
def size(self, item):
raise NotImplementedError
@reg.dispatch_method(
reg.match_instance('obj'),
reg.match_key('request_method',
lambda self, obj, request: request.request_method))
def view(self, obj, request):
return "Generic content of {} bytes.".format(self.size(obj))
We can now register an implementation of CMS.size
for a
Document
object:
@CMS.size.register(item=Document)
def document_size_as_method(self, item):
return len(item.text)
Note that this is almost the same as the function document_size
we
defined before: the only difference is the signature, with the
additional self
as the first argument. We can in fact use
reg.methodify()
to reuse such functions without an initial
context argument:
from reg import methodify
CMS.size.register(methodify(folder_size), item=Folder)
CMS.size.register(methodify(image_size), item=Image)
CMS.size.register(methodify(file_size), item=File)
CMS.size
now behaves as expected:
>>> cms = CMS()
>>> cms.size(Image("123"))
3
>>> cms.size(Document("12345"))
5
Similarly for the view
method we can define:
@CMS.view.register(request_method='GET', obj=Document)
def document_get(self, obj, request):
return "{}-byte-long text is: {}".format(
self.size(obj), obj.text)
This works as expected as well:
>>> cms.view(Document("12345"), Request("GET"))
'5-byte-long text is: 12345'
>>> cms.view(Image("123"), Request("GET"))
'Generic content of 3 bytes.'
For more about how you can use dispatch methods and class-based context, see Context-based dispatch.
Lower level API¶
Component lookup¶
You can look up the implementation that a generic function would
dispatch to without calling it. You can look that up by invocation
arguments using the reg.Dispatch.by_args()
method on the
dispatch function or by predicate values using the
reg.Dispatch.by_predicates()
method:
>>> size.by_args(doc).component
<function document_size at 0x...>
>>> size.by_predicates(item=Document).component
<function document_size at 0x...>
Both methods return a reg.LookupEntry
instance whose
attributes, as we’ve just seen, include the dispatched implementation
under the name component
. Another interesting attribute is the
actual key used for dispatching:
>>> view.by_predicates(request_method='GET', obj=Document).key
(<class 'Document'>, 'GET')
>>> view.by_predicates(obj=Image, request_method='POST').key
(<class 'Image'>, 'POST')
Getting all compatible implementations¶
As Reg supports inheritance, if a function like size
has an
implementation registered for a class, say Document
, the same
implementation will be available for any if its subclasses, like
HtmlDocument
:
>>> size.by_args(doc).component is size.by_args(htmldoc).component
True
The matches
and all_matches
attributes of
reg.LookupEntry
are an interator and the list, respectively,
of all the registered components that are compatible with a
particular instance, including those of base classes. Right now this
is pretty boring as there’s only one of them:
>>> size.by_args(doc).all_matches
[<function document_size at ...>]
>>> size.by_args(htmldoc).all_matches
[<function document_size at ...>]
We can make this more interesting by registering a special
htmldocument_size
to handle HtmlDocument
instances:
def htmldocument_size(doc):
return len(doc.text) + 1 # 1 so we can see a difference
size.register(htmldocument_size, item=HtmlDocument)
size.all()
for htmldoc
now also gives back the more specific
htmldocument_size
:
>>> size.by_args(htmldoc).all_matches
[<function htmldocument_size at ...>, <function document_size at ...>]
The implementation are listed in order of decreasing specificity, with
the first one as the one returned by the component
attribute:
>>> size.by_args(htmldoc).component
<function htmldocument_size at ...>
Context-based dispatch¶
Introduction¶
Consider this advanced use case for Reg: we have a runtime with multiple contexts. For each context, you want the dispatch behavior to be different. Concretely, if you have an application where you can call a view dispatch function, you want it to execute a different function and return a different value in each separate context.
The Morepath web framework uses this feature of Reg to allow the developer to compose a larger application from multiple smaller ones.
You can define application context as a class. This context class defines dispatch methods. When you subclass the context class, you establish a new context: each subclass has entirely different dispatch registrations, and shares nothing with its base class.
A Context Class¶
Here is a concrete example. First we define a context class we call
A
, and a view
dispatch method on it:
import reg
class A(object):
@reg.dispatch_method(
reg.match_instance('obj'),
reg.match_key('request_method',
lambda self, obj, request: request.request_method))
def view(self, obj, request):
return "default"
Note that since view
is a method we define a self
argument.
To have something to view, We define Document
and Image
content classes:
class Document(object):
def __init__(self, text):
self.text = text
class Image(object):
def __init__(self, bytes):
self.bytes = bytes
We also need a request class:
class Request(object):
def __init__(self, request_method, body=''):
self.request_method = request_method
self.body = body
To try this out, we need to create an instance of the context class:
a = A()
Before we register anything, we get the default result we defined in the method:
>>> doc = Document('Hello world!')
>>> a.view(doc, Request('GET'))
'default'
>>> a.view(doc, Request('POST', 'new content'))
'default'
>>> image = Image('abc')
>>> a.view(image, Request('GET'))
'default'
Here are the functions we are going to register:
def document_get(obj, request):
return "Document text is: " + obj.text
def document_post(obj, request):
obj.text = request.body
return "We changed the document"
def image_get(obj, request):
return obj.bytes
def image_post(obj, request):
obj.bytes = request.body
return "We changed the image"
We now want to register them with our context. To do so, we need to
access the dispatch function through its class (A
), not its
instance (a
). All instances of A
(but not instances of its
subclasses as we will see later) share the same registrations.
We use reg.methodify()
to do the registration, to keep our view
functions the same as when context is not in use. We will see an
example without reg.methodify()
later:
from reg import methodify
A.view.register(methodify(document_get),
request_method='GET',
obj=Document)
A.view.register(methodify(document_post),
request_method='POST',
obj=Document)
A.view.register(methodify(image_get),
request_method='GET',
obj=Image)
A.view.register(methodify(image_post),
request_method='POST',
obj=Image)
Now that we’ve registered some functions, we get the expected behavior
when we call a.view
:
>>> a.view(doc, Request('GET'))
'Document text is: Hello world!'
>>> a.view(doc, Request('POST', 'New content'))
'We changed the document'
>>> doc.text
'New content'
>>> a.view(image, Request('GET'))
'abc'
>>> a.view(image, Request('POST', "new data"))
'We changed the image'
>>> image.bytes
'new data'
A new context¶
Okay, we associate a dispatch method with a context class, but what is the
point? The point is that we can introduce a new context that has
different behavior now. To do, we subclass A
:
class B(A):
pass
At this point the new B
context is empty of specific behavior,
even though it subclasses A
:
>>> b = B()
>>> b.view(doc, Request('GET'))
'default'
>>> b.view(doc, Request('POST', 'New content'))
'default'
>>> b.view(image, Request('GET'))
'default'
>>> b.view(image, Request('POST', "new data"))
'default'
We can now do our registrations. Let’s register the same
behavior for documents as we did for Context
:
B.view.register(methodify(document_get),
request_method='GET',
obj=Document)
B.view.register(methodify(document_post),
request_method='POST',
obj=Document)
But we install different behavior for Image
:
def b_image_get(obj, request):
return 'New image GET'
def b_image_post(obj, request):
return 'New image POST'
B.view.register(methodify(b_image_get),
request_method='GET',
obj=Image)
B.view.register(methodify(b_image_post),
request_method='POST',
obj=Image)
Calling view
for Document
works as before:
>>> b.view(doc, Request('GET'))
'Document text is: New content'
But the behavior for Image
instances is different in the B
context:
>>> b.view(image, Request('GET'))
'New image GET'
>>> b.view(image, Request('POST', "new data"))
'New image POST'
Note that the original context A
is of course unaffected and still
has the behavior we registered for it:
>>> a.view(image, Request('GET'))
'new data'
The idea is that you can create a framework around your base context
class. Where this base context class needs to have dispatch behavior,
you define dispatch methods. You then create different subclasses of
the base context class and register different behaviors for them. This
is what Morepath does with its App
class.
Call method in the same context¶
What if in a dispatch implementation you find you need to call another
dispatch method? How to access the context? You can do this by
registering a function that get a context as its first argument. As an
example, we modify our document functions so that document_post
uses the other:
def c_document_get(context, obj, request):
return "Document text is: " + obj.text
def c_document_post(context, obj, request):
obj.text = request.body
return "Changed: " + context.view(obj, Request('GET'))
Now c_document_post
uses the view
dispatch method on the
context. We need to register these methods using
reg.Dispatch.register()
without reg.methodify()
. This way
they get the context as the first argument. Let’s create a new context
and do so:
class C(A):
pass
C.view.register(c_document_get,
request_method='GET',
obj=Document)
C.view.register(c_document_post,
request_method='POST',
obj=Document)
We now get the expected behavior:
>>> c = C()
>>> c.view(doc, Request('GET'))
'Document text is: New content'
>>> c.view(doc, Request('POST', 'Very new content'))
'Changed: Document text is: Very new content'
You could have used reg.methodify()
for this too, as
methodify
inspects the first argument and if it’s identical to the
second argument to methodify
, it will pass in the context as that
argument.
class D(A):
pass
D.view.register(methodify(c_document_get, 'context'),
request_method='GET',
obj=Document)
D.view.register(methodify(c_document_post, 'context'),
request_method='POST',
obj=Document)
>>> d = D()
>>> d.view(doc, Request('GET'))
'Document text is: Very new content'
>>> d.view(doc, Request('POST', 'Even newer content'))
'Changed: Document text is: Even newer content'
The default value for the second argument to methodify
is app
.
Patterns¶
Here we look at a number of patterns you can implement with Reg.
Adapters¶
What if we wanted to add a feature that required multiple methods, not just one? You can use the adapter pattern for this.
Let’s imagine we have a feature to get the icon for a content object in our CMS, and that this consists of two methods, with a way to get a small icon and a large icon. We want this API:
from abc import ABCMeta, abstractmethod
class Icon(object):
__metaclass__ = ABCMeta
@abstractmethod
def small(self):
"""Get the small icon."""
@abstractmethod
def large(self):
"""Get the large icon."""
We define Document
and Image
content classes:
class Document(object):
def __init__(self, text):
self.text = text
class Image(object):
def __init__(self, bytes):
self.bytes = bytes
Let’s implement the Icon
API for Document
:
def load_icon(path):
return path # pretend we load the path here and return an image obj
class DocumentIcon(Icon):
def __init__(self, document):
self.document = document
def small(self):
if not self.document.text:
return load_icon('document_small_empty.png')
return load_icon('document_small.png')
def large(self):
if not self.document.text:
return load_icon('document_large_empty.png')
return load_icon('document_large.png')
The constructor of DocumentIcon
receives a Document
instance
as its first argument. The implementation of the small
and
large
methods uses this instance to determine what icon to produce
depending on whether the document is empty or not.
We can call DocumentIcon
an adapter, as it adapts the original
Document
class to provide an icon API for it. We can use it
manually:
>>> doc = Document("Hello world")
>>> icon_api = DocumentIcon(doc)
>>> icon_api.small()
'document_small.png'
>>> icon_api.large()
'document_large.png'
But we want to be able to use the Icon
API generically, so let’s
create a generic function that gives us an implementation of Icon
back for any object:
import reg
@reg.dispatch('obj')
def icon(obj):
raise NotImplementedError
We can now register the DocumentIcon
adapter class for this
function and Document
:
icon.register(DocumentIcon, obj=Document)
We can now use the generic icon
to get Icon
API for a
document:
>>> api = icon(doc)
>>> api.small()
'document_small.png'
>>> api.large()
'document_large.png'
We can also register a FolderIcon
adapter for Folder
, a
ImageIcon
adapter for Image
, and so on. For the sake of
brevity let’s just define one for Image
here:
class ImageIcon(Icon):
def __init__(self, image):
self.image = image
def small(self):
return load_icon('image_small.png')
def large(self):
return load_icon('image_large.png')
icon.register(ImageIcon, obj=Image)
Now we can use icon
to retrieve the Icon
API for any item in
the system for which an adapter was registered:
>>> icon(doc).small()
'document_small.png'
>>> icon(doc).large()
'document_large.png'
>>> image = Image('abc')
>>> icon(image).small()
'image_small.png'
>>> icon(image).large()
'image_large.png'
Service Discovery¶
Some applications need configurable services. The application may for instance need a way to send email, but you don’t want to hardcode any particular way into your app, but instead leave this to a particular deployment-specific configuration. You can use the Reg infrastructure for this as well.
The simplest way to do this with Reg is by using a generic service lookup function:
@reg.dispatch()
def emailer():
raise NotImplementedError
Here we’ve created a generic function that takes no arguments (and thus does no dynamic dispatch). But you can still plug its actual implementation into the registry from elsewhere:
sent = []
def send_email(sender, subject, body):
# some specific way to send email
sent.append((sender, subject, body))
def actual_emailer():
return send_email
emailer.register(actual_emailer)
Now when we call emailer, we’ll get the specific service we want:
>>> the_emailer = emailer()
>>> the_emailer('someone@example.com', 'Hello', 'hello world!')
>>> sent
[('someone@example.com', 'Hello', 'hello world!')]
In this case we return the function send_email
from the
emailer()
function, but we could return any object we want that
implements the service, such as an instance with a more extensive API.
Replacing class methods¶
Reg generic functions can be used to replace methods, so that you can
follow the open/closed principle and add functionality to a class
without modifying it. This works for instance methods, but what about
classmethod
? This takes the class as the first argument, not an
instance. You can configure @reg.dispatch
decorator with a special
Predicate
instance that lets you dispatch on a class argument
instead of an instance argument.
Here’s what it looks like:
@reg.dispatch(reg.match_class('cls'))
def something(cls):
raise NotImplementedError()
Note the call to match_class()
here. This lets us specify that
we want to dispatch on the class, in this case we simply want the
cls
argument.
Let’s use it:
def something_for_object(cls):
return "Something for %s" % cls
something.register(something_for_object, cls=object)
class DemoClass(object):
pass
When we now call something()
with DemoClass
as the first
argument we get the expected output:
>>> something(DemoClass)
"Something for <class 'DemoClass'>"
This also knows about inheritance. So, you can write more specific implementations for particular classes:
class ParticularClass(object):
pass
def something_particular(cls):
return "Particular for %s" % cls
something.register(
something_particular,
cls=ParticularClass)
When we call something
now with ParticularClass
as the argument,
then something_particular
is called:
>>> something(ParticularClass)
"Particular for <class 'ParticularClass'>"
API¶
Dispatch functions¶
-
reg.
dispatch
(*predicates, **kw)¶ Decorator to make a function dispatch based on its arguments.
This takes the predicates to dispatch on as zero or more parameters.
Parameters: - predicates – sequence of
reg.Predicate
instances to do the dispatch on. You create predicates usingreg.match_instance()
,reg.match_key()
,reg.match_class()
, or with a custom predicate class. You can also pass in plain string argument, which is turned into areg.match_instance()
predicate. - get_key_lookup – a function that gets a
PredicateRegistry
instance and returns a key lookup. APredicateRegistry
instance is itself a key lookup, but you can return a caching key lookup (such asreg.DictCachingKeyLookup
orreg.LruCachingKeyLookup
) to make it more efficient.
Returns: a function that you can use as if it were a
reg.Dispatch
instance.- predicates – sequence of
-
class
reg.
Dispatch
(predicates, callable, get_key_lookup)¶ Dispatch function.
You can register implementations based on particular predicates. The dispatch function dispatches to these implementations based on its arguments.
Parameters: - predicates – a list of predicates.
- callable – the Python function object to register dispatch implementations for. The signature of an implementation needs to match that of this function. This function is used as a fallback implementation that is called if no specific implementations match.
- get_key_lookup – a function that gets a
PredicateRegistry
instance and returns a key lookup. APredicateRegistry
instance is itself a key lookup, but you can return a caching key lookup (such asreg.DictCachingKeyLookup
orreg.LruCachingKeyLookup
) to make it more efficient.
-
add_predicates
(predicates)¶ Add new predicates.
Extend the predicates used by this predicates. This can be used to add predicates that are configured during startup time.
Note that this clears up any registered implementations.
Parameters: predicates – a list of predicates to add.
-
by_args
(*args, **kw)¶ Lookup an implementation by invocation arguments.
Parameters: - args – positional arguments used in invocation.
- kw – named arguments used in invocation.
Returns:
-
by_predicates
(**predicate_values)¶ Lookup an implementation by predicate values.
Parameters: predicate_values – the values of the predicates to lookup. Returns: a reg.LookupEntry
.
-
clean
()¶ Clean up implementations and added predicates.
This restores the dispatch function to its original state, removing registered implementations and predicates added using
reg.Dispatch.add_predicates()
.
-
register
(func=None, **key_dict)¶ Register an implementation.
If
func
is not specified, this method can be used as a decorator and the decorated function will be used as the actualfunc
argument.Parameters: - func – a function that implements behavior for this
dispatch function. It needs to have the same signature as
the original dispatch function. If this is a
reg.DispatchMethod
, then this means it needs to take a first context argument. - key_dict – keyword arguments describing the registration, with as keys predicate name and as values predicate values.
Returns: func
.- func – a function that implements behavior for this
dispatch function. It needs to have the same signature as
the original dispatch function. If this is a
-
reg.
match_key
(name, func=None, fallback=None, default=None)¶ Predicate that returns a value used for dispatching.
Name: predicate name.
Func: a callable that accepts the same arguments as the generic function and returns the value used for dispatching. The returned value must be of an immutable type.
If
None
, use a callable returning the argument with the same name as the predicate.Fallback: the fallback value. By default it is
None
.Default: optional default value.
Returns: a
Predicate
.
-
reg.
match_instance
(name, func=None, fallback=None, default=None)¶ Predicate that returns an instance whose class is used for dispatching.
Name: predicate name. Func: a callable that accepts the same arguments as the generic function and returns the instance whose class is used for dispatching. If None
, use a callable returning the argument with the same name as the predicate.Fallback: the fallback value. By default it is None
.Default: optional default value. Returns: a Predicate
.
-
reg.
match_class
(name, func=None, fallback=None, default=None)¶ Predicate that returns a class used for dispatching.
Name: predicate name. Func: a callable that accepts the same arguments as the generic function and returns a class used for dispatching. If None
, use a callable returning the argument with the same name as the predicate.Fallback: the fallback value. By default it is None
.Default: optional default value. Returns: a Predicate
.
-
class
reg.
LookupEntry
¶ The dispatch data associated to a key.
-
all_matches
¶ The list of all compatible implementations.
-
component
¶ The function to dispatch to, excluding fallbacks.
-
fallback
¶ The approriate fallback implementation.
-
matches
¶ An iterator over all the compatible implementations.
-
-
class
reg.
DictCachingKeyLookup
(key_lookup)¶ A key lookup that caches.
Implements the read-only API of
reg.PredicateRegistry
using a cache to speed up access.This cache is backed by a simple dictionary so could potentially grow large if the dispatch in question can be called with a large combination of arguments that result in a large range of different predicate keys. If so, you can use
reg.LruCachingKeyLookup
instead.Param: key_lookup - the PredicateRegistry
to cache.
-
class
reg.
LruCachingKeyLookup
(key_lookup, component_cache_size, all_cache_size, fallback_cache_size)¶ A key lookup that caches.
Implements the read-only API of
reg.PredicateRegistry
, using a cache to speed up access.The cache is LRU so won’t grow beyond a certain limit, preserving memory. This is only useful if you except the access pattern to your function to involve a huge range of different predicate keys.
Param: key_lookup - the
PredicateRegistry
to cache.Parameters: - component_cache_size – how many cache entries to store for
the
component()
method. This is also used by dispatch calls. - all_cache_size – how many cache entries to store for the
the
all()
method. - fallback_cache_size – how many cache entries to store for
the
fallback()
method.
- component_cache_size – how many cache entries to store for
the
Context-specific dispatch methods¶
-
reg.
dispatch_method
(*predicates, **kw)¶ Decorator to make a method on a context class dispatch.
This takes the predicates to dispatch on as zero or more parameters.
Parameters: - predicates –
sequence of
Predicate
instances to do the dispatch on. You create predicates usingreg.match_instance()
,reg.match_key()
,reg.match_class()
, or with a custom predicate class.You can also pass in plain string argument, which is turned into a
reg.match_instance()
predicate. - get_key_lookup – a function that gets a
PredicateRegistry
instance and returns a key lookup. APredicateRegistry
instance is itself a key lookup, but you can return a caching key lookup (such asreg.DictCachingKeyLookup
orreg.LruCachingKeyLookup
) to make it more efficient. - first_invocation_hook – a callable that accepts an instance of the class in which this decorator is used. It is invoked the first time the method is invoked.
- predicates –
-
class
reg.
DispatchMethod
(predicates, callable, get_key_lookup)¶ -
add_predicates
(predicates)¶ Add new predicates.
Extend the predicates used by this predicates. This can be used to add predicates that are configured during startup time.
Note that this clears up any registered implementations.
Parameters: predicates – a list of predicates to add.
-
by_args
(*args, **kw)¶ Lookup an implementation by invocation arguments.
Parameters: - args – positional arguments used in invocation.
- kw – named arguments used in invocation.
Returns:
-
by_predicates
(**predicate_values)¶ Lookup an implementation by predicate values.
Parameters: predicate_values – the values of the predicates to lookup. Returns: a reg.LookupEntry
.
-
clean
()¶ Clean up implementations and added predicates.
This restores the dispatch function to its original state, removing registered implementations and predicates added using
reg.Dispatch.add_predicates()
.
-
register
(func=None, **key_dict)¶ Register an implementation.
If
func
is not specified, this method can be used as a decorator and the decorated function will be used as the actualfunc
argument.Parameters: - func – a function that implements behavior for this
dispatch function. It needs to have the same signature as
the original dispatch function. If this is a
reg.DispatchMethod
, then this means it needs to take a first context argument. - key_dict – keyword arguments describing the registration, with as keys predicate name and as values predicate values.
Returns: func
.- func – a function that implements behavior for this
dispatch function. It needs to have the same signature as
the original dispatch function. If this is a
-
-
reg.
clean_dispatch_methods
(cls)¶ For a given class clean all dispatch methods.
This resets their registry to the original state using
reg.DispatchMethod.clean()
.Parameters: cls – a class that has reg.DispatchMethod
methods on it.
-
reg.
methodify
(func, selfname=None)¶ Turn a function into a method, if needed.
If
selfname
is not specified, wrap the function so that it takes an additional first argument, like a method.If
selfname
is specified, check whether it is the same as the name of the first argument offunc
. If itsn’t, wrap the function so that it takes an additional first argument, with the name specified byselfname
.If it is, the signature of
func
needn’t be amended, but wrapping might still be necessary.In all cases,
inspect_methodified()
lets you retrieve the wrapped function.Parameters: - func – the function to turn into method.
- selfname – if specified, the name of the argument
referencing the class instance. Typically,
"self"
.
Returns: function that can be used as a method when assigned to a class.
Argument introspection¶
-
reg.
arginfo
(callable)¶ Get information about the arguments of a callable.
Returns a
inspect.ArgSpec
object as forinspect.getargspec()
.inspect.getargspec()
returns information about the arguments of a function. arginfo also works for classes and instances with a __call__ defined. Unlike getargspec, arginfo treats bound methods like functions, so that the self argument is not reported.arginfo returns
None
if given something that is not callable.arginfo caches previous calls (except for instances with a __call__), making calling it repeatedly cheap.
This was originally inspired by the pytest.core varnames() function, but has been completely rewritten to handle class constructors, also show other getarginfo() information, and for readability.
Low-level predicate support¶
Typically, you’d be using reg.match_key()
,
reg.match_instance()
, and reg.match_class()
to define
predicates. Should you require finer control, you can use the
following classes:
-
class
reg.
Predicate
(name, index, get_key=None, fallback=None, default=None)¶ A dispatch predicate.
Parameters: - name – name used to identify the predicate when specifying
its expected value in
reg.Dispatch.register()
. - index – a function that constructs an index given
a fallback argument; typically you supply either a
KeyIndex
orClassIndex
. - get_key – a callable that accepts a dictionary with the invocation arguments of the generic function and returns a key to be used for dispatching.
- fallback – optional fallback value. The fallback of the the most generic index for which no values could be found is used.
- default – default expected value of the predicate, to be
used by
reg.Dispatch.register()
whenever the expected value for the predicate is not given explicitly.
- name – name used to identify the predicate when specifying
its expected value in
Developing Reg¶
Install Reg for development¶
Clone Reg from github:
$ git clone git@github.com:morepath/reg.git
If this doesn’t work and you get an error ‘Permission denied (publickey)’, you need to upload your ssh public key to github.
Then go to the reg directory:
$ cd reg
Make sure you have virtualenv installed.
Create a new virtualenv for Python 3 inside the reg directory:
$ virtualenv -p python3 env/py3
Activate the virtualenv:
$ source env/py3/bin/activate
Make sure you have recent setuptools and pip installed:
$ pip install -U setuptools pip
Install the various dependencies and development tools from develop_requirements.txt:
$ pip install -Ur develop_requirements.txt
For upgrading the requirements just run the command again.
If you want to test Reg with Python 2.7 as well you can create a second virtualenv for it:
$ virtualenv -p python2.7 env/py27
You can then activate it:
$ source env/py27/bin/activate
Then uprade setuptools and pip and install the develop requirements as described above.
Note
The following commands work only if you have the virtualenv activated.
Running the tests¶
You can run the tests using py.test:
$ py.test
To generate test coverage information as HTML do:
$ py.test --cov --cov-report html
You can then point your web browser to the htmlcov/index.html
file
in the project directory and click on modules to see detailed coverage
information.
Running the documentation tests¶
The documentation contains code. To check these code snippets, you can run this code using this command:
(py3) $ sphinx-build -b doctest doc doc/build/doctest
Or alternatively if you have Make
installed:
(py3) $ cd doc
(py3) $ make doctest
Or from the Reg project directory:
(py3) $ make -C doc doctest
Since the sample code in the documentation is maintained in Python 3 syntax, we do not support running the doctests with Python 2.7.
Building the HTML documentation¶
To build the HTML documentation (output in doc/build/html
), run:
$ sphinx-build doc doc/build/html
Or alternatively if you have Make
installed:
$ cd doc
$ make html
Or from the Reg project directory:
$ make -C doc html
Various checking tools¶
flake8 is a tool that can do various checks for common Python mistakes using pyflakes, check for PEP8 style compliance and can do cyclomatic complexity checking. To do pyflakes and pep8 checking do:
$ flake8 reg
To also show cyclomatic complexity, use this command:
$ flake8 --max-complexity=10 reg
Tox¶
With tox you can test Morepath under different Python environments.
We have Travis continuous integration installed on Morepath’s github repository and it runs the same tox tests after each checkin.
First you should install all Python versions which you want to test. The versions which are not installed will be skipped. You should at least install Python 3.5 which is required by flake8, coverage and doctests and Python 2.7 for testing Morepath with Python 2.
One tool you can use to install multiple versions of Python is pyenv.
To find out which test environments are defined for Morepath in tox.ini run:
$ tox -l
You can run all tox tests with:
$ tox
You can also specify a test environment to run e.g.:
$ tox -e py35
$ tox -e pep8
$ tox -e docs
To run a simple performance test you can use:
$ tox -e perf
Internals¶
This section of the documentation descibes the internal code objects of Reg. The information included here is of interest only if you are thinking of contributing to Reg.
-
class
reg.predicate.
PredicateRegistry
(*predicates)¶ -
key
(**kw)¶ Construct a dispatch key from the arguments of a generic function.
Parameters: kw – a dictionary with the arguments passed to a generic function. Returns: a tuple, to be used as a key for dispatching.
-
key_dict_to_predicate_key
(d)¶ Construct a dispatch key from predicate values.
Uses
name
anddefault
attributes of predicates to construct the dispatch key.Parameters: d – dictionary mapping predicate names to predicate values. If a predicate is missing from d
, its default expected value is used.Returns: a tuple, to be used as a key for dispatching.
-
History of Reg¶
Reg was written by Martijn Faassen. The core mapping code was originally co-authored by Thomas Lotze, though this has since been subsumed into the generalized predicate architecture. After a few years of use, Stefano Taschini initiated a large refactoring and API redesign.
Reg is a predicate dispatch implementation for Python, with support
for multiple dispatch registries in the same runtime. It was
originally heavily inspired by the Zope Component Architecture (ZCA)
consisting of the zope.interface
and zope.component
packages. Reg has strongly evolved since its inception into a general
function dispatch library. Reg’s codebase is completely separate from
the ZCA and it has an entirely different API. At the end I’ve included
a brief history of the ZCA.
The primary use case for Reg has been the Morepath web framework, which uses it heavily.
Reg History¶
The Reg code went through a quite bit of history as our insights evolved.
iface¶
The core registry (mapping) code was conceived by Thomas Lotze and
Martijn Faassen as a speculative sandbox project in January
of 2010. It was called iface
then:
http://svn.zope.org/Sandbox/faassen/iface/
This registry was instrumental in getting Reg started, but was subsequently removed in a later refactoring.
crom¶
In early 2012, Martijn was at a sprint in Nürnberg, Germany organized by Novareto. Inspired by discussions with the sprint participants, particularly the Cromlech developers Souheil Chelfouh and Alex Garel, Martijn created a project called Crom:
https://github.com/faassen/crom
Crom focused on rethinking component and adapter registration and
lookup APIs, but was still based on zope.interface
for its
fundamental AdapterRegistry
implementation and the Interface
metaclass. Martijn worked a bit on Crom after the sprint, but soon
moved on to other matters.
iface + crom¶
At the Plone conference held in Arnhem, the Netherlands in October 2012, Martijn gave a lightning talk about Crom, which was received positively, which reignited his interest. In the end of 2012 Martijn mailed Thomas Lotze to ask to merge iface into Crom, and he gave his kind permission.
The core registry code of iface was never quite finished however, and while the iface code was now in Crom, Crom didn’t use it yet. Thus it lingered some more.
ZCA-style Reg¶
In July 2013 in development work for CONTACT (contact.de), Martijn found himself in need of clever registries. Crom also had some configuration code intermingled with the component architecture code, and Martijn wanted to separate this out.
So Martijn reorganized the code yet again into another project, this
one: Reg. Martijn then finished the core mapping code and hooked it up
to the Crom-style API, which he refactored further. For interfaces, he
used Python’s abc
module.
For a while during internal development this codebase was called
Comparch
, but this conflicted with another name so he decided to
call it Reg
, short for registry, as it’s really about clever
registries more than anything else.
This version of Reg was still very similar in concepts to the Zope Component Architecture, though it used a streamlined API. This streamlined API lead to further developments.
Generic dispatch¶
After Martijn’s first announcement of Reg to the world in September 2013 he got a question why it shouldn’t just use PEP 443, which has a generic function implementation (single dispatch). This lead to the idea of converting Reg into a generic function implementation (with multiple dispatch), as it was already very close. After talking to some people about this at PyCon DE in october, Martijn did the refactoring to use generic functions throughout and no interfaces for lookup. Martijn then used this version of Reg in Morepath for about a year.
Predicate dispatch¶
In October 2014 Martijn had some experience with using Reg and found some of its limitations:
- Reg would try to dispatch on all non-keyword arguments of a function. This is not what is desired in many cases. We need a way to dispatch only on specified arguments and leave others alone.
- Reg had an undocumented predicate subsystem used to implement view lookup in Morepath. A new requirement lead to the requirement to dispatch on the class of an instance, and while Reg’s generic dispatch system could do it, the predicate subsystem could not. Enabling this required a major reorganization of Reg.
- Martijn realized that such a reorganized predicate system could actually be used to generalize the way Reg worked based on how predicates worked.
- This would allow predicates to play along in Reg’s caching infrastructure, which could then speed up Morepath’s view lookups.
- A specific use case to replace class methods caused me to introduce
reg.classgeneric
. This could be subsumed in a generalized predicate infrastructure as well.
So in October 2014, Martijn refactored Reg once again in the light of this, generalizing the generic dispatch further to predicate dispatch, and replacing the iface-based registry. This refactoring resulted in a smaller, more unified codebase that has more features and was also faster.
Removing implicitness and inverting layers¶
Reg used an implicit lookup
system to find the current registry to
use for dispatch. This allows Morepath to compose larger applications
out of smaller registries, each with their own dispatch context. As an
alternative to the implicit system, you could also pass in a custom
lookup
argument to the function to indicate the current registry.
In 2016 Stefano Taschini started pushing on Morepath’s use of dispatch functions and their implicit nature. Subsequent discussions with Martijn led to the insight that if we approached dispatch functions as dispatch methods on a context class (the Morepath application), we could get rid of the implicit behavior altogether, while gaining performance as we’d use Python’s method mechanism.
In continuing discussions, Stefano also suggested that there was no need for Reg in cases where the dispatch behavior of Reg was not needed. This led to the insight that this non-dispatch behavior could be installed as methods directly on the context class.
Stefano also proposed that Reg could be internally simplified if we made the multiple registry behavior less central to the implementation, and let each dispatch function maintain its own registry. Stefano and Martijn then worked on an implementation where the dispatch method behavior is layered on top of a simpler dispatch function layer.
Brief history of Zope Component Architecture¶
Reg is heavily inspired by zope.interface
and zope.component
,
by Jim Fulton and a lot of Zope developers, though Reg has undergone a
significant evolution since then. zope.interface
has a long
history, going all the way back to December 1998, when a scarecrow
interface package was released for discussion:
http://old.zope.org/Members/jim/PythonInterfaces/Summary/
http://old.zope.org/Members/jim/PythonInterfaces/Interface/
A later version of this codebase found itself in Zope, as Interface
:
http://svn.zope.org/Zope/tags/2-8-6/lib/python/Interface/
A new version called zope.interface was developed for the Zope 3 project, somewhere around the year 2001 or 2002 (code historians, please dig deeper and let me know). On top of this a zope.component library was constructed which added registration and lookup APIs on top of the core zope.interface code.
zope.interface and zope.component are widely used as the core of the Zope 3 project. zope.interface was adopted by other projects, such as Zope 2, Twisted, Grok, BlueBream and Pyramid.
CHANGES¶
0.11 (2016-12-23)¶
Breaking change
The
key_predicate
function is gone. You can now usePredicate(..., index=KeyIndex)
ormatch_key
instead.Breaking change
The
class_predicate
function is gone. You can now usePredicate(..., index=ClassIndex)
,match_instance
ormatch_class
instead.Breaking change
The undocumented
Sentinel
class andNOT_FOUND
object are gone.Breaking change
The class
PredicateRegistry
is not longer part of the API. Internally, the classesMultiPredicate
,MultiIndex
,SingleValueRegistry
have all been merged intoPredicateRegistry
, which should now considered an implementation detail.The second argument for
match_key
is now optional; if you don’t supply itmatch_key
will generate a predicate function that extracts that name by default.The documentation now includes a section describing the internals of Reg.
Upload universal wheels to pypi during release.
0.10 (2016-10-04)¶
Breaking change
Reg has undergone another API breaking change. The goals of this change were:
- Make everything explicit.
- A simpler implementation structure – dispatch functions maintain their own registries, which allows for less interacting objects.
- Make the advanced context-dependent dispatch more Pythonic by using classes with special dispatch methods.
Detailed changes:
reg.Registry
is gone. Instead you register directly on the dispatch function:@reg.dispatch('a') def foo(a): ... def foo_implementation(a): ... foo.register(foo_implementation, a=Document)
Caching is now per dispatch function, not globally per lookup. You can pass a
get_key_lookup
function that wrapsreg.PredicateRegistry
instance inside areg.DictCachingKeyLookup
cache. You can also use areg.LruCachingKeyLookup
if you expect a dispatch to be called with a large amount of possible predicate combinations, to preserve memory.The whole concept of a “lookup” is gone:
reg.implicit
is gone: everything is explicit. There is no more implicit lookup.reg.Lookup
itself is gone – its now implemented directly in the dispatch object, but was already how you accessed it.- The special
lookup
argument to pass through the currentLookup
is gone. If you need context-dependent dispatch, you use dispatch methods. - If you need context dependent dispatch, where the functions
being dispatched to depend on application context (such as
Morepath’s application mounting), you use
reg.dispatch_method
to create a dispatch method. A dispatch method maintains an entirely separate dispatch registry for each subclass. You usereg.methodify
to register a dispatch function that takes an optional context first argument.
If you do not use the context-dependent dispatch feature, then to upgrade your code:
remove any
reg.set_implicit
from your code, setup ofLookup
and the like.If you use an explicit
lookup
argument you can just remove them.You also need to change your registration code: no more
reg.Registry
setup.Change your registrations to be on the dispatch objects itself using
Dispatch.register
.To enable caching you need to set up
get_key_lookup
on the dispatch functions. You can create a partially applied version ofdispatch
to make this less verbose:import reg from functools import partial def get_caching_key_lookup(r): return reg.CachingKeyLookup(r, 5000, 5000, 5000) dispatch = partial(reg.dispatch, get_key_lookup=get_caching_key_lookup)
dispatch_external_predicates
is gone. Just usedispatch
directly. You can add predicates to an existing Dispatch object using theadd_predicates
method.
If you do use the context-dependent dispatch feature, then you also need to:
- identify the context class in your application (or create one).
- move the dispatch functions to this class, marking them with
@reg.dispatch_method
instead of@reg.dispatch
. - Registration is now using
<context_class>.<method>.register
. Functions you register this way behave as methods tocontext_class
, so get an instance of this class as the first argument. - You can also use
reg.methodify
to register implementation functions that do not take the context as the first argument – this is useful when upgrading existing code. - Call your context-dependent methods as methods on the context instance. This way you can indicate what context you are calling your dispatch methods in, instead of using the lookup` argument.
In some cases you want a context-dependent method that actually does not dispatch on any of its arguments. To support this use case you can simply set function (that takes an app argument) as a the method on the context class directly:
Context.my_method = some_function
If you want to set up a function that doesn’t take a reference to a
Context
instance as its first argument, you can usereg.methodify
to turn it into a method that ignores its first argument:Context.my_method = reg.methodify(some_function)
If you want to register a function that might or might not have a reference to a
Context
instance as its first argument, called, e.g.,app
, you can use the following:Context.my_method = reg.methodify(some_function, selfname='app')
Breaking change
Removed the helper function
mapply
from the API.Breaking change
Removed the exception class
KeyExtractorError
from the API. When passing the wrong number of arguments to a dispatch function, or when using the wrong argument names, you will now get a TypeError, in conformity with standard Python behaviour.Breaking change
Removed the
KeyExtractor
class from the API. Callables used in predicate construction now expect the same arguments as the dispatch function.Breaking change
Removed the
argnames
attribute fromPredicate
and its descendant.Breaking change
Remove the
match_argname
predicate. You can now usematch_instance
with no callable instead.The second argument for
match_class
is now optional; if you don’t supply itmatch_class
will generate a predicate function that extracts that name by default.The second argument for
match_instance
is now optional; if you don’t supply itmatch_instance
will generate a predicate function that extracts that name by default.Include doctests in Tox and Travis.
We now use virtualenv and pip instead of buildout to set up the development environment. The development documentation has been updated accordingly.
As we reached 100% code coverage for pytest, coveralls integration was replaced by the
--fail-under=100
argument ofcoverage report
in the tox coverage test.
0.9.3 (2016-07-18)¶
- Minor fixes to documentation.
- Add tox test environments for Python 3.4 and 3.5, PyPy 3 and PEP 8.
- Make Python 3.5 the default Python environment.
- Changed location
NoImplicitLookupError
was imported from in__init__.py
.
0.9.2 (2014-11-13)¶
- Reg was a bit too strict; when you had multiple (but not single) predicates, Reg would raise KeyError when you put in an unknown key. Now they’re just being silently ignored, as they don’t do any harm.
- Eliminated a check in
ArgExtractor
that could never take place. - Bring test coverage back up to 100%.
- Add converage configuration to ignore test files in coverage reporting.
0.9.1 (2014-11-11)¶
- A bugfix in the behavior of the fallback logic. In situations with multiple predicates of which one is a class predicate it was possible for a fallback not to be found even though a fallback was available.
0.9 (2014-11-11)¶
Total rewrite of Reg! This includes a range of changes that can break code. The primary motivations for this rewrite:
- unify predicate system with class-based lookup system.
- extract dispatch information from specific arguments instead of all arguments.
Some specific changes:
Replaced
@reg.generic
decorator with@reg.dispatch()
decorator. This decorator can be configured with predicates that extract information from the arguments. Rewrite this:@reg.generic def foo(obj): pass
to this:
@reg.dispatch('obj') def foo(obj): pass
And this:
@reg.generic def bar(a, b): pass
To this:
@reg.dispatch('a', 'b') def bar(a, b): pass
This is to get dispatch on the classes of these instance arguments. If you want to match on the class of an attribute of an argument (for instance) you can use
match_instance
with a function:@reg.dispatch(match_instance('a', lambda a: a.attr))
The first argument to
match_instance
is the name of the predicate by which you refer to it inregister_function
.You can also use
match_class
to have direct dispatch on classes (useful for replicating classmethods), andmatch_key
to have dispatch on the (immutable) value of the argument (useful for a view predicate system). Like formatch_instance
, you supply functions to these match functions that extract the exact information to dispatch on from the argument.The
register_function
API replaces theregister
API to register a function. Replace this:r.register(foo, (SomeClass,), dispatched_to)
with:
r.register_function(foo, dispatched_to, obj=SomeClass)
You now use keyword parameters to indicate exactly those arguments specified by
reg.dispatch()
are actually predicate arguments. You don’t need to worry about the order of predicates anymore when you register a function for it.The new
classgeneric
functionality is part of the predicate system now; you can usereg.match_class
instead. Replace:@reg.classgeneric def foo(cls): pass
with:
@reg.dispatch(reg.match_class('cls', lambda cls: cls)) def foo(cls): pass
You can do this with any argument now, not just the first one.
pep443 support is gone. Reg is focused on its own dispatch system.
Compose functionality is gone – it turns out Morepath doesn’t use lookup composition to support App inheritance. The cached lookup functionality has moved into
registry.py
and now also supports caching of predicate-based lookups.Dependency on the future module is gone in favor of a small amount of compatibility code.
0.8 (2014-08-28)¶
- Added a
@reg.classgeneric
. This is like@reg.generic
, but the first argument is treated as a class, not as an instance. This makes it possible to replace@classmethod
with a generic function too. - Fix documentation on running documentation tests. For some reason this did not work properly anymore without running sphinxpython explicitly.
- Optimization: improve performance of generic function calls by
employing
lookup_mapply
instead of generalmapply
, as we only care about passing in the lookup argument when it’s defined, and any other arguments should work as before. Also added aperf.py
which is a simple generic function timing script.
0.7 (2014-06-17)¶
- Python 2.6 compatibility. (Ivo van der Wijk)
- Class maps (and thus generic function lookup) now works with old style classes as well.
- Marked as production/stable now in
setup.py
.
0.6 (2014-04-08)¶
- Removed unused code from mapply.py.
- Typo fix in API docs.
0.5 (2014-01-21)¶
- Make
reg.ANY
public. Used for predicates that match any value.
0.4 (2014-01-14)¶
- arginfo has been totally rewritten and is now part of the public API of reg.
0.3 (2014-01-06)¶
- Experimental Python 3.3 support thanks to the future module.
0.2 (2013-12-19)¶
- If a generic function implementation defines a
lookup
argument that argument will be the lookup used to call it. - Added
reg.mapply()
. This allows you to call things with more keyword arguments than it accepts, ignoring those extra keyword args. - A function that returns
None
is not assumed to fail, so no fallback to the original generic function is triggered anymore. - An optional
precalc
facility is made available onMatcher
to avoid some recalculation. - Implement a specific
PredicateMatcher
that matches a value on predicate.
0.1 (2013-10-28)¶
- Initial public release.
\ Sort by:\ best rated\ newest\ oldest\
\\
Add a comment\ (markup):
\``code``
, \ code blocks:::
and an indented block after blank line