Morepath 0.6 released!

What's Morepath? Morepath is your friendly neighborhood web framework with super powers. It lets you easily create links between resources, and offers a range of mechanisms that allow you to better organize and reuse code. Morepath is geared towards this modern age of the web where more and more UI logic is moving into JavaScript, into the browser -- it does this by being great at creating RESTful hypermedia APIs.

Besides a few documentation fixes, Morepath 0.6 has a minor improvement and a major improvement.

Both improvements have to do with a relatively obscure use case that I ran into lately. Application composition should be an important feature in a modern web framework, and so does linking, but we only rarely see things like this. That we run into use cases like this goes to show just how far Morepath is ahead in exploring this area. See nesting applications and linking to things in other apps for more information on these subsystems of Morepath.

The major improvement is the ability to link to other applications by the name under which they've been mounted into their parent. By default the name is the path under which they were mounted. Imagine you have the following URL space:

/v1/
/v1/a
/v2/a

You can model this as two applications, A and B that are mounted under a core application mounted at v1. That would look like this in Morepath:

class V1(morepath.App):
    pass

# makes a root object exist under /v1
@app.path(path='/v1')
class Root(object):
    pass

class A(morepath.App):
    pass

class B(morepath.App):
    pass

// mounts everything in app A under /v1/a
@V1.mount(app=A, path='a')
def a_context():
    return {}

// mounts everything in app B under /v1/b
@V1.mount(app=B, path='b')
def b_context():
    return {}

Consider how you'd make a link from app A to a resource in app B given this setup. In Morepath before 0.6, you'd have to write:

request.parent.child(B).link(obj)

This would create a link to whatever obj is (which depends on its path), for instance:

/v1/b/items/3

The minor improvement is that we realized the .parent.child combination happens a lot and we've introduced a new sibling method to combine them in one step:

request.sibling(B).link(obj)

Now considers what happens when a new incompatible version of your overall API arises, because you've changed something fundamentally in app B. Perhaps items appear on a /foos path instead of an /items path, like:

/v2/b/foos/3

You've not changed anything in app A though. What you'd like to do is mount the new B and the old A into a V2 app and have everything work as expected:

class V2(morepath.App):
    pass

// mounts everything in app A under /v2/a
@V2.mount(app=A, path='a')
def a_context():
    return {}

// mounts everything in app NewB under /v2/b
@V2.mount(app=NewB, path='b')
def b_context():
    return {}

But this is problematic, as we have a hardcoded dependency on app B in app A in the link generation code. Now we'd like to link to app NewB instead of B. But we'd want the original v1 URLs to still work as before, so we can't just modify app A so to include a link to NewB. So in /v1/a we'd like links to look like this:

/v1/b/items/3

But in /v2/a we'd like links to go to the new place in NewB:

/v2/b/foos/3

The solution is the new ability to find mount applications by name instead of by class. By the default the name is the same as the path argument you give in the mount directive.

If you write linking code in app A to read like this:

request.sibling('b').link(obj)

there is no more hardcoded dependency on app B. Instead the system now relies on the sibling app mounted under b to create the link, whatever it may be. And if A is mounted under /v1 the sibling will be B, but if it's mounted under /v2 the sibling will be NewB. So the links will be correct in both cases, and we're saved!

Comments

Comments powered by Disqus