Git hook scripts are useful for identifying simple issues before committing your code.
Hooks run on every commit to automatically point out issues in your code,
such as trailing white space and debug statements.
By pointing out these issues before you get to a code review,
this allows the code reviewer to focus on the architecture of a change
while not wasting time with trivial style nitpicks.
As we create more libraries and projects,
we recognize that sharing pre-commit hooks across projects is painful.
That's why I'm happy to welcome Anthony Sotili to the show to discuss pre-commit,
a framework for managing and maintaining multi-language pre-commit hooks.
This is Talk Python to Me, episode 282, recorded July 1st, 2020.
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.
This episode is brought to you by Brilliant.org and us.
Python's async and parallel programming support is highly underrated.
Have you shied away from the amazing new async and await keywords because you've heard it's way too complicated or that it's just not worth the effort?
With the right workloads, a hundred times speed up is totally possible with minor changes to your code.
But you do need to understand the internals.
And that's why our course, Async Techniques and Examples in Python, show you how to write async code successfully as well as how it works.
Get started with async and await today with our course at talkpython.fm/async.
Anthony, welcome to Talk Python to Me.
Yeah, glad to be here.
It's great to have you here.
You and I have been on together on Python Bytes along with Brian.
We had you over there once, I believe, but never on Talk Python.
So welcome to the show.
Really happy to have you around.
And you have this cool project that we're going to talk about.
And it's kind of like the old thing is new again, right?
It's been around for a little while, but it's some of the other stuff that seems to have boosted its popularity.
And to me, it seems like a fantastic idea with pre-commit hooks and a framework for running them and building them.
So I'm excited to dig into it with you.
Sounds great.
Happy to talk about it.
Yeah.
Before we do, though, let's get to your story.
How did you get into programming in Python?
My first Python programming was actually at Yelp, where I worked as a software engineer.
I was hired as a front-end developer and mostly did CSS and JavaScript.
But quickly realized that in order to unblock my own job, it made a lot more sense for me to learn the back-end and write my own APIs instead of waiting for a back-end engineer to implement the same APIs.
So I picked up a bunch of Python and made my job a lot easier in that way.
Nice.
What did you use on the back-end?
Was that Flask, Django, something else?
So unfortunately, Yelp was created before there were a lot of these larger frameworks.
And so Yelp had kind of their own homegrown framework.
I eventually shifted from front-end to full-stack to back-end to infrastructure.
And part of the infrastructure work that we did there was to move the monolithic code base to be based on Pyramid.
So kind of like the Pyramid pylon sort of approach.
And it was actually really nice because we could basically cut out parts of the proprietary Yelp main code and stick in bits of Pyramid so that we could offload some of the logic to open source software.
Oh, nice.
Yeah, that's really cool.
Most of my sites are built in Pyramid as well.
I like that framework.
It gives you a ton of customization.
It's a little bit daunting to start with like small projects.
I usually prefer to use like Flask or whatever.
Yeah.
But it definitely lets you do everything.
Yeah.
Well, if I'm looking at new things that look really sweet, FastAPI is looking sweet.
Yeah, I've heard some good things.
I haven't had a chance to try it out yet, though.
You know what I really like about it is you can define models, classes in Pydantic, and then say your view method takes one of those models.
If you do get receiving like a JSON post and it'll do validated pre-population of all the fields using the Pydantic validation, which I'm just like, oh, that's just saved me like two thirds of my whole app right now.
That's awesome.
Yeah, that's pretty good.
Yeah, I feel like I spend most of my time in validation, input validation.
So if it's...
Exactly.
Or converting.
I know it's a string, but then we're going to cast it to an int if it can be casted to an int.
All that kind of stuff.
Like, yeah, it's all built in.
So anyway, if I go build some new APIs, that's not just part of the main app.
I probably would do some FastAPI stuff, but that's pretty new.
It's only been around a year and a half.
Nice.
Awesome.
So you moved from the front end stuff over to the back end at Yelp, and it sounds like you got to do some really cool transformation, sort of empowering things there.
What are you doing now?
Well, technically, I'm currently unemployed, but my current passion is developer tooling and kind of infrastructure in that space.
I was recently at Lyft where I led the developer experience organization and, you know, building tools and infrastructure that make developers productive.
The idea being like, I can invest some in tooling and give that to all developers and then they can get their jobs done faster and better and safer and etc.
Yeah.
Awesome.
Are you looking for that consulting stuff?
People are out there or are you got plans already?
So right now, right now I'm trying to build my own project and see where it goes.
But I've kind of time boxed myself to like six to nine months.
And if it doesn't turn out well, when it doesn't turn out, I'll be looking for new employment after that.
It might turn out.
You're a good programmer.
I know you can do it.
Awesome.
There's always a chance.
Yeah, for sure.
You got to give it a shot.
That's awesome.
Well, let's talk about this project that you've been working on for a while.
But like I said, has gotten a little bit of traction, a lot of traction lately because of tools like Black and other things that have made pre-commit hooks awesome and exciting all of a sudden.
But before we talk about what you've been doing, let's just talk about the idea of pre-commit hooks in general.
Sure.
What is this for a lot of people who are like, yeah, I kind of know what Git is.
I kind of use that or maybe have even used like zip as a zip and name of a date as a source control.
Final one, one, one.
But yeah, the idea behind Git hooks and specifically the pre-commit hook, because I think that's probably the one that most people get the most interaction with.
But there are a bunch of commands in Git where you can register callbacks as scripts to either do like validation or like seeing some people use it to like send emails or like close tickets, all sorts of other stuff.
The main focus around Git hooks to me is the pre-commit hook where you can do like linting and code validation, code formatting.
You can run tests or other stuff like that.
I guess the pre-push hook is another one that's also kind of big in that same space where you want to do validation of your changes before you send them off to like a...
Right. So you could like maybe reject some kind of reject the Git push if the formatting is wrong or the header is missing or something like that, right?
Yep. You can take a lot of those like easy to validate things and do them in a kind of a quick fast manner before you would do your larger test suite or something or catch a syntax error before you spend a bunch of time spinning up your CI systems.
Right. Well, speaking of CI, to me, this seems like the next natural progression from having CI do these tests, right?
So there's different levels. The developer should probably be writing and running tests and making sure that the tests pass.
They should be like formatting their code before they check it in and stuff like that.
But when you work on a team, my experience has been there's a wide, wide range of how much people are willing to do that, how much they care about those kinds of things.
And what that means is maybe you have CI continuous integration that runs automatically during check-in or once check-in is done.
And so then they might check in something and might say, oh, the build is now broken because you didn't bother to run the test, but you broke the test.
But because you didn't run them, you didn't know it.
So, you know, a tree falls in a forest. No one hears it, right?
That sort of thing.
And so then you end up in the situation, the people that care about the build working have to track down the person who broke it, who didn't actually care.
Like, it's just these layers of like annoying type of thing.
Yeah.
And if you can make the sort of push that validation to the location where the person is and all the people, right?
So even if you care, like you might not want to break the build, you might rather just get a warning or just automatically have it fixed.
And pre-commit hooks seem like that's the natural place for that.
Yeah. Kind of the mentality that I always had is like, if I'm waiting till CI to get feedback on like nitpicks around commas or white space or syntax or whatever, that to me is way too late in the process.
Because I've already like, you know, I've pushed, I've already gone off to the next thing.
I'm already answering my email or looking at GitHub issues or talking in Slack or whatever.
And like, I've already context switched to a completely different situation.
I'm like, I could have been, I assumed it was good because I already committed and pushed, right?
I never make mistakes.
So yeah, exactly.
Zero fault code.
Git push is like the final action.
Now you're done, right?
Yeah.
But like, it was incredibly frustrating to like make a push and then have some either build system telling me that something was wrong or in code review, someone was like, oh, well, you could have reordered these imports.
So they're alphabetical or something.
It's just like, yeah, this is a big waste of time.
Let's kind of push this as far towards the developer as possible such that we can make that a better, a better situation.
The other thing I think is interesting around these ideas is there's been studies that have shown that people are more willing to take nitpicky advice from a computer than from a human.
There's like, oh, okay, well, the computer requires that I have this kind of white space or this kind of indentation or like this type of fort, like you said, order alphabetical ordering or whatever.
And when it comes from a person in a code review, it's like, well, that person is just a jerk, right?
I wrote good code.
Here I am taking this flack for this thing.
So having this happen like automatically, I think takes away the need to review that kind of stuff.
It takes away the need to complain and be that grumpy person that does that.
And you can even go farther with tools like Black, where it doesn't just complain.
It just goes, I fixed it for you.
Yeah, actually, the way that I usually talk about this is like the absolute, the worst situation is that a human tells you that something is wrong.
The next worst situation is that like a CI system tells you that something's wrong.
Better than that is that an automated local tool tells you that something is wrong.
And the like golden standard is that an automated tool just fixes it for me.
Like I don't, don't have to worry about it all.
It just makes it happen.
Yeah.
And it also results, you don't have this sort of like dueling alternate format style, right?
Like if I like to work in PyCharm, you like to work in VS Code and our formatting rules vary ever so slightly, you know, like commas between parameters or there are two spaces between the colon on a type annotation.
You know, if we both keep reformatting document, they cycle back and forth.
Right.
And this way you can sort of just hit it with the same formatter right before it goes every time.
Of course.
Yeah.
And that also helps with things like get blame or like noticing when patches are minimal or whatever.
And like being able to triage a change way down the road, not be distracted by two different editors reformatting in a different way.
This portion of Talk Python to Me is brought to you by Brilliant.org.
Brilliant has digestible courses in topics from the basics of scientific thinking all the way up to high-end science like quantum computing.
And while quantum computing may sound complicated, Brilliant makes complex learning uncomplicated and fun.
It's super easy to get started and they've got so many science and math courses to choose from.
I recently used Brilliant to get into rocket science for an upcoming episode and it was a blast.
The interactive courses are presented in a clean and accessible way and you could go from knowing nothing about a topic to having a deep understanding.
Put your spare time to good use and hugely improve your critical thinking skills.
Go to talkpython.fm/brilliant and sign up for free.
The first 200 people that use that link get 20% off the premium subscription.
That's talkpython.fm/brilliant or just click the link in the show notes.
So that brings us to your project, pre-commit.com, which to me, I thought this was kind of something new because I hadn't heard of it before.
And I feel like with all the excitement around Black being one of these auto-formatters that we just discussed, some of the other tooling around get pre-commit hooks,
that this was like this big new thing, but we were talking and you told me it's been around for a little while and it's just gaining a lot of momentum now.
Yeah.
The original project actually came out of my want to enforce my partners in group projects in college to make sure that their white space was nice and that formatting was good.
But it was originally just like a several hundred line Python script pre-commit.py way back in 2012.
The first public version of pre-commit, I believe, was May of 2014, which means that it just turned six years old a little while ago.
And for the most part, the framework's idea has remained basically the same since the beginning.
Most of what's happened since 2014 has been a lot of bug fixes, a lot more platform support.
So like making things work well on Windows and other exotic platforms like macOS, which is basically the Wild West, right?
These uncommon.
Yeah, exactly.
Exactly.
But also like support for other programming languages.
So even though pre-commit is written in Python, it aims to target a bunch of different programming languages.
It can both lint and run tools in, I think it's like nine or 10 or 11 different programming languages now and continues to support more going forward.
It's pretty easy to add support for another language.
Yeah, very cool.
So the idea is this is a framework for creating pre-commit hooks and some tooling to install and initialize some of the various plugins.
And one of the things that's like you just covered is pretty unique to it, I think, is that it happens to be implemented in Python, but it'll let you both install linters that require different runtimes like Ruby or Java.
I'm not sure about Java.
I don't remember.
It doesn't quite have Java support yet, but it's in the works.
But the cool thing about it is that it aims to make it so you don't have to set up anything locally.
You just install pre-commit, you have a configuration file, and it manages installing and running all those tools for you.
So you don't have to worry about, like as a sysadmin, you don't have to worry about distributing Ruby to your machines or like installing some weird Node.js package on your system or like maintaining the version of that or updating and downgrading or whatever you need to do with that.
And like pre-commit will just install those tools for you.
Right.
And there's a lot of tools that we may care about, even if we're just Python developers that are not in Python.
For example, we might be creating a Pyramid web app that has a SCSS, like a less SAS type of programming language for CSS.
There's a nice linter in Ruby, but we got to have Ruby in order to do that, right?
Yeah.
Unfortunately, pre-commit makes setting up SCSS lint in particular, if that's the one you're referring to, makes that pretty easy to set up and go.
You just add a little configuration and it will set up a Ruby environment, even if you don't have Ruby installed on your machine, and install SCSS lint and make that easy to go.
That's pretty cool.
The weird thing about SCSS lint, though, is it's kind of stopped development as SCSS has moved from either the Ruby implementation to the C++ implementation or the Dart implementation.
And there are actually linters for SCSS in JavaScript as well, which is another popular programming language that pre-commit supports.
And a lot of people have been moving to those other ones as well.
But again, like pre-commit will just install that and make that easy to run for you.
So how's that work? Talk me through the, I don't have Ruby on my machine. I want to run some tool as a pre-commit hook that needs the Ruby runtime. Where's the magic?
Yeah, so I actually think that's probably the biggest selling point of pre-commit is all of the language smarts where it knows how to install and run things.
But let's talk about SCSS lint specifically. So what you would do is you would install pre-commit on your machine.
And there's directions in like the quick start guide on pre-commit.com.
You would set up a configuration file, a small YAML file, which points at some repositories which provide hooks.
In this case for SCSS lint, I believe it's pre-commit slash mirrors SCSS lint.
There are some repositories where we have to mirror them because the upstreams are either dead or, or like, don't want to add a small bit of metadata to their repository.
And once you have the configuration file, you can run pre-commit run or pre-commit install to make it part of your Git hooks.
And pre-commit run will, in the case of Ruby, it uses several different approaches to try to set up a Ruby environment.
So the first thing it'll attempt is if you already have Ruby installed, it'll just kind of skip all of the environment setup and it'll just reuse the Ruby that you have.
But it'll install SCSS lint in an isolated fashion.
So it tries to stay away from everything else that's running on your system.
Basically, the idea is like pre-commit provides virtual environments, but for every other program.
Right, right.
That's cool.
And when you don't have Ruby, it will either download it using RVM or RB env, which I love.
I love the Ruby tooling landscape.
It's like impossible to talk about either of the tools because they both sound the same if you slur your words together like I do sometimes.
Yeah.
But RVM provides a bunch of pre-built binaries for a bunch of platforms.
So it makes it easy to just like download and run Ruby.
And R-B-E-N-V, the other tool, makes it really easy to build Ruby from source.
And so pre-commit uses both of those tools to provide Ruby environments.
I see.
So there's this background thing that just, if you don't have the right runtime, it'll go and get it.
Yep, it'll make it happen.
Yeah.
And there's a similar tool called NodeM or JavaScript that pre-commit leans on as well to set up those environments.
NodeM is kind of a mesh between the ideas of like PyEnv and VirtualEnv.
It actually has some like direct integration with VirtualEnv, but it basically allows you to go from nothing to a working JavaScript environment without having to install anything on your system.
Nice.
Yeah, yeah.
That's super cool.
I do think that that's the magic, right?
You can use all these different pre-commit hooks without a setup beyond just pip install or pipx install pre-commit.
By the way, does pipx make sense?
Are you familiar with pipx?
Yeah, I'm familiar with pipx.
I think it works for pre-commit.
I haven't tried it myself, mostly because I manage stuff with virtual environments, but it should work.
There's nothing super special about it.
Yeah, it seems like it would as well to me.
Yeah, yeah.
So you install it and then that's all you got to worry about.
You don't have to worry about having Ruby or Node or how about even Python?
You do need Python.
That's the one dependency.
Right, because it's implemented in Python, right?
Yeah.
There's plans to build a, what is it called?
Py installer?
Is that the one?
I don't know.
There's a bunch of different tools.
Yeah, yeah.
Py installer or PyToApp or yeah.
Like one of those or CXFreeze.
So you've been thinking about potentially like turn it into one of these raw executables?
Yeah.
Unfortunately, I know almost nothing about them.
So I haven't had a chance to look into that.
But it should be pretty straightforward to make that happen.
Yeah, that'd be cool.
But yeah, one other thing before we move on, like I think the two, there's a bunch of these
GitHub frameworks.
And like, I think pre-emit is pretty unique in this installs the tools for you sort of aspect.
I think like a lot of these other frameworks make two main mistakes.
One of them is like, well, the first mistake that everyone seems to make is like you check
in this 2000 blind bash script and that's how you manage your GitHub.
And like anytime someone needs to change that, they've got to dive into this terrible script
that always breaks and makes people's workflows really frustrating, which is actually what we
had at Yelp before Yelp moved to pre-commit.
But the other thing is managing tools.
I find that a lot of shops will install these linters globally and their get hooks will assume
that they're installed globally.
And you instantly have like three different problems from that.
One is version drift, both that all of your developers will need to like make sure that
they're on a specific version.
The other is you need environment setups.
You can't just like switch from your laptop to your personal machine or, you know, maybe
I'm working on a netbook in a coffee shop or whatever.
You would need to make sure that you have your very specific global environment setup.
Right.
That can always kind of drift and be painful.
And the third thing is if tools are managed globally, upgrading is impossible because I have
an old version of the code checked out and a new version of linter is now on the machine.
And so my code doesn't pass the new linter and the new code doesn't pass the old linter.
And like you have this, you kind of have this like lockstep deployment problem.
That's really hard.
I see.
Yeah.
Right.
And then if you have a global, you have two projects and they're out of sync, then it's
even worse.
Oh, and yeah.
The multi-project problem gets even harder.
Like microservices become almost impossible.
Right.
But the cool thing with pre-commit is the tools are all versioned directly in the configuration
file.
And so upgrading a linter can be done in one atomic commit to move forward or backward.
And so everyone is always at like a consistent state there.
Yeah.
That's really a good philosophy or good design principle there.
So you spoke a little bit about the steps to get going, but maybe just walk us really quickly
through a quick start just to what's it like if I've got a machine with no get commit hooks,
no pre-commit installed, how do I make my project start doing some of these cool validations?
Yeah, sounds good.
So there is a quick start guide on pre-commit.com.
So all of this stuff that I'm about to say here is pretty easy to, you know, follow the
instructions there and get the same thing.
But I'm just going to run through it quickly here as well.
So the first step is acquiring the pre-commit tool.
And you can do this in a number of different ways.
Some of the operating system package managers have packaged it.
So sometimes you can, you know, apt install or DNF install pre-commit and get it that way.
I usually don't suggest that because the operating system package managers are usually like six
to eight months behind and you won't get the newest features and stuff.
But, you know, if you're on macOS, like brew install pre-commit is usually up to date.
And if you're a Python project, you can just use pip install.
I mean, even if you're not a Python project, you can also use pip install.
There's also a condo package and a few other ways you can acquire pre-commit.
But anyway, the first step is to somehow install the pre-commit tool.
Right.
Pick your favorite pick.
Pick your favorite.
Go with it.
Yeah.
Yep, for sure.
The next step is to set up some amount of pre-commit configuration file.
You can usually find one in a project that you use and know uses pre-commit.
So I often copy and paste from another project that I've done or a project that someone else
has done that involves pre-commit.
But pre-commit also comes with a pre-commit sample config command.
So you can get kind of a very, very basic configuration that you can expand out further.
I think the default configuration comes with a YAML checker and some whitespace fixers.
And you can always add your favorite code formatter or linter to that.
There's a list of supported hooks on pre-commit.com slash hooks.html.
And there's hundreds of different tools at this point that can be installed and run directly
without any setup.
But yeah, the second step is setting up a configuration file.
The next step after that is to opt into the git hooks.
So the git hooks are actually, you know, it was originally the point of pre-commit to be
just like a git hooks manager.
But at this point, I think pre-commit works almost better as just like a linter code formatter
runner.
If I would, honestly, if I went back in time, I would probably be like, Anthony, don't call
it pre-commit.
And Anthony, maybe don't use YAML.
But yeah, because pre-commit now supports like a bunch of different git hooks, like pre-push,
commit message, prepare commit message, post checkout, a bunch of other ones.
Right.
But also like, it's useful even if you're not using it in Git workflow.
But yeah, anyway, the next step is to install the git hook scripts.
And you can do that by doing pre-commit install.
You need to install it for a particular hook type.
It defaults to pre-commit, but you can install like pre-commit install --hook type.
I don't know, like post checkout or whatever.
Right, right.
Prepare commit message.
And so this looks in the YAML file where you specify the hooks that you want.
And so when you install it, it goes, okay, these are the three I need.
So it defaults.
So the YAML file can be used for a bunch of different hooks.
So the hooks in the YAML file specify a stages property, and that will decide which of the
git hooks they fire for.
You might, you know, have like a trailing whitespace hook, and it might run on commit, push, and post
checkout, or I don't know.
And so like, the specific git hook that you opt into is only part of the install setting.
And so like, if you wanted to use pre-push with pre-commit, you would do pre-commit install
hook type pre-push.
But yeah, once you've set up the git hooks, it's usually a good idea to run pre-commit against
your entire project, especially when you're just starting out with a new code formatter or
something.
And pre-commit makes this really easy with pre-commit run --all files.
This will kind of take all of the tools, install all of them, and then run them against every
file in your repository.
Right.
So this is as if you had done, you basically added everything, and then somehow triggered
a git push to evaluate those files, but you can just make it happen to sort of set the baseline.
Yeah, it's for setting a baseline.
Yeah, because your first commit isn't going to touch every file in your code base.
And pre-commit tries to be very smart about the files that it passes to linters to make
sure that your git hooks are as fast as possible.
Because you could lynch your entire code base every commit, but no one has time for that.
Yeah, exactly.
And yeah, once you've kind of got set up there, you can kind of iterate on your pre-commit
configuration, like add more tools that you might want to see or might want to use things
like FlakeGate or Black or your favorite import reorder or your SCSS formatter or whatever
tools that you might want to do.
You can also add your own custom tools as well.
If you have existing scripts that you want to migrate to pre-commit, pre-commit makes it
pretty easy to run stuff directly in your repository as local hooks or things that happen to
be globally installed as you migrate to more of a managed hook situation.
And kind of the last step that I usually suggest is if you're going to add this pre-commit validation.
So pre-commit validation can always be skipped.
There's --no verify in git commit, which allows you to just skip all the git hooks.
So you could just turn it off if you wanted.
Pre-commit also provides a skip environment variable that lets you skip individual hooks.
So if you have like some buggy hook that you don't want to check or actually we had this one hook at Yelp that was hilariously slow.
And so I just never wanted to run that hook.
So I would just always skip it and make sure that I was adhering to the policy manually.
Right.
But you don't have to skip hooks.
But anyway, they're always just client validation and client validation can always be bypassed.
So it's usually a good idea to set up some amount of this linting validation in your continuous integration.
Right.
And fortunately...
Hold on.
First, it's probably a good idea to have continuous integration and then to set this up for it, right?
Yeah, that's true.
You have tests, right?
Right.
But yeah, it's always a good idea to have some amount of continuous integration.
AKA users?
No, I'm just kidding.
Just kidding.
I'm just going to hide because my chat in my stream the other day was hounding me for not having tests on my most recent project.
And I was like, oh, yeah.
I did write some yesterday, though.
So I have non-zero tests now.
That's good.
Sorry, I didn't mean to derail you.
You're saying you should also set this up at CI.
Oh, yeah, yeah.
And pre-commit makes that really easy to get set up in CI.
You can essentially run the same command we ran earlier, which is pre-commit run --all files, and set that up as something that validates all of your code changes.
How does it get set up in CI?
Like, do you have to do the pre-commit dash install on every run?
Does that get cached and only do the delta?
Or what's the CI look like there?
So, unfortunately, it's kind of a little bit fiddly to set up right now in CI, which is actually part of the project that I'm building right now.
I'm actually working on building a kind of generic continuous integration solution that's aimed only at pre-commit.
Oh, nice.
So if you want to check out more information on that, it's pre-commit.ci.
It's kind of a work in progress right now.
But you can set up pre-commit with basically any CI provider that you have right now.
But you have to manage the cache yourself.
You have to figure out what command you want to run.
You have to figure out, like, what change set boundaries you want to run against.
So, like, maybe on a pull request, you only want to validate the files that changed between master and your branch.
And, yeah, it's all pretty manual setup right now.
And for Travis CI, it's five to ten lines of YAML.
For Azure Pipelines, it's, like, 30 lines of YAML.
And, like, if you don't get it quite right or if the cache is invalidated for weird reasons, like, your run will be significantly slower.
And that's actually one of the big selling points that I'm trying to push for on pre-commit CI, which is that everything will basically always be cached because it can share all these tools amongst other developers, which is pretty cool.
Right, right.
Yeah, that's really cool.
Yeah, but you've got a lot of examples, and I'll try to link to them in the show notes, of how to set it up for Travis, Azure Pipelines, all the various things, right?
So that's pretty straightforward.
Yeah, I think most popular ones are covered there.
And if there's others that are missing, like, feel free to send a pull request.
Yeah.
Another cool thing that I'm trying to build into my CI tool is that, like, and this happens probably, like, ten or so times a month, where someone will come to one of my projects and submit a pull request.
And they haven't set up the client tooling locally, so they aren't running the pre-commit hooks.
They haven't run the code formatters or any of these things.
And so they'll get kind of that middle to worst case of the CI system is telling them, oh, your code isn't formatted this way.
Like, here's a patch that would fix it.
But one of the features that I'm planning to build is that pre-commit CI will just automatically fix pull requests for you.
So you won't have to, even if you forgot to set up the local tooling, the remote tooling will just auto fix it for you.
Oh, that'd be nice.
Instead of just saying, oh, it's wrong, you have to fix it.
It just, it always comes in right.
Yeah, it just fixes it for you.
There's actually a tool.
I guess it doesn't always come in right, because it could be, like, actually broken Python, and you could be validating.
Like, there's things that can't be fixed.
But if it could be fixed, it should be auto fix, right?
Yeah, it tries to auto fix things that are auto fixable.
There's actually a tool, I think Mariota made it.
I believe it's called Blackout, which is a Black-specific tool that kind of has this same idea of, like, just fix the pull request for me.
Sorry, I didn't set up Black locally.
Yeah.
But Pre-Commit CI kind of aims to be a generic tool for these sort of auto fixing things.
Yeah, that's really cool.
So you talked about, if I didn't set this up locally, the way this works is all of these get, in general, get pre-commit hooks, they don't stick to the repository.
They're not, like, now part of the repo because I set them up.
Like, every person on the team has to go through this three or four step process.
Yeah, so they won't have to set up the configuration because usually you check that in so that it's shared amongst all your peers.
But yeah, setting up the tool would be something that everyone else has to do.
Okay.
There are ways.
So there are ways to kind of automate this among peers.
So, like, one thing that we did at Yelp was we made pre-commit one of the Python dependencies of the application.
So the installation part was skipped.
And we also made the get hook set up part of the common make targets.
So, like, you used to run, like, make minimal to get Yelp application working or whatever.
And make minimal would install the hooks so that you didn't have to think about that sort of thing.
Yeah, that's cool.
In other, in managed environments, there are other things you can do as well.
Like, you can set a get init template.
And the init template could already contain the get.
Pre-commit actually has a command that makes it easy to set up an init template.
But yeah, if you're working just, like, on vanilla laptops or, like, on open source projects,
like, you would need to consciously opt into this behavior.
And actually, that's one of the bigger design principles of Pre-commit is, like,
none of this stuff happens super automatically.
Like, you're always opting into the behavior.
Right.
It's all optional.
Okay.
Yeah, I think that's positive, right?
That's a good thing that you're not forcing it on people.
But with the CI, you're kind of catching the mist.
Yeah.
The other part is, like, if stuff runs too automatically, people get concerned about security.
And so it tries to not be, you know, an arbitrary code execution engine, even though it is with opt-in.
Yeah.
Well, people do get a little touchy when you automatically run tools against their source code.
Yes, I've received quite a few issues that are like,
why does this thing exist?
You should delete it now.
And it's like, oh, well, you don't have to use it.
Exactly.
Talk Python to me is partially supported by our training courses.
How does your team keep their Python skills sharp?
How do you make sure new hires get started fast and learn the Pythonic way?
If the answer is a series of boring videos that don't inspire,
or a subscription service you pay way too much for and use way too little, listen up.
At Talk Python Training, we have enterprise tiers for all of our courses.
Get just the one course you need for your team with full reporting and monitoring.
Or ditch that unused subscription for our course bundles,
which include all the courses, and you pay about the same price as a subscription once.
For details, visit training. talkpython.fm/business,
or just email sales at talkpython.fm.
Let's talk about some of the tools.
You know, we've set the groundwork, right?
It's like this framework for bringing in all these different types of pre-commit hooks.
And when you say there's a lot that you can bring in, there are a ton.
So maybe we could talk through some of the ones that come built in that you think are cool.
I'll grab a couple as well.
And then there's a bunch of others that are sort of external, but can be loaded.
Yeah, sounds great.
So pre-commit itself is a framework,
but there's also an official set of pre-commit hooks that I and other contributors have written.
And those are available at pre-commit slash pre-commit hooks on GitHub.
And there's, what, like 29 of them that are provided kind of out of the box there.
The original intent of the pre-commit hooks repository was to be Python specific.
However, it's more shifted to like take the Python parts and split them out
and kind of aim to be language agnostic.
So there's a lot of checks in there for like your common configuration formats.
So like there's checkers for like JSON, Toml, YAML, et cetera.
There's actually some really cool ones for Python specific stuff.
Like there's one that checks that you don't check in breakpoints or debugger statements.
So like you accidentally put import PDB.
It'll flag that and make sure that that doesn't end up in production.
Right, right.
You definitely do not want to leave a import PDB set breakpoint sort of thing because I don't know why the server is locked up,
but it just seems like it's, there's not, it's not using the CPU, but it's just stuff's timing out.
What's going on?
Yeah.
The number of times I've seen PDB quit in production, I'm like, oh, damn it.
This is entirely my fault.
But yeah, this kind of helps prevent that.
Or like, you know, I left a breakpoint and my test suite exploded because there was a breakpoint in it or stuff like that.
Right.
So some of the stand out to me, like one was check AST, which checks whether or not Python files can be parsed.
That's pretty cool because Python doesn't have a compiler, not in the command line version sense.
I know it sort of, it does generate the PYC, but kind, kind does.
There's not a build step is what I'm saying.
Right.
For sure.
Right.
Which goes through and verifies all the files.
So this is a little bit like a build step for like really basic stuff.
Yeah.
Check AST kind of aims to be like a first line of defense.
In a lot of ways, like other linters like Flick 8 could be a better choice than check AST.
But yeah, check AST is kind of like the most basic, is this valid syntax check?
We actually use this as part of moving to Python 3 at Lyft.
We kind of had this like three to four stage process.
And like the first stage was make sure all the syntax is valid Python 3 syntax.
And like make sure that linters pass in Python 3.
And then like make sure that Python 3 specific linters pass in Python 3.
And then run the test in Python 3.
And then go to staging and then production.
And then delete all the Python 2 code.
And that was kind of our, check AST was kind of our first step there in that process.
I see.
Yeah, that's really cool.
Another is about checking for AWS secrets.
Ah, yes.
How many times have you accidentally your entire AWS account to the public on GitHub?
Actually, GitHub has some special checking for this now as well to make sure that your AWS
secrets or whatever are not leaked on commits.
And Amazon actually has invested a lot in scanning public source code repositories to
invalidate these tokens as quick as possible.
Oh, yeah, that's cool.
Because it costs, you know, it costs individuals a lot of money, but it also, Amazon spends CPU
and other resources finding Bitcoin or whatever, whatever people are doing nefariously with
these leaked tokens.
But yeah, this is kind of like, again, a first line of defense for not checking in.
Yeah, you don't want to do it.
It's not like eventually someone's going to find it.
It's bad.
So there's systems like...
It's like seconds.
Yeah, it's like shh git is a project.
There's a bunch of these.
But this one is, should git find secrets and sensitive files across GitHub, including gist,
git lab and bit bucket in near real time.
And it does so by hooking up to like the public stream of activity.
And like, as soon as there's a check in, it's just after it, right?
And so there's three or four of these types of tools.
And basically, it would be really nice if something like this detect AWS credentials,
pre-commit hook, didn't let that get into the mix.
For sure.
Yeah.
And there's actually a lot of other tools around detecting credentials in the pre-commit space.
So there's direct integration for, I believe the tool is called Bandit, which is a Python
specific...
Yeah.
Bandit's cool.
...tool.
And I believe they have direct pre-commit integration.
So you can just set up Bandit as a pre-commit hook.
I don't know.
There's a Go project that's fairly popular that does the same thing.
And pre-commit has direct support for Go in that particular tool.
But there's a bunch of tools in this space that make it really easy to prevent check-in
of sensitive credentials.
Another one that stood out to me was no commit to branch.
Yeah.
This one actually was a community-contributed hook.
The idea being that you don't accidentally commit directly to your master branch or to
your particular development branch.
And this helps you enforce your...
Production or whatever.
Yeah.
It helps you enforce your specific Git workflow that you want to do.
So you're specifically targeting your own feature branch, or you could use this to
enforce a branch naming scheme or a bunch of other stuff like that.
But yeah, that one was actually contributed externally.
Yeah, exactly.
Basically, you require people to use a proper PR workflow, like a Git flow type of thing,
rather than just jam it straight in.
Yeet it straight to master.
Heck, just edit it on production.
That's even quicker.
I deploy via SSH.
Exactly.
Yeah.
Another cool one that we used at Yelp was forbid new submodules.
We went through this.
This is actually kind of a long story, so I'll keep it a little bit short.
But Yelp went through a migration of installing all Python packages globally on the system to
virtual environments.
And there are some grumpy sysadmins that were super against virtual environments.
And so we kind of cheesed our way to getting virtual environments by using submodules instead.
And Git submodules, not Python submodules, Git submodules.
And so there was one point where Yelp main had 98 Git submodules.
And we were doing this fancy pip install workflow that allowed us to locally install all of these tools so that we didn't have to manage them at the system.
But eventually, the grumpy people left the company and we moved everything to virtual environments.
And part of moving to virtual environments was to burn down the tech debt of submodules.
And so we added this forbid new submodules hook, which prevents newly introduced submodules while allowing existing ones such that we could kind of do some graph driven development and bring that down to zero while we migrated towards the new world.
That seems like a good path there.
Very nice.
One that I was wondering about is check executables have shebangs, the little hash exclamation point, something at the top, which is fine.
But how do you know it's an executable?
Is it because it has a dunder main equals dunder name thing in it?
Or what counts as an executable?
So this is more about file permissions and accidentally checking in files that are not executable.
So one example where this commonly gets triggered is when you copy files from a thumb drive, they'll commonly be on a FAT32 file system, which doesn't have full support for all of the permission bits.
And so almost always the permission bits will be 777 and you'll end up checking in all sorts of non-executable and non-script files with executable bit permissions.
And depending on your particular system that you're using, this can often trigger lint errors.
And so this is kind of a first defense against that.
One particular system is like Debian is real grumpy about, oh, this PNG file has the executable bit set.
And so I'm going to reject this package or whatever.
Right, right.
But this also helps you if you've forgotten to put a shebang.
So like sometimes you'll have like an entry point to your application that you intend to be executable, but you've forgotten to put user bin env Python 3 or whatever.
Right.
So this kind of checks for that.
Okay, cool.
So those are a bunch of the built-in ones.
Any of the other external ones you want to give a shout out to?
Yeah.
So if you're setting up a Python project, you probably want an import sorter.
And there are two, the two most popular ones.
One of them I wrote, which is called Reorder Python Imports.
It's very basic in what its name is.
But the idea behind Reorder Python Imports is you set up essentially no configuration and it just does the right thing all the time.
And if you kind of want the opposite of that spectrum, there's a tool called iSort, which has direct support in pre-commit.
iSort has 50 or 60 configuration options, so you can customize your import sorting to whatever thing you want.
Some others that I would suggest is getting a linter like Flake 8 set up.
And Flake 8 has direct support for pre-commit as well.
I'm also the maintainer of Flake 8, so of course it has pre-commit support.
Of course. Awesome.
And perhaps the most popular and probably one of the reasons that pre-commit has taken off a lot recently is Black, which is, we've talked a couple of times on this already about Black.
But Black is a code formatter that there's one way to do it right, and Black does it in a very specific way.
And Black has direct integration with pre-commit.
You can also set up things like type checkers, like there's mypy integration with pre-commit.
There's other sorts of stuff like that.
And one more that I'll shout out, which is one that I've written, a tool called PyUpgrade, which allows you to kind of upgrade your syntax to newer versions of the language.
So things like automatically making f-strings, or like removing Python 2 syntax constructs, or unsixing your code, so to speak.
And that's another one that has decent pre-commit integration as well.
I see. It looks for old idioms and styles and says, stop doing that.
Yeah, well, not only does it say stop doing that, it just auto-fixes it for you.
Oh, that's even better.
But yeah, I really like my code formatters.
Like, if a linter is grumbling at me, I'd rather have formatter just auto-fix it for me.
Yeah, absolutely.
All right, so that's the big list of things that you could use.
But what if you have an idea for a new one?
Part of the whole idea of pre-commit is it's a framework for building and distributing these things, right?
Yep, and it's really easy to make your own set of hooks.
That's actually another problem with the other sort of frameworks, is that if you want to add tools to it, you kind of have to fork their framework and, like, inject your code directly into their framework.
But pre-commit takes kind of a distributed model to this.
Basically, any git repository that has a little bit of metadata in it can provide a git hook, as long as it's installable in some way.
And the process is outlined on the pre-commit documentation, but there's a bunch of different programming languages that this works with.
And you basically set up a pre-commit hooks.yaml to provide hooks to other repositories.
And there's a, you know, you can always follow an example on any of the other repositories that already provide stuff.
And it's also pretty easy to take an existing tool and add the small metadata file to it that can be used directly with pre-commit.
Of course, there's also escape hatches.
Like, if you don't want to use the repository-managed approach, you can set up local hooks that go directly to PyPI or, like, use a Docker image or other stuff like that instead of going through the managed approach.
Although there's some disadvantages to that around, like, automatic updates and workflow management and that sort of deal.
Right. Yeah, that looks really cool.
So it seems pretty easy to make them.
And as long as it's installable via IPI or gem or NPM or is it executable, it's more or less ready to go along with that metadata.
Yep. Essentially, like, if you can be git cloned and something can run, like, pip install or the equivalent, you're golden.
You're ready to go.
Yeah. And then under supported languages, where maybe languages is in, like, quotes or something, you've got Conda, Docker, Docker images, fail.
What is fail?
So there's a special hook.
This was actually added specifically for pytest, which I'm a maintainer of.
Fail takes a file name regular expression and will always return one if anything matches that.
So one example is, like, we wanted to enforce that our changelogs were a specific file name pattern because it's very common for somebody to write a changelog but forget the RST extension.
And when they forgot the RST extension, then would go to run a release, we would forget their changelog fragment.
And so their change wouldn't be called out.
And so this was a way to enforce with a particular message that a file name matches specifically.
Okay. Yeah, cool.
And then you got Golang, Node, Perl, Python, Ruby, Rust, Swift, Script, I'm guessing Bash and whatnot.
So that's a lot of options.
And with Docker, like, that's a pretty wide open.
Yeah, with Docker, you can basically do anything.
Yeah, exactly.
Exactly.
Yeah, and it's actually pretty easy to add a new language.
So if there's another language that you would like to see pre-commit support, there's actually a little guide in the contributing documentation for pre-commit on how to set up another one.
Someone's actually working on support for Crystal right now, which is a language that I had not heard of until relatively recently, but it's basically like compiled Ruby.
It's pretty cool.
Okay.
Yeah, there may be support for that.
Yeah, nice.
Something that I also saw in the docs was talking about automatically enabling pre-commit on repositories.
Can you, like, register it with Git so when you get a new repo or clone something, it'll just be part of it?
Yeah, so there's this template directory concept in Git.
Basically, you can run pre-commit init templater and then set a specific configuration value in your Git config.
And Git will splat out files in a particular way in your clone such that you'll automatically be enabled.
Okay.
Yeah, that's nice.
This usually involves just, like, writing the Git hooks pre-commit file magically.
Right, yeah.
So if you really like this whole idea, then just make it automatic, right?
Yep.
We use this at Lyft so that whenever you would clone a Lyft repository, you would automatically have everything set up, which worked pretty well.
Yeah.
And one of the things that all the cool repositories are doing these days is they have those little badges, like, on their readme.
Mm-hmm.
Right?
So it's Python 3, or it has this many downloads on PyPI.org, installs via pip, and so on, or whatever.
And so you guys have a nice way to badge your project as well to say that it's powered by pre-commit to sort of spread the word.
Yep.
Actually, this one's a really cool thing that I want to shout out.
This was actually contributed by an external contributor, which was like, hey, I want a way to, like, show other people, like, what tools I'm using.
And, like, pre-commit is a really cool tool, and I want to see it succeed.
And so they made SVG badge and, like, made it really easy to set up a little bit of markdown or RST or whatever text document format you want to use to badge your repository and spread the word.
Yeah, awesome.
That's really nice.
And then last question on this, I guess, is are you looking for contributors to the project?
There's a lot of people who ask me, hey, I want to get started in open source.
Could you recommend something I work on?
Would this project count as one of those?
Yeah, we're always looking for new contributors.
The thing with pre-commit is, I don't want to say it's feature complete, but it hasn't really gained any large features in quite a long time.
Most of the places to expand pre-commit is to add more tooling support or to add more programming language support.
And I'm always looking for expansions in those spaces.
Or, you know, new ideas that I just haven't really thought of yet.
Yeah, maybe there's some cool tool out there that's not integrated with you guys.
Maybe they could commit to pre-commit by actually committing to that other project to get the right metadata.
For sure.
Yeah.
There are a few like help wanted getting started smaller issues that are in the issue tracker.
But I usually try and keep the backlog pretty well trimmed.
Yeah.
Nice.
All right.
Well, what a cool project.
And it's definitely something I'm going to be checking out as well because it looks neat.
Thanks.
I'm really proud of it.
Yeah, you should be.
It's great.
All right.
Before we get out of here, though, got to ask you the two final questions.
You're going to write some Python code.
And I know you write a lot.
What editor do you use?
Oh, no, this is always a fun one.
So if you would have asked me this six months ago, I would have been a little bit embarrassed
about my answer, which maybe I'm still embarrassed about my final answer.
But six months ago, I would have said nano as my text editor of choice.
I've actually I'm actually really good at using nano.
Before that, I was a IntelliJ user and I used that for the longest time.
But I found that like context switching between the terminal and not was was a lot of work.
But anyway, my real answer to this question, I have actually written my own text editor,
which in retrospect, I would not suggest anyone to do this.
It's been quite a roller coaster.
But my texter is called Babby.
The name is a little bit silly.
It's actually nano.
But if on a QWERTY keyboard, if you shift your hand over one and typo nano, you end up with
Babby.
And this was like a common typo that I would I would make pretty frequently.
But yeah, it's open source.
It's written in Python.
Some of the ideas behind it were like, make it really easy to hack on and like add features
to and like I wrote a lot of it on my Twitch stream.
And interestingly, a bunch of the features in the text editor were actually contributed by
just like people in chat, which were like, hey, this would be cool if Babby did this and
like run off for 15 or 20 minutes and come back and have a working patch with the
new command or new cool way to do something.
That's awesome.
But yeah, it's pretty easy to hack on.
And that's part of the reason that I kind of like it.
Is it pip install Babby?
Yep.
Pip install Babby.
And that should work on most platforms.
It works on Windows, oddly enough, which was actually also another thing that chat contributed.
Like I developed it entirely on Linux and it's based on curses.
Windows doesn't quite have native curses support, but you can install it like Windows curses package
to make that work.
Okay.
Yeah.
It should just be pip install Babby and start hacking on it.
It's not feature complete in any way.
So like there's some stuff that's, you know, like horribly broken.
Like if you try and rename a file in Babby, it's just like, no, I don't I don't know how
to do that.
Sorry.
But I use it as my daily driver now.
So it's I know the shortcomings and I'll eventually get around to implementing those.
But yeah, cool.
It's pretty neat.
Awesome.
All right.
That is the first time anyone has ever said Babby.
So very cool.
Now we're getting the word out about it.
I'd be surprised if anyone else uses it, to be honest.
All right, cool.
Well, very interesting.
And then notable PyPI package, something that maybe people haven't heard about.
Ooh, something that people haven't heard about.
Or just that you came across, you're like, it's, you know, it's not requests.
It's not Django.
It's like, oh, this thing I found is pretty awesome.
People should know about it.
It's not requests.
You mean, you mean HTTPX?
HTTPX is also cool.
But it's just, you know, it's like the most common answer, I would say, is requests.
That's true.
Yeah.
I actually, controversial opinion, I really don't like requests.
So can we do an unpackage for this one?
You can.
Well, how about it?
What do you use instead of requests?
So if I get away with it, like I don't need H2 support or I don't need like special streaming
APIs, I'll mostly just use standard library.
Yeah.
Although for like newer stuff where I need like streaming APIs or H2 or I need async support,
I'll reach for like HTTPX or AIO.
Yeah.
AIO client.
Yeah.
In there.
Yep.
That's nice.
I've actually like, I've not done a lot of asyncio yet.
I've been dabbling with it a little bit.
I'm writing a chatbot for Twitch and I've kind of started sprinkling around some of the AIO
libraries.
What do you think about async in Python?
I think it's pretty good.
There's a few like get it set up and get it moving things that kind of bug me.
It bothers me that I can't just fire and forget async stuff.
Yeah.
You have to await it eventually.
Yeah.
Or like, like if you just call the async function, you get a coroutine, but the coroutine isn't
started until you like turn it into a task or you give it to the loop to run.
Yeah.
So that bugs me a little bit.
The fact that like the threading module doesn't support async and await.
Yeah.
But the IO module does, you know, the multiple, like all of them should.
So I actually am a huge fan of unsync, which is a library that puts a unified API on top of
those and gives you the fire and forget.
And it has a background thread that manages the runtime loop for you.
So, oh yeah, I've played a little bit with unsync.
It seems really cool.
So I think the, what we have in Python is really close to right.
There's just like a few rough edges.
And I think it's not necessarily unsync is the right answer, but something like that is
really close to like the final polish.
It needs to take it from like a, a B to an A minus or something level like that.
Right.
Yeah.
And then maybe once we get sub interpreters dropping the guild, you can take it up a notch
as well.
Oh yeah.
Sub interpreters can be really cool.
Yeah.
My problem with async is I feel like I have to rewrite all my code.
Sometimes that barrier to entry.
Yeah.
The challenge can be like it propagates, right?
You want to do it way down and well, that becomes async.
And then the things that call it become async.
And the things that call it become async.
And yeah, that can be a challenge.
And your testing library has to be async.
And then like all this, all this other stuff.
Yep.
I think it'll get there though.
I think it's just like Python has 20 years of history and async was introduced pretty late
into that history.
And there's going to be some growth pains as it gets better, so to speak.
Yeah.
But places where I think that really shines is where you don't have to think about it.
Like where the thing that you're doing as async is on the boundary.
So for example, FastAPI, right?
You can just make your view method async or not make it async, right?
It's up to you.
And then if you want to do that part of your app async, you can.
But it's not from the bottom up, but it's kind of from the outside in.
And you can pick on the entry points from the outside where you want to adopt it.
And then I think that that makes a lot of sense there.
Yeah.
Especially like because you have a framework calling you, so you don't necessarily have
to do all async or all not.
Exactly.
You get a plug into it or opt into it.
Yeah.
But as a library designer, it's more challenging, I think.
How do you expose an async version and a non-async version and so on?
Definitely.
Cool.
All right.
Well, that's not exactly one package, but that's kind of a conversation around packages.
So let's maybe just put an answer to HTTPX or AIO, HTTP client.
Those are two cool HTTP libraries that kind of fit in that world.
I like them.
Yeah.
Sounds good.
Cool.
Yeah.
I mean, usually my answers would be around like a code formatter or linter.
I'll actually shout out like two of my favorite linters that are code formatters that I tend
to use.
I do really love Black, but I actually don't use it myself.
I actually tend to use a combination of two formatters.
One of them is called Autopep8, which is very similar to Black and some of its ideas and
like fixing lint errors.
And the other one, which is very similar to Black, but has existed for like four or five
years, is a tool called AddTrailingComma, which literally does what it says on the tin.
It tries to enforce trailing commas in the same way that Black does.
Right.
So the final element in like a list format, it would have that so that that line doesn't
get a diff when you add the next element.
Yep.
It tries to make minimal diffs when adding or removing like parameters and like lists or
tuples or function signatures or class signatures or type annotations or the whole gamut of
places where you can have braces and commas.
Cool.
All right.
That's a bunch of good ones.
Okay.
Final call to action.
People are interested in pre-commit.
They want to check it out.
They want to start using it.
What do you tell them?
So I would tell them to go to pre-commit.com.
That's probably the place where you can get the most.
It's pre-commit.com, right?
Correct.
Yeah.
With a dash.
The one without a dash is like some weird real estate website that I've been trying to
bug the guy to like give up his domain for years, but I don't know that he actually reads
his email at all.
But yeah, I would check out pre-commit.com and that's where you can get all the information
about that.
And pretty soon, pre-commit.ci will have more information about the CI system that I'm building
around pre-commit.
Oh yeah.
That's really exciting.
That's going to be cool as well.
I'll put a link to that in the show notes also.
Cool.
All right.
Thank you, Anthony.
It's great to chat with you and see all these amazing things you're creating.
Yep.
Always glad to chat.
Yep.
But see you later.
Bye.
This has been another episode of Talk Python to Me.
Our guest on this episode was Anthony Sotili and it's been brought to you by Brilliant.org
and us over at Talk Python Training.
Brilliant.org encourages you to level up your analytical skills and knowledge.
Visit talkpython.fm/brilliant and get Brilliant Premium to learn something new every
day.
Want to level up your Python?
If you're just getting started, try my Python Jumpstart by Building 10 Apps course.
Or if you're looking for something more advanced, check out our new Async course that digs into
all the different types of async programming you can do in Python.
And of course, if you're interested in more than one of these, be sure to check out our
Everything Bundle.
It's like a subscription that never expires.
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, the Google Play feed at /play, and the
direct RSS feed at /rss on talkpython.fm.
This is your host, Michael Kennedy.
Thanks so much for listening.
I really appreciate it.
Now get out there and write some Python code.
Thank you.