In a previous article I wrote about the need to update your Datastore indexes on App Engine before deploying code that relies on them. To simplify the deployment process, I put together a script that waits for your indexes to be built before deploying new code.
#!/usr/bin/env python # Need to import and fix the sys path before importing other things. import remote_api_shell remote_api_shell.fix_sys_path() import time from google.appengine.api import datastore_admin from google.appengine.ext.remote_api import remote_api_stub from google.appengine.tools import appengine_rpc APP_ID = 'your-app-id' def configure_remote_api(): def auth_func(): return ('[email protected]', 'your.application.specific.password') remote_api_stub.ConfigureRemoteApi( APP_ID, '/_ah/remote_api', auth_func, servername='%s.appspot.com' % APP_ID, save_cookies=True, secure=False, rpc_server_factory=appengine_rpc.HttpRpcServer) remote_api_stub.MaybeInvokeAuthentication() def main(): print 'Checking indexes...' configure_remote_api() interval = 10 # seconds. building = True while building: # Start with a fresh run: maybe we're done building? building = False for index in datastore_admin.GetIndices('s~%s' % APP_ID): # If any single index is building, we're not done. # Sleep for a bit and then this cycle should repeat. if not index.has_state() or index.state() != index.READ_WRITE: building = True print 'Indexes are still building... Waiting %s seconds.' % interval time.sleep(interval) break print 'All indexes are up to date.' if __name__ == '__main__': main()
Since this script will "block" until all indexes are built and ready, you can add this line to your deployment script, which should look something like...
Every so often, I get an e-mail asking me for feedback on an idea, with a generic NDA attached that I'm asked to sign before I can hear what the idea is... I don't want to come off the wrong way, so I try to let others do the talking:
TL;DR: Nobody should sign an NDA just to hear your idea.
Mar 2000: http://www.joelonsoftware.com/articles/fog0000000071.html
Oct 2005: http://www.iwillteachyoutoberich.com/blog/the-myth-of-the-great-idea/
Feb 2006: http://www.feld.com/wp/archives/2006/02/why-most-vcs-dont-sign-ndas.html
Feb 2006 (agreement): http://www.avc.com/a_vc/2006/02/why_vcs_dont_si.html
Jun 2009: http://mixergy.com/why-i-wont-sign-your-nda/
Aug 2009: http://cdixon.org/2009/08/22/why-you-shouldnt-keep-your-startup-idea-secret/
May 2010: http://dashes.com/anil/2010/05/one-more-time-no-ndas.html
Jan 2011: http://programmers.stackexchange.com/questions/33560/do-you-keep-your-ideas-secret-and-why
Feb 2011: http://boss.blogs.nytimes.com/2011/02/21/got-a-great-idea-tell-everyone/
Feb 2011: http://articles.businessinsider.com/2011-02-17/strategy/30002166_1_business-advice-secret-job-title
Mar 2012: http://www.yusufuqdah.com/2012/03/tell-everyone-about-your-business-idea.html
Apr 2012: http://blog.jpl-consulting.com/2012/04/why-i-wont-sign-your-nda/
Oct 2012: http://boss.blogs.nytimes.com/2012/10/19/stop-trying-to-protect-your-business-ideas/
I'm happy to give feedback (who knows if it'll even be useful to you...), however the points in the last article summarize the theme very well:
Your idea already exists or Being new can be a problem
The value is not in the idea
Sharing ideas makes them better
If you're going to share anything generally considered "secret sauce" (such as access to your code repository or the special formula for the Red Bull competitor you're starting), an NDA is fine (a first e-mail typically wouldn't be the place to discuss sharing that level of detail though...).
Otherwise, see your idea for what it is: something that hasn't materialized yet and will likely change a lot as you work more on it.
As a long-time user of GNU screen, I knew it would be difficult to switch, but I've made the move and am so far pretty happy. tmux does all the same things that screen does and was a lot easier to customize. I figured I would share my tmux configuration file and some of the things that made the switch relatively painless.
Keyboard bindings
I changed the default keyboard bindings to be more like screen:
Ctrl-b is the default for tmux; I prefer Ctrl-a.
Ctrl-(left/right) arrow to move between windows. No screen equivalent (that I know of).
Ctrl-a twice to go "back".
Setting Ctrl-a, r to reload the config file made it very easy to debug.
Status bar at the bottom
I set my status bar to use the session name and my machine name on the far left, open tab names centered in the middle, and the system time and date on the far right.
Project-specific configurations (and attach tool)
I forked a repository called "tat" that does some neat things to make project-specific configurations really easy to set up, and attach to them later on. It also includes tab-completion for existing sessions and your project directory names. Check out my fork to see the details. (Update: the pull request was merged.)
With tat you can create a .tmux file in your project directory, which will be run with the session name as first argument. You can use this to configure your project work space easily and consistently. Here's one of my example .tmux project files.
Tab-completion
Enabling tab-completion for tmux was easy. On Ubuntu I did the following:
A while back I wrote some (now deprecated) code that allowed you to easily test your Python App Engine applications and their interaction with the App Engine APIs. When we got acquired by Google, I started working with some of the awesome App Engine engineers on making that code part of the official App Engine codebase.
We launched that, but one of the APIs that was noticeably lacking helper-methods was the Task Queue. I added one of the old methods (get_filtered_tasks()) but a bit later I noticed a pretty serious bug where timezones weren't handled properly.
So that code hid quietly in the App Engine code base, undocumented and technically broken. Sorry :(
I wrote some extra code to fix it, but that didn't get merged into the main repository for quite a while.
But now it's here.
If you're curious about the change, feel free to check out the diff (scroll to the bottom). The method is still called get_filtered_tasks() and takes several arguments as "filters" for various properties of tasks -- and it should properly handle timezones when you set the eta or countdown properties when creating tasks.
Sorry for the delay. And enjoy.
PS: Since this isn't documented on the official App Engine page, the API may change. I don't intend to change it, but I can't promise it won't. Apologies in advance if you use this helper method and have it break out from under you.
If you're building a web-app in Python that requires people to sign in, you are probably pretty familiar with the concept of a @login_required decorator. For those that might not be, it's pretty straight forward: the decorator wraps a handler method with a check for whether the current request is coming from someone who has been through your sign in process.
When I first saw this however many years ago, I was pretty new to Python decorators and thought it was pretty awesome. I still think the whole concept is pretty awesome. This seems to fit the mold of a "decorator" pretty perfectly... almost as if decorators were built with this in mind...
But what if you forget your @login_required decorator? It's so easy to do. When you're testing that "Edit your account" page, you're authenticated, you assume everything should go to plan. You could (and should) add a functional test to check that an unauthenticated request is redirected, but this is just a quick little side project...
So why don't we just make login_required the default? I think that it should be, so here's some code to save somebody else some time.
It works by wrapping all local methods with your login_required method during object creation (aka,__new__). If you want a method to be public, you decorate it with the login_not_required method -- which sets a flag on the method saying ... that login is not required.
Here's the BaseHandler (and Meta class) which does the wrapping:
import types import webapp2 from auth import login_required class BaseHandlerMeta(type): """Meta class for all request handlers. This automatically wraps all handler methods with the login_required decorator. If something should be exposed publicly, it should be wrapped with the login_not_required decorator. """ def __new__(cls, name, bases, local): if name != 'BaseHandler': for func_name, func in local.iteritems(): if isinstance(func, types.FunctionType): local[func_name] = login_required(func) return type.__new__(cls, name, bases, local) class BaseHandler(webapp2.RequestHandler): """Base class for all RequestHandlers.""" __metaclass__ = BaseHandlerMeta def user_is_logged_in(self): # Do some magic here to check if someone is logged in return False
Here are the login_required and login_not_required decorators:
def login_not_required(handler_method): """Allows a user to *not* be logged in. The login_required attribute is inspected by BaseHandlerMeta and is used as the flag for whether to wrap a method with login_required or not. """ handler_method.login_required = False return handler_method def login_required(handler_method): """Requires that a user be logged in.""" required = getattr(handler_method, 'login_required', True) already_wrapped = getattr(handler_method, 'wrapped', False) # If the method doesn't require a login, or has already been wrapped, # just return the original. if not required or already_wrapped: return handler_method def check_login(self, *args, **kwargs): if not self.user_is_logged_in(): uri = self.uri_for('login', redirect=self.request.path) self.redirect(uri, abort=True) else: return handler_method(self, *args, **kwargs) # Let others know that this method is already wrapped to avoid wrapping # it more than once... check_login.wrapped = True return check_login
This would make your request handlers look something like this:
import base from auth import login_not_required class MyHandler(base.BaseHandler): @login_not_required def my_public_method(self): self.response.write('Hello world!') @login_not_required def my_public_method_with_a_problem(self): # Calling this should force a redirect to the login page! self.my_other_handler_that_is_protected() def my_other_handler_that_is_protected(self): self.response.write('Hello privately!') def protected_by_default(self): self.response.write('Once you log in, you can view this!')
Notice that if you are in a public method (@login_not_required) and you call a method that does *not* have that decorator, you'll get redirected to a login page. If you want something to be public, it and all of the functions it calls (inside the handler) should be explicitly defined as public.
I used to do crazy things like hard-code URLs in my templates:
<a href="/accounts/create/">Create your account</a> or <a href="/accounts/view/?id={{ the_id }}">View account {{ the_id }}</a>
Ever since webapp2 came around, I've been really into the URL routing helpers, which make it much easier to keep long lists of URLs organized. I started using a new project layout that makes routing really simple -- maybe even fun.
Before I get to the interesting part I need to digress slightly. A while back, I posted a question on Stack Overflow asking whether it was better to break your app apart into separate routes in app.yaml or inside aWSGIApplication. Put simply: should you create one WSGIApplication object and route everything there? Or many separate ones, each with their own routing in app.yaml?
The short answer was: It doesn't really matter.
The longer answer was: if you're particularly sensitive about start-up time or memory, you might break things apart.
I decided to use one big app. Having a single app makes unit testing with ext.testbed a bit easier.
In addition to making testing easier, I've been using the Python 2.7 runtime which lends itself nicely to a single WSGIApplication: I put my code in main.py and then route /.* to main.app and don't think about it anymore. But how are you supposed to keep track of all the URLs, Handlers, methods, and routes in your app as it grows larger and larger?
webapp2 to the rescue.
Take a look at routes.py in this gist:
from webapp2_extras.routes import RedirectRoute from handlers.account import AccountHandler __all__ = ['application_routes'] application_routes = [] _route_info = [ ('account.list', 'GET', '/accounts/', AccountHandler, 'list'), ('account.create', None, '/accounts/create/', AccountHandler, 'create'), ('account.view', 'GET', '/accounts/<id:\d+>/', AccountHandler, 'view'), ('account.delete', None, '/accounts/<id:\d+>/delete/', AccountHandler, 'delete'), ('account.update', None, '/accounts/<id:\d+>/update/', AccountHandler, 'update'), ] for name, methods, pattern, handler_cls, handler_method in _route_info: # Allow a single string, but this has to be changed to a list. # None here means any method if isinstance(methods, basestring): methods = [methods] # Create the route route = RedirectRoute(name=name, template=pattern, methods=methods, handler=handler_cls, handler_method=handler_method) # Add the route to the public list application_routes.append(route)
There are a few things to notice here:
You could type out all the boiler-plate every time. I don't think routes have enough distinguishing factors to merit that. I removed the boiler plate and put together a list of tuples.
Each tuple defines a route: (name, methods, URL pattern, HandlerClass, handler_method).
Routes are named as thing.action (thing is singular -- not things).
The method can be 'GET', 'POST', None, or any other method (or list of methods). None simply means "I don't care".
URL patterns allow fancy matching (<id:\d+>).
Rather that having many handlers (CreateUserHandler, DeleteUserHandler), I use one handler per "thing" and have the methods correspond to the actions (UserHandler.create, UserHandler.delete). If necessary, I can use self.request.method to distinguish between intent (ie, "show me the account creation form" versus "create my account and rediect me somewhere").
The handler method name (AccountHandler.list) and the "action" part of the route name ('account.list') are identical. This makes linking easy -- you know the route's name because it lines up with your code.
I break from PEP8 about lining data up vertically. This is data you'd put in a table. I don't like jagged tables.
Now to tie this all together, I just put some configuration for webapp2 into config.py:
import webapp2 from config import webapp2_config from routes import application_routes app = webapp2.WSGIApplication(routes=application_routes, config=webapp2_config)
After that, your handlers might look like this:
import webapp2 from webapp2_extras import auth from webapp2_extras import jinja2 from webapp2_extras import sessions class BaseHandler(webapp2.RequestHandler): @webapp2.cached_property def jinja2(self): return jinja2.get_jinja2(app=self.app) @webapp2.cached_property def auth_config(self): return {'login_url': self.uri_for('login'), 'logout_url': self.uri_for('logout')} @webapp2.cached_property def auth(self): return auth.get_auth() @webapp2.cached_property def session_store(self): return sessions.get_store(request=self.request) def dispatch(self): """Override dispatch to persist session data.""" try: super(BaseHandler, self).dispatch() finally: self.session_store.save_sessions(self.response) def render_template(self, template, context=None): context = context or {} extra_context = { 'request': self.request, 'uri_for': self.uri_for, } # Only override extra context stuff if it's not set by the template: for key, value in extra_context.items(): if key not in context: context[key] = value rendered = self.jinja2.render_template(template, **context) self.response.write(rendered)
from handlers import base class AccountHandler(base.BaseHandler): def create(self): if self.request.method == 'POST': # Create the account pass # Redirect them to the dashboard page return self.redirect(self.uri_for('account.view')) else: return self.render_template('account_create.html')
And your templates can now create links that look like this:
<a href="{{ uri_for('account.create', my_param='something') }}">Create your account</a> or <a href="{{ uri_for('account.view', id=the_id) }}">View account {{ the_id }}</a>
Extra bonus: If you delete a URL and the template tries to look it up, you'll get an exception -- and so will your unit tests. I guess this could be a scary thing if you don't have tests, but hopefully it is helpful.
Since we were acquired by Google, I figured that it would be a fitting 20% project to work with some App Engine engineers to move GAE Testbed into the App Engine Python SDK. The benefits are pretty obvious:
No third-party package to install (ie, no more easy_install gaetestbed)
"Official" documentation for how to write your tests
More people working on making it easy for users to test their code
Many maintainers, meaning fewer regressions (ie, no more "whoops, the API changed a bit... GAE Testbed doesn't work right now...")
And I'm incredibly happy to say that that the 1.4.3 release includes the first steps toward no longer needing GAE Testbed at all: google.appengine.ext.testbed! The testbed module sets up your environment so that you're able to verify that API calls you expected to be made are actually made.
For example, you can put something in memcache and then get it back to out to verify your application logic. Of course, this isn't using an actual memcache instance, but looks and acts mostly the same.
I had very little involvement with the code in this release (just a reviewer), but there's plenty more to come in the future. Many thanks to Robert Schuppenies and Guido van Rossum for helping get this huge improvement out the door.
P.S. Although there isn't yet a way to do things like self.assertEmailSent(to="[email protected]"), I hope that feature parity between google.appengine.ext.testbed and GAE Testbed is only a short while away.
So, you have an AVI file and a SRT file, and you want to put them together so that you don't have to carry around the two files... (and you're on Ubuntu...)
There have been a lot of blog posts (one of mine included...) that talk about using feature branches to have an "agile" workflow while you code. This is all great to talk about but it seemed to me that a lot of people weren't doing it. Not because they were against the idea but because it was too tricky to remember or too complicated to do or too annoying to do for that one little feature.
Then there are tools like git flow that do the trick but they seemed like kind of a lot of typing to me, so I threw something together that was three aliases to help make my workflow involve less typing, less remembering, and less confusion.
Note: If you ask me, "what git workflow tool should I use?" my answer will be git flow. git flow is FAR better documented, supported, and tested than the tool that I threw together today...
Now... it is with great pleasure that I introduce git-feature. It's simple to install (all three commands are git aliases -- you know, those things in .git/config?), simple to remember (if you forget arguments, it will prompt you for them), mostly safe (it prompts you before doing anything that changes your repository), and has very little magic (those git aliases are just shell commands, think of this as a few friendly shortcuts...).
So, getting right down to the dirt... here's how I currently use it:
$ git branch * master $ git feature my-new-feature this will create a feature branch my-new-feature to be merged into master (Y/n): Y $ git branch * features/my-new-feature master $ # Do some work here, commit it... $ git finish this will integrate my-new-feature into master (Y/n): Y $ git branch * master
If you're actually wondering, "Hmm... maybe I could use this!", you'll probably have lots of questions like...
what if I forget arguments? or...
what if I want my feature to based off a different parent? or...
what if I want to integrate my feature into two different parents?
Most of there are answered on the project's GitHub page: http://github.com/jgeewax/git-feature but if you have one that isn't answered, I'd guess somebody else had that question, so let me know and I'll try to come up with an answer for everyone.
Deploying code on App Engine? Be careful with your indexes...
Have you ever deployed a new version of your application on App Engine and end up with this rude error saying something like, "You need an index to run that query"? So you scratch your head for a minute, check index.yaml, and sure enough that index is there just like you thought... After all, most (if not all) of your index.yaml is auto generated by the SDK...
It looks like the SDK is so good about keeping the contents of index.yaml off your mind that you forget about the time it takes to build those indexes!
I'm sure it's written down somewhere but those of us that are so eager to get code out the door are clearly not the ones reading the fine print in the documentation... The short version of all of this is...
When you have modifications to index.yaml (adding a new index for example), it takes time for App Engine to build those indexes. However, the code you deployed is running right away and that code needs those indexes right away. So you end up with a nasty mean error all because you were a bit too hasty with kicking out your code (and the SDK is too busy pushing your files up to Google's servers to warn you: "Hey wait a minute! This index is going to take a while to build...")
Anyway, I figured I'd share the simple way I avoid getting bitten by this little annoyance...
First, I use git. If you're not using any sort of source control, you should probably start doing that now. Even if you don't want to share your source with anyone, having a local git repository is great... "Whoops! Let me revert back to where things were 10 minutes ago..."
If you're using git, this problem is fixed by breaking apart your changes into two pieces: the index changes and the code that needs the index changes. If you're working on Feature X and there are changes to index.yaml (git diff index.yaml), I commit those changes first (git add index.yaml && git commit -m "Updating indexes") and then stash the other stuff on a new branch (git checkout -b feature-x && git add -A && git commit -m "Feature X"). Then switch back to your previous branch (git checkout master) and deploy JUST those index changes (make deploy*) and then wait!
Go into the Data store Indexes page on the App Engine admin console and keep an eye on that page. It will show your new indexes building and queued and all that lovely stuff (which could take a while).
Once you see that your new index is "serving", just merge in your other branch (it should be a simple fast-forward) (git merge feature-x) and deploy again (make deploy). And you're all set!
I'm sure there are other ways of doing this so don't take this as the only way to handle the problem, but this way seems to work pretty well for me.
Happy deploying (and indexing).
* Deploying with "make deploy" is thanks to AppMake. It makes deploying really simple...
First, let me apologize in advance: I really hate reinventing the wheel.
OK, now that that's out of the way...Helipad is a very light-weight framework built on top of webapp (the framework built-in to Google App Engine) that adds some of the stuff I felt was stalking me across all my projects:
Less boilerplate code for handlers (in particular for routes pointing to a file with only one handler)
import helipad class HelloHelipadHandler(helipad.Handler): def get(self): self.response.out.write('Hello, Helipad!') main, application = helipad.app(HelloHelipadHandler) if __name__ == "__main__": main()
Cool -- that was pretty easy...
For comparison, here's the way webapp has you do a HelloWorld application:
from google.appengine.ext import webapp from google.appengine.ext.webapp.util import run_wsgi_app class MainPage(webapp.RequestHandler): def get(self): self.response.headers['Content-Type'] = 'text/plain' self.response.out.write('Hello, webapp World!') application = webapp.WSGIApplication([('/', MainPage)], debug=True) def main(): run_wsgi_app(application) if __name__ == "__main__": main()
Take note of a few things that you didn't have to do:
Re-define the URL mapping in hellohelipad.py
Manually create a WSGI application based on your handler
Define a main method that serves your application
Now that you have the basics, let's take a look at some of the other fun stuff :)
Simpler URL Routing
If you have just a single route in app.yaml pointing at your script, you don't need to redefine the route in the file, but what if you have a wildcard pointing at your script? For example, say your app.yaml file looks like this:
from google.appengine.ext import webapp from google.appengine.ext.webapp.util import run_wsgi_app class AboutMeHandler(webapp.RequestHandler): def get(self): self.response.out.write('About Me!') class AboutCompanyHandler(webapp.RequestHandler): def get(self): self.response.out.write('About Company!') application = webapp.WSGIApplication([('/about/me/', AboutMeHandler), ('/about/company/', AboutCompanyHander)], debug=True) def main(): run_wsgi_app(application) if __name__ == "__main__": main()
With Helipad, you can do your routing the same way (with a list of tuples) like this:
import helipad class AboutMeHandler(helipad.Handler): def get(self): self.response.out.write('About Me!') class AboutCompanyHandler(helipad.Handler): def get(self): self.response.out.write('About Company!') main, application = helipad.app([ ('/about/me/', AboutMeHandler), ('/about/company/', AboutCompanyHandler), ]) if __name__ == "__main__": main()
or if the order doesn't matter to you (which in this case it doesn't) you can use a simple Python dictionary to map URLs to handlers like this:
import helipad class AboutMeHandler(helipad.Handler): def get(self): self.response.out.write('About Me!') class AboutCompanyHandler(helipad.Handler): def get(self): self.response.out.write('About Company!') main, application = helipad.app({ '/about/me/': AboutMeHandler, '/about/company/': AboutCompanyHandler, }) if __name__ == "__main__": main()
Finally, if you have a common prefix across all your URLs (in this example, all of our routes are prefixed with "/about/"), you can define that prefix once and it will be automatically prepended to your URLs. This might make the previous handler look like this:
import helipad class AboutMeHandler(helipad.Handler): def get(self): self.response.out.write('About Me!') class AboutCompanyHandler(helipad.Handler): def get(self): self.response.out.write('About Company!') main, application = helipad.app('/about/', { 'me/': AboutMeHandler, 'company/': AboutCompanyHandler, }) if __name__ == "__main__": main()
Finding and opening files
One of the things that I realized I never have in my projects is a consistent way of finding and opening files. When I was working on Django applications I would always create a directory for my templates and add that relative to my project directory using os.path and __file__ which has started to feel like boilerplate code. To deal with this, I threw together a simple way of defining a root module and then two helper methods (find_file and open_file) which will get the file you request relative to your root module. That is, if Helipad is located at /home/jj/helipad.py, and you set the root to the Helipad module, 'path/to/myfile.html' will resolve to '/home/jj/path/to/myfile.html'.
This is extremely useful when you don't care where your project is on the disk, you just care about files relative to your project. For example, say the About Me page needed to read from a file... (we're dropping the About Company page for now)
import helipad class AboutMeHandler(helipad.Handler): def get(self): about_me = helipad.open_file('templates/about_me.html') self.response.out.write(about_me.read()) # This sets the root to our project module helipad.root('hellohelipad') main, application = helipad.app(AboutMeHandler) if __name__ == "__main__": main()
This will let you read static content that you've uploaded to App Engine and need to display in one way or another, or if you need to render a template you now don't have to worry "where is my template file?". This actually brings us to our next topic...
Serving static files (with fancy URLs)
The example above shows you how to use helipad.root(), helipad.find_file(), and helipad.open_file() to access files you've included with your project on App Engine, but that seems still like boilerplate code that you may end up copying and pasting around a lot. Keeping with the theme of DRY, Helipad provides a few different ways of serving static files without so much typing.
The three ways to do this are...
Using helipad.Handler.static() inside a regular handler
Using helipad.static() to create a StaticApplication for serving a single file
Using helipad.static() to create a StaticApplication with a URL mapping for many static files
For the first, instead of using helipad.open_file() and self.response.out.write(), you can just use self.static() like this...
import helipad class AboutMeHandler(helipad.Handler): def get(self): self.static('templates/about_me.html') helipad.root('hellohelipad') main, application = helipad.app(AboutMeHandler) if __name__ == "__main__": main()
If there really is nothing else your handler needs to do besides serve a single static file, you can use helipad.static() to generate the boilerplate handler for you like this:
If we were to bring back the About Company page, you could map a bunch of URLs to static files just like you'd map a bunch of URLs to handlers. You can do this with a list of tuples or, if order doesn't matter, with a Python dictionary. Additionally, just like with helipad.app(), you can use a URL prefix to be prepended to all of your URLs. Here's an example showing all of that:
I had to make a call on templating and what to include, but I personally really like Jinja2. The syntax is pretty similar to Django's template language except it's a bit less restrictive. Because of my preference for Jinja2, this template language comes baked in to Helipad. Just like helipad.Handler.static(), you can use helipad.Handler.template(). The one extra piece is that if you get tired of defining your template directory over and over, you can define a template_root which is just prepended to all of your template file paths (which is then based on your helipad.root()). Perhaps this is best explained with an example... (see comments in-line)
import helipad helipad.root('hellohelipad').template_root('templates/') class AboutMeHandler(helipad.Handler): def get(self): # This will render the template hellohelipad/templates/template.html # Where hellohelipad is wherever the hellohelipad module is located. return self.template('template.html', { 'my_template_variable': 'This is a template variable!', }) main, application = helipad.app(AboutMeHandler) if __name__ == '__main__': main()
Cookies
Handling cookies with Helipad is really naive. helipad.Handler has three methods related to cookies:set_cookie(), get_cookie(), and clear_cookie(). These should be self explanatory and take care of all the garbage with headers and whatnot which I got tired of having to reinvent every time I made a project. There may be some subtle bugs in here, but so far everything has been fine for me.
Sessions
Sessions in Helipad are equally simple. helipad.Handler has a property called session which gives you back a Session object which has a few methods for you to use: get(), set(), and delete(). The way this works is the Session generates a random key (stored in a cookie) which then serves as the namespace for Memcache operations (get, set, and delete). Since Memcache is behind all of this, expiration is out of our hands, but hopefully you're not putting anything critical in your sessions anyway...
Conclusion and future plans
So far, Helipad has just been a place where I end up putting stuff I notice needing in every project (such as cookies, sessions, etc). The goal has mainly been to make it simpler to do the common stuff (like serving a static file) but still make it easy to bypass all of that stuff if I need to (for example, you can do anything you want in a handler...) and I think that's worked out pretty well. There must be a few bugs here and there (particularly in the cookie and session area...) so any help fixing those would be awesome, but otherwise Helipad seems to be meeting my needs, and hopefully it meets yours and can help to speed up your webapp development.
If you're interested in helping out at all, you can find all the code on GitHub at http://github.com/jgeewax/helipad
Managing lots of different App Engine applications gets pretty tricky, so I threw together a little Makefile to make it easy to run the debug server, start the development console, deploy the application, etc.
This Makefile turns running the development server from:
$ python /usr/local/google_appengine/dev_appserver.py -a 0.0.0.0 -p 9091 --datastore_path=./datastore --disable_static_caching .
into
$ make serve
The full list of commands is:
$ make help AppEngine make file. Options are: test Runs the test suite coverage Runs the test suite and prints a coverage report deploy Deploys the current project to AppEngine rollback Rolls back a unclosed update to the application serve Runs the development web server console Opens a development console to your remote application (Only works if you've enabled the /remote_api URL)
The code is open sourced on GitHub at http://github.com/jgeewax/appmake, and to get started, all you have to do is type...
In the early days of Invite Media we always talked about being acquired but I didn’t really think it would ever happen. Amazingly, back in June we were acquired by Google. (You can read about it in the official DoubleClick blog post.)
We’re all still getting ourselves situated at Google — our NYC engineers are happily settling into Google’s NYC office and Google is setting up a new office in Center City for the Philly engineers — but for the most part the work is the same. I’m still trying to find my way around, but everybody I’ve met so far has been really helpful.
Looks like I officially work for Google. Let the excitement begin…
We use Jira for a whole lot of things, but one of the major uses is for Bug reports. That said, sometimes people forget important information when filing a bug so I wanted to have the default description for Bug reports have some prompts for what information we’d need to reproduce the bugs (sort of like Google Code’s bug report interface).
Unfortunately, there’s no nifty UI to edit the default value for the description field in Jira, but there’s a nice Knowledge Base article which describes how you can do it by editing the template file used to render the description field.
That file is: /path/to/atlassian-jira/WEB-INF/classes/templates/jira/issue/field/description-edit.vm
It wasn’t entirely clear how to do different things based on the issue type, etc so I had to poke around a bit to figure out what’s different, the section I wrote ended up as:
#if ($description == '' && $issue.getIssueType().getString('name') == 'Bug') #set ($description = "Steps taken to produce this bug:\ \ \ Expected Results:\ \ \ Actual Results:\ \ \ Extra Information:\ \ ") #set ($description = $description.replace('\', '')) #end
This makes the entire description-edit.vm file look like this:
jira@jira1:~$ cat ~jira/atlassian-jira/WEB-INF/classes/templates/jira/issue/field/description-edit.vm #controlHeader ($action $field.id $i18n.getText($field.nameKey) $fieldLayoutItem.required $displayParameters.get('noHeader')) ## setup some additional parameters $!rendererParams.put("rows", "12") $!rendererParams.put("wrap", "virtual") ## Add our default value #if ($description == '' && $issue.getIssueType().getString('name') == 'Bug') #set ($description = "Steps taken to produce this bug:\ \ \ Expected Results:\ \ \ Actual Results:\ \ \ Extra Information:\ \ ") #set ($description = $description.replace('\', '')) #end ## let the renderer display the edit component $rendererDescriptor.getEditVM($!description, $!issue.key, $!fieldLayoutItem.rendererType, $!field.id, $!field.name, $rendererParams, false) #controlFooter ($action $fieldLayoutItem.getFieldDescription() $displayParameters.get('noHeader'))
Hopefully this helps someone else who wants to provide defaults for system fields like Description. In the meantime, please go vote on the feature request so that this blog post won’t be needed anymore.
Keeping track of scripts that run regularly and e-mail the output is pretty simple when only one person is managing the schedules, scripts, and server. When you throw many more people into the mix, keeping track of who wants to see the output of which jobs and when jobs should run, you end up with a nasty crontab, and lots of cooks in the kitchen.
I went looking around for a simple way to throw a GUI ontop of a crontab, and stumbled across the Chronograph Django application. It had most of the stuff that we needed (including some really advanced scheduling options thanks to the dateutil library) so I tried spinning it up. What came out was a really nice user interface where we could see just about everything we wanted.
There were a few tweaks I wanted to make for my own purposes, but one major thing that was missing was a list of e-mail subscribers. This was surprisingly simple to throw in, and works really well since Django does pretty much all of the work for me.
Anyway, I’m hoping these changes can get pulled back into the main repository on Google Code, but if not, I’ve got a fork going on GitHub for anybody that’s interested: