Rewriting Reddit

2012 note: This article was first published in 2005. After it was published, Django launched a RemovingTheMagic project to address some of my criticisms (though personally I still find it unusable), web.py inspired FriendFeed’s tornado.web and Google’s gae.webapp and others (though I still prefer web.py), and this article led to a permanent surge in Reddit traffic that still hasn’t really stopped growing.

Over at reddit.com, we rewrote the site from Lisp to Python in the past week. It was pretty much done in one weekend. (Disclosure: We used my web.py library.) The others knew Lisp (they wrote their whole site in it) and they knew Python (they rewrote their whole site in it) and yet they decided liked Python better for this project. The Python version had less code that ran faster and was far easier to read and maintain.

The idea that there is something better than Lisp is apparently inconceivable to some, judging from comments on the reddit blog. The Lispers instead quickly set about trying to find the real reason behind the switch.

One assumed it must have been divine intervention, since “there seems to be no other reason for switching to an inferior language.” Another figured something else must be going on: “Could this be…a lie? To throw off competition? It’s not as though Paul Graham hasn’t hinted at this tactic in his essays…” Another chimed in: “I decided it was a prank.” Another suggested the authors simply wanted more “cut corners, hacks, and faked artisanship.”

These were, of course, extreme cases. Others assumed there must have been outside pressure. “Either libraries or hiring new programmers I guess.” Another concluded: “some vc suit wants a maintainable-by-joe-programmer product. I hope he pays you millions.”

The more sane argued along the lines of saying Lisp’s value lies in being able to create new linguistic constructs and that for something like a simple web app, this isn’t necessary, since the constructs have been already built. But even this isn’t true. web.py was built pretty much from scratch and uses all sorts of “new linguistic constructs” and — even better — these constructs have syntax that goes along with them and makes them reasonably readable. Sure, Python isn’t Perl 6, so you can’t add arbitrary syntax, but you can often find a clever way to get the job done.

Python, on the other hand, has problems of its own. The biggest is that it has dozens of web application frameworks, but none of them are any good. Pythonists are well aware of the first part but apparently not of the second, since when I tell them that I’m using my own library, the universal response is “I don’t think Python needs another web application framework”. Yes, Python needs fewer web application frameworks. But it also needs one that doesn’t suck.

The framework that seems most promising is Django and indeed we initially attempted to rewrite Reddit in it. As the most experienced Python programmer, I tried my best to help the others out.

Django seemed great from the outside: a nice-looking website, intelligent and talented developers, and a seeming surplus of nice features. The developers and community are extremely helpful and responsive to patches and suggestions. And all the right goals are espoused in their philosophy documents and FAQs. Unfortunately, however, they seem completely incapable of living up to them.

While Django claims that it’s “loosely coupled”, using it pretty much requires fitting your code into Django’s worldview. Django insists on executing your code itself, either through its command-line utility or a specialized server handler called with the appropriate environment variables and Python path. When you start a project, by default Django creates folders nested four levels deep for your code and while you can move around some files, I had trouble figuring out which ones and how.

Django’s philosophy says “Explicit is better than implicit”, but Django has all sorts of magic. Database models you create in one file magically appear someplace else deep inside the Django module with a different name. When your model function is called, new things have been added to its variable-space and old ones removed. (I’m told they’re currently working on fixing both of these, though.)

Another Django goal is “less code”, at least for you. But Django is simply full of code. Inside the django module are 10 different folders and inside each of those are a few more. By the time you actually build a site in the Django tutorial, you’ve imported django.core.meta, django.models.polls, django.conf.urls.defaults.*, django.utils.httpwrappers.HttpResponse, and django.core.extensions.render_to_response. It’s not clear how anyone is supposed to remember all that, especially since there appear to be no guiding principles for what goes where or how it’s named. Three of these are inserted automatically by the start scripts, but you still need to memorize such names for every other function you want to use.

But Django’s most important problem is that its developers seem incapable of designing a decent API. They’re clearly capable Python programmers — their code uses all sorts of bizarre tricks. And they’re clearly able to write code that works — they have all sorts of interesting features. But they can’t seem to shape this code into something that other people can use.

Their APIs are ugly and regularly missing key features: the database API figures out queries by counting underscores but has no special syntax for JOINs, the template system requires four curly braces around every variable and can’t do any sort of computation, the form API requires 15 lines to process a form and can’t automatically generate the template.

I tried my best to fix things — and the Django community was extremely supportive — but the task simply dwarfed me. I just couldn’t do it mentally, let alone with the time constraints of having to actually build my own application for my own startup.

And so, Lisp and Django found wanting, we’re left with web.py. I’d like to say that web.py learned from these mistakes and was designed to avoid them, but the truth is that web.py was written long before all this and managed to avoid them anyway.

The way I wrote web.py was simple: I imagined how things should work and then I made that happen. Sometimes making things just work takes a lot of code. Sometimes it only takes a little. But either way, that fact is hidden from the user — they just get the ideal API.

So how should things work? The first principle is that code should be clear and simple. If you want to output some text, you call web.output. If you want to get form input, you call web.input. There’s nothing particularly hard to remember.

The second principle is that web.py should fit your code, not the other way around. Every function in web.py is completely independent, you can use whichever ones you want. You can put your files wherever you like, and web.py will happily follow along. If you want a piece of code to be run as a web app, you call web.run, you don’t put your code in the magical place so that web.py can run you.

The third principle is that web.py should, by default, do the right thing by the Web. This means distinguishing between GET and POST properly. It means simple, canonical URLs which synonyms redirect to. It means readable HTML with the proper HTTP headers.

And that, as far as I’m concerned, are pretty much all the principles you need. They seem pretty simple and obvious to me and I’m even willing to fudge on some of them, but no other Python web app framework seems to even come close. (If you know of one, tell me and I’ll happily recant. I don’t want to be in this business.) Until then, it looks like I’m forced to do that horrible thing I’d rather not do: release one more Python web application framework into the world.

Just wondering if you happened to check out TurboGears? (www.turbogears.org).

At a minimum, it too uses SQLObject. And they have the oh so pretty screencasts.

And I think that “unsatisfactory” may have been a better word, as opposed to “broken”. The word “broken” really has an implication of “there’s no way whatsoever to get this thng to work”, which is obviously not the case for Django. It just happened to be the case for what you wanted to do with it.

Problem one with TurboGears is that their website is hideous. Other problems from a skim: It creates a lot of files, Kid seems pretty unfriendly, CherryPy doesn’t let you pick your own URL scheme, and functions seem to be distributed randomly across cherrypy and turbogears modules

The Reddit guys have started a funny geek drama. The snarkyness of the language camps is killing me.

To people that might be sitting on the fence about which language is best or most powerful: try them and find out for yourself. These advocacy outbursts are just as much about justifying the zealot’s choice as they are about influencing yours.

What’s P.G. talking about? Could one of the projects for next year’s Summer Founders be to finish Arc? The Lisp guys need some cheering up.

Always seems to me that web publishing is a fairly complex business, there’s just lots of different things that need to be handled: form processing, ORM, templates/output, then app level stuff, pagination, search, permissions, admin, etc, etc. And that’s before you get into your own app code. Web developers should not rewrite this code per app, their platform should do it. Their platform should be a large OSS project to provide security, management and code reuse benefits.

Django, Rails, even Zope 3, try to provide that answer: lots of code for almost everything you need and an API for putting together your own stuff, with the emphasis on cohesion. From what you’ve revealed of web.py so far it doesn’t seem to provide cohesion, the glueing-together tasks need to be done and done again. Django may have magic innards, but it provides more code for solving more tasks. Zero to simple site time might be the same, but zero to site with all the niceties required by an increasingly savvy audience will always be quicker with a more full-featured environment.

It’s a templating mini-language. These things always turn into unmaintainable goo. As templating goo languages go, it’s not that bad. Functions are split across CherryPy and Turbogears because they are reusing CherryPy, which has been debugged.

Still, I can understand the file layout objection. web.py seems like a pretty similar setup to TBNL, which I really like. Let me hazard a guess about the proximate cause: CMUCL on FreeBSD, UFFI, and/or mod_lisp falling over for no good reason.

Don’t understand the Cheetah choice. What are the benefits that make you favor Cheetah over Kid?

I think in many cases, the framework is more important as the language itself. I’m inclined to think web.py must do something right if the Reddit guys gave up TBNL for it. I look forward to the public release; it’s one more sharp thing for my toolbox.

I’ve tried out TurboGears and Django, and I agree that they involves too many files and too much boilerplate code. Still, I really like most of the decisions that went into TurboGears. (Web.py looks even better in a lot of ways, and I look forward to using it.)

Regarding Kid: I’ve worked in a lot of templating systems and template languages (Cheetah, CFML, Mason, ASP, PHP…) and Kid is my favorite. But I realize that this depends a lot on use cases and on personal aesthetics.

Kid involves a small amount of bondage-and-discipline, which seems like a contrast with Python. But I really like the “feel” of Kid, and I find that it has important benefits. For example, Kid’s guarantees about well-formed XML mean that data that you insert is escaped as needed unless you explicitly say otherwise. Among other things, this helps Kid avoid the “insecure by default” problem in most templating systems, where the programmer has to escape every output string explicitly or cause cross-site-scripting vulnerabilities.

“Broken” usually means that something doesn’t work the way it was intended, and is often seen as disparaging to the designers or implementors of the artifact. I certainly admit to having been guilty of using the word in a disparaging manner, too. But you should know that it’s not a respectful way to talk about someone else’s work.

As for a better word, well, respect isn’t something that comes across merely by the choice of a single word. I’m still working on learning it myself.

Sorry, but actually all that above on “perfect API” and stuff just reads to me as yet another case of “not invented here”. It’s fun to discuss web frameworks (as it is fun to write one or two or more), but it’s not fun to have highly emotional attacks like “can’t design a decent API” that are just based on personal preference, stated as universal truth …

Nice to see the HTTP method thing. Just one question about it:
From the tutorial, I gather that you’re mapping URI’s to classes by using regular expressions (I’m not sure whether I prefer this over the cherrypy approach, but that’s another matter). The leftover captures seem to get passed to the GET, (or POST or whatever) method. Any reason why not to the init method? (URI gets resolved to an object, method gets called on that object, instead of: URI gets partially resolved, further resolving is done in the method)

That statement is obviously absurd. Among the many counter examples is Ruby on Rails. I don’t understand why reddit went with a closed solution in python that currently has a small user base (no offense) rather than the rapidly swelling, open source RoR, written in Ruby which, imho, is much shinier. I wonder if they considered Rails and found it technically lacking, or if they suffer from some kind of popularity snobbery (hence lisp, but there it did them a favour) which prevents them using saomething that everyone else uses.

The same motivation led me to do something very similar to web.py, specifically for use with mod_python, Pymplex I wonder if there’s any way the things could be combined (or…if there’s anything in Pymplex that might be usable in web.py, help yourself ;-)

Yes, web.py looks very nice, but only because I now nothing about it. Others (Django, TG, ) have published their source so it is easy to comment: favorably or not. web.py yet remains to be seen: so far it looks like simple url to function mapper with everything else left to the app developer.

IMO the web app problem has not found any single framework simply because the problem of building web applications can be very easily modularized, and there are already excellent modules that provide specific orthogonal functionalities.

When I also faced the task of having to write web app code that I did not want to commit to any framework (I want future flexibility of changing the components), I defined a set of interfaces (http://furius.ca/indra), and then provided them by plugging a variety of existing and non-existing modules (I had to write some of the modules, e.g. http://furius.ca/atocha for form processing, http://furius.ca/htmlout for HTML output).

The fact is that you do not need a framework. You need to choose the technology you’d like to use (i.e. do you need templates? What database back-end are you using? What language do you want to write to? How do you want to bind the URLs to code? etc.) and then pick the modules and put them together.

Unfortunately, this is a fair bit of work in itself, which is why people build frameworks. I have yet to see a framework which works for everyone.

I don’t think this problem has a solution. You either bend over to the limits of a framework, or you assemble your own.

As for LISP: it would be worthwhile looking into a way to access the Python libraries from a LISP interpreter. Sure enough, the interfaces are not going to be very lispy and the speed is going to be as slow as Python (that is, much slower), but it would at least provide a good set of well-tested libraries to work from. I would love to see that happen…

Aaron: Looks very nice—-in the style of OpenACS and WWDJBD (What Would DJB Do :) When I was doing some web programming a little while back, I tried out several of the Python frameworks. But none of them gave me an easy way to do the exact two things you mentioned: abstract URI mapping, and ability to distinguish between the different HTTP verbs. IMHO, these are the basic building blocks of any web application or framework; the rest of the infrastructure can be built around them.

What an API can do is an objective question, not a personal preference. How easy it is to use is, ultimately, also an objective question — you can look at its the experience across a large number of users. I sat down with the reddit team and watched them try to use the Django API and the web.py API. For both of them I did my best to help modify the API so things were easier, but it became pretty clear which won out. Sure, it’s a small sample size, but I think the results wouldn’t be much different with a larger one.

As for the people calling web.py closed source, it seems a little silly — it says right on the web site that it’s free software. I just thought I’d clean it up before I released it.

It was game over for Lisp when the authors limited themselves to free implementations of Lisp (even though they could have purchased a commercial version).

Then they chose to do cross platform development on Mac and FreeBSD, which no free lisp supports (but which some commericial lisps support).

If I’d seen this on a business plan I’d have rejected it right away. It was a plan doomed to failure. Sadly it was Lisp that got a black eye in this and not the people who decided to start the project with such crazy preconditions.

There are commercical Lisps which can support development across Mac OS and FreeBSD and which have serious libraries for web programming and database access. If one of these were chosen there would have been no need to switch to a lesser language.

What? Yet another blog article telling us how the Python Web frameworks scene is, making sweeping statements like “none of them are any good”, focusing on the bad points of one singled-out framework, and then presenting a magic solution whose source code isn’t out there yet.

Django is open source, not closed as robbie’s message implied. (To be fair, robbie may have only been referring to web.py as closed, but his message could have been interpreted either way.)

As far as Python templating goes, Spyce is easily the best in terms of flexibility and ease of integration of HTML and complex coding. (See this new Wikipedia page: http://en.wikipedia.org/wiki/Spyce ) If only it wasn’t such a jumbled mess in places.

I have already used an alternative templating system (HTML Template — http://freespace.virgin.net/hamish.sanderson/htmltemplate.html ) within a Django app and that worked fine. I intend to try Django/Spyce and perhaps webpy/Spyce over the holidays. Interesting times.

A common issue, across languages and domains, that deserves much more attention than the Lisp vs trivia, the Django vs trivia, etc that the reddit controversy has generated. Good API design is not accidental, or easy, a fact too many code wizards ignore.

Oh, and isn’t reddit’s real problem that they’re busy changing implementation languages (!) while digg is sorta kicking their butts? I don’t know which site is doing better financially (does either one even have a biz model? What might it be, one wonders), but digg seems more polished, has a more pleasent UI, seems more featureful, and more popular.

Setting aside the Python v. Lisp debate (which is so boring and pointless), I’d hate to be in what seems to be reddit’s position: serious, robust competition and remaking fundamental tech decisions.

Why does everyone take this attitude of creating an entirely new framework? In most other places, people fix bugs in existing software and contribute their skills rather than creating yet another framework. Though I may be biased, web.py looks suspiciously similar to Subway (as does TG). Why didn’t you opt to contribute to Subway rather than, again, divide the Python web framework community’s limited resources? When I started Subway, Django or TG hadn’t come out yet, and there wasn’t a non-Zope standard full-stack framework. I wanted to reuse as many existing components as possible to minimize this sort of thing. Don’t you think it would be a bit more productive if you had contributed to one of the many existing frameworks rather than invent yet another half-baked SQLObject/Cheetah framework? “They all suck” isn’t a good answer; the only way to create a non-sucky framework is to work together.

What lisp needs the most is a lot of new hackers willing to play with it. Lisp was abandoned for a very long time. Those other languages mentioned in the discussion were not. Right now I’m expecting very good things from projects like CFFI, wxCL and others.

It’s a good thing they rewrote reddit. I usually implement and refactor an algorithm in lisp, because it has lots of features to help that, and then, I just implement the whole thing in other language just because it fits better with the task at hand, other languages have more libraries and that helps a lot.

I just like lisp more than Python and Ruby but this is just a personal preference. We need better lisp implementations, because almost nobody used lisp in a long time some problems went unnoticed but this is starting to change.

A good thing that Lisp makes very well is helping us identify the features and abstractions of other languages. A good lisp hacker has to be a good programmer in several languages, not just lisp.

Why does everyone take this attitude of creating an entirely new framework? In most other places, people fix bugs in existing software and contribute their skills rather than creating yet another framework. Though I may be biased, web.py looks suspiciously similar to Subway (as does TG). Why didn’t you opt to contribute to Subway rather than, again, divide the Python web framework community’s limited resources? When I started Subway, Django or TG hadn’t come out yet, and there wasn’t a non-Zope standard full-stack framework. I wanted to reuse as many existing components as possible to minimize this sort of thing. Don’t you think it would be a bit more productive if you had contributed to one of the many existing frameworks rather than invent yet another half-baked SQLObject/Cheetah framework? “They all suck” isn’t a good answer; the only way to create a non-sucky framework is to work together.

What lisp needs the most is a lot of new hackers willing to play with it. Lisp was abandoned for a very long time. Those other languages mentioned in the discussion were not. Right now I’m expecting very good things from projects like CFFI, wxCL and others.

It’s a good thing they rewrote reddit. I usually implement and refactor an algorithm in lisp, because it has lots of features to help that, and then, I just implement the whole thing in other language just because it fits better with the task at hand, other languages have more libraries and that helps a lot.

I just like lisp more than Python and Ruby but this is just a personal preference. We need better lisp implementations, because almost nobody used lisp in a long time some problems went unnoticed but this is starting to change.

A good thing that Lisp makes very well is helping us identify the features and abstractions of other languages. A good lisp hacker has to be a good programmer in several languages, not just lisp.

Spyce is the most flexible tool out there. You can start of with a php like approach and then go on to use active handles and custom tags. I like turbogears (the screen cast is cool) but i’ve figured out how to do everthing in spyce (authetication, charting, ajax) so its really hard to switch for work

Lisp’s value does not only lie in macros; it’s just the most hyped feature. Take for example the Condition System, which goes beyond exceptions and return codes. For robust software. Maybe one day a Matz or Guido will offer it to his users.

Lisp users are likely insane, but that’s probably not a bug, it’s a…

While I thought Reddit’s technical decisions were reasonable, if Marc Battyani feels like spending several hours replicating their site, more power to him. People constantly demand that “the Lisp community” replicate things like Ruby on Rails or provide “proof” for their productivity claims or… ok, so someone decides to take the challenge, and it’s damned-if-you-do-damned-if-you-don’t.

The Lisp guys rewriting Reddit are missing the point. Who here doubted that some Lisp pros could build Reddit? Building something that already exists is easy: the idea and prototype have already been made for you.

The point is that some relative Lisp newbies tried to build their project in Lisp and found it not worth it. Every time someone builds something in a language that isn’t Lisp is there going to be a big freakout followed by some Lisp pros duping something that’s already been done? What a waste of time! The Lisp guys rewriting Reddit should be looking at where the finger is pointing instead of the finger itself.

I (respectfully) disagree, Egg Shen. The biggest waste of time I see is the soap opera of emotional pundits. (If any of my comments have been emotional, then I surely admit I’m part of the problem.)

Take for example the fellow who’s competing with Reddit. I think despite the non-technical criticism which is all too common on blogs, whose only real effect is to increase Lisp’s bad-boy notoriety, some tech-minded developers are honestly waiting to see Marc’s progress and code. In fact, I’ve watched one or two very interesting presentations he gave on web frameworks. 1

People interested further in honest discussion might look at Espen Vestre’s slides on his successful stocktrading startup, PrimeTrader. 2

We’ve been fixing bugs, smoothing corners and adding features to twisted nevow over the last three years and it’s doing agreat job on a variety of sites handling traffix in excess of 50 pages per second. It’s also delivering multi-lingual content for the country of Oman and powering Athena powered websites for the health care industry (check your greek history for the her relation with Ajax). However, instead of starting with the flashy bits, the time has been spent getting the foundations right first so don’t expect a ruby on rails replacement just yet (in fact probably never, which is a good thing).

Don’t know if you’ve noticed, but Webware has stagnated for the past few years and doesn’t support WSGI. It’s also a bit too J2EE-ish for many Python people. Have you ever even used Webware? You’ll see that the current types of Rails-inspired frameworks are totally different.

I think stagnation in terms of webware means stability rather then a lack of use or interest. Really all the new frameworks like TG, subway, django don’t really offer much more in terms of web framework then webware. They are just slightly different. The likes of FormKit (or FFK) are only just now being implemented in TG and from reading the forums, it seem as if these groups think the ideas are new and unique. Don’t get me wrong, there are improvements, but to think that all this is somehow new and exciting is ego fluff. Same goes for web.py it may seem simple now, but that’s only because it was written to do something simple. Just wait until it gets used, then the bloat and complexity will come. It’s just the nature of things.

Peter Hunt: “When I started Subway, Django or TG hadn’t come out yet, and there wasn’t a non-Zope standard full-stack framework.”

Incorrect. There was Webware, SkunkWeb, and possibly one or two others.

“Don’t know if you’ve noticed, but Webware has stagnated for the past few years and doesn’t support WSGI.”

Straight away, you contradict yourself: how many years has Subway been around?

“It’s also a bit too J2EE-ish for many Python people. Have you ever even used Webware? You’ll see that the current types of Rails-inspired frameworks are totally different.”

Yes, but Webware was a “full-stack framework”: URL dispatching, presentation, persistence, the rest. It might not have been polished or even 1.0, and it might have been Java-like, but Rails and friends don’t suddenly define “full-stack”. Moreover, there are Webware derivatives that do support WSGI.

OK - I have read Aaron’s post and most of the commentary about reddit, django, why the switch, etc… Most because it got redunant towards the end… I have one question…

The religious arguement seems to have started over a new site (Reddit) that is a weak copy of Digg. Am I missing something here? Arent we talking about the best way to do something that has been very well done in the first place?

I think the main point of the comp.lang.lisp newsgroup posters was that they not only created two different renditions of reddit within 24 hours, but that one rendition was up and running within 3 hours of initial code and was in production in seven hours!

So, hmmmm, 7 hours for Common Lisp and 7 days for Python - which is most productive/stable?

In the end, the Reddit people were Pythonistas who had merely dabbled in Common Lisp. Their most serious problems arose from their insistence upon conflicting choices of development platform versus implementation platforms. Pardon me if I do not sympathize with stupidity.

That could hardly have been the point since at the time I wrote this they’d written nothing. if it was their point, it was a stupid one, since reddit did just as much as they did (in fact, probably more) in the same amount of time. The reason it took a week is because the reddit team built a production service, with reliability and scalability, and numerous little features. Some of the lisp clones didn’t even save things to disk. I fail to see how something that loses all your users data when the computer is rebooted counts as “more stable”.

Furthermore, reddit’s author is not a “pythonista” but a long-time dedicated lisp fan who wrote a thesis with the language.

And how is it “stupidity” to want to be able to develop on your laptop? It’s convenient to be able to work from planes, for example. (In fact, I’m posting this from one.)

I already knew that self-aggrandizement on web logs knows no bounds, but still I must say this was a terrific stunt - first complain that ‘none of the web application frameworks are any good’ and then publish some cobbled-together, copied-and-pasted piece of …. such as web.py as the solution. Wow. Congrats to the first hot-air balloon ride into outer space.

I can’t wait to see the Reddit team
crash and burn. Their implementation choiced and
‘reasons’ for the switch (let alone the actual product) clearly demonstrate to me that they are
a bunch of incompetents, lured by the latest hype
disregarding common sense in the process.

I sincerely hope that they are equipped with the
King Midas touch of shit.

Some reflection:
1. way “less code” - is good!
2. template independent - is good! (not only chetaah, may be EMPY as less (1 file!)
3. independent moduls - is good! (I can change one module by other or remove it from my distribution)

i’m using web.py with kid (or vice versa?) and this works just fine. switching from cheetah to kid was a total no-brainer, and i’m both a python n00b and a web.py n00b. this is a testament to how little web.py gets in your way. however, i don’t object to cheetah being the default templating engine and being nicely wrapped in web.py; IMHO it’s slightly inferior to kid, but it’s not that bad either.

i’m just not sure yet if the frustration of being given so little to work with from web.py weighs less than the frustration of being burdened with the superfluous fluff of full-blown frameworks. i hope that there will soon be a lib that lets you do anything you need, in a simple, straightforward way, with Less Code(tm), but still allows you to do it your way, without hidden magic, auto-generated code, etc. and if i have to write that lib myself …

I’ve been learning web.py these two days, and I have to say that web.py is cool, elegant and well-designed. Thanks so much for Aaron’s good job! Also thanks the current maintaining team. And I really hope that Aaron may continue to improve web.py to make it “next to RoR”.