Posts about python

How I Learned to Stop Worrying and Love JSON Schema


This post operates on a few shared assumptions. So, we need to explicitly state them, or otherwise you will read things that are more or less rational but they will appear to be garbage.

  • APIs are good
  • Many APIs are web APIs
  • Many web APIs consume and produce JSON
  • JSON is good
  • JSON is better if you know what will be in it

So, JSON Schema is a way to increase the number of times in your life that JSON is better in that way, therefore making you happier.

So, let's do a quick intro on JSON Schema. You can always read a much longer and surely better one from which I stole most examples at Understanding JSON Schema. later (or right now, it's your time, lady, I am not the boss of you).


So, a JSON Schema describes your data. Here is the simplest schema, that matches anything:

{ }

Scary, uh? Here's a more restrictive one:

  "type": "string"

That means "a thing, which is a string." So this is valid: "foo" and this isn't 42 Usually, on APIs you exchange JSON objects (dictionaries for you pythonistas), so this is more like you will see in real life:

  "type": "object",
  "properties": {
    "street_address": { "type": "string" },
    "city": { "type": "string" },
    "state": { "type": "string" }
  "required": ["street_address", "city", "state"]

That means "it's an object", that has inside it "street_address", "city" and "state", and they are all required.

Let's suppose that's all we need to know about schemas. Again, before you actually use them in anger you need to go and read Understanding JSON Schema. for now just assume there is a thing called a JSON Schema, and that it can be used to define what your data is supposed to look like, and that it's defined something like we saw here, in JSON. Cool?

Using schemas

Of course schemas are useless if you don't use them. You will use them as part of the "contract" your API promises to fulfill. So, now you need to validate things against it. For that, in python, we can use jsonschema

It's pretty simple! Here is a "full" example.

import jsonschema

schema = {
  "type": "object",
  "properties": {
    "street_address": {"type": "string"},
    "city": {"type": "string"},
    "state": {"type": "string"},
  "required": ["street_address", "city", "state"]

    "street_address": "foo",
    "city": "bar",
    "state": "foobar"
}, schema)

If the data doesn't validate, jsonchema will raise an exception, like this:

>>> jsonschema.validate({
...     "street_address": "foo",
...     "city": "bar",
... }, schema)
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "jsonschema/", line 541, in validate
    cls(schema, *args, **kwargs).validate(instance)
  File "jsonschema/", line 130, in validate
    raise error
jsonschema.exceptions.ValidationError: 'state' is a required property

Failed validating 'required' in schema:
    {'properties': {'city': {'type': 'string'},
                    'state': {'type': 'string'},
                    'street_address': {'type': 'string'}},
     'required': ['street_address', 'city', 'state'],
     'type': 'object'}

On instance:
    {'city': 'bar', 'street_address': 'foo'}

Hey, that is a pretty nice description of what is wrong with that data. That is how you use a JSON schema. Now, where would you use it?

Getting value out of schemas

Schemas are useless if not used. They are worthless if you don't get value out of using them.

These are some ways they add value to your code:

  • You can use them in your web app endpoint, to validate things.
  • You can use them in your client code, to validate you are not sending garbage.
  • You can use a fuzzer to feed data that is technically valid to your endpoint, and make sure things don't explode in interesting ways.

But here is the most value you can extract of JSON schemas:

You can discuss the contract between components in unambiguous terms and enforce the contract once it's in place.

We are devs. We discuss via branches, and comments in code review. JSON Schema turns a vague argument about documentation into a fact-based discussion of data. And we are much, much better at doing the latter than we are at doing the former. Discuss the contracts.

Since the document describing (this part of) the contract is actually used as part of the API definitions in the code, that means the document can never be left behind. Every change in the code that changes the contract is obvious and requires an explicit renegotiation. You can't break API by accident, and you can't break API and hope nobody will notice. Enforce the contracts.

Finally, you can version the contract. Use that along with API versioning and voilá, you know how to manage change! Version your contracts.

  • Discuss your contracts
  • Enforce your contracts
  • Version your contracts

So now you can stop worrying and love JSON Schema as well.

Creating Languages For Dummies


I don't have the usual programmer's education. I studied maths, and then dropped out of that, and am mostly self-taught. So, there are some parts of programming I always saw wearily, thinking to myself that I really should go to school to learn them. One remarkable such area is parsing and implementing languages.

Well... sure, school is always a good idea, but this is not that hard. In this article I will explain how to go from nothing to a functioning, extensible language, using Python and PyParsing. If you are as scared of grammars, parsers and all that jazz as I used to be, come along, it's pretty simple,

Read more…

Brute Force Works

Last night, Juanjo Conti tweeted this:

Or, in english: "Using exactly once the digits 1,3,4 and 6, and any of the four basic operations, obtain 24."

I first spent a couple of minutes thinking about it and then it hit me: there is no point in thinking this sort of problem, because:

  1. Brute forcing it will take less time
  2. What you do while "thinking" it is sort of lame, isn't it?

So, here is a more-or-less general solution for any of these problems.

from __future__ import print_function, division
import itertools

numbers = ['1','3','4','6']
target = 24

# Having '' as an operation allows for solution (14-6)*3, which
# may or may not be valid depending on rule interpretation.
operations =  ['*','/','+','-','']


for nums in itertools.permutations(numbers):
    for ops1 in itertools.combinations_with_replacement(operations, 3):
        for ops2 in itertools.permutations(ops1):
            for t in (t1, t2):
                s = t.format(*(nums+ops2))
                    if eval(s) == target:
                except (ZeroDivisionError, SyntaxError, TypeError):

Of course you can make it solve any problem of this class by adjusting numbers and target. There is also a possible extra solution if eval(s) == -target where you just need to add a unary - to the expression, but who cares.

Did I miss something? Is this really a general solution?

FLOSS Decision Making in Action

If you are reading this there is a good chance you are involved somehow in open source development, or software development in general. One thing lots of people ask me when they know I have lead this sort of projects for a long time is "how do you decide things?". To which I have all sorts of bad answers like:

  • "It's a consensus thing"
  • "It happens organically"
  • "Sometimes it just happens"
  • "Anarchy!"
  • "You do what you do"

So, now here I have an AWESOME example of FLOSS decision making in action, which is ... all of the above.

Some context: Nikola is a static site generator, so it deals with reading and writing textual data from disk. It's also an internationalized project, which supports multilingual sites and translated data. It also runs un multiple platforms, like Windows, OSX, Linux, etc.

And to make that more fun, it also works on Python 2.7, and 3.3 or later. Which means it has to handle two different models on how to work with unicode data, in the same codebase. And that's not fun. So, we have been floating around the idea of deprecating python 2.7. And so, when s2hc_johan walks in with a unicode problem...

14:23:16 <s2hc_johan> I don't have a site with sections, but I tested it for the other case
14:35:42 <s2hc_johan> strange it worked for a while broken again, probably because I've got åäö in it now.
14:35:45 <s2hc_johan>
14:36:17 <s2hc_johan> if you wrap data with unicode it works, but I'm not sure that works in python3
14:36:37 <ChrisWarrick> s2hc_johan: how do you wrap it with unicode?
14:36:48 <s2hc_johan> unicode(data)
14:37:05 <s2hc_johan> but is that valid in  python3?
14:37:11 <ChrisWarrick> s2hc_johan: this is wrong on so many levels
14:37:16 <ChrisWarrick> s2hc_johan: please don’t do that, ever
14:37:48 <ChrisWarrick> s2hc_johan: This won’t work in Python 3 either.  You must have an actual encoding, and use the decode method.   try: foo = foo.decode('utf-8'); except AttributeError: foo = foo  # python 3
14:38:02 <s2hc_johan> what do you mean, that is like my standard when I get strnage data in, undoce(data) data.encode(whatever) data.decode(whatever) :)
14:38:23 <s2hc_johan> one of them ussually work
14:39:22 <ChrisWarrick> s2hc_johan: unicode() assumes ASCII, it never works right
14:39:32 <s2hc_johan> true
14:39:40 <ChrisWarrick> s2hc_johan: encode/decode with a specified encoding is fine
14:40:00 <ChrisWarrick> s2hc_johan: but you might need a try/except for Python 3 if it could have Unicode data already
14:40:16 <s2hc_johan> I'm a bit confused in this case since the output comes from json.dumps
14:40:34 <s2hc_johan> thought that would produce a unicode object
14:40:51 <ChrisWarrick> s2hc_johan: not necessarily on python 2
14:41:05 <ralsina_> if isinstance(thing, utils.str_bytes): thing=thing.decode('utf8')
14:41:15 <ralsina_> that works in py2 and py3
14:42:12 <ChrisWarrick> easier to ask for forgiveness imo
14:43:07 <ralsina_> maybe we should have helpers in utils enforce_unicode and enforce_bytes
14:43:13 -GitHub[nikola]:#nikola- [nikola] Aeyoun pushed 1 new commit to feed-previewimage:
14:43:13 -GitHub[nikola]:#nikola- nikola/feed-previewimage 4b79e20 Daniel Aleksandersen: Deprecated RSS_READ_MORE_LINK and RSS_LINKS_APPEND_QUERY...
14:44:58 <Aeyoun> Or upgrade to Py3.
14:45:11 <ChrisWarrick> ++
14:45:47 <Aeyoun> Unicode in Py27 is a nightmare. It tries as hard as it can to kill you at every turn.
14:48:09 -travis-ci:#nikola- getnikola/nikola#6426 (feed-previewimage - 4b79e20 : Daniel Aleksandersen): The build is still failing.
14:48:10 -travis-ci:#nikola- Change view:
14:48:10 -travis-ci:#nikola- Build details:
14:48:27 <ralsina_> ok, let's consider py3-only seriously.
14:48:40 <ralsina_> 1) Is there any distro commonly used with py3 < 3.3 ?
14:48:55 <ralsina_> 2) Do we just stop using py2, or we deprecate slowly?
14:49:15 <ralsina_> 3) Do we just start doing py3-only code, or we actively de-hack the codebase?
14:49:21 <ralsina_> That's my 3 questions :-)
14:50:13 <SteveDrees> Unicode is a nightmare
14:50:53 <SteveDrees> different python versions just changes where the pain point is
14:50:53 <s2hc_johan> which one is better isinstance... or hasattr('decode', ..)
14:51:02 <ralsina_> isinstance
14:51:08 <s2hc_johan> oki then
14:51:10 <ralsina_> hasattr is evil in itself
14:51:26 <s2hc_johan> just going to feed the kids then I'll make another pr
14:51:28 -GitHub[nikola]:#nikola- [nikola] Aeyoun pushed 1 new commit to feed-previewimage:
14:51:28 -GitHub[nikola]:#nikola- nikola/feed-previewimage 4c950ac Daniel Aleksandersen: flake8
14:52:13 <Aeyoun> ralsina_: user survey? pip download data?
14:52:33 <gour> ralsina_: create some poll at website/mailing-list about it?
14:53:18 <ralsina_> dude, I offered free shirts and I got only 10 requests ;-)
14:53:30 <ralsina_> so, how many answers do you expect about that sort of thing?
14:53:43 * gour thought shirts are jsut for devs :-(
14:53:47 <Aeyoun> ralsina_: release a unchanged version on pip that is flagged as py3 only. see how many downlaod it versus previous version in same amount of time.
14:53:51 <ralsina_> gour: go add yourself dude
14:54:18 <ralsina_> gour: TO THE SHIRT LIST! I just notced that sounded very rude :-)
14:54:43 <gour> ralsina_: where it is?
14:54:43 <Aeyoun> ralsina_: or one py27 version number and and one version py3 only version number at the same time.
14:55:17 <ralsina_> gour:
14:55:17 <gour> found it
14:56:00 <gour> ralsina_: wonder if xxl is too large or xl is enough
14:56:00 <Aeyoun> ralsina_: american or european sizes by the by?
14:56:03 <ralsina_> Aeyoun: that reflects how many people use py2.7 by reflex. I know *i* do because it's "python" and not "python3"
14:56:20 <ralsina_> Aeyoun: no idea about sizes to be honest... probably american
14:56:21 <Aeyoun> American sizes are … a big bigger. I’m probably a XS/S american but M european. :P
14:56:28 <Aeyoun> *bit bigger
14:56:39 <gour> ok
14:56:57 * gour submitted request
14:57:17 <ralsina_> So, what I would prefer to do is make people use py3 if they can. And it seems to me that pretty much everyone can, regardless of whether they still use py2 by defect.
14:57:26 <ralsina_> by default*, spanishism leaked there.
14:57:52 <ChrisWarrick> technically, using py2 is a defect
14:57:59 <ralsina_> So, if we all agree that most users *could* run nikola in py3... then let's do it.
14:58:02 <Aeyoun> Agreed.
14:58:15 <gour> sites won't stop working :-)
14:58:26 <Aeyoun> ralsina_: act on data not dev agreement?
14:58:42 <ChrisWarrick> guess we could change our docs/webiste to highlight 3.x
14:58:59 <ralsina_> Aeyoun: the only data we'd need is to know how many people have py2.7 and no py3.3
14:59:14 <ralsina_> not how many are *using* 2.7 instead of 3.3
14:59:38 <ChrisWarrick> micro-survey via ml?
14:59:39 <ralsina_> How about: let's announce that, unless lots of people complaint, we deprecate py2 by end of october
14:59:45 -travis-ci:#nikola- getnikola/nikola#6429 (feed-previewimage - 4c950ac : Daniel Aleksandersen): The build was fixed.
14:59:46 -travis-ci:#nikola- Change view:
14:59:46 -travis-ci:#nikola- Build details:
14:59:47 <Aeyoun> Mac is shipping with Py2.7 and no Py3. BUT MacPorts and Homebrew offer painfree Py3 installs.
14:59:58 <ralsina_> ok, mac is a good point
15:00:25 <ChrisWarrick> it’s not like we have Homebrew/MacPorts/Fink-based install instructions for them…
15:00:27 <Aeyoun> ralsina_: we could add a deprecation message every time `nikola` is run and ask people to bitch in a bug?
15:00:32 <Aeyoun> ChrisWarrick: hehe. ;)
15:00:50 <ralsina_> "I see you have python3 installed but I am running on 2.7 ... dude, what's wrong with you?"
15:00:51 <Aeyoun> Or maybe once per 24 hour rather  than every time its run.
15:01:00 <ralsina_> doit timed tasks :-)
15:01:12 <Aeyoun> ralsina_: "Don’t get in the way of progress! Upgrade to Py3 and save a developer’s mind today!"
15:01:32 <ralsina_> "niec unicode you have there, would be a shame something happened to it.. switch to python 3!"
15:01:39 <ChrisWarrick> ralsina_: hey, let’s start with a Google Docs survey on the ML.  One question: what Python version and OS are you using for Nikola? 2.7/3.3/3.4/3.5; Windows/OS X/[other: linux/bsd distro]
15:01:57 <gour> "Free t-shirt foreveryone switching from py2.7 to py3.3"
15:01:58 <ChrisWarrick> ralsina_: Just don’t require a Google account like you did last time.
15:02:00 <ralsina_> Second question: "Do you have python 3.3 or later installed?"
15:02:03 <Aeyoun> How much code can be removed with dropping Py27? Lowers maintenance cost and increases performance. That is also an important datapoint.
15:02:11 <ralsina_> ChrisWarrick: I needed to know who was asking for the shirt :-)
15:02:21 <ChrisWarrick> ralsina_: good point
15:02:25 <ralsina_> Aeyoun: not all that much, really
15:02:47 <ChrisWarrick> Aeyoun: it would need to start with a huge rewrite to remove all of our pointers in nikola.utils
15:03:00 <ralsina_> Aeyoun: there are a number of tiny hacks, which were a pain to get right but they always amount to one if and/or one decode :-)
15:03:26 <ralsina_> We can just turn a bunch of helpers in utils into noops
15:04:52 <gour> py3-only nikola is going to become v8?
15:05:15 <Aeyoun> gour: seems like a likely outcome. you’re following the discussion live.
15:06:34 <ChrisWarrick> if we do v8, we’ll have to merge the early tasks garbage
15:07:03 <ralsina_> Is it technically backwards-incompatible if we just stop working on py2.7?
15:07:21 <ralsina_> gour: welcome to open source software: behind the code.
15:07:30 <gour> ralsina_: :-)
15:07:35 <Aeyoun> Someone call in a documentary crew!
15:07:43 <ralsina_> Aeyoun: we have logs!
15:07:51 <Aeyoun> Oh, wait. This is already logged for prosperity.
15:07:57 <ralsina_> I am totally posting this somewhere as "this is how decisions are made in FLOSS"
15:08:40 <ralsina_> Ok, who creates the poll and who posts it in the blog, and who makes sure it appears on planet, and who sends it to the list?
15:08:49 <ralsina_> I would do it but I have work to do :)
15:08:51 <ChrisWarrick> ralsina_: I’ll do it
15:08:57 <ralsina_> ChrisWarrick: you rock dude!
15:09:01 <ChrisWarrick> ralsina_: should be really simple
15:09:03 <ralsina_> Ok, we have a plan!
15:09:17 <ralsina_> Let's consider the poll results in ... a week?
15:09:25 <Aeyoun> Let the logs show we’re all in favor of this plan of action. ;-)
15:09:29 <ralsina_> aye
15:09:51 <ralsina_> Also: can I do the "shame on you" thing on nikola build? It sounds like fun :-)
15:10:27 <ChrisWarrick> ralsina_: for the python version question: radiobox vs checkbox?
15:10:28 <gour> ralsina_: you can mention that Nikola (Tesla) was always for innovation ;)
15:10:44 <Aeyoun> "You’re using FIVE YEAR OLD SOFTWARE. Update your system."
15:11:00 <ralsina_> Aeyoun: I am totally getting at least 5 different comments there
15:11:01 <Aeyoun>
15:11:05 <ralsina_> ChrisWarrick: checkbox... maybe 2?
15:11:23 <ralsina_> ChrisWarrick: one for python version, one for operating system
15:11:32 <ChrisWarrick> ralsina_: ?
15:11:38 <ralsina_> ChrisWarrick: two questions
15:11:54 <ChrisWarrick> ralsina_: there will even be three questions (py2/3 used, OS, has py3)
15:11:57 <ChrisWarrick> ralsina_: and checkboxes it is
15:12:02 <ralsina_> right
15:12:05 <ralsina_> awesome
15:14:44 <ralsina_> Copied / Pasted for posterity

There you go, half an hour later, we have a plan to (maybe) deprecate it.

Now go vote here: Should Nikola support python2.7? Gives us data to decide!

Nikola v7 finally out!

I am thrilled to announce version 7 of Nikola, a static site and blog generator is out, with a bazillion features and bugfixes (see below).

You can get it at all the usual places, and here's the release announcement

Here's the new features, the bugfixes list would make the post too long :-)

  • Added UNSLUGIFY_TITLES option for making titles fetched via the filename regexp prettier (Issue #1282)
  • New dependencies: natsort (natural sorting in galleries) and dateutil (replaces pytz)
  • Nikola.commands are now the user-friendly wrappers from console (Issue #1177)
  • Add a github_deploy command to deploy to GitHub pages (Issue #1208)
  • Remove tidy filter (it was broken due to tidy being ancient) (Issue #1164)
  • Added GENERATE_RSS setting to allow disabling RSS in Nikola (Issue #1236)
  • Link listings raw sources if COPY_SOURCES is True (Issue #1214)
  • Much more powerful nikola plugin command (Issue #1189)
  • More powerful console mode allows access to all nikola commands (Issue #830)
  • New `ROBOTS_EXCLUSIONS` option listing resources to exclude from sitemap and include in new generated /robots.txt (Issue #804)
  • Generate sitemapindex containing RSS and sitemap files (Issue #804)
  • Support hooks in templates, for use by plugins (Issue #896)
  • Use readline if available (Issue #1238)
  • Added reading_time, remaining_reading_time, paragraph_count, remaining_paragraph_count tags for READ_MORE_LINK (Issue #1220)
  • Add canonical link in listings.
  • Added support for new meta files that are the same format as 1-file metadata, allowing for greater flexibility (Issue #954)
  • Colorbox is now internationalized (Issue #1205)
  • Added LOGO_URL and SHOW_BLOG_TITLE=True settings to facilitate showing off logos (Issue #1122)
  • Create automatic story index pages for subfolders, too (Issue #793)
  • New Slovak translation by Tomáš Prékop
  • Created a MarkdownExtension plugin class (Issue #1175)
  • The base theme produces properly sectioned and semantic HTML5 (Issues #1123, #1137)
  • The base theme comes with a new stylish look by default (Issue #1137)
  • The base theme supports Right-to-Left by using ::dir(rtl) CSS4 rules and <html dir="rtl"> tags where valid (Issue #1146)
  • Bootstrap 2 updated to 2.3.2 (via Issue #1137)
  • Added FORCE_ISO8601 setting that currently makes new_post use ISO 8601 dates (via Issue #1156)
  • Added support for TZ specified in post date (Issue #1118)
  • Make nikola init ask about the site’s settings (Issue #1080)
  • Use natural sorting for files and folders list in listings and galleries (Issue #1144)
  • Added invariance testing (Issue #672)
  • Plugins can inject templates in the system (Issue #1139)
  • nikola import_wordpress now has a --qtranslate option, to parse posts in the qtranslate wordpress plugin format and turn them into multilingual Nikola posts (Issue #1072)
  • nikola console allows for interpreter choice via -b, -i, -p; moreover, support for bpython is not deprecated anymore (Issue #1126)
  • retired tag for posts has been replaced with private (via Issue #686)
  • Changed the default TRANSLATIONS_PATTERN to "{path}.{lang}.{ext}". (Issues #990, #829)
  • Backwards compatibility with v5 is broken. Added backwards-incompatible changes. (Issue #829)
  • Added a CONTENT_FOOTER_FORMATS config option. It is used to format the CONTENT_FOOTER variable properly, for compatibility with the Translatable Settings feature. The variable takes a dict, the keys of which are languages, and values are (args, kwargs). (Issue #1112)
  • Certain settings are now translatable. As of now, the settings are: BLOG_AUTHOR, BLOG_TITLE, BLOG_DESCRIPTION, LICENSE, CONTENT_FOOTER, SOCIAL_BUTTONS_CODE, SEARCH_FORM, BODY_END, EXTRA_HEAD_DATA, NAVIGATION_LINKS, READ_MORE_LINK (the up-to-date list is available in SITE.TRANSLATABLE_SETTINGS) (Issues #851, #1057, #1061, #1112)
  • New returns meta 'author' or BLOG_AUTHOR (Issue #1117)
  • Ship base-jinja, bootstrap-jinja, bootstrap3-jinja with Nikola (Issue #1104)
  • Remove old messages left over for backwards compatibility: (Issues #829, #1105)
    • "More posts about", replaced by "More posts about %s"
    • "Posted", replaced by "Posted:"
    • "Also available in", replaced by "Also available in:"
  • Remove old "sl_SI", "tr_TR" locale aliases (use "sl" and "tr") (Issue #829, #1105)
  • New option RSS_PLAIN to optionally strip HTML from RSS feeds (Issue #1107)
  • Support content key in compilers' create_post (Issue #1098)
  • Use setuptools’ extras feature. Use pip install nikola[extras] to install Nikola with extras (requirements-extras.txt, formerly requirements-full.txt -- note the name change!) (Issue #1089)

Nikola Logo Contest!

So you have design skills? Want to make a little money while doing a good thing? Then join the contest! there is a 200 US dollars guaranteed prize, and the contest will be judged by a group of Nikola developers.

Some guidance:

  • The name Nikola comes from Nikola Tesla, so think concepts connected to that
    • The Wardenclyffe tower
    • Tesla Coils
    • Lightning
    • Electricity
    • Early 20th century
  • I hope the logo works well in a variety of formats so, keep it simple. B/W is good.
  • A drawing without the word Nikola is good, but a good design with the word in it could be good also.

The contest lasts a week, you have 4 days for the qualifying round.

And remember: it has to be better than these

Nikola is now more compatible with Sphinx

Sometimes, you need to use the same text in more than one place. For example, you may want to use fragments of your software manuals in your software's site.

If you are part of the Python-verse then it's likely that your docs are written using Sphinx. Ǹikola and Sphinx share roots in reStructuredText so they are already compatible in many important ways.

However, Sphinx adds a whole bunch of extensions to reStructuredText and Sphinx users use them. So, what's a dev to do? He'll write code. So I wrote a nikola plugin which adds support for a large chunk of the Sphinx-specific markup.

How big a chunk? Quite big since it adds support for over 35 extensions, roles and directives (no, toctree is not one of them yet).

This way, you may be able to enjoy a good documentation-specific tool and a (if I may say so myself) good website tool from common sources.


Preview of Nikola Webapp

I have already made a few attempts at turning Nikola, a static site and blog generator into a webapp, with varied success. One of the bigger problems was, I think, one of scale. I was thinking of something to replace ... something where everyone could host his own blog in very low cost infrastructure.

But then again... why not try to replace all those self-hosted wordpress instances out there? So, here's the webapp plugin!

It's very early stages, no support for pages, only supports markdown, no configuration changes, and you can't even really delete things. But hey, you can post, which is 95% of the usage this will get.

UPDATE: if you are running Nikola from github master, you can install this using the plugins repo: and it supports a lot more than on the video above.