00:00 You've built an awesome set of APIs, and now you have a wide array of devices
00:04 and clients using them.
00:05 Congratulations.
00:07 Then you need to upgrade an endpoint or make a change that is meaningful in some way.
00:12 Now what?
00:13 That's the conversation I dive into over the next hour with Stanislav Zmiev
00:17 We're talking about versioning APIs on this, the 450th episode of Talk Python to Me,
00:23 recorded January 8th, 2024.
00:25 (upbeat music)
00:31 Welcome to Talk Python to Me, a weekly podcast on Python.
00:43 This is your host, Michael Kennedy.
00:45 Follow me on Mastodon, where I'm @mkennedy, and follow the podcast using @talkpython,
00:50 both on fosstodon.org.
00:52 Keep up with the show and listen to over seven years of past episodes at talkpython.fm.
00:58 We've started streaming most of our episodes live on YouTube.
01:01 Subscribe to our YouTube channel over at talkpython.fm/youtube to get notified about upcoming shows
01:07 and be part of that episode.
01:08 This episode is sponsored by Neo4j.
01:13 It's time to stop asking relational databases to do more than they were made for
01:18 and simplify complex data models with graphs.
01:21 Check out the sample FastAPI project and see what Neo4j, a native graph database,
01:27 can do for you.
01:28 Find out more at talkpython.fm/neo4j.
01:32 And it's brought to you by Sentry.
01:35 Don't let those errors go unnoticed.
01:37 Use Sentry like we do here at Talk Python.
01:39 Sign up at talkpython.fm/sentry.
01:42 Stanislav, welcome to Talk Python to Me.
01:46 It's great to have you here.
01:47 - It's good to be here.
01:48 - Yeah, it's good to have you here.
01:49 I wonder what version we're gonna end up shipping of this episode.
01:53 (laughing)
01:55 - That's a good question. - We're gonna talk about API versions.
01:57 API versions, API versions.
01:59 - It's good to have many of them, you know?
02:00 - Yeah, it's one of those problems that, you know, as soon as you create an API
02:05 and people start to use it, they don't wanna keep rewriting their code.
02:09 But you need to evolve your API over time as it gets new features, as maybe more importantly, you realize,
02:16 like, oh, that was a bad choice.
02:18 How do you fix this, right?
02:19 So that's kind of the theme of this episode.
02:22 We're gonna talk about concrete tools that you can use to add versioning
02:28 to various API frameworks in Python and some of the ideas behind it.
02:32 So, gonna be a ton of fun.
02:34 Let's start, though, with your story.
02:37 Tell us a bit about yourself.
02:38 - Previously, I have been a backend engineer for six or even seven years now.
02:44 But recently, I have shifted into infrastructure.
02:48 Right now, I'm a platform engineering tech lead at Monite.
02:52 What I do is I make architectural decisions, I mentor the teams, I help the teams with Python-specific questions.
03:01 But mostly, I build Python-specific infrastructure, and my main job is making our business teams happy.
03:07 I try to do that in my open-source work as well.
03:11 I like to make my own open-source projects.
03:13 I like to contribute to big projects, small projects.
03:16 It's all over the place.
03:17 And we're going to be mentioning one of them today.
03:19 - Yeah, you build a really cool library that we're gonna talk about, and that'll be a lot of fun.
03:23 It looks really useful.
03:24 And some other third-party ones as well.
03:27 So, I wanted to ask you a little bit about what is the difference between
03:30 being a backend software developer, which I think a lot of people get the sense of,
03:35 like building APIs, building the backend, at least, aspect of websites and so on,
03:41 versus, say, building infrastructure in Python.
03:44 Like, what are these two worlds?
03:45 How are they the same?
03:46 How are they different?
03:46 - So, when you're building them, especially when you're building products,
03:49 it's very similar.
03:51 However, in backend, there is already a lot of infrastructure around you.
03:56 There is already a lot of people who do portions of your job.
04:01 So, like, there are people who can sell your product.
04:04 There are people who are thinking about how to move it forward, how to evolve it.
04:08 There are people who are designing it, thinking about it.
04:12 But when you're a platform engineer, you gotta do all of that yourself.
04:16 And also, one big portion of any library framework or anything else, any developer tools development,
04:23 is the fact that, unlike an API, well, a web API, a REST API, which has a very strict sort of interface
04:33 where the developer wouldn't be able to use it in a million ways, a library, especially in a language
04:39 as introspectable as Python, the developers can do crazy things with your stuff.
04:43 So, you gotta think forward for all of these use cases, and you gotta be sure that you don't expose
04:50 extra private interfaces, because once you do expose them, it won't be nice to break them in the future.
04:56 - I guess it's a little more restricted with APIs than it is with handing out packages, right?
05:01 'Cause people, they can just dive into the packages and do whatever you want.
05:05 And the API is more or less limited by what the website's willing to process, right?
05:10 - Yep. - On the flip side, you have code on the internet, and anytime you have code on the internet,
05:16 that's always a little stressful, right?
05:17 Just like, whatever's out there, it's incredible if you have a website, you pull up the logs,
05:23 how much abuse your website is taking constantly.
05:26 - True. (Gene laughs)
05:27 The great thing about building packages is that you're not worried about what happens with them after,
05:31 you just give 'em, and then if it breaks, it's not you, it's not on your servers, hopefully.
05:38 But also, as I mentioned, you gotta both be its designer, you gotta sell it,
05:43 you gotta people do that, you gotta document it.
05:45 You gotta, as a platform engineer, you do all of the parts that backend engineers
05:50 are distracted away from, or at least in most bigger companies, distracted away from.
05:55 - Yeah, super interesting.
05:56 All right, let's talk about, I guess, one more thing for your introduction before we move on.
06:02 You've worked on quite a few interesting projects here, so maybe give us just a little bit of background
06:07 on some of your open-source work.
06:09 - I'll just talk about, I guess, the most popular ones.
06:12 The coolest one was PathWalk.
06:14 If you remember, in Python, there is oswalk, but then in Python 3.12, I added PathWalk.
06:20 I have also added the type-ins for that into TypeShed, I think some other things,
06:24 and I am, I think, top 10 contributor of Tortoise ORM, the asynchronous ORM that claims to be the fastest,
06:33 or at least one of the fastest in Python.
06:36 And a few smaller projects, which I don't think there is anything particularly fun to talk about there.
06:43 It's just mostly bug fixes and small features.
06:46 But then my other projects, the ones that I maintain, are way cooler.
06:50 For example, Pydantic Duality enables you to have multiple versions of your Pydantic models
06:57 for almost free in terms of performance and without any code generation.
07:02 And CADWYN, the project we're going to be discussing today, implements, I guess, the fullest
07:08 and most sophisticated API versioning paradigm there is at this point.
07:12 - Excellent, yeah.
07:13 - A lot of cool stuff. - Pydantic Duality looks, yeah, Pydantic Duality looks really, really cool.
07:17 So a lot of neat stuff here.
07:19 A lot of different ways people might know about you.
07:21 So yeah, CADWYN's the one we're gonna be focusing on for here.
07:25 So let's start with talking about APIs.
07:29 You talked about API-first companies, what's that?
07:32 - API-first company, just like any other term that gets popular, is a buzzword, obviously.
07:39 But the truth behind it is an API-first company is any company whose main product is an API
07:47 and the SDKs associated with it.
07:50 And as a result, API-first company has to document their API really thoroughly,
07:55 think about their API, both the internal one and the external one, and take great care of that because it's their main product.
08:03 We have a lot of API-first companies right now, but with every year, new industries are joining.
08:10 And if you ever heard of open banking, things like that, banks are going to join us soon.
08:16 In some countries, they already have.
08:17 And Monite is doing similar things and tries to bring that innovation, that open banking,
08:24 and that API-first approach into banking.
08:27 - Yeah, so the company you work for, Monite, they build, you all build APIs
08:32 to basically facilitate other people doing invoicing and bill pay in their apps, is that correct?
08:38 - Yeah, you could call us a B2B2B company 'cause essentially many banks, many marketplaces
08:45 are very outdated in terms of how they handle invoicing and payments.
08:50 And we try to take all of that, combine that, and offer that as a white label product to them.
08:56 So we do this for them, they integrate with us once, and then they forget it was ever a problem.
09:03 They just give our product as their straight to their clients.
09:06 - Yeah, that's excellent.
09:08 So this seems like kind of in that category of API-first.
09:12 - Oh, yes. - Yeah, okay.
09:14 It's cool that you have concrete experience with it, right, of course.
09:18 Give us some of the laws.
09:20 We coordinated a little bit beforehand and there's some interesting laws for designing good APIs.
09:26 Like one of them is the reverse Conway maneuver, which to me sounds like a jujitsu sort of move
09:33 that you might try to pull.
09:34 What is this? - Oh, yes.
09:35 So when you are designing any system, you gotta think about the way it's going to end up
09:42 and the laws that are going to govern them.
09:45 And Conway's law is one of the main laws that do that.
09:48 To explain that to people in simple terms, if you have four teams designing a compiler,
09:53 you're going to have a four-pass compiler.
09:56 Essentially, the way you set up communications within your company, the way you set up your teams,
10:01 your structure is going to be reflected in your product and you can't do anything about it.
10:06 You can spend money, you can coordinate people, but at some point, Conway's law is going to affect you
10:12 and it's gonna be harsh.
10:14 So one of the ways to go around it is to first think of an interface that you wanna provide
10:20 and it applies to anything, not just APIs.
10:22 API is just a really good example of that.
10:24 And then design your company and the communications within your company,
10:29 the teams within your company, to reflect the interfaces that you wanna get in the end.
10:35 And that's reverse Conway maneuver.
10:36 - Okay, because your code and your APIs and all those things are gonna reflect
10:40 your company's structure and culture.
10:42 So instead of having that being implicit, make it more explicit by saying,
10:47 well, this is the endpoint that we want, the end result that we want, how do we work that backwards to end up
10:53 structuring teams or whatever to get this, right?
10:55 - Precisely, yes.
10:56 - Yeah, it is a little bit like jujitsu.
10:58 - But there are more laws that do that.
11:02 Of course, there is REST, which I guess not really laws, but principles.
11:07 Which most people, or at least most companies I saw, don't really like to follow it to the letter.
11:13 But if you do follow REST, if you try to make your API as available as you can
11:22 and as standardized as you can, then it's much easier for new people, especially junior developers to learn.
11:28 And then that makes it much easier for your clients to integrate with you, which is great.
11:33 There are also a few, I guess, more advanced things.
11:37 For example, when you are designing an API, when you're doing microservices
11:41 and when you have many services talking to each other, which in the end provide your final product
11:47 to your customers, a good idea is to omit any internal APIs and make all of your APIs,
11:53 or at least the majority of your APIs external.
11:56 That is also one of the things about API-first.
12:00 Once all of your APIs are external, it means that your clients will be able to build a product
12:06 that is of similar quality to yours.
12:08 We're using your APIs, using your data, which provides them with infinite capabilities of using you,
12:14 which is great.
12:15 - Yeah, that is great.
12:16 It's certainly frustrating when you see, it's not on the API level, but you see things in like the iOS platform
12:23 or in Windows or wherever, you're like, "Why can't I do this?" These are private things that either Apple or Microsoft
12:30 or whoever are keeping to themselves and clearly are important for building apps,
12:34 but you can't use them.
12:36 So you're suggesting that by having basically whatever APIs you need, I guess there's probably limits.
12:42 You don't wanna expose, say, your HR API if you're not an HR company.
12:47 But in general, having your APIs just be all public, it probably makes them better as well
12:52 because they just get so many more use cases.
12:54 They get treated with things like versioning and stuff more seriously.
12:58 - Exactly. - Excellent.
12:59 - It's a complex topic that could span, we could discuss it for hours.
13:03 Which parts of your API do you make public?
13:06 Which parts of your API do you start versioning?
13:09 And if you're combining your APIs using a gateway or a federation, it's a long story,
13:16 but I guess with a federation, you wouldn't really make it public.
13:18 - Sure. Let's dive into that a little bit.
13:20 Let's talk about API versioning.
13:22 We know what versioning is in Python.
13:25 We have lock files, we pin the version, we might say we're using Pydantic 2.0.5 or whatever it is,
13:34 and that leaves the Pydantic team free to do whatever they want, and we can trust that our code
13:40 is gonna stay the way it stayed.
13:41 I guess API versioning is that, but for web endpoints rather than software
13:46 links to packages, right?
13:47 - It's a little different though in some regards.
13:50 For example, when you're packaging your, I don't know, software and you just send it to clients,
13:56 thing is you need to have versions for every single thing.
13:59 For example, if we are doing a bug fix and we need to ship that to our clients,
14:03 that is going to be a separate version.
14:05 But in web, when you're hosting it on a server and your clients just make requests to your servers,
14:11 you don't need that.
14:12 You don't need to ship bug fixes as separate versions.
14:15 You can just ship them because they're not going to break anything for your clients
14:19 and your clients will be able to automatically get them.
14:21 Same goes for minor versions.
14:23 Minor versions that just add features don't really need to be for a version on APIs.
14:28 That leaves us with just major versions, which is why many companies like Stripe at Monite
14:34 use dates for their versions because we don't really need all of the power of semantic versioning.
14:41 - Yeah, yeah, sure.
14:42 More calendar versioning, because all you really need to say is, this one is different than before, right?
14:47 It doesn't--
14:48 Stripe does a lot of interesting things.
14:50 We're going to talk a little bit about what they're up to.
14:54 But you're right.
14:55 When I think about APIs that I've created, sometimes I've had other versions, right?
15:01 One of the main areas is in the apps that we have, or it's a URL work it does,
15:08 in the apps for consuming the courses, right?
15:10 We had two completely different versions of the app.
15:14 One got completely rewrote it.
15:17 And many of the API endpoints were just fine.
15:20 We could just use them over, and it didn't have to change anything.
15:23 But three or four were like, you know, this really would be better if it was different.
15:27 And so added like a slash V2 or something in there, right?
15:32 But really, really limited.
15:33 So the thing that's interesting though, that you point out here is like,
15:36 until that happened, I'd shipped many, many versions of that API.
15:41 And long as the schema didn't change, I didn't bother even specifying that it had a version, right?
15:47 It was just like slash course, my courses or whatever it is, right?
15:51 Like just give those to me.
15:52 And long as it kept giving the courses back, it was good.
15:54 - That's the thing.
15:55 As long as you extend stuff, it's going to be fine.
15:59 But as you mentioned in the beginning, you can't really do that forever.
16:02 At some point, you're going to make wrong design decisions.
16:06 At some point, even if you did everything perfectly, you know, your product is just going to evolve.
16:11 It's just going to evolve.
16:12 And then you'll need to use old endpoints that alter or, you know, get old data
16:18 and extend them in some breaking ways.
16:21 Of course, there are ways around it.
16:22 You can use API evolution.
16:24 You can just, you know, add fields constantly and duplicate the old functionality.
16:29 But after a few of such versions, your API will look terrible, which is why we use versioning.
16:34 And which is why when your company's main product is an API, when you are an API first company,
16:41 you can't do anything without versioning.
16:44 You can live for a short period of time.
16:46 For example, a pre-Series A company, I don't think should deal with versioning.
16:51 But then after Series A, after you gather, I don't know, 15, 20 clients,
16:56 it will be really hard to survive without versioning.
16:59 - Yeah, and it's a really quick and easy way to make loyal customers angry
17:05 by making changes to your API that just start breaking code.
17:08 - Even if you wait for your clients, even if you, you know, warn them, it's still not nice when you're trying to use some API
17:14 and then every three months or every month, it breaks everything.
17:18 You know, you wouldn't like that.
17:19 - Yeah, exactly.
17:20 - Even with a warning.
17:21 - You put in all the work to do the integration.
17:23 You got it working, you tested it.
17:25 Your job is, your product is not the integration.
17:28 The integration facilitates what you're trying to build.
17:30 And every time you got to go back and rewrite that stuff, it's not great.
17:34 I was working, I have MailChimp for our newsletter, marketing side of stuff, the courses,
17:40 and use them for a long time.
17:42 And I got a message like, "Hey, we're retiring this API you're using." And their API is dreadful.
17:49 Like their Python library API is dreadful.
17:52 It's like star args, star star KW args, you better pass the right things,
17:56 but there's no enforcement or instruction about what goes in there, right?
18:00 And so some random dictionary that was going in there was no longer right and had to be like recreated.
18:05 So they just decided, you know, we're gonna just take different stuff and we're just gonna shut down that endpoint.
18:10 And when they did, all of the aspects of my website just started breaking.
18:16 Like it wasn't crashing, but just anything around, "Hey, put me on your newsletter."
18:19 Like, nope, you couldn't be on the newsletter 'cause it, you know, people started sending me message,
18:23 "Michael, I'm trying to sign up to be on the newsletter, "but it just keeps saying that something went wrong
18:28 "with our site, we're sorry." I'm like, "Oh boy, go look." And they just turned off that endpoint.
18:33 And it was just like, nope, not doing that anymore.
18:36 And it was super frustrating.
18:37 It took me eight hours to rewrite it because like I said, there was no real guidance on like,
18:43 "Oh, you just pass the dictionary." Like, well, what goes in the dictionary?
18:46 You know, it was like some word of one of the keys change.
18:49 I was so, it just took forever, it was frustrating.
18:51 So what we're gonna talk about here is a lot about how do you not do, how do you not pull a MailChimp?
18:59 How do you set it up so that your code keeps running?
19:02 You know, the reason why they did that is they're like, "Well, we feel like we wrote it wrong before.
19:06 "We wanna fix those to make it better, "but we don't wanna maintain two versions."
19:11 That's probably the heart of it.
19:12 - And that's a sane idea.
19:13 - It is, until you have half a million users of the old API.
19:18 And how many of those, like, so for me, yeah, I was frustrated.
19:20 Yeah, I'm like, "All right, well, "I had plans today for constructive work,
19:23 "but I didn't go rewrite what I did four years ago.
19:26 "And then I'll do something again tomorrow." But okay, that's like a pretty good outcome.
19:30 A more common outcome is probably, I hired a consultant to build me an app
19:35 and integrate MailChimp, and they're gone.
19:38 And now my thing that I built a year ago doesn't even work.
19:41 And I don't have the skills.
19:43 I gotta go hire somebody again to dig back in there and fix it, right?
19:46 Like, that would be really frustrating.
19:48 - Yeah, that's crazy.
19:49 And it could be devastating to some clients, especially if the changes are huge,
19:54 especially if it's not just a single endpoint, or if there is only one endpoint
19:58 and it does a lot of things, then, oh my God, it's scary.
20:02 - It is, it is.
20:05 This portion of Talk Python to Me is brought to you by Neo4j.
20:09 Do you know Neo4j?
20:10 Neo4j is a native graph database.
20:13 And if the slowest part of your data access patterns involves computing relationships,
20:18 why not use a database that stores those relationships directly in the database,
20:23 unlike your typical relational one?
20:25 A graph database lets you model the data the way it looks in the real world,
20:29 instead of forcing it into rows and columns.
20:32 It's time to stop asking a relational database to do more than they were made for
20:36 and simplify complex data models with graphs.
20:40 If you haven't used a graph database before, you might be wondering about common use cases.
20:44 You know, what's it for?
20:45 Here are just a few.
20:47 Detecting fraud, enhancing AI, managing supply chains, gaining a 360 degree view of your data,
20:54 and anywhere else you have highly connected data.
20:57 To use Neo4j from Python, it's a simple pip install Neo4j.
21:03 And to help you get started, their docs include a sample web app demonstrating how to use it both from Flask and FastAPI.
21:10 Find it in their docs or search GitHub for Neo4j movies application quick start.
21:15 Developers are solving some of the world's biggest problems with graphs.
21:19 Now it's your turn.
21:20 Visit talkpython.fm/neo4j to get started.
21:25 That's talkpython.fm/neo4j, the number four, and the letter J.
21:30 Thank you to Neo4j for supporting Talk Python to Me.
21:33 Before we move on to maybe some of the approaches, Alex, Alexia out in the audience says,
21:40 "One more law.
21:41 "Hiram's law is also great.
21:43 "With sufficient number of users of an API, "it does not matter what you promise in the contract.
21:48 "All observable behaviors of your system "will be depended on by somebody."
21:53 - It is one of my favorite laws about any system.
21:56 - That's true.
21:58 - It is very true, and it's a scary law, you know, because at some point, you'll have to version everything,
22:03 which is why it's a good idea to make things, you know, I guess, to describe all the behaviors
22:10 and to try to handle all of the possible behaviors early so that you know what is going to become a part of it.
22:18 Because at some point, you'll just not be able to stop it, and then you break something that was never intended to work
22:25 and then 100 of your users are like, "Oh, we were using that.
22:28 "It wasn't a bug, it was a feature for us." (laughing)
22:31 - Yeah, we needed that.
22:33 We found that's the only way we could use it, yeah, for sure.
22:36 All right, let's talk about some of the techniques you've put together, a nice thing,
22:39 you know, like how to maintain hundreds of API versions and survive.
22:43 So what are some of the ways in which people version APIs?
22:46 - First of all, it's really hard to really figure out how people do this and how to do it,
22:53 because 90% of the time when you're going to be researching API versioning, what you're going to see is people
22:59 having tough decisions between putting it into a header or putting it into a path.
23:03 But then when you ask, "Well, how do you version?" Nobody really has an answer.
23:08 Well, there are a few ways.
23:09 First of all, you can go from the lowest level and version everything, including your database.
23:15 You just make a separate copy of your app, you can host it on a separate server,
23:19 even a separate Kubernetes namespace and forget about it.
23:23 And you have two completely separate apps and now you need to have double the people
23:29 for the maintenance, or at least a smaller separate team to maintain the second product.
23:34 This approach is really nice if you have a product that cannot break, like there needs to be zero possibility
23:40 of that breaking.
23:41 If you are on a nuclear power plant, you'll probably do that.
23:46 You can't have the old one broken and the new one working, but it's the most expensive approach, obviously.
23:53 You can, however, limit that.
23:55 You can start versioning just the application, but not the database.
23:59 Okay, now we have the same thing, a separate branch in our code, a separate, not in our code, in our repository,
24:06 a separate Kubernetes namespace, whatever, but now it's still expensive.
24:11 It's still a whole separate copy of everything you have built.
24:14 And now you have to make sure that the new one or the old one doesn't break the database
24:19 because the database might have changed, but one of the versions of code might not know about that.
24:25 And then you realize that you can go here.
24:28 - Yeah, the database aspect of it sounds especially tricky, especially a relational database with super strong schemas.
24:35 Like, you know what?
24:37 That used to be a VAR char, and now it's an int.
24:40 And so, nope, it's just not gonna work, right?
24:43 There's really little flexibility.
24:45 So even if you run the two things in parallel, then you almost have to have two databases,
24:50 and then like a mill points out, and then you have to sync the databases,
24:55 and like, it's non-trivial.
24:56 - Oh, yes, and then you start realizing, okay, why do I need a separate branch
25:00 and a separate namespace?
25:01 Let's just keep them within the same app, because as long as you have separate paths for this,
25:08 even you can build separate swaggers from them, you can have them within the same branch,
25:13 within the same repository, just in different directories, and you can just copy all of your code.
25:18 And now I call that versioning by suffering, because you think, oh, I'm going to version less this time.
25:24 You know, now I'm not versioning the database.
25:26 Now it's not a separate branch.
25:27 It's gonna be much easier to make fixes in both places.
25:31 But now you have to support both of the code bases.
25:35 And once you update anything, any of the related resources, for example, the database models,
25:40 you have to update them twice.
25:42 If you update any of the dependencies, you have to update them twice.
25:45 And that can get painful.
25:47 And after a while, if you have more than two of these versions, you're going to be asking yourself,
25:52 okay, what is the difference between these versions?
25:56 And if you weren't careful with documentation and code review, oh, you're in for a treat.
26:01 - Yeah. - But that's just the beginning.
26:02 You can version much less.
26:04 Now, instead of the whole directories, you can version just separate endpoints.
26:09 And I think that's the way that I recommend for everyone.
26:12 Everyone who is doing API versioning, who wants to be with the cool kids,
26:16 I recommend this approach.
26:18 As long as you are a small company or an individual and you want something really simple, that's a good way.
26:24 You just copy a single endpoint that you have changed, create another schema for that,
26:29 and then create another branch in your business logic, or maybe even no branch.
26:33 That's even better, using the same business logic.
26:36 But then at some point, once you want to support many versions or your versions become too different,
26:43 that's where you get a problem.
26:44 Because now all of a sudden you have all of these, oh, I have this endpoint in this version,
26:49 that endpoint in that version.
26:50 Oh, it's gonna be bad, really bad.
26:53 Again. - We've been talking like there's the old version and the new version,
26:55 but in practice, somebody starts to depend on the new version, and then you evolve the new version,
27:00 and it just, it combinatorially explodes and it's out of control, yeah?
27:05 - Precisely.
27:06 It's gonna be really bad for your developers.
27:08 Your clients are going to be fine with it.
27:09 Your developers are going to hate you.
27:11 Although not as bad as with the previous approach.
27:14 Now, the next one you get to is you start thinking, okay, what if instead of duplicating all of that stuff,
27:22 I am just going to change the schemas?
27:25 So, okay, if I do need to rename my endpoint, I am going to duplicate it.
27:28 But most of the time you're not.
27:30 Most of the time you're just changing some field in some schema.
27:32 You're renaming it, you're extending it, changing its type, stuff like that.
27:36 That's like 90% of your use cases.
27:38 Okay, what if we just change the schemas and then have some serializers?
27:42 For example, Django, what is it?
27:45 Django DRF loves that.
27:47 They love the serializers and reserializers, and you can just have each,
27:52 we have five versions, and version one serializes into version five, version two serializes into version five,
27:57 yada, yada, yada.
27:58 And so our client sends us a request, we reserialize it into the correct version,
28:03 then we return a response, we use the same business logic, and then we serialize it back into each version.
28:09 Great.
28:10 Now, we have solved all the problems of the previous approaches.
28:13 Now, you need to realize that when you're doing something like this, you are in a little bit of danger
28:19 of creating bugs in the new versions, and these bugs cascading to the old versions,
28:25 which hopefully you have tests to handle that.
28:27 But at least your clients are happy because you can support versions for a long time,
28:32 and your developers are happy because the burden of support is now on the serializers.
28:38 But you still have to support these serializers.
28:40 It's still a lot of stuff, you still have to put every field in there, and take care of any problems that happen.
28:48 And each new version will change basically all of your serializers.
28:52 A little bit, yes, but you won't be able to support 20 versions like that.
28:56 It's going to be crazy.
28:57 And now that's where we get to the final approaches, the approaches that I guess
29:03 the most sophisticated in the industry.
29:05 If you are making a backend for frontend, for your own frontend, then you can just use GraphQL.
29:11 GraphQL basically requires no versioning, and that's one of the benefits of it.
29:16 I would recommend that.
29:18 If you have developers that are good with GraphQL, that's great, you can omit versioning altogether.
29:24 But if you're not, if it's a public API, then that's where Stripe's approach comes.
29:29 I think they were the first ones to publish an article about that.
29:33 It was Brander Leach, the author of this article, he has supported API versioning at Stripe for a while,
29:38 and he has helped us with building our API versioning approach.
29:43 Essentially what Stripe did is said, "Okay, let's imagine that our developers
29:48 "are only supporting one version, the latest one, "just like with serializers.
29:52 "But instead of serializing every version to the latest one, "let's say that each version knows how to serialize
29:59 "into the next version." - Kind of like database migrations.
30:02 - Let's say that I return response from the latest version.
30:05 This response, or at least they call them, I think, version gates.
30:10 These version gates, the version gate for fifth version, knows how to convert back my response back
30:15 into the fourth version.
30:17 The version gate for the fourth version knows how to convert it back into the third version.
30:21 And all of a sudden, you get simple version gates, because if it's just field renaming in a single version,
30:28 then it's just a single line, they're really simple.
30:30 But at the same time, they're immutable, because these version gates, they're not describing the whole schema.
30:36 They're describing the difference between two versions.
30:39 And because we know that we don't wanna change the old version, we want the old version to work,
30:45 which is why the difference between version two and version three is going to stay.
30:49 It's not going to change, which is why these converters are also immutable,
30:53 which means that you don't need to spend much time supporting them.
30:57 - Right, once you get, if you're on version three and you get the transition from one to two set,
31:01 like that's not gonna change ever, right?
31:03 - Precisely.
31:04 Well, at some point you need it for optimizations or for returning extra data,
31:09 but it's such a rare use case that I haven't seen it so far.
31:13 And I hope that I won't.
31:15 - So this is Stripe's approach.
31:16 And one of the things that's really interesting that Stripe does and is laid out in this article
31:20 that you're bringing to the table here is, they also implicitly assign you a version
31:26 when you first make a call, which was really interesting.
31:30 I use Stripe and I didn't realize that when I've made calls into Stripe that the first time you make a call,
31:36 it pins you somewhere like on your user account on their side, pins you to that version of the API.
31:42 And you never know you're on a different, you're on a particular version,
31:46 but they keep you on that version.
31:47 That's wild.
31:48 - That's great.
31:49 Imagine you log in into Stripe, you make a call, and then every time you open a documentation in your account
31:56 you're going to see your version.
31:57 Every time you make a call, you're going to get response from your version.
32:00 And then because they keep versions for years, for example, we are using, I think,
32:06 three or four year old version of Stripe's API and they still keep it.
32:10 According to this article, they keep all of their versions, all since the company's beginning.
32:16 I'm not sure how true that is.
32:18 - It's 2011.
32:19 - They keep years worth of versions.
32:20 That is very true.
32:21 - Now you might see that as a negative.
32:23 Like you might think, well, I'm pinned to this version.
32:25 Like how do I escape the old version if I want the new version?
32:28 But you can pass it in a header or there's other ways to say, I explicitly choose something else.
32:34 But by default, you're just, you're living in the past.
32:36 - That's really cool.
32:37 You integrate and then you live in the past for as long as you want.
32:40 The thing is, yes, you can change it using a header.
32:44 Monite has a similar structure.
32:46 You can also change it in your partner's portal and you can also, or I guess developer's portal,
32:53 and you can also specify, for example, if you receive webhooks from Stripe,
32:56 you can specify, okay, I want this webhook of this version and that webhook of that version.
33:00 - That's cool, yeah.
33:01 - It's a really neat interface that if you don't wanna think about versioning,
33:05 you don't, you forget about it.
33:07 But the second you want to, the second you wanna have the latest API, they give you all the tools to work with it nicely.
33:14 And that's the thing.
33:15 This approach allows you to keep years worth of versions or maybe even decades worth of versions, actually,
33:22 without your developers getting overburdened because there is still a single version
33:26 of the business logic.
33:28 They work on a single version of Schemas and they just need to make sure
33:31 that when they release a new version, they write a converter for it.
33:34 And your clients are also happy because they never need to switch.
33:37 - It sounds really nice.
33:38 I suspect there's not complete ability to just ignore the old versions because maybe you want, in your new version,
33:46 you're gonna imagine this piece of data doesn't even exist and we're gonna remove it from the database.
33:51 But if it needs to be there for the old ones, there's probably some level of,
33:55 you're constrained a little bit, but it sounds a lot better.
33:57 - You're still constrained, but remember, API versioning, if we translate it,
34:03 application programming interface versioning, we're not versioning the app.
34:07 We're versioning the interface to the app.
34:09 - Yeah, so a lot of times it's like, what fields are we passing or something, right?
34:13 - If you are trying to change the data between versions, if the data is different, entirely different, I mean,
34:19 then you're probably not doing API versioning.
34:21 Then you probably should be calling it application versioning.
34:25 And actually Stripe has, I guess, a hack for that too.
34:30 If you take a look at their API, they still have a V1, but that's the thing.
34:33 They don't use this V1.
34:35 They use date-based versioning that is in the headers and their settings.
34:39 But the V1 is precisely for this situation.
34:42 If someday they will need to have something else, if someday they will need to break everything
34:48 and change the data required or any other crazy changes, they will be able to do it using that V1.
34:55 - Yeah, it's just there in case.
34:56 - Exactly.
34:57 - Cool, all right, well, let's talk about your project.
34:59 Let's talk about your project that makes this possible in Python because the Stripe examples,
35:04 I believe this is Ruby that they all have here, right?
35:06 - It is really nice to read until you try to understand it, until you try to understand what it actually does.
35:12 And it's crazy.
35:13 - I know, when I was reading this today, I was like, you know, I'm pretty glad
35:17 that I'm not writing Ruby right now.
35:18 Okay, all right, let's talk about Python though.
35:21 You've got a project called CAD1 that takes the Stripe philosophy and applies it to versioning
35:28 for the most popular API framework for Python, FastAPI.
35:33 Tell us about it.
35:34 - Here's the thing, when I started developing it, we were using versioning by suffering.
35:39 So we were using directory copies and none of us really enjoyed it.
35:43 So there needed to be a solution.
35:45 And I went to Stripe, I went to other companies, I spoke with them, I tried to understand the nitty gritty details.
35:53 As I mentioned, Brander Leach extended us huge help when he, I guess, described how he thinks
36:00 about API versioning right now.
36:02 And I built it.
36:03 The first version was just like Stripe.
36:05 It was exactly what Stripe did to the letter.
36:08 But then at some point, we started applying it at Monite and I realized, wow, Stripe had a lot of legacy.
36:15 So they didn't implement some things.
36:17 For example, in Stripe's article, you can see that they're not using any schema validation
36:23 or at least how do I describe it?
36:25 Their business logic just receives a hash map, a dictionary.
36:30 It's not statically typed, but FastAPI uses Pydantics.
36:33 So it's a bit more complex than that.
36:35 Okay, we added support for that.
36:38 It's a bit more complex.
36:39 It's a bit more, I guess, sophisticated now.
36:42 But then I also realized that Stripe, they don't migrate requests.
36:46 They only migrate responses because emigration of a request is usually not just a breaking change,
36:53 it's a change in a business logic.
36:55 Because when you return a response, you can just return the whole thing and then just play around with it.
36:59 But when you are changing the request, your business logic also needs to change.
37:04 And that's what they did.
37:06 They have extra ifs and extra levels of business logic to handle that.
37:10 Well, apparently it's not necessary.
37:11 Apparently requests are also migratable.
37:14 And that's the second feature that greatly distinguishes us from Stripe.
37:20 This thing can statically, well, by statically, I mean, it co-generates your schema
37:26 so that you can see everything before you run your app.
37:31 This thing can migrate both requests and responses, and it supports everything that Stripe does.
37:37 And we have pretty big plans for extending it.
37:40 And as an added bonus, we also have a pretty big documentation.
37:46 Well, not compared to Django or FastAPI.
37:48 Django is older than me and FastAPI, it's pretty amazing in terms of its documentation.
37:55 Sebastian Ramirez is an expert of making tutorials.
37:58 And if you take a look at our reference documentation to the left and our, how do you describe it,
38:03 recipes documentation, they basically describe to you every single use case that you could have
38:11 with API versioning, every single problem that you could solve with it.
38:15 And then we try to apply our approach to it.
38:20 So it's not just the framework, it's all the ways you could use the framework.
38:24 Someday, hopefully we will also extend it beyond FastAPI and Bidantic.
38:29 And we also have sections for theory for people who are not Pythonistas,
38:34 who just wanna learn how to do API versioning, but failed to find any resources.
38:39 So Cadwyn also becomes this, I guess, hub for API versioning resources and anything related to them.
38:46 - It seems like it would probably be pretty easy to adapt to anything that uses Pydantic
38:51 for its data exchange.
38:52 - Oh, yes.
38:53 - Yeah, so like, for example, in Flask, Flask doesn't do the model binding type stuff
38:58 on the endpoint, but you can just take the input data and just star star it into a Pydantic model.
39:04 And then, you know, now you're in Pydantic world, right?
39:06 So it sounds like maybe something along those lines might be a good bridge.
39:10 - There's a lot of magic involved in trying to interface with FastAPI simply due to its internal complexity.
39:17 - Right.
39:18 This portion of Talk Python to Me is brought to you by Sentry.
39:22 You know Sentry for their error tracking service, but did you know you can take that all the way
39:27 through your multi-tiered and distributed app with their distributed tracing feature?
39:31 Distributed tracing is a debugging technique that involves tracking requests of your system,
39:37 starting from the very beginning, like a user action, all the way to the backend, database,
39:41 and third-party services.
39:43 This can help you identify if the cause of an error in one project is due to the error in another.
39:48 Every system can benefit from distributed tracing, but they're especially useful for microservices.
39:53 In this architecture, logs won't give you the full picture, so you can't debug every request in full
39:58 just by reading the logs.
40:00 Distributed tracing with a platform like Sentry gives you a visual overview about which services
40:05 were called during the execution of certain requests.
40:08 Aside from debugging and visualizing your architecture, distributed tracing also helps you identify
40:13 performance bottlenecks.
40:15 Through a visual like a Gantt chart, you can see if a particular span in your stack
40:19 took longer than expected, and how it could be causing slowdowns in other parts of your app.
40:24 Learn more and see some examples in the tracing section at docs.sentry.io.
40:29 To take advantage of all the features of the Sentry platform, just create your free account.
40:33 And for all of you Talk Python listeners, use the code Talk Python, all one word,
40:38 and you'll activate a free month of their premium paid features.
40:42 Get started today at talkpython.fm/sentry-trace.
40:46 That link is in your podcast player show notes and the episode page.
40:50 Thank you to Sentry for supporting Talk Python To Me.
40:53 So some good questions, but I think they'll be somewhat answered if we just maybe talk us through your tutorial here.
41:02 Like how do we use Cadwyn and how does it manifest?
41:06 How does it work at runtime?
41:07 Stuff like that.
41:08 - First, before using Cadwyn, you know, you write your regular app, but then you just structure it a little bit differently.
41:13 When you are versioning, you need a directory that you will consider versioned.
41:19 So, and this directory will be duplicated for every version.
41:23 No worries, you're not going to support the version code.
41:26 It's going to be code generation all the way through.
41:29 And you can use it to generate connectors for your application, or you could use it to generate schemas or enums,
41:36 you know, the different version of them and anything else related to them.
41:40 So you pick such a directory, you call it latest as for your latest version.
41:44 Then you define a single version bundle.
41:48 If you go a little bit down, you will see somewhere in here after defining versions.
41:53 Yes, we define our version bundle with a single version, whichever version it is for you,
41:58 whichever date it is for you.
42:01 And then you run code generation.
42:03 It generates a directory that simply imports from your latest, and that's where you can start doing the magic.
42:10 Now you just use our versioned app.
42:13 The, if you take a look, we have a versioned API router.
42:17 Then you use the Cadwyn versioned app.
42:20 You generate version routers, and then it's going to generate a really nice swagger for them.
42:27 If you open it somewhere, I think, yep, this one.
42:30 So now your dashboard, the one on docs, is not the swagger page, but the dashboard of all your versions.
42:37 And that's it.
42:37 After that, you can simply repeat the steps above.
42:41 So you write a migration, write that converter that describes the difference
42:46 between the versions.
42:47 We have it somewhere there.
42:49 If we had an address as a string and we want to change it to a list, we say, okay, schema, base user,
42:55 field addresses didn't exist.
42:57 - You have a user object, which is a pydantic model.
43:00 The user object in version, the initial version, just had a field called address, which was a string.
43:06 But you said, you know what?
43:07 People might have multiple addresses that they live at.
43:10 So let's make it a list of strings and make it address as, right?
43:15 Which seems like a minor change, but clearly that's going to be a major problem
43:19 for the things that assume there's an address and it's no longer there, right?
43:22 So you create this version change derived class that has a upgrade downgrade basically feature, right?
43:30 - You simply describe how do you migrate your schemas?
43:32 How do you change your schema classes, your pydantic models?
43:36 And then you describe how you convert your requests and response, and that's it.
43:41 This class is the only thing you write for the new versions.
43:44 Then you run code generation, run your app.
43:46 That's it.
43:47 Everything works.
43:48 - Yeah, that's super cool.
43:49 So couple of questions that were, or comments that are interesting here is,
43:53 Emil asks, is it like database migrations?
43:57 And Mike Fila points out, it's really, really similar to like Alembic, right?
44:01 You say, generate me the changes.
44:02 Alembic is a little different in that it looks at the two different database schemas
44:06 and tries to actually write the migration code to do the DDL changes.
44:11 But in principle, it's like, every time there's a version change, there's some bit of code that is generated that will run.
44:18 And then the other one is about, how does this affect performance?
44:21 And I guess the only real performance change is like, there'll be a couple of steps to the serialization,
44:27 but they're often small.
44:28 Like in this case, the new version takes the old data and says, well, we're just gonna have a list
44:32 with one address.
44:33 So we're gonna stick the one address into the list of addresses and vice versa.
44:38 You just set the address field to be the first thing in the address list.
44:41 And like, that's a really low amount of code overhead to go back.
44:45 And hopefully people are moving towards the new versions eventually, you know, that kind of thing, right?
44:50 But if they don't, then this code just runs to transform the dictionaries that goes back to them, right?
44:55 - Imagine you have like Stripe, I don't know, 13 years worth of versions.
45:00 Now, if you are a successful company, and if you are alive, you are probably a successful company after 13 years,
45:06 you know, if you're a successful company, that most of your clients will probably be newer ones.
45:12 The longer you exist, the more clients you have, and the more clients you have,
45:15 the more, you know, newer clients you have, the newer API versions they will use.
45:19 - Even if they're old clients, they might decide to, they might rewrite their own app and get a new version,
45:25 right?
45:25 Like they could upgrade their code 'cause their code's gonna be 13 years old
45:29 or 12 years old at this point, right?
45:31 They need to change too.
45:32 - At some point, yes, your old client might, you know, from the very first version might request something
45:38 and then we'll go through all converters.
45:40 Well, not all, all converters that are related to the specific request and response,
45:45 and that is probably going to be in the tens, not even in the hundreds, even after 13 years.
45:50 But your clients from the latest versions, or even from the middle versions, will not suffer that.
45:56 So the majority of your clients will actually see almost no performance difference.
46:01 And even your old clients, even your clients for 13 years old will probably,
46:06 even if they do see a performance difference, it's not going to be multiple orders,
46:10 it's not going to be 2X, 3X, it's probably going to be maybe 20%, maybe even 10%, depending on how slow your app is.
46:17 And that's considering how many years have passed is crazy fast.
46:22 - Yeah, absolutely.
46:23 That's very cool.
46:24 So what about the URL?
46:27 What about the end point?
46:28 How much do consumers of your API have to actually think about, I'm using this version or that version?
46:36 Is it passed as a, is the version passed as a header, it's a query string, part of the URL?
46:41 - The version is passed as a header and Cadwyn by default requires it to be.
46:47 But if you have some developer portal, if you want to store your version in some database,
46:52 then even though it's a little more advanced, you can write yourself your own middleware
46:58 that will check if the client already has a version and then pick that version
47:02 and put it into the context var and it's possible.
47:06 So by default, you're going to have, no, no, no, middlewares are the more advanced sections,
47:13 not in the tutorial.
47:14 - There's this other one we're gonna talk about in a section has just an example of all,
47:17 you basically just in FastAPI, when you create the app, like app equals FastAPI,
47:23 you just say middleware equals a list of classes that implement these behaviors.
47:28 So what you're suggesting is like, you just create a class and you pass it in at FastAPI startup.
47:33 And it just goes, is there a version being passed to me?
47:36 If not, let's see who the user is based on their API key, pull that from a database
47:41 and maybe just set that header right there before the request and then let it fly, right?
47:45 Something like that.
47:46 - Yep, and then your API will essentially be Stripe-like where your client won't need to
47:50 think about versioning at all.
47:51 But by default, of course, the clients need to always pass it with a header,
47:56 which might sound like something really unnecessary and hard for the clients.
48:01 But if you imagine that you're writing code, it's exceptionally easy to just put it into your,
48:06 I don't know, requests or HTTPS client and forget about it, you know?
48:11 - It is pretty easy.
48:12 If you're consuming APIs in the fashion that we're talking about, you're probably already putting an API key
48:19 or some kind of thing into the header as well, right?
48:23 Like, so you're already messing with the header anyway.
48:26 Just put another value in there.
48:27 - Precisely.
48:27 - All right, so right now this is a FastAPI thing, but you're considering opening it up to other areas,
48:34 other platforms, other web frameworks?
48:36 - It has a, what is it called?
48:39 Milestone for that, which I am planning to extend with tasks, with plans.
48:44 First, you know, it's like with Pydantic, you know, at some point it separated the core
48:49 when it was moving to Pydantic 2, and that's one of the plans for Kadwin, you know?
48:53 Separate the core without breaking the public interfaces.
48:57 And then at some point, use this core to build more Kadwins, you know?
49:01 Cadwyn Django, Kadwin Flask, whatever.
49:03 Or it doesn't even have to be a part of Kadwin.
49:06 The approach is there, so anyone can use it.
49:09 Because 99% of complexity in implementing Kadwin was actually figuring out these patterns
49:16 and understanding how to do it.
49:18 Because unlike a web app, unlike, I don't know, many apps that we develop daily,
49:24 it doesn't, these things haven't been done.
49:27 People have migrated stuff, but it was mostly, you know, oh, change this dictionary without a proper DSL
49:34 or without code generation, you know, without the niceties that would make your IDs happy
49:41 and that would make, you know, autocompletion happy.
49:44 - Yeah, you are a contributor to TypeShed, which is awesome.
49:48 So you must care about, you know, conveying the type information of the different versions
49:55 and stuff like that.
49:56 That's tricky, right?
49:57 And of course, working on FastAPI and Pydantic, those are very type, oh,
50:01 and the shirt says TypeHints for life, I love it.
50:03 I don't have any cool slogan on mine, I just have gray stripes, but I should, very cool.
50:09 So yeah, wanna speak to the TypeHint, type information aspect of this whole thing?
50:13 - Essentially, as I mentioned, Stripe did it using, you know, simply dictionaries
50:19 and it was mostly untyped.
50:22 As probably many people know, Stripe, after having so many problems with scalability
50:28 in terms of like the team scalability and problems with typing, they have decided to adopt Sorbet,
50:36 well, not adopt, create Sorbet, which is essentially a type checker.
50:41 It's like a type checker and DSL for Ruby for TypeHints, which is really nice.
50:47 It has both the runtime component and a static component as far as I remember,
50:52 but they still, most of the time, work with untyped code, which essentially can lead to many errors.
50:57 Cadwyn tries to make sure that you still work with your schemas, you still know which schema you work with,
51:03 it's always the latest.
51:05 And Cadwyn tries to make sure that you're not going to have any type errors
51:11 if you don't want to, and you're going to have all of the niceties of having this dynamic request response conversion thing.
51:19 So we're trying to take the best of both worlds, have you support very little code,
51:23 but like make sure that you can type hint everything without any issue and you're not going to have problems
51:29 and your type checker is going to check everything for you.
51:31 And your, I don't know, VS Code is going to see which fields something has, which type something has.
51:37 - Yeah, excellent.
51:37 One of the big problems with like all the runtime modifications, like one way you could have done this is that app startup,
51:44 it could look at all of those things and it could just dynamically generate
51:49 all the different versions and all the things, you know, using meta programming,
51:54 but then the editors hate that.
51:55 - Yeah, it does that with the endpoints because endpoints are not your editors,
52:01 they don't care about endpoints.
52:03 You never call your endpoint from your code.
52:06 No, you usually call the business logic from it, which is why I decided to do it in runtime.
52:10 You don't really need it.
52:11 But for Schemas, yes, for Schemas, having them generated and then having people guessing
52:16 and then having their IDs just put everything in red, that is horrible.
52:21 I wouldn't want anyone to have that.
52:23 - Yeah, yeah, I don't like it either.
52:24 Not at all.
52:25 It's right up there with the star, star, star KW orgs.
52:28 Have a good time guessing in my world.
52:31 - I don't like either of them.
52:33 Awesome, all right.
52:34 Well, let's just maybe touch real quickly on a couple of other choices that are out here.
52:39 You know, Kadwin looks awesome, but we also have, for example, we have from Dean Way,
52:44 we have FastAPI versioning, which has a very different philosophy.
52:47 It has kind of the V1, the V2 style that you might maintain multiple versions of.
52:54 You wanna just maybe speak to your thoughts on this one real quick and people can take that if they like this style.
52:59 - I think it's either the first or the most popular.
53:01 It's definitely the most popular, but I think it might even be the first library in FastAPI
53:06 to implement versioning, implement proper versioning.
53:09 It has a few issues.
53:10 First of all, it only, as far as I remember, it is mostly duplication based,
53:15 so it doesn't automate anything for you, which is a good thing if you know what you're doing
53:19 and you're not going to have a lot of versions.
53:21 But if you have 10 or 20 versions, if you wanna keep them for a long time,
53:27 this is the endpoint copying approach that I mentioned, that with time is going to kill you,
53:33 which is a problem here.
53:35 The second problem is that it, as far as I understand, kind of recommends semantic versioning,
53:41 though it doesn't bind you to it.
53:43 You can use dates, though you will have to make sure that they're dates yourself,
53:47 and you can use just simple integers.
53:50 So you're not bound by semantic versioning, but it has a few examples with that.
53:55 Aside from these two, it's an amazing library, and for anyone who just wants to version a small app,
54:01 just add a single version that is going to be, the old version is going to be deleted in like two months,
54:06 use that, use FastAPI versioning.
54:08 It's an amazing library and you don't need anything else.
54:12 CADWYN is for use cases where you need to keep a larger number of versions,
54:18 or if the changes between your versions are much trickier.
54:21 - Yeah, like this might be pretty good for my example I talked about with the courses app that we built, right?
54:27 We literally have two versions.
54:28 It's been around since 2018, and there's two versions.
54:31 You know, like that would be a decorator, and I'm probably good to go.
54:35 I don't need to think too much about it.
54:36 - Precisely.
54:37 No code generation, no nothing.
54:39 You just put it in and you go.
54:41 - Another one, if you're a Flask person, you could have Flask Rebar, which seems to have a real similar philosophy
54:49 to what you just described, but it works for Flask.
54:52 I feel like it's a little more complex than that.
54:56 It is also the parent point.
54:59 So it also allows you to duplicate single endpoints.
55:02 It also allows you to do arbitrary prefixes.
55:05 So you can have integers, you can have dates, you can have, you know, whatever.
55:09 - Old, new, whatever.
55:11 - Yeah, old, new.
55:12 I guess some people do that.
55:14 - If you want.
55:16 - Yeah, latest, latest.
55:17 - Yeah, yeah.
55:18 Latest, really latest, final.
55:19 - Like a new folder, new folder.
55:21 - Yes, exactly.
55:22 - But yeah, it's really similar.
55:26 And I guess-
55:26 - They probably use zipping up folders for version control as well.
55:29 I'm just naming them by the date.
55:31 Final, final, final.
55:32 - Let's not use, let's not use Git.
55:34 Let's just copy them.
55:36 As for versioning, that's like a really good metaphor.
55:38 If you are a person that just needs another version of their Google Doc, you wouldn't use Git.
55:44 Well, unless you use Git for everything else.
55:46 And same here.
55:47 You don't need a huge combine for using Flask.
55:52 And Flask usually, at least in my experience, tries to build simpler APIs.
55:56 And you wouldn't use, well, most companies that do API first don't use Flask.
56:02 They try to do, you know, Django DRF or FastAPI, which is why this library seems like a perfect choice
56:09 for that.
56:09 - Yeah, indeed.
56:10 It seems really nice.
56:11 One of the things that, we were speaking of duplication, one of the things that they just jumped right into
56:16 to demonstrate here, and I imagine the FastAPI version one as well could do it,
56:22 but there's one endpoint that doesn't change.
56:25 And there's one that is different.
56:27 So they copy the one that's different, but for the one that doesn't change,
56:29 they just put a V1 and a V2 decorator on that one.
56:33 And it remains unchanged basically, right?
56:35 - Cadwyn tries to escape this kind of verbosity.
56:39 And essentially you have all endpoints everywhere.
56:41 And if you wanna, you know, omit some endpoint or change some endpoint or add some endpoint into the new version,
56:47 you just add a version change and say, oh, this endpoint didn't exist in the old version.
56:53 That's it.
56:54 - Very cool.
56:54 Yeah, because if you had 20 of these, you'd have 20 decorators in the little function, right?
56:59 That's where it starts to get.
56:59 - Not a huge issue for Flask.
57:01 I haven't seen anyone building, I don't know, 50 or 200 endpoints apps with Flask.
57:08 I'm sure people do that, but I think it's the minority.
57:10 - Other side of the fence, Django REST framework, DRF.
57:13 You already mentioned them a little bit.
57:15 And so Django, they already have like versioning built into the framework itself, right?
57:21 - They have versioning interfaces built into.
57:23 So like, it's not, they're not implementing versioning for you, but they give you all the tools
57:29 to implement your own versioning.
57:30 - Yeah, okay.
57:31 So they kind of have the building blocks for it.
57:33 Excellent. - Exactly.
57:34 - All right.
57:35 Well, I think we're pretty much out of time, but thank you for being here.
57:39 This has been really interesting.
57:40 - Oh, it was for me as well.
57:41 Thank you for having me here.
57:42 - Yeah, you've given everyone a lot to think about.
57:44 You know, before we wrap it up, two quick things, you know, notable PyPI package.
57:49 I mean, we've already given Cadwyn a shout out.
57:51 Anything else that like, oh, I came across this and it was super cool.
57:55 Maybe it deserves a little bit of a shout out.
57:57 Anything that like you use a lot that you would recommend?
57:59 - For us in our company, we started using one more package of mine.
58:04 It's called pytest fixture classes.
58:06 - Okay. - Essentially, when you are building groups of fixtures that create some objects for you,
58:12 for you to use in your tests, and then all of a sudden one of your tests
58:16 needs an object that's a little different, you know, just a little bit, and then everything just falls apart.
58:21 - Yeah.
58:22 - A factory fixture pattern that you can see in pytest allows you to, you know, make a function within a function
58:29 and one of them is a fixture and that's all cool and nice, but then it's not typed.
58:35 You can use, you know, the callable syntax and then you're not going to have
58:39 the keyword arguments there.
58:41 And otherwise you can write a protocol and it's a lot of code.
58:45 What pytest fixture classes does is it allows you to type everything there.
58:50 So you write the same amount of code, but it's all typed and now you have,
58:55 you can have groups of fixture classes building on top of each other, which is a nice pattern to have,
59:01 but a really bad pattern to abuse.
59:03 - Very cool.
59:04 All right, well, nice, nice recommendation.
59:06 People can check that out.
59:07 Emil, our audience already says, "Amazing thing, I'm already using it." So final call to action, people have APIs,
59:15 they've listened to us talk for an hour and they're like, "Oh, we should be versioning our APIs.
59:20 "We probably should be doing something here." What do you recommend people do?
59:24 How do they get started?
59:25 How do they go further?
59:26 - So first you need to decide whether your use case is simple.
59:30 How many versions are you going to support?
59:31 If you're not planning to support more than two versions or if you are supporting versions
59:36 only for your internal microservices or only for your front end, then consider using the simple tools.
59:44 Consider using the FastAPI versioning, you know, Flask, Rebar, things like that.
59:49 But then once you realize that you need to support versions for months and you need three or more versions
59:56 and you're providing your versions as a public, you know, to the public,
01:00:01 then I advise moving straight into CADWYN or CADWYN-like frameworks.
01:00:06 If you use FastAPI, you can just use CADWYN.
01:00:08 It has a bit of a learning curve, but within making one version, you will realize just how simple it is.
01:00:14 And if you're not using CADWYN, that's fine as well.
01:00:17 I have all of the resources that I used to build it and all of the resources, you know,
01:00:23 describing different ways of API versioning there in the repository in the theory section.
01:00:28 So you can just read that.
01:00:30 And even if you don't like that approach, we describe everything else there.
01:00:34 So you'll be able to pick the one you like.
01:00:36 But my personal recommendation, don't pick the directories.
01:00:40 They're painful.
01:00:41 - Awesome. Really good advice.
01:00:43 Super cool project.
01:00:44 So thank you for being here Stanislav.
01:00:47 And talk to you later.
01:00:48 - Talk to you later.
01:00:49 - This has been another episode of "Talk Python to Me." Thank you to our sponsors.
01:00:54 Be sure to check out what they're offering.
01:00:55 It really helps support the show.
01:00:57 It's time to stop asking relational databases to do more than they were made for
01:01:03 and simplify complex data models with graphs.
01:01:06 Check out the sample FastAPI project and see what Neo4j, a native graph database, can do for you.
01:01:13 Find out more at talkpython.fm/neo4j.
01:01:17 Take some stress out of your life.
01:01:20 Get notified immediately about errors and performance issues in your web
01:01:25 or mobile applications with Sentry.
01:01:26 Just visit talkpython.fm/sentry and get started for free.
01:01:31 And be sure to use the promo code, talkpython, all one word.
01:01:36 Want to level up your Python?
01:01:37 We have one of the largest catalogs of Python video courses over at Talk Python.
01:01:41 Our content ranges from true beginners to deeply advanced topics like memory and async.
01:01:46 And best of all, there's not a subscription in sight.
01:01:49 Check it out for yourself at training.talkpython.fm.
01:01:52 Be sure to subscribe to the show.
01:01:54 Open your favorite podcast app and search for Python.
01:01:57 We should be right at the top.
01:01:58 You can also find the iTunes feed at /itunes, the Google Play feed at /play,
01:02:03 and the direct RSS feed at /rss on talkpython.fm.
01:02:08 We're live streaming most of our recordings these days.
01:02:10 If you want to be part of the show and have your comments featured on the air,
01:02:14 be sure to subscribe to our YouTube channel at talkpython.fm/youtube.
01:02:19 This is your host, Michael Kennedy.
01:02:20 Thanks so much for listening.
01:02:21 I really appreciate it.
01:02:22 Now get out there and write some Python code.
01:02:25 (upbeat music)
01:02:43 Thank you for watching.