惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
S
Securelist
P
Proofpoint News Feed
H
Help Net Security
S
Schneier on Security
T
Tenable Blog
C
Cisco Blogs
S
Security @ Cisco Blogs
博客园 - 司徒正美
博客园 - 叶小钗
Cisco Talos Blog
Cisco Talos Blog
Google DeepMind News
Google DeepMind News
C
Cybersecurity and Infrastructure Security Agency CISA
Google Online Security Blog
Google Online Security Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hacker News: Ask HN
Hacker News: Ask HN
NISL@THU
NISL@THU
云风的 BLOG
云风的 BLOG
V
Vulnerabilities – Threatpost
T
The Blog of Author Tim Ferriss
aimingoo的专栏
aimingoo的专栏
W
WeLiveSecurity
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Jina AI
Jina AI
腾讯CDC
WordPress大学
WordPress大学
Simon Willison's Weblog
Simon Willison's Weblog
Vercel News
Vercel News
小众软件
小众软件
N
Netflix TechBlog - Medium
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
雷峰网
雷峰网
Forbes - Security
Forbes - Security
The Hacker News
The Hacker News
博客园 - 聂微东
F
Full Disclosure
量子位
Scott Helme
Scott Helme
宝玉的分享
宝玉的分享
A
About on SuperTechFans
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Schneier on Security
Schneier on Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
Kaspersky official blog
AI
AI
SecWiki News
SecWiki News
Webroot Blog
Webroot Blog
Martin Fowler
Martin Fowler

Secret Weblog

Becoming More Xee: A Modern XPath and XSLT Engine in Rust Looking for new challenges! Repeat Yourself, A Bit The Curious Case of Quentell The Humble For Loop in Rust The Humble For Loop in JavaScript Don Question Best Practices I Was a 1980s Teenage Programmer Part 5: Achieving Assembly I Was a 1980s Teenage Programmer Part 4: The Call of Assembly The Tooling Shift I Was a 1980s Teenage Programmer Part 3: MSX-2 JavaScript: when you need two ways to do it! Empowering Programming Languages Bloat and Retrofuturism Refreshing my Blog Again Random Rust Impressions Apilar: An Alife System I Was a 1980s Teenage Programmer Part 2: Olivetti M24 I Was a 1980s Teenage Programmer: the Alphatronic SolidJS fits my brain Is premature optimization the root of all evil? Framework Patterns: JavaScript edition Roll Your Own Frameworks Framework Patterns Secret Weblog Highlights Refactoring to Multiple Exit Points mstform: a form library for mobx-state-tree Seven Years: A Very Personal History of the Web Morepath 0.16 released! Is Morepath Fast Yet? Introducing Bob Strongpinion Punctuated Equilibrium in Software Morepath 0.15 released! Impressions of React Europe 2016 Morepath 0.14 released! Morepath 0.13 now with Dectate Dectate: advanced configuration for Python code JavaScript Dependencies Revisited: An Example Project The Incredible Drifting Cyber A Brief History of Reselect The Emerging GraphQL Python stack Thoughts about React Europe Build a better batching UI with Morepath and Jinja2 GraphQL and REST Server Templating in Morepath 0.10 10 reasons to check out the Morepath web framework in 2015 A Review of the Web and how Morepath fits in Morepath 0.9 released! Better REST with Morepath 0.8 Morepath 0.7: new inter-app linking They say something I don Life at the Boundaries: Conversion and Validation BowerStatic 0.4 released! Morepath 0.6 released! Morepath 0.5(.1) and friends released! New HTTP 1.1 RFCs versus WSGI Against On Naming In Open Source My visit to EuroPython 2014 Morepath 0.4.1 released (with Python 3 fixes) Morepath 0.4 and breaking changes Announcing BowerStatic Morepath 0.3 released! Morepath 0.2 Morepath Python 3 support The Call of Python 2.8 Morepath 0.1 released! WebOb and Werkzeug compared Morepath: from Werkzeug to WebOb Racing the Morepath: SQLAlchemy Integration The Centre Cannot Hold Breaking Morepath Changes Morepath Update How to do REST with Morepath Morepath Security the Gravity of Python 2 #python2.8 discussion channel on freenode Alex Gaynor on Python 3 Morepath Documentation Starting to Take Shape Back to the Center Implementing Grok Grok: the Idea Why Linux Works for Me On the Morepath Reg, Now With More Generic! The New Zope as a Web Framework Jim Fulton, Zope Architect Renewing Zope Object Publishing The Weirdness of Zope The Rise of Zope My Exit from Zope Reg: Component Architecture Reimagined JSConf EU 2013 impressions Obviel 1.0! JS Dependency Tools Redux Obviel 1.0rc1 released! Succinct data structures
Morepath App Reuse
Martijn Faassen · 2013-12-03 · via Secret Weblog

I'm working on a new Python web microframework called Morepath, as I've mentioned before. Here's the code and here's a draft quickstart. Morepath is a microframework with a difference: it's small and easy to learn like the others, but has special super powers under the hood.

One of those super powers is Reg, which along with Morepath's model/view separation makes it easy to write reusable views. But in this article I'll talk about another super power: Morepath's application reuse patterns.

We'll talk about how Morepath lets you isolate applications, extend and override applications, and compose applications together. Morepath tries to make these possibilities simple, even obvious. Morepath strives to make the possible as obvious as possible.

Application Isolation

Morepath lets you create app objects like this:

app = morepath.App()

These app objects are WSGI applications, but also serve as registries for application configuration information. This configuration is specify used decorators. Typically apps consist of models and views:

@app.model(model=User, path='user/{username}',
           variables=lambda user: { 'username': user.username })
def get_user(username):
    return query_for_user(username)

@app.view(model=User)
def render_user(request, model):
    return "User: %s" % model.username

Here we've exposed the User model class under the path /user/{username}. When you go to such a URL, the default (unnamed) view will be found, and we've provided that too: it just renders "User: {username}".

What now if we have another app where we want to publish User in a different way? No problem, we can just create one:

other_app = morepath.App()
@other_app.model(model=User, path='different_path/{username}')
def get_user(username):
    return different_query_for_user(username)

@other_app.view(model=User)
def render_user(request, model):
    return "Differently Displayed User: %s" % model.username

Here we expose User to the web again, but use a different path and a different view. If you run this app (even in the same runtime), this will be separate.

This app isolation is nothing really special; it's kind of obvious that this is possible. But that's what we wanted. Let's look at a few more involved possibilities next.

Application Extension

Let's look at our first application app again. It exposes a single view for users (the default view). What now if we want to add a new functionality to this application so that we can edit users as well?

This is simple; we can add a new edit view to app:

@app.view(model=User, name='edit')
def edit_user(request, model):
    return 'Edit user: %s' % model.username

The string we return here is of course useless for a real edit view, but you get the idea.

But what if we have a scenario where there is a core application and we want to extend it without modifying it?

Why would this ever happen, you may ask? Well, it can, especially in more complex applications and reuse scenarios. Often you have a common application core and you want to be able to plug into it. Meanwhile, you want that core application to still function as before when used (or tested!) by itself. Perhaps there's somebody else who has created another extension of it.

This architectural principle is called the Open/Closed Principle in software engineering, and Morepath makes it really easy to follow it. What you do is create another app that extends the original:

extended_app = morepath.App(extends=[app])

And then we can add the view to the extended app:

@extended_app.view(model=User, name='edit')
def edit_user(request, model):
    return 'Edit user: %s' % model.username

Now when we publish extended_app using WSGI, the new edit view will be there, but when we publish app it won't be.

Kind of obvious, perhaps. Good. Let's move on.

Application Overrides

Now we get to a more exciting example: overriding applications. What if instead of adding an extension to a core application you want to override part of it? For instance, what if we want to change the default view for User?

Here's how we would do that:

@extended_app.view(model=User)
def render_user_differently(request, model):
    return 'Different view for user: %s' % model.username

We've now overridden the default view for User to a new view that renders it differently.

You can also do this for what is returned for model paths. We might for instance want to return a different user object altogether in our overriding app:

@extended_app.model(model=OtherUser, path='user/{username}')
def get_user_differently(username):
    return OtherUser(username)

To make OtherUser actually be published on the web under /user/{username} it either needs to be a subclass of User, for which we've already registered a default view, or we need to register a new default view for OtherUser.

Overriding apps actually doesn't look much different from how you build apps in the first place. Hopefully not so obvious that it's boring. Let's talk about something new.

Nesting Applications

Let's talk about application composition: nesting one app in another.

Imagine our user app allows users to have wikis associated with them. You would have paths like /user/faassen/wiki and /user/bob/wiki.

One approach might be to implement a wiki application within the user application we already have, along these lines:

@app.model(model=Wiki, path='user/{username}/wiki')
def get_wiki(username):
    return wiki_for_user(username)

@app.view(model=Wiki)
def wiki_default_view(request, model):
    return "Default view for wiki"

(this is massively simplified of course. we'd also have a Page model that's exposed on a sub-path under the wiki, with its own views, etc)

But this feels bad. Why?

  • Why would we implement a wiki as part of our user app? Our wiki application should really be an app by itself, that we can use byitself and also test by itself.
  • There's the issue of the username: it will appear in all paths that go to wiki-related models (the wiki itself, any wiki pages). But why should we have to care about the username of a user when we are thinking about wikis?
  • It would also be nice if we can use the wiki app in other contexts as well, instead of only letting it be associated with users. What about associating a wiki app with a project instead, like you can do in github?

A separate app for wikis seems obvious. So let's do it. Here's the wiki app by itself:

wiki_app = morepath.App()

@wiki_app.model(model=Wiki, path='{wiki_id}')
def get_wiki(wiki_id):
    return query_wiki(wiki_id)

@wiki_app.view(model=Wiki)
def wiki_default_view(request, model):
    return "Default view for wiki"

This is an app that exposes wikis on URLs using wiki_id, like /my_wiki, /another_wiki.

But that won't work if we want to associate wikis with users. What if we want the paths we had before, like /user/faassen/wiki?

Morepath has a solution. We can mount the wiki app in the user app, like this:

@app.mount(app=wiki_app, path='user/{username}/wiki')
def mount_wiki(username):
    return {
       'wiki_id': get_wiki_id_for_username(username)
    }

We do need to adjust the wiki app a bit as right now it expects wiki_id to be in its paths, and the wiki id won't show up when mounted. This is a simple adjustment: we need to register the model so that its path is empty:

@wiki_app.model(model=Wiki, path='')
def get_wiki(wiki_id):
    return query_wiki(wiki_id)

But where does wiki_id come from now if not from the path? We already have it: it was determined when the app was mounted, and comes from the dictionary that we return from mount_wiki().

What if we want to use wiki_app by itself, as a WSGI app? That can be useful, also for testing purposes. It needs this wiki_id parameter now. We can construct this WSGI app from wiki_app by giving it a context explicitly:

wiki_app.context(wiki_id=5)

Application Reuse

Many web frameworks have mechanisms for overriding specific behavior and to support reusable applications. These tend to have been developed in an ad-hoc fashion as new needs arose.

Morepath instead has a general mechanism for supporting app extension and reuse. You use the same principles and APIs you already use to create new applications. Any normal Morepath app can without extra effort be reused. Anything registered in a Morepath app can be overridden. This is because Morepath builds on a powerful general configuration system.

This is because Morepath, like Grok or Pyramid, comes from the rich Zope heritage, where we've thought about this stuff. And Morepath wraps all that power in a small, easy, reusable little framework.

Let me know what you think!