One of the areas where Python truly shines is on the web.
Many well-known websites like YouTube, Pinterest, and Spotify are powered by Python.
In the mid-2000s, a number of powerful and popular web frameworks were created,
such as Django, Flask, and Pyramid.
It may feel like the choices of web frameworks and innovation in that space is basically baked and done.
But we're actually seeing an explosion of new frameworks because of some new web paradigms.
In this episode, you'll meet Martin Fassen, who created MorPath,
a relatively new web framework built to be a first-class citizen of this JavaScript,
client-side, heavy web paradigm that's popular in many apps today.
This is Talk Python to Me, episode 99, recorded January 19th, 2017.
Welcome to Talk Python to Me, a weekly podcast on Python, the language, the libraries, the ecosystem,
and the personalities.
This is your host, Michael Kennedy.
Follow me on Twitter, where I'm @mkennedy.
Keep up with the show and listen to past episodes at talkpython.fm,
and follow the show on Twitter via at Talk Python.
Martin, welcome to Talk Python.
Hello.
I'm excited to have you here.
I really love web development in Python.
And I think web development in Python is actually one of the really early places that Python
became super relevant.
And we've seen it spread to other places really well, like data science is the most recent of
these.
But, you know, at the heart, I think a lot of Python developers are web developers.
When people ask me, hey, what do you do?
And I don't want a long conversation.
I just want a quick answer.
Like, oh, I write web apps.
I'm a developer.
Something like this.
So I love web apps.
And I think it's really cool, even though there are many Python web apps around, that you've
created a new one relatively recently, within the last couple of years.
So I'm excited to talk to everyone about MorePath.
Before we do, though, let's hear your story.
How did you get into Python and programming?
Well, programming I've been doing since I was a child, trying to create games in BASIC on
various computers, first at my father's office, because we had no computer at home, and then
later on, on home computers.
What was that first computer?
The very first computer that I remember using, I think that's actually the very first computer
I used, was called a Triumph Adler, which is, I don't know what kind of brand that is.
It was a CPM machine, and then after that, moved to early PCs, Olivetti M24s are the ones
I recall also being very early.
And my home computer was an MSX2, which was C80-based and more popular in Japan and Europe
than the United States.
Okay.
And so you started out originally just poking around at these things, and did you try to
write some games?
Yes, yes.
Like, I imagine many programmers tried to write some games, very simple things.
I think it's interesting.
Like, people really want to get into it and write games, and games are some of the hardest
types of software to write.
Yeah, it used to be easier, I think, back then, because even mobile phone games are more complicated
than the games that were commercial back then.
But even, there are a lot of sort of details, like real-time requirements and things like that,
that you don't really have so much in other contexts.
Yeah, absolutely.
So where'd you go from there?
Are you planning on it at home?
Did you study computer science in university, or where?
Yeah, I did something more artificial intelligence-oriented, but that's a very long time ago.
But I had some courses, various languages, LISP, Prologue, taught myself C.
I had a Pascal course.
I did some work with Delphi for a while, back in the 90s.
And then I was programming in C++, was writing this artificial life simulation.
And then I heard about scripting languages, read an article in some magazine at some point
that listed Perl and Tickle and Python, maybe one or two others.
And I basically just wanted to write some tools that could help me manage my C++ code base.
And I looked at Perl, because that was sort of the obvious candidate at the time, and it was on my computer.
And messed around with it for a while, and I could see how that would be a useful language.
Like, it had made things like data types really easy.
But it never quite clicked in my brain.
Then I remember printing out the Python tutorial from python.org.
And I read it one evening before sleeping, and the next day I was a Python programmer.
That's awesome.
I'm sure it's one day, right?
I've done this as well, but it's been very, very long that I printed.
Like, I remember printing like 100 pages of source code in C++, trying to understand some application.
And printed it and took it with me, because I was going away somewhere for the summer,
and I didn't have a computer with me the whole summer.
Like, the world has changed so much, right?
Yes, yes.
This was just without a computer for during the evening, but I just had printed out the tutorial.
And yeah, then a few weeks later, I actually managed to get a job writing Python code in 1998,
which was kind of...
That's awesome.
...kind of surprised myself.
But I was at a...
Well, it was actually the veterinary department of Dutch University in Utrecht,
and they were looking for somebody who would clean up some script that had fallen apart
that was written in some combination of DOS batch files and D-based code.
And they wanted me to rewrite it.
And I said, can I use Python?
And they said, what's that?
And I said, well, it's a programming language.
And is it readable?
I said, yeah, yes.
It's very readable.
Which is really what they were looking for.
And then I found myself having a job writing Python code, which then quickly sort of moved into the web as well.
I started...
That was 98 as Zope was released as an open source product back in 98 as well.
So I started playing around with that and then moved into web development from there.
That's cool.
Yeah.
That first week or two of the job was just like, what am I doing here?
I can't believe they just hired me.
I just barely know this.
Well, I mean, I guess I had just been playing with Python for like a few weeks, I guess.
But it just clicked in my brain so much that I thought, okay, this is the right tool for the job.
And it was sort of more or less a scripting job anyway.
So that fit.
And I remember I was reading like complex Python, the Python news group.
And people were saying, oh, I wish I had a Python job and I'm stuck here programming in this other language.
That it looks really unrealistic to get a real Python job.
You know, back then those were very rare.
And I was like, hey, I actually have a Python job.
And I, you know, I didn't even particularly look for one.
I just had one.
So I was lucky.
Yeah.
Yeah, that's really lucky.
I still hear that from people today that, you know, they're working in some other technology.
But they play with Python at home and they're looking to get there.
So that's really cool.
Yeah, it's everywhere though now.
I mean, Python, it's really a different world.
I remember Python.
I had to explain to everybody what Python was.
I mean, even other software developers.
And these days, I don't have to do that anymore.
It's really everywhere.
Yeah.
There's some stats.
Somebody that did a study recently analyzing active, non-trivial,
GitHub repositories, which was something like they have 10K lines of code.
No, 10K is worth of file content.
And they've been updated within the last year.
And if you take that in there, Python is the number two most popular language on GitHub
behind JavaScript, which I think is overcounted.
So it's definitely a different world.
Uh-huh.
Yes, yes.
And there's just lots of commercial projects that use Python in one another way that don't
even show up on GitHub as well.
Yeah, absolutely.
So you talked about Zope and some of the, I think, you know, the successors a little
bit like Pyramid and whatnot.
It seems to me like those were kind of inspirations in your new web project because you reference
them somewhat positively in your documentation and things like that.
Sure.
Yeah.
No, I started with Zope in 98 and I was very involved with it until I think even, yeah,
about up to about 2010.
I was even chairman of the Zope Foundation for a few years.
And Zope was really, it was really a killer app for Python in a way because there are a lot
of the, as you mentioned, web development was an early thing for Python though.
I think data science was there very early as well.
But the conferences like the early EuroPythons and the International Python Conference that I
went to even before then, a lot of the people there were there because of Zope.
That's how they were attracted to the conference and to Python.
So it drew in a lot of people similarly to, I imagine, Django does now.
Zope was a web framework before we even had a word for that because web framework only arose
as a concept later.
So it was called a web application server, I believe.
These frameworks were extracted out of like, let's build this web app.
Oh, and actually there's some kind of framework we could almost use here, right?
In that sense.
Zope was actually, yeah, it was a very strange beast.
And in part it's because there were no, there was no normal beast out there at the time because
everybody was exploring.
And in part it's because it was enormously creative.
What it had was a web-based user interface, which doesn't sound very amazing now because
lots of things do.
back then that's kind of unusual for web application development framework.
And through that you would add templates.
And later on you could add little pieces of Python code.
And then it sort of slowly morphed into doing more and more sort of larger scale Python projects
using Zope's plugin architecture.
And fairly quickly we were dissatisfied with Zope's plugin architecture.
And then this whole Zope 3 project emerged,
which was sort of intending to rewrite Zope and make it all better and create the future paradise.
And while we created a lot of interesting technology and we learned a lot and actually built some real applications
with Zope 3, it never really took off because we sort of overshot in the other direction as you tend to get.
Yeah, you rarely make one rash decision.
It's usually swing back the other way, right?
Like it's just, I see that all the time in technology that people have some kind of monolithic thing
and they're like, oh, we're going to fix this.
We're going to make it extensible.
You can enable, disable, et cetera.
Yes.
And then it just becomes like, well, wait, this is no longer easy to use.
I liked it because it was easy.
And now it's like there's a hundred choices and it's hard to fix when it's broken and all that kind of stuff.
Yeah, the early Zope was never really easy if you were going to build a larger Python-based project with it.
But if you just wanted to write something quick, sort of using the web user interface, then yeah, it was very easy.
And yeah, I actually spent time in making the Zope 3 technology available in Zope 2.
So I created this little glue framework that I called 5, which is what inspired the Python name for 6.
The name 5 was already taken.
So they multiplied 2 and 3.
Yeah, oh, that's cool.
I didn't know you were behind that.
Yeah.
So I'm not behind 6, but 2 plus 3 was already taken.
So they had to go to 6 instead of 5.
Nice.
Yeah, you already had the package name.
So that's all that debate.
That technology is still in use inside of the blown CMS to this very day.
And then what I did in 2006 is actually started together with a bunch of other people.
But sort of I kicked off that project.
The Grok web framework, which was based on the Zope 3 technology, tried to put a friend of your face on it.
Also inspired by sort of the new wave of web frameworks that had been coming then.
Right.
That's like Flask and Django time.
Yeah, Ruby.
Flask was not around yet.
But yeah, Ruby on Rails and Django were new at the time.
So we tried to do something like that.
So with Grok, we had some sort of moderate success because it sort of benefited also from the larger Zope community.
People did start using it.
And it was a lot easier to start a new web framework project back in 2006 than it is in 2013.
Yeah, the expectations were way lower of what a web framework had to be, right?
It didn't have to be as polished.
And the web itself was simpler, right?
I think the web itself was simpler.
But I think the main thing that I think has changed a lot is documentation.
Back then you could start a new web framework and have, you know, some documentation, but not really exhaustive documentation.
Or very good documentation and still get some traction.
And these days that's a lot harder, I think.
The web was simpler, but the basics of Ajax, I remember I implemented those early on in Grok as well.
So that hasn't changed all that much in some ways.
But of course, client-side development was still very different.
There wasn't as much of it going around back then.
Yeah, it was much, much smaller scale.
I mean, maybe jQuery was around then.
I don't even remember the time.
Maybe not.
I think it was right.
Yeah, I remember.
I think I used MochiKit for a while.
But yeah, I don't know where the jQuery is around then.
But definitely not AngularJS and a lot of those types of things, right?
No, I did a framework there as well.
I just keep creating stuff.
You do create a lot of frameworks, I know.
Yeah, because I can't help myself.
So back in, I think, around 2010, it was just before all those client-side frameworks started emerging.
I thought, oh, I have a great idea.
I'll take my experience from server-side frameworks like templates and models and views and things like that.
And I'll take some of those ideas and then see what happens when I use them on the client.
So I created this JavaScript framework called OVL, which was based around jQuery, but allowed you to create views, view components for little bits of JSON that could identify themselves with a type.
And then it would look up the appropriate view for it.
And then you could just basically render a tree of client-side components driven by the server.
And just around that time, Backbone emerged and then Angular and then all the others.
And then I thought, okay, yeah, it was a good idea.
But I just, because other people are having the same idea.
And yeah, it was definitely an idea ripe for its time to take some of these server framework concepts and move them to the client.
So OVL didn't get any traction.
But I did learn a lot from creating it, which helped me.
That's cool.
It was absolutely the time.
Yeah, it was definitely the time when there was many, a lot of evolution and creation around that kind of stuff.
There still is, but a lot of, it was like every, every month you'd hear about something that's going to change the world.
Yes.
As you know, right.
It's still changing quite rapidly.
I, myself, I mean, people complain about JavaScript fatigue, but I kind of like it because I like the creativity around it.
Yeah, and I can say I'm a client-side web framework hipster because I was doing that before it was cool.
Definitely doing it before it was cool.
Yes.
Very interesting.
So let's talk about your project, MorePath.
One of the things, I watched a nice talk from you at EuroPython in 2014.
I'll be sure to link to the video.
And one of the things you started out talking about was why would one go and create a new web framework when Flask, Django, Pyramid, and lots more exist already?
Yes.
And that's basically because I can't help myself.
It's just a creative urge.
I mean, of course, there are reasons why I created one.
I had a customer at the time who wanted something that could do REST APIs really well and was small enough to plug into a larger application.
So I could have used an existing framework for it.
But I said, you know, what if I just take some ideas I already have and I had some pieces of code lying about and I just put them together and I'll create a little framework.
And that's where MorePath came from.
And it's still a relatively small framework.
It hasn't grown that much.
Yeah, you describe it as a micro framework and it seems certainly like it fits well in there.
It seems like somewhere between Bottle and Flask, maybe.
I cheat a little bit because MorePath, the core itself, is just a couple of thousand lines of code.
It's built on top of this predicate dispatch library called reg, which I also created, which is just 500 lines of code, I think.
But then I spun off another library from MorePath called Dectate, which is a configuration Python sort of decorator configuration library that MorePath then uses to implement its directives.
And that's also some, I don't know, hundreds of lines of code as well.
So, you know, you can still be micro if you depend on a few libraries that you're also maintaining.
But nonetheless, it's still pretty small.
I think it's about the size of Flask.
So it depends a little bit on what you count.
If you count the template language implementation or you count the request response implementation, which is WebOp in case of MorePath.
How do you add up these things?
How many lines of code is a micro framework?
But yes, it's small.
Yes.
Yeah, in my mind, micro framework is more about the exposed feature set and how much I have to learn to work with it.
More than whether it's a thousand or two thousand lines or whatever.
That's true.
Yeah, that's true.
And in that sense, I feel like your framework is something you can pick up pretty quickly and you can kind of understand a good portion of it.
Yes.
Pretty quickly.
So let's talk about the goals first.
Like what were the goals of this project?
So one of the things that I had been doing with Grok is I started using...
So I have to go back to ZOPE again.
ZOPE and also Grok use this database called the ZDB, the ZOPE object database, which is a pretty nice object store for Python.
You can just put in any almost arbitrary Python object, hook it up to another Python object just as an attribute.
And as long as there's some path to a root object, then everything gets persisted automatically for you.
That makes it really flexible because you can just basically store any kind of Python object to represent any model that you'd like.
It sounds like a kind of early concept of what document databases became.
Yeah.
I mean, it's very tied to Python, which is both its strength and its weakness.
Because, you know, if you change your class, then suddenly the instances need to be updated.
But it used to be very hard to explain because when you said database, the late 90s, early 2000s, people just assumed you were talking about a relational database with SQL on it.
And there was nothing really else that people were familiar with.
And then no SQL happened, and then it became a lot easier to explain.
It's like a hipster NoSQL database because there was NoSQL before there was NoSQL also.
Yes.
I mean, that makes some things very easy, but I needed to integrate a relational database with Grok.
And one of the nice features about the ZOPE database is that you can vary.
Well, okay, we have to go back to the ZOPE publisher.
ZOPE has this sort of publisher that's based on traversal.
So you just traverse a Python object.
So if you have a path A slash B slash C to resolve that to an object and then call a method on that object to represent it as HTML or JSON or whatever,
it would basically just do a lot of get that there and get item into the object tree.
So you would just get attribute A from the root, and then you would get item B from the item that you just got.
And then you ask for get item from that object, and then you get another object, and then you call it represent me as HTML method on it.
So that's how ZOPE worked.
And the nice feature of that is that it also allows you to construct URLs really well because you can retrace the path that you took.
So if you have an object like a document and you want its URL, you just can construct it automatically.
So I like that feature of object database and traversal.
So then with Grok, I wanted to integrate a relational database, and I was using SQLAlchemy, but I wanted to retain that feature.
And then first I sort of tried to coax SQLAlchemy to become a traversable tree.
And while SQLAlchemy, it being very flexible, made that possible.
It was not really very – it didn't really fit the models very well.
And to have to adjust the models just so you can actually traverse to it with URL paths, this didn't really work very well.
So then I had this idea, okay, I'll do it another way.
I'll create a routing framework, but one with the property that you route not to a view but to the model, just like I was used to from the ZDB.
And also I have the property that you can take instance of a class that is in the routing system and ask for a link to it.
And then it will just construct it automatically from the routing information that it has.
Right.
So you could do something like if you had, say, like a customer, let's say.
Uh-huh.
Your routing URL might look like something like slash customers slash seven.
Sure.
It might know that, okay, well, that seven actually corresponds to the customer ID.
And then you have attributes – you have decorators that will indicate, like, well, here's the function that you call to actually do that query and represent it however it's going to be represented.
Yes.
That's how it works in mobile path.
In Crockett still had a slightly different sort of – there were no decorators.
But otherwise, yeah, that's more or less the principle.
So to resolve something, you first look up the model instance.
You first do your query or you construct some kind of Python instance.
You return this from a function that's connected to the route.
And then after that, you look up a view function for it, and it can do this based on the class of what you just found and information in the request and things like that.
So you can find this view function, and the view function gets the request and also the instance that you just returned from the path function.
And then you do your rendering in there.
And that has the nice property that you can link to any class that you exposed to that routing system.
Do you, like, register documents or type entities?
Can you say these are the entities that you might be able to go to?
I register whatever models my views need.
So in some cases, those are backed by a relational database like a customer.
And in some cases, I need to create a model that represents, for instance, a collection, which is not really backed directly by a relational database.
But you can implement it in terms of queries as well.
And then you just attach views to that collection class.
So, yeah, with Grok, I figured this out.
And then with Moapath, I sort of took that idea and cleaned it up and made that sort of one of the central principles of the web framework.
And I think sort of besides the easy linking, which was sort of the primary motivation, I think it also really helps structuring code.
With other web frameworks, you often you see some kind of get this object from the database or if you can get it, do a 404 error kind of convenience function.
And if you don't use that convenient function, you have to watch for, you know, a failure to find the object.
And if you do so, you have to remember to raise a 404 error yourself.
And with Moapath, because it has more knowledge about being able to find the model or not.
It's not hidden inside of a view function like you would have in Flask or Django.
Right.
And if you screw that up in, let's say, Flask, you probably get like a none type does not have like whatever property you're looking for error, right?
Yeah.
You'll get a 500.
Yeah.
You're getting a 500 error.
That surfaces as, you know, cannot process requests, 500, which always gives people confidence in your application.
Yes.
Yeah.
It turns out that if you have to write that boilerplate code anyway in your application, it almost doesn't cost anything anymore to just write a separate function to separate it into two stages.
And you get other features from that as well.
You can do permission checks also because you have an idea of the instance that is being represented.
So you can say, okay, well, customers are only accessible.
Views for customers are only accessible if you have a certain permission.
And you can actually make sort of the concept of customer play a part in your authorization system as well.
That sort of all these, because you constrain yourself to really route to a model instance first and then to a view you get from that constraint to framework and benefit and build on top of it.
Let me take just a moment and tell you about Metis.
Metis, a sponsor of this episode.
A data scientist's responsibilities can range far and wide.
How can you be sure you have the necessary skills and training to enter the field or keep up with emerging technologies?
Metis, a data science training company based in New York City, San Francisco, Chicago, and Seattle, provides full-time immersive boot camps, evening part-time professional development courses, online resources, and corporate programs.
All with a goal of training you to become a data scientist and help you stay on top of new and necessary skills.
Metis has a long list of professional development courses starting soon in any of these four cities taught by industry leaders.
Interested in honing your skills?
Then these courses are for you.
How do they work?
Courses on topics like data visualization, deep learning with TensorFlow, machine learning, and statistical foundations run two nights a week for six weeks during the evening hours that fit into your busy schedule.
These courses are laser-focused on relevant topics and skills that are sure to enhance your career.
They have a special offer for you at thisismetis.com slash talkpython.
Be sure to check out what they're offering.
It helps support the show.
Sounds to me like you focused very heavily on RESTful type of ideas, right?
Like the routing framework assumes that there's some kind of resource or model at the end,
and you can use like get to do default queries on it and things like that.
Yeah.
One of the sort of the basic ideas behind Moapath when I started developing it is to be good at doing REST.
It turned out that actually, you know, a framework that's good at doing REST actually is also pretty good at doing traditional server-side web applications as well that do generate HTML.
Those things are not in conflict with each other at all.
But, yeah, the initial goal was to do REST well.
And actually template language implementation took, I think, a year and a half or so until I finally got around to doing that.
Okay.
So some of the origins was almost to make this something that you can serve up almost static HTML with rich JavaScript that reaches back to the server for all sorts of data and whatnot.
Yeah, because I had been developing, you know, even with OVL, I had been developing that way.
So I had some ideas of what I wanted my framework to be able to do also from the client-side perspective.
And one of the things that I sort of always have been is I tried to actually do real REST.
These days, REST, the term has become so eroded that it basically just means, yeah, it means endpoints that the client just needs to know about.
And it returns some kind of data format, but it doesn't involve linking or anything like that.
So I always actually try to do and work with real REST where your JSON data structures also include links to other structures to sort of the other endpoints and that you can sort of enter your application with one root endpoint and then find sort of all the links and follow them.
And perhaps also embed objects if you want to get to me.
To me, REST has become a synonym for HTTP-based services.
Yes.
Usually that return JSON.
But I think the two things that are often neglected that you are hinting at is one is content-type negotiation.
Uh-huh.
And the other is the dynamic linking and discovery, right?
You request an original resource and it tells you like how you can continue to explore the service.
MorePath has good support for content-type negotiation, right?
Well, actually, that's a thing I have cared less about.
So it's actually, we do have a really powerful system which allows you to build that should you want to.
But I haven't seen a lot of people actually try.
So the idea that if you request HTML, you'll get HTML from the same URL as where you, if you request JSON, then you'll get JSON.
Or perhaps you have multiple, even multiple versions of your API that are sort of negotiated that way.
In practice, I haven't seen a lot myself yet.
That doesn't mean very much.
Sure.
But you can go to like a view and say, you know, at app.json, at app.html and things like that, right?
Like theoretically.
Yes.
What you can do is you can name multiple views and then you can do, well, it's not negotiation,
but you can just say, okay, I want my different version of this or a different view entirely, actually.
Because views are looked up by the class of the model, but also by the request method
and also by the name of the view, which is the last part of the path.
If it's there, you can use an add sign.
And you can also look up views if they have a body.
You can actually look, interpret somehow the post body if it's JSON and then switch to a different view if you post something else.
Just have it dispatched to a different function.
And that whole system is extensible.
So you could just write a new directive and add more criteria for your views in your own application.
And it's actually possible to have, in the same runtime, to have one application that uses more criteria than another one.
And they will just live next to each other because of more path configuration system.
So you can add stuff that looks at the accept header.
That's pretty easy to implement sort of in a basic case.
But if you want to do full accept header support, it tends to get a little bit hairy because there's a lot of different possibilities there.
I find, yeah, right, that's a very rarely used feature of services.
Usually people say, go to this URL, do a get, and you're going to get JSON or something like that.
Yeah, because the information is encoded in the URL, right?
You can just see it.
And if you suddenly have to worry about, I mean, yeah, content types that you do the request with,
it becomes a lot harder to sort of get an idea in your head of what's going on.
Yeah, yeah, for sure.
So one of the things that you talk about that I thought was cool, and you even had some nice hand-drawn pictures.
Nice, yes.
In your presentation, yeah.
And one was like a web framework and embodied as a person pulling their dress shirt open,
and there's like a more path superman type suit underneath.
You talk about the superpowers of more path.
Do you want to touch on those a little?
Right.
So some of them I've touched on already, but one of the superpowers, I would say, is the dispatch system.
So the view dispatch system, but it's also used in other areas like this authorization system uses it, for instance,
allows you to write Python functions that dispatch on not just one argument like you have normally in Python with a method that dispatches on self.
At least you can look at it that way.
So the class of the self-argument of a method with the same name determines which actual method gets called.
You can also dispatch on other arguments as well, and not just its class, but also other attributes of those arguments.
And that allows the view system to dispatch on the class of the model.
The request methods get boot post of the request, the view name, which is also stored on the request, the last part of the path.
But also if the accept header or, you know, the type of the JSON body that you submit or whatever else you come up with.
Yeah, I can tell linking is super important in this whole routing and linking is really a key part of what you're trying to focus on.
Right.
But yeah, this is the dispatch system.
Okay.
That's not the linking or the routing system.
It allows you to write views that can be really general, work for, you know, any subclass of a particular base class, or be really specific for a specific class as well.
And you can dispatch a lot of different factors.
So that's the reg bit.
Yeah.
There's almost like an inheritance thing that you can do with that stuff, right?
Yes.
You can kind of override a view in a specialized class.
Okay.
Gotcha.
Yes.
So it's like, basically, if you just look at the model bit of how you register views, it's very much like you define methods, but you don't define them inside of the class.
You define them just as functions outside of the class.
And your decorator says where they belong.
But basically, that's just one factor of what it does to look up with.
And authorization also works that way.
It's like, do I have this particular permission for this particular model instance?
And then it also knows about inheritance.
So you can be very specific about, well, this subclass needs to have a different permission rule than everything else.
And that's reg.
Then there is the, well, the automatic link generation already talked about, which I think is really neat.
Most frameworks don't really do it.
I mean, they do it sort of halfway, where you say, okay, well, you need to give all your routes a name.
You need to come up with a name and remember that and maintain that.
And then when you want to generate a link from your Python code or your template or whatever, you need to use a special API or do this special thing.
And then you need to give it all the variables that are needed to construct the link again.
So you need to know still what those things are, whether that's the ID or whatever the link construction is based on.
Yeah.
And it usually appears in multiple places.
There's usually some configuration thing where you define the route for the URL dispatch.
And then there's the actual template where you put the thing.
And you have to know, okay, I have to replace, you know, these two parts of the URL with this part of data.
And then in your actual view, you've got to pull that data out of the route data that was passed.
And it's at least in three places.
So you have to do that.
And if you have multiple places where you link to the same thing, you have to do it sort of in multiple places.
And that also makes the code less generic because suddenly it's dependent on sort of the particular structure of what you're linking to.
And if you were to change that, then you would have to change all the templates to link to it.
Yeah.
Everything breaks.
And so what I do on my web apps is on my model, I will use like some kind of rich class and then convert it to a dictionary at sort of render time.
But I'll put like a method like here's the function or the property that gets me the URL with all the pieces filled in.
So I'll go in like template and say, you know, dot like purchase URL or whatever.
Yeah, exactly.
You kind of bake this concept directly into more path in a sense, right?
Yes, yes.
So it's that concept.
But then you don't have to burden your classes, your model classes with information about the web, which is basically, I mean, it's a reasonable compromise.
I see Django.
You can also do it with Django.
There's a convention there as well.
It's still, I mean, why would my model class know about URLs?
That's the whole point about routing framework is to not have to worry about that kind of stuff in the model classes.
Exactly.
You look at the standard MVC design pattern.
There's, you know, an arrow from down to the model, but there's no arrows back from the model, right?
Which is like these basically what I'm proposing, right?
That it knows how to get back to these things.
Yeah.
And more path knows how to do that.
But the information is stored outside of the model classes themselves.
Nice.
In the configuration system.
But that allows generic code as well.
If you, you know, make, have a list of objects and you just want to create a list of links to it.
And those objects might be completely different places in your website.
I mean, if you were to solve that with, well, you can solve it if you, again, burden your model with this get URL method, then you can do it.
And without that, you would have to use a lot of special cases in your template or whatever to create the right link.
So more path does that for you.
It gets the information that it needs from the models.
By default, just there's a convention that the name that you use in the route is the name of the attribute, but you can override that to reconstruct it.
So that's really powerful.
And then there's the configuration system.
So more path has an actual sort of thought through configuration system as an entity by itself, which should not be that unusual for web frameworks, but it actually is.
And what I mean with the configuration system is the system that lets you express things like, okay, well, this route exists and this view exists and I have this permission to access that view.
Or I want to install a middleware that sits between the request and the handler and the response and sort of can interfere in there.
Or I have a template rendering engine that I want to plug in.
All those things I consider to be configuration.
And that's really my SOAP history speaking, because in SOAP 3, that was one of the big innovations.
We created this XML-based confirmation language called ZCML, where you would express all of this in XML files, which sounded really cool at the time.
But one of the things that I did...
You just put this namespace here on this XML element that it'll...
Oh, yeah.
Just kidding.
Oh, yeah.
Well, you know, you would just have an XML, a little XML language.
That was namespaced, of course, where you would say, okay, well, this...
Well, you did have routes, but you would say, this view has this permission.
And if you...
There were all kinds of things that could be configured.
You would have adapters and things like that.
But that's all pretty low-level stuff.
But also services like, you know, the service that can send email is here.
So if the application asks for it, just look at the configuration, basically, for the service that can send email.
That kind of pattern you tend to see a lot in larger applications that need to be customized in different contexts, like a CMS, for instance.
This portion of Talk Python is brought to you by Hired.
Hired is the platform for top Python developer jobs.
Create your profile and instantly get access to 3,500 companies who will work to compete with you.
Take it from one of Hired's users who recently got a job and said,
I had my first offer on Thursday, after going live on Monday, and I ended up getting eight offers in total.
I've worked with recruiters in the past, but they've always been pretty hit and miss.
I tried LinkedIn, but I found Hired to be the best.
I really liked knowing the salary up front.
Privacy was also a huge seller for me.
Sounds awesome, doesn't it?
Well, wait until you hear about the sign-in bonus.
Everyone who accepts a job from Hired gets $1,000 sign-in bonus.
And as Talk Python listeners, it gets way sweeter.
Use the link Hired.com slash Talk Python to me, and Hired will double the sign-in bonus to $2,000.
Opportunity's knocking.
Visit Hired.com slash Talk Python to me and answer the door.
What I did with Grok is already tried to merge that back into Python, so you could express it.
Well, we didn't really have class decorators at the time, and function decorators were very new,
so we did it in another way.
But you can express it inside a Python code.
And then with Morpath, I express it with decorators.
And all those decorators are attached to classes, application classes, subclasses of the Morpath application,
that allows you to be classes you could inherit.
So you can say if you want to inherit all the configuration of a particular base class,
and then override specific bits of configuration, like one view or added piece of middleware or whatever,
you could just do that with subclassing.
And then you add your configuration to the subclass, and then your base class still has the original configuration.
So if you were to run that by the WSGI server, it would still behave as it did originally.
But your subclass has the new, if you instantiate that, you again get a Whiskey app,
and that will have the new behavior.
And that's really powerful.
And that was also powerful is that you can actually compose those little applications together into a larger one.
So Morpath allows you to mount applications onto other applications,
which all could have their complete different configuration.
It could even override some very basic behavior of Morpath and run in the same runtime.
Sure.
So give me some examples of what two or more applications might look like,
how do they work together, what's a practical story there?
Well, so for instance, if you wanted to develop something like a GitHub,
where you have projects, projects could have issue trackers.
And you also have a wiki, perhaps, in a project.
Now those issue trackers and wikis could be implemented as separate applications
that don't know anything about projects or each other.
So you could configure them with their own routes and views and things like that.
And then you can mount them together onto the project application,
should you want to use the wiki and the issue tracker there.
But if you wanted to mount them somewhere else in a completely different project,
you wanted to use the wiki, you could mount it there.
And in the mounting, the active mounting, you define sort of how to translate from the
sort of the outer applications set of concepts to the, for instance,
find the idea of the wiki app or whatever to find the wiki in the database.
So they're really isolated from each other.
But you could combine them with composition.
And then, you know, if you have a customer that likes the wiki and it's all perfect,
but just wants to tweak this one little thing, you know, in their deployment,
then you could subclass the wiki and change it in that one little way.
Hopefully, your wiki will let you do that.
That's still up to the design of the application.
But yeah, that kind of sort of feature is something that I was used to from Zope 3.
And Pyramid is the other Python web framework that has this kind of capability,
though it does not have the mounting system.
I don't believe Pyramid allows you to run multiple configurations in the same application.
But I might be wrong.
Sure.
You might have to just merge it.
But at least it has this notion of configuration, override, extension, that kind of thing.
And Moopart tries to let you do that while also being really simple to use.
So you just, you know, you have application classes and you can subclass them.
Sure.
That's really cool.
So instead of having one big monolithic app,
you could write a bunch of small apps and then sort of glue them together.
Yes.
Okay.
That's really, yeah, that's really cool.
You have a couple of ways to get started.
Like if I want to get started with Moopart, like what are the few steps?
You know, obviously pip install, Moopart, then what?
Well, there is a...
Do you scaffolding?
Yeah.
We have a cookie cutter template out there now.
It's in the documentation.
So you could use that and then you get set up with a slightly bigger Moopart application.
I've been resistant sort of to doing something like this for quite a while
because I find that if you do a lot of scaffolding,
it sort of becomes tempting to put a lot of,
sort of generate a lot of code basically when your project starts.
And then I always think, okay, why is that not a library?
Why cannot, is that not configuration?
Why is there so much sort of stuff that gets generated just to get an application going?
Yeah.
I think that, you know, Django sort of has that, right?
Yes.
It's a sense that people think Django is huge, but you can actually create like a one file simple Django app.
It's just nobody does it.
So it feels larger.
I think it's so...
Yeah.
I think also Django is larger in other senses, but I believe...
No, yeah.
It is larger, but it has its ORM and a template language and a lot of other things.
You know, and I think Pyramid suffers from this as well in that it feels really big
because when you create it, there's so many pieces.
I don't necessarily mind that, but I think that's the message that it does send, right?
It's a different philosophy, I think, and both of them have their benefits.
So Pyramid, I think, really tries to keep a lot of options open and to be sort of a little toolkit
that you can use in very different ways with a ZDB, with a relational database, with routing,
with traversal.
Sort of it has all those different options and it tries to be pretty neutral about sort of
what the way that you pick is.
And that's...
I mean, that's useful, but with Moopata, I try to be a little bit more opinionated.
I mean, it's still a micro framework.
You can still do a lot of different things, but I do say, okay, there's routing and you
route the models.
There's no traversal.
There's no routing to views.
You could plug it, the OPEC database is completely database agnostic, but it tries to be a bit
more opinionated about some bits in the hopes that that also makes it more approachable
so that basic application is not so much heavy lifting.
Sure.
Well, I think opinionated has been the success of Ruby on Rails.
It's been the success of Django to some degree.
So it's not necessarily bad.
No, no.
Opinionated is not bad, but it's sort of a tricky balance between being a micro framework
and being flexible, but still being opinionated.
But, you know, if you're going to be a framework, you have to have some opinions because otherwise
there's no point at all, right?
You're a framework.
You're supposed to guide people in one direction or another.
And Pyramid does have opinions.
It has opinions about the configuration system, about these.
It uses the SOAP component architecture to put things together, and those things are pretty
fundamental to its use.
Yeah, absolutely.
So there's a place in MorePath where I can plug in some kind of middleware?
Like, what are the options for that?
Are there many things I can choose from and wire together?
There's a tween system, and I just took that term and the concept from Pyramid.
I stole a lot of ideas from Pyramid and Chris McDonough because he went there first, right?
He was coming from SOAP, and then he thought, okay, I'm going to do it better.
And then he was very successful doing that.
And then I thought, okay, well, let's try as well, see where I end up.
So tell people about what tweens are in case they don't know.
It's a slightly higher level abstraction of what a whiskey middleware is.
A whiskey middleware is a little bit too low level because whiskey is very low level.
But a tween is a function that takes a request object and calls some kind of handler, which
is your application or another tween that's sitting below, that returns a response object.
And because you have this function that you plug in, you can do stuff to the request just
before it goes into the handler, or you can do stuff to the response just before it comes
out of the handler.
And that allows you to do all kinds of neat stuff like commit transactions or do stuff
to the HTML that comes out of the system or whatever.
Right.
Like I think rollbar is a tween in Pyramid, and you can install it.
And anytime there's a 500 error, it'll capture all the details and send that off.
And all you've got to do is like pyramid.include rollbar middleware name.
It's like pyramid.trib.rollbar or some variation of that name.
Yeah.
Django also has its own middleware system, which is very similar.
With Moabot, it plays sort of into the configuration system.
Though, of course, only the outer application, mounted applications, they all share the same
middleware of the other application.
But with inheritance, yeah, it allows you to basically make available a Moabot application
that has some middleware installed.
And then if you subclass that, then that middleware is there as well for you.
So you don't have to know very much to use middleware.
And you can also configure a middleware to go on top of that or just under it.
And then Moabot takes care of doing the sorting so that everything happens in the way you specified,
if it's possible at all, of course.
Sure.
Of course.
Okay.
Yeah, very nice.
What kind of templates are supported?
Like, do you have Chameleon?
Do you have Jinja 2?
Yes, so Chameleon, Jinja 2, and Mako, I believe.
Okay.
Yeah, those seem to be...
Much plugins.
They're not out of the box.
You just need to install the template dependency, more.chameleon or more.Jinja2, etc.
And then you just subclass from a base class that those packages expose.
And then you have that template language available.
So then when you use that extension, those particular extensions, like .pt for Chameleon
and .Jinja2, I believe, for Jinja 2, then it will just render using that template
language.
You could even use both in the same app if you wanted to.
Yeah, and one of the other types that you have is just straight-up JSON, right?
You can just put it in decorator save.
This thing renders as JSON.
It just converts directly.
Yeah, then you just return a Python structure that can be serialized to JSON.
Okay, excellent.
What's the deployment story?
Like, this is just a straight-up WSGI application, so I can put it on micro WSGI backed by Nginx
or G Unicorn or whatever I want, right?
Yes, yes.
If you take the application class and you instantiate it, then you get a WSGI application
and you could just hook it into whatever you like.
Okay, excellent.
Excellent.
How about Python 3 versus Python 2?
What's the status of the various versions?
Well, we support both.
We have for some years now.
For more path, that's fairly simple.
For reg, there's a few very dark corners of Python that we have to be aware of.
But yeah, both are supported.
Okay.
Yeah, excellent.
It's good to see lots of projects supporting Python 3 these days.
Nice to hear that one does.
Yeah, it's easier for libraries than for applications.
So if you have a test suite, that's pretty comprehensive.
It's pretty doable.
Okay.
Excellent.
So you're also involved in some other projects, and we're getting kind of the end of the show.
So maybe we just really quickly say what they are and then one or two sentences about it.
I don't want to go deep on it, but just to let people know the other projects that you've worked on.
So we talked about Zope and Grok already and Ovil, more path.
But you've also done some stuff with LXML, right?
Yeah, I created LXML years ago.
I've been messing around with XML and Python.
XML was way cool back in 2000.
I remember.
So I messed around with Python and XML, and I tried to write my own DOM implementation a few times
and created sort of a start of an XPath implementation at one point.
And it was all very interesting, and I learned a lot, but none of them sort of became anything very useful.
And then at one point, I had already worked with the LXML2 library, which is this really fast,
very featureful XML library and LXML2 on top of that, written in C.
And they had Python minings, but they really sucked because it was like programming C in Python.
So if you were to initialize something wrong, then you get a sec fault in Python.
And if you forget to freeze something, you would just get a memory leak in Python.
And those things are not something you want from a Python library.
So I decided, okay, let's be minimally ambitious and build on top of this LXML2 library because it's really fast.
And I tried to create a Pythonic API and also not invent a Pythonic API for XML because Fred DeKlant already had done that.
He created element tree.
And there wasn't any C element tree at the time, just element tree.
So what I did is, okay, let's create an element tree API with extensions for XSLT and XBot and all those things on top of a libxml2.
So that's what I did.
That actually is quite tricky because of trying to do garbage collect C objects that can be in tree shape to each other.
Things that, I mean, can be separate from each other, but maybe, you know, there's all kinds of sort of interesting details there.
And I used a technology called Pyrex at the time, but it's now been renamed to Cython.
So I was a very early adopter of Pyrex because I didn't want to write all that secret myself.
And then one of the best things in my open source career sort of happened.
So I worked on Alex Melt for maybe a year or so, and I got sort of it working, and I was doing garbage collection, and there was an element tree API, and it was all working pretty nicely.
And then this German guy, Stefan Benel, he started sending me lots and lots of patches and all kinds of enhancements and features and things like that.
And I basically, I was busy.
I couldn't really keep track of all the patches, and then I made the conscious decision at one point.
I remember it being the end of the year at one point, and I mailed him, and I said, you know, now you're co-maintainer of the project.
You can do whatever you like.
So I gave him that power and responsibility.
And that was a very good move because he's been maintaining Alex Melt since then, which is like, I don't know, more than 10 years now.
And added lots of features and improvements and things to it.
And he also descended down the stack because he ran into Pyrex because I was using that, and he wanted to improve that as well.
And in the end, he and some sort of people from the data sciences angle, they forked Pyrex, and they basically created Cython because, you know, he was descending through that as well.
And I sort of always wondered, like, if he keeps descending, you know, eventually he'd be doing microcode architecture for CPUs or whatever.
But he stopped there.
As far as I know, he's only doing Cython, but he's been a very capable maintainer.
So I always say that I put AlexML in the open source bank, and I'm getting interest on it because people have added features to it since then.
And I still use – I was using AlexML just this week in a project.
It's still – I still run into it, and I just use it as a library now, but I did create it originally.
It's probably a good feeling, though, to run across other people's projects and go, oh, there it is.
Yeah.
It's a very good feeling.
It's also very useful to say to Potential, because I'm a freelancer, so it's very useful to tell Python people that you created AlexML because then, oh, yeah, we used that.
Most people know.
Yeah, yeah.
Oh, prove to me that you actually know what you're doing.
Yeah, that library you're using?
Yeah.
I read that already.
Yeah.
Oh, all right, then.
So that's pretty neat.
Plus, yeah, I've been drawing interest on the – I mean, usually when you have an open source project, you don't really draw interest on it.
This is an exception.
Yeah, there have been features added to AlexML, like HTML parsing and all that stuff that I've used in the past that they were just pure interest payments on my initial investment.
Oh, absolutely.
Cool.
You also did something called Fantastic and Bower Static.
What are those two?
Yeah, FanStatic, actually.
Yeah, it's very hard to pronounce.
Oh, FanStatic.
Yeah, I got to read more carefully.
Yeah, it's a joke.
I mean, it's the plain words.
But it's about – those projects came out of something I actually started with, hurry.resource, which was a SOP project.
So what I needed was sort of JavaScript files in my application, and those JavaScript files had dependencies, and there was no dependency system for JavaScript.
So I created one in Python, and a resource eventually got sort of renamed FanStatic and refactored into it.
And the idea was to piggyback on Python's packaging infrastructure and Python's dependency infrastructure by just having modules where you declare that you have a JavaScript file or maybe a CSS file, other statistics resources, and how they depend on other ones.
And then it can automatically generate include statements or generate rollups for you in a web page.
Okay.
Oh, very nice.
And that's FanStatic, and it was sort of piggybacking, so you could release, like, jQuery as a Python package, and then you could just install it like that and start using it.
And it would be – you could use it as a whiskey middleware, so you would just insert FanStatic on top of your web framework.
And then inside of your view code, you would say, okay, I need this jQuery or whatever, or jQuery UI, and then it would figure out it would need jQuery as well, and then it would just include the right script text for you automatically.
By the middleware, we just automatically do that.
So that became an open source project a bunch of us worked on.
But, of course, JavaScript now does have a dependency system.
It took a long time, but – and a packaging system.
So that became –
And Power is one of those, right?
Yeah, Power is one of those.
And, you know, when I started BowerStatic, it was basically, okay, I'll just whip up something that's based around Power because there's a packaging system already there.
In some ways, BowerStatic is less powerful than FastStatic, but at least you can install a Power package.
You don't have to wrap all your JavaScript code inside of a Python package and then upload it to PyPy anymore.
You can just use the Power package database.
These days, you know, I use BowerLess and less as well.
I tend to just go for CommonJS packages on NPM and then use the tools that exist for that to package up my JavaScript code.
There are still use cases like BowerStatic can solve.
They're a little bit harder to solve with those tooling because you have a – if you have the use case that you have that's typical for enterprise software, where you have one core application that needs to be deployed in multiple places with particular extensions.
And if those extensions are also JavaScript code, bundling everything up for one deployment is not good enough.
You would need to do something to bundle up things in pieces.
And then if you want to ship them separately, it all becomes pretty hairy because the bundling tool needs to be run sort of at the very end.
So for use cases like that, something like FastStatic or PowerStatic can still work better if at least your native enterprise installation system is something like Python packages.
So that's a very specific use case.
Those are both really nice touches.
All right.
So I think I'll have to leave it there for more, Pat.
We've definitely got into a lot of detail, but I know there's a lot more we could touch on.
All right, Martin, before I let you out of here, how about your favorite editor?
When you write some Python code, what do you open up?
I use Emacs.
I sometimes try out other editors and then I go back to Emacs.
I realize now what it is because I'm not really an Emacs power user.
At least that's what I say, but I've been using Emacs so long that I...
Probably are.
...I, you know, learn more and more features over the years.
But nonetheless, I wouldn't call myself an Emacs power user.
But, you know, if you use it for 10 years and you learn a few features every year, you do know a bunch of features in the end.
But the thing I really like about Emacs is that it just knows how my Python code is supposed to be indented.
And if I press tab, it tends to do the right thing.
Also in other languages, it indents stuff.
And I'm so used to that that I tried Atom a while ago and it just didn't do indentation right for my tastes.
I could press tab, I could install an extension and edit.
It would let me press tab, but then it would just indent stuff wrong.
And then I went back to Emacs again.
So, yeah, we'll see.
Yeah.
All right.
Cool.
Yeah.
There's a lot of...
There's increasingly more options around the editors that are good, I think.
Yes.
That's cool.
Yes.
And favorite PyPI package?
We've got 96,000, almost 97,000.
I have trouble deciding.
I mean, I like AlexML, of course, because I mentioned already I'm drawing interest on that one.
So maybe I'll just stick to that.
All right.
I use so many and there are so many very nice ones, but I just can't really think of anyone in particular that stands out.
Sure.
It's like, which one of your children do you love the most, huh?
Well, yeah, it's not even my own children.
It's not even my own children.
It's other people's children do I love the most.
That's true.
No, there are a lot of nice packages there.
Let's see, whatever.
I mean, I used Click recently.
I like that.
Yeah, that is nice.
That's a nice little library.
What else have I used recently?
Well, I like, I've been messed around with numeric and pandas recently, but I was playing around with developing a data-driven game engine because, you know, why use numeric and panda for what they're really for?
So I was trying to blast data into OpenGL with Piglet.
Piglet is also a very nice library.
See there?
I remember packages now.
So I blast OpenGL data into Piglet coming out of, oh, sorry, NumPy.
I mean, of course, not numeric.
So out of NumPy arrays, blast them into OpenGL to try to reduce overhead.
Okay.
That's a pretty interesting use case.
Yeah.
All right.
Awesome.
Well, very interesting answers.
Okay.
So you got a final call for action.
Like, how do people get started with MorePath?
Like, what can they do?
What do you need?
Do you need contributors?
What's on your radar that you'd love to have?
Well, contributors are always great.
We have a few core contributors now, which I'm very happy about.
And I noticed that if I do, like, one major outreach thing, like, when I spoke at EuroPython in 2014 for, like, hundreds of people, one guy started using MorePath and he became a core contributor.
So that's just a few core contributors already, such a wealth in an open source project.
So those things I like very much.
We have a documentation website, morepath.read.docs.io.
There's also a link to a Discord chat.
So it just opens in your web browser and you can talk to MorePath developers and other people use MorePath.
Create an issue on the issue tracker.
That's also a way to reach us.
And, yeah, I started thinking a little bit about, I was looking at Django a little bit recently.
So I started a little bit about trying to create something like the Django admin UI, but then for MorePath using client-side technology somehow and perhaps using, I've never used it before, but I looked at the Pony ORM.
And that looks like a nice ORM, so maybe I'll try that one day.
Yeah, Pony ORM is really nice.
I had those guys on the show about 10 weeks ago, and it's really cool.
Yes, yeah.
That's excellent.
Everyone get out there and check out MorePath.
And, Martin, thank you for being on the show.
It was great to talk with you.
Thank you very much.
Thank you for letting me talk.
Absolutely.
This has been another episode of Talk Python to Me.
Today's guest has been Martin Fasson, and this episode has been sponsored by Metis and Hired.
Thank you both for supporting the show.
Want to learn data science?
Well, don't forget to visit thisismetis.com slash talkpython to learn more about their upcoming courses.
Get the skills that you need to succeed in the fast-paced world of data science.
Hired wants to help you find your next big thing.
Visit hired.com slash talkpython to me to get five or more offers with salary and equity presented right up front,
and a special listener signing bonus of $2,000.
Are you or a colleague trying to learn Python?
Have you tried books and videos that just left you bored by covering topics point by point?
Well, check out my online course, Python Jumpstart, by building 10 apps at talkpython.fm/course
to experience a more engaging way to learn Python.
And if you're looking for something a little more advanced, try my Write Pythonic Code course at talkpython.fm/pythonic.
Be sure to subscribe to the show.
Open your favorite podcatcher and search for Python.
We should be right at the top.
You can also find the iTunes feed at /itunes, Google Play feed at /play,
and direct RSS feed at /rss on talkpython.fm.
Our theme music is Developers, Developers, Developers by Corey Smith, who goes by Smix.
Corey just recently started selling his tracks on iTunes, so I recommend you check it out at talkpython.fm/music.
You can browse his tracks he has for sale on iTunes and listen to the full-length version of the theme song.
This is your host, Michael Kennedy.
Thanks so much for listening.
I really appreciate it.
Smix, let's get out of here.
Stay tuned.
I'm sleeping, I've been using lots of rest. I'll pass the mic back to who rocked his best.
I'm first developers.
Developers, developers, developers, developers.
you