WEBVTT

00:00:00.001 --> 00:00:05.160
For all the amazing powers of Python, deploying packaged apps that leverage native OS-level

00:00:05.160 --> 00:00:10.240
capabilities isn't really one of them, but it can be done. We have a great guest,

00:00:10.240 --> 00:00:16.000
Rhett Turnbull, here to tell us how he built his distributable macOS app, Textinator,

00:00:16.000 --> 00:00:20.760
that uses macOS's native vision recognition framework through Python.

00:00:20.760 --> 00:00:26.160
This is Talk Python To Me, episode 338, recorded September 25th, 2022.

00:00:26.160 --> 00:00:44.380
Welcome to Talk Python To Me, a weekly podcast on Python. This is your host, Michael Kennedy.

00:00:44.380 --> 00:00:49.020
Follow me on Twitter where I'm @mkennedy and keep up with the show and listen to past episodes

00:00:49.020 --> 00:00:55.560
at talkpython.fm and follow the show on Twitter via at Talk Python. We've started streaming most of

00:00:55.560 --> 00:01:00.320
our episodes live on YouTube. Subscribe to our YouTube channel over at talkpython.fm

00:01:00.320 --> 00:01:04.840
slash YouTube to get notified about upcoming shows and be part of that episode.

00:01:04.840 --> 00:01:07.220
Rhett, welcome to Talk Python To Me.

00:01:07.220 --> 00:01:08.500
Thanks, Michael. Glad to be here.

00:01:08.500 --> 00:01:13.720
I'm glad to have you here. So many interesting things to cover on the show. I'm really,

00:01:13.720 --> 00:01:18.940
really excited. Let's start with the excitement, I guess, of saying we're both excited about Mac

00:01:18.940 --> 00:01:26.120
computers. It's about, I guess, a one-third split, maybe not quite that many for Mac developers in the

00:01:26.120 --> 00:01:32.060
Python space. But there's a lot of people who write code either on a Mac or for people who use a Mac.

00:01:32.060 --> 00:01:36.680
And it would be nice to make a little bit more of a native thing with Python. And so we're going to

00:01:36.700 --> 00:01:44.080
talk about this really awesome app that you wrote. And here we are because of a tweet follow-up for the

00:01:44.080 --> 00:01:47.400
Python Byte show. You sent a really cool tweet. And I'm like, this is amazing. We have to talk about

00:01:47.400 --> 00:01:52.940
this. And so we're going to talk about building Mac apps with Python, like native apps that go into the

00:01:52.940 --> 00:01:58.920
dock, not just something that could run a script, but a thing you could get a regular user to believe

00:01:58.920 --> 00:02:04.500
as an app, and they will call it an app. And the other is you also happen to work for the U.S.

00:02:04.580 --> 00:02:08.420
Space Force, which is very interesting. So we'll touch on that maybe a little bit at the end. There's

00:02:08.420 --> 00:02:10.680
some programming tie-ins as well, right?

00:02:10.680 --> 00:02:12.880
Yeah. Yeah, definitely. Sounds good.

00:02:12.880 --> 00:02:19.320
Awesome. Well, I'm, again, happy to have you here. Before we get into that, though, let's start with

00:02:19.320 --> 00:02:21.460
your story. How do you get into programming, Python?

00:02:21.460 --> 00:02:26.900
I'm a hobbyist programmer, self-taunt, but I've been programming since I was eight years old. And

00:02:26.900 --> 00:02:34.000
roughly around 1981, my dad brought home a TRS-80 Model 3 from Radio Shack. It was one of those

00:02:34.000 --> 00:02:38.160
all-in-ones. It's a 12-inch black and white screen, built-in keyboard, and it had a five

00:02:38.160 --> 00:02:41.340
and a quarter-inch floppy, which was a pretty big deal for those days.

00:02:41.340 --> 00:02:45.440
That is a big deal. I was going to ask, did it have a cassette player or did it have a floppy?

00:02:45.440 --> 00:02:49.040
It did not. It had the floppy actually built into the consoles and all-in-one.

00:02:49.040 --> 00:02:53.600
People must have been jealous. You're like, where's your cassette player? No, no, let me show you.

00:02:53.600 --> 00:02:54.960
It's this huge thing.

00:02:54.960 --> 00:03:00.420
Yeah. And I don't remember how much it was, but it was the floppy disk held, but it was like 80K or

00:03:00.420 --> 00:03:05.740
something. It was a lot of for back then. He had it for work, but I was, I just was fascinated by it.

00:03:05.740 --> 00:03:11.780
I thought, you know, I love the idea that you can, this machine can respond to what I tell it to do

00:03:11.780 --> 00:03:16.840
and do something completely different. And so I got into programming. He taught me a little bit of

00:03:16.840 --> 00:03:21.980
basic. It came with basic, handy basic installed on there. I got into that a little bit. And then

00:03:21.980 --> 00:03:28.660
from there, I moved into a Commodore 64 later and IBM PC. And then eventually found my way to the

00:03:28.660 --> 00:03:34.840
Mac and just, yeah, I've been using the Mac for a long time. I found Python about four years ago.

00:03:34.840 --> 00:03:38.980
I've used a number of different languages through the years. Probably most of the work I've done

00:03:38.980 --> 00:03:44.540
was in Perl. A lot of Fortran in college. I'm an engineer by trade. And back then we used Fortran.

00:03:44.540 --> 00:03:47.840
In fact, I programmed Fortran on the backs back in the day.

00:03:47.840 --> 00:03:49.620
Cool. What type of engineering?

00:03:49.620 --> 00:03:54.500
Astronautical engineering. So everything dealing with space, satellites, rockets,

00:03:55.060 --> 00:03:57.720
spacecraft, all of this type of engineering, it sounds like. Yeah.

00:03:57.720 --> 00:04:01.600
That was a lot of fun. But we did a lot of Fortran. But, you know, I taught myself Perl

00:04:01.600 --> 00:04:05.860
along the way and I used that for years. That was sort of my go-to anytime I needed to get something

00:04:05.860 --> 00:04:11.320
done. And then I remember a few years ago, I read an article about Python. It was the up and coming

00:04:11.320 --> 00:04:16.740
thing. And so I thought, well, let me give this a try. So I started playing around with it and I've

00:04:16.740 --> 00:04:19.580
done almost exclusively Python since then. It's been a lot of fun.

00:04:19.580 --> 00:04:23.840
Well, so once you found it, you're like, all right, this is a, this place, I feel comfortable in

00:04:23.840 --> 00:04:26.940
this place and this language and I want to do more and just stuck with it.

00:04:26.940 --> 00:04:32.980
Yeah. It's an easy language to learn, right? But it grows with you is what I found with Python.

00:04:32.980 --> 00:04:38.700
And, you know, you can obviously build industrial grade apps with Python, but it's really easy to

00:04:38.700 --> 00:04:44.600
start out too. And I will say, I just, I actually first came across Python, oh, quite a few years ago

00:04:44.600 --> 00:04:50.060
in the late nineties when I discovered Perl. And I'd come from a C and Fortran background. And I remember

00:04:50.060 --> 00:04:54.880
looking at this whole white space thing and thinking, there's no way that I can program with

00:04:54.880 --> 00:05:00.800
that. I need the curly braces. I need the semicolon. And now, you know, to be fair back then, you know,

00:05:00.800 --> 00:05:06.920
on a windows PC programming, it was literally with notepad. There wasn't black, there weren't Pepe,

00:05:06.920 --> 00:05:10.920
you know, there weren't any formatters, any of that kind of stuff. Right. So, so white space was hard

00:05:10.920 --> 00:05:15.700
actually getting it right. But, you know, once I got over that, I actually really liked the white

00:05:15.700 --> 00:05:19.980
space. Now I think it makes the code a lot easier to read and cleaner. And, I,

00:05:19.980 --> 00:05:24.880
yeah, I really have enjoyed working with Python. Yeah. The white space, I do feel like there's a

00:05:24.880 --> 00:05:28.640
certain number of people that just look at it and go, nope, next language. What else am I going to,

00:05:28.640 --> 00:05:33.520
what else might I learn today? Cause that one's out, but it really is something that it seems like

00:05:33.520 --> 00:05:38.220
it's a big deal. And then you kind of work with it. You're like, oh, especially now the tools are just

00:05:38.220 --> 00:05:44.340
auto indenting for you, you know, you're like, oh, this is kind of, it's just omnipresent, but it's,

00:05:44.340 --> 00:05:46.480
you don't have to think about it so much. Exactly.

00:05:46.480 --> 00:05:49.560
Cool. And you hit enter and like, oh, I'm indented already. That's cool. Just keep typing,

00:05:49.560 --> 00:05:53.440
you know? So very neat. How about now? What are you doing these days? I mentioned something about

00:05:53.440 --> 00:05:57.740
the space force. Yeah. So I work for the space force. I'm an off, actually an officer in the

00:05:57.740 --> 00:06:03.480
space force. I've been a career air force officer. I've been in the air force since 1995 as an

00:06:03.480 --> 00:06:08.240
astronomical engineer. And then last year had an opportunity to transfer from the air force into

00:06:08.240 --> 00:06:12.540
the space force and being an astronomical engineer and a guy who's worked in space my whole

00:06:12.540 --> 00:06:15.620
career, that was a no brainer. It's like, yeah, it seems like exactly.

00:06:15.620 --> 00:06:21.060
That's exactly where I, where I should be. And so today I'm an officer in the space force

00:06:21.060 --> 00:06:26.280
and my day job is I'm a chief engineer for an organization called space systems command,

00:06:26.280 --> 00:06:31.620
which is part of the space force that develops and acquires all of our new satellite systems,

00:06:31.620 --> 00:06:35.960
rocket systems, ground systems, all of our networks and infrastructure, that kind of thing. So

00:06:35.960 --> 00:06:41.600
that's a lot of fun. I get to do a lot of fun engineering in my day job and do get to do Python

00:06:41.600 --> 00:06:46.720
on the weekend. Yeah. Do you get to run any Python on any of the satellites? I know that some of the

00:06:46.720 --> 00:06:52.080
satellites are controlled with Python or API, not necessarily for the space force, but you know,

00:06:52.080 --> 00:06:56.980
like some of the scientific ones. Yeah. So none of the one systems that I've used are using Python

00:06:56.980 --> 00:07:01.720
to actually directly interact with the satellite, but we are using Python and a number of different

00:07:01.720 --> 00:07:08.740
places. Some of the apps that we run that help process data or analyze display data to those kinds

00:07:08.740 --> 00:07:14.140
of things. But the data science dashboard thing that happens to data after it gets out of space.

00:07:14.140 --> 00:07:14.880
Yeah, exactly.

00:07:14.880 --> 00:07:20.960
Comes back. Yeah. Well, well, that sounds like a super, super fun job. And I was talking to somebody

00:07:20.960 --> 00:07:26.120
get together just earlier this week. Everyone's kind of fascinated that I can have a job that is

00:07:26.120 --> 00:07:29.980
podcasting. They're like, okay, well, who are you interviewing this week? Well, I'm going to interview

00:07:29.980 --> 00:07:35.300
the guy who does cool Python stuff and he works at the space force. And someone else is like, oh,

00:07:35.300 --> 00:07:40.240
that's amazing. And then someone else is, what's the space force? So maybe for those of us who don't

00:07:40.240 --> 00:07:44.600
know, we know what the air force is. What's the space force? Yeah, sure. The space force is the

00:07:44.600 --> 00:07:52.840
newest of the military services. We were established just in December of 2019 as, and sort of birthed out

00:07:52.840 --> 00:07:58.360
of the air force. And so the initial people that came over to space force came from the air force,

00:07:58.360 --> 00:08:04.020
although we've now brought in people from all the other services, army, Marines, Navy, to be part of

00:08:04.020 --> 00:08:10.060
space force, people who transfer just like I did. And the job of the space force is to organize, train,

00:08:10.060 --> 00:08:16.000
and equip guardians is what we call people in the space force to do space operations for the nation.

00:08:16.660 --> 00:08:20.940
And so if you, you know, things like GPS, for example, which I know everybody's familiar with,

00:08:20.940 --> 00:08:27.100
if you pull out your phone and use Waze or a maps app, or even do baking, which all relies on GPS timing

00:08:27.100 --> 00:08:31.900
signals, those kinds of things. We've even used GPS in a corn maze recently. Yeah. Yeah. So all,

00:08:31.900 --> 00:08:38.040
all of that is coming from a signal from a satellite in space that is run by the space force. I mean,

00:08:38.040 --> 00:08:43.420
we do a lot of other things to weather missile warning communications, the remote sensing,

00:08:43.660 --> 00:08:49.000
all sorts of different missions that the space force does, but we do all of the space systems

00:08:49.000 --> 00:08:55.040
that support the rest of the services, you know, communications, navigation, location, all those

00:08:55.040 --> 00:09:00.020
kinds of things. Sounds fascinating. And I suspect that many of these things were happening often under

00:09:00.020 --> 00:09:05.940
the air force banner or some other, other banner, right? Yeah. And now it's just more formalized,

00:09:05.940 --> 00:09:10.700
right? Yeah, exactly. So the air force has been doing this mission for a long time. In fact,

00:09:10.700 --> 00:09:15.660
the air force originally designed and launched the GPS constellation that is still in operations today

00:09:15.660 --> 00:09:21.060
and is now run by the space force. But what we found was that space was becoming more and more

00:09:21.060 --> 00:09:26.580
important. Certainly everybody's familiar with what SpaceX and a lot of the other commercial space

00:09:26.580 --> 00:09:30.780
companies are doing a lot of really exciting things going on, but it's a lot more happening in space.

00:09:30.780 --> 00:09:35.480
The importance of space to our economy and our nation is increasing. And quite frankly,

00:09:35.480 --> 00:09:40.240
the threat to all of that is increasing. A number of other countries have paid a lot of attention to

00:09:40.240 --> 00:09:47.580
what we're doing in space and, and they are actively working on ways to deny the U.S. the ability to use

00:09:47.580 --> 00:09:52.600
some of those capabilities if they want to. We've seen some of that play out in, you know, things going

00:09:52.600 --> 00:09:59.000
on in Europe right now, in Ukraine, for example. So because of the increased threat and the increase of

00:09:59.000 --> 00:10:04.800
importance of space to our economy and our, and our nation, we decided that we really wanted a team

00:10:04.800 --> 00:10:09.720
that was focused only on space. And that's all we did because for the air force, it was always,

00:10:09.720 --> 00:10:14.680
you know, an extra job in addition to everything else that they're doing. And so this, the only job

00:10:14.680 --> 00:10:21.160
space force has is doing space operations for the country. And so we have guardians, as I mentioned,

00:10:21.280 --> 00:10:23.720
focused on doing that job day in and day out.

00:10:23.720 --> 00:10:30.580
Well, very cool. It strikes me that this is one of the areas of the defense and military industry

00:10:30.580 --> 00:10:37.240
that is the least hands-on. It's, you know, the most, you almost never get to interact with,

00:10:37.240 --> 00:10:42.540
you know, things in space directly, right? Even with cameras necessarily, right? It's a little bit

00:10:42.540 --> 00:10:44.680
different than running over a hill or something.

00:10:44.680 --> 00:10:49.420
Well, it is different. And, you know, let me, I, first, I just want to add that, that anything I,

00:10:49.520 --> 00:10:53.300
I'm excited about space force and happy to talk about it, but anything I say is my opinion

00:10:53.300 --> 00:10:57.400
and not necessarily the opinion of the space force, the department of defense.

00:10:57.400 --> 00:10:58.720
Absolutely. Yeah. Thanks for saying that.

00:10:58.720 --> 00:11:03.840
Yeah. But I will tell you, you know, you're right, Michael, that we, people that interact

00:11:03.840 --> 00:11:07.940
with the space domain as part of the space force, except for the very small number of astronauts

00:11:07.940 --> 00:11:13.060
we have that work with NASA, largely do that through a computer screen. We do that through a piece

00:11:13.060 --> 00:11:19.400
of glass, a keyboard and a network. And that makes the space force different than the other

00:11:19.400 --> 00:11:25.920
services as you, as you alluded to. But that also means that we really want a workforce that's really

00:11:25.920 --> 00:11:31.400
understands that, right. That understands the technical domain. That's really digitally fluent.

00:11:31.400 --> 00:11:36.640
Space force is really focused on building digital service and having a digitally native workforce that

00:11:36.640 --> 00:11:42.720
really understands computers, understands coding, understands cloud infrastructures, and, you know,

00:11:42.720 --> 00:11:46.160
those kinds of things, all the underlying technologies that we need to do our job.

00:11:46.400 --> 00:11:50.320
We're really focused on building a workforce that understands those things.

00:11:50.320 --> 00:11:56.840
Yeah. That's what I was hinting at is I feel like a lot of the coding skills are somewhat in the same

00:11:56.840 --> 00:12:02.160
realm as the kind of things that you know, you all need to do. Probably just a ton of custom software as

00:12:02.160 --> 00:12:07.340
well. Yeah, definitely. We use a lot of commercial software, but we also do a lot of our own custom

00:12:07.340 --> 00:12:12.460
software. We have a couple of software factories where we've got people from the space force guardians

00:12:12.460 --> 00:12:16.780
who actually write code for other guardians to use as part of their day job.

00:12:16.780 --> 00:12:21.440
Nice. Well, if we have some extra time at the end, we'll come back to that. But let's switch over to

00:12:21.440 --> 00:12:29.420
our main topic, I guess. And let's start with the genesis of this idea. And you created this project

00:12:29.420 --> 00:12:35.300
called Textinator. And it's open source and Python, obviously, which is why we're talking about it.

00:12:35.940 --> 00:12:42.900
And I'll link to the GitHub repo. And it says that the about for it says simple macOS status bar menu

00:12:42.900 --> 00:12:49.480
bar app to automatically detect text and screenshots, which is pretty awesome. So yeah, it's one of those

00:12:49.480 --> 00:12:55.800
apps. There's just an icon in the upper right by by the clock would be in the task bar, a little hidden

00:12:55.800 --> 00:13:00.460
down there if it was a Windows app, but it's a Mac app. So that's it lives up in the menu bar alone.

00:13:01.060 --> 00:13:02.580
And what does it do?

00:13:02.580 --> 00:13:07.260
So it's really simple. If you take a screenshot, if Textinator is running, and you take a screenshot,

00:13:07.260 --> 00:13:12.560
it will search that screenshot for any text that it finds in the screenshot, and copy that text to

00:13:12.560 --> 00:13:17.520
the clipboard. That's all it does. Super simple. But you know, a useful thing if you've got, you know,

00:13:17.520 --> 00:13:22.540
trying to take a screenshot, there's text on the screen, like on a link on a video or something,

00:13:22.540 --> 00:13:27.580
and you can't copy and paste it. All you got to do is screenshot it and Textinator will pick it up and

00:13:27.580 --> 00:13:31.760
stick it on your clipboard. That's fantastic. Does it automatically capture that? Is it just

00:13:31.760 --> 00:13:37.520
constantly looking for screenshots and then clipboard, you know, doing text recognition and then clipboarding

00:13:37.520 --> 00:13:39.000
that? Or do you have to trigger it?

00:13:39.000 --> 00:13:45.440
No, it'll do it all automatically. And what it's doing is it's using this Mac built in spotlight

00:13:45.440 --> 00:13:51.780
feature behind the scenes. And basically, it sets up a query that's running in the background all the

00:13:51.780 --> 00:13:57.540
time. And the query says, anytime there's a new screenshot, send me an alert. And then so when

00:13:57.540 --> 00:14:02.160
it gets that alert from the operating system that there's a new screenshot, it then it does the

00:14:02.160 --> 00:14:05.860
processing and copies everything to the clipboard. Otherwise, it's just sitting there waiting for an

00:14:05.860 --> 00:14:06.620
alert to pop up.

00:14:06.620 --> 00:14:13.820
Yeah, fantastic. I said this show sort of originated from a tweet. So just as a user, I talked about this

00:14:13.820 --> 00:14:20.420
app called Text Sniper, which I thought was pretty cool in the Mac App Store that lets you kind of

00:14:20.420 --> 00:14:26.740
screenshot an area of text and will capture it. It costs money and you got to get it at the App Store

00:14:26.740 --> 00:14:32.720
and so on. And you said in your tweet said, that's a really cool idea. And it inspired me to create

00:14:32.720 --> 00:14:37.900
Textinator. I love the name as well. And I just think it's fascinating. You were able to knock this

00:14:37.900 --> 00:14:39.100
out pretty quickly in Python.

00:14:39.100 --> 00:14:44.060
Yeah, the initial, it's grown a little bit because I've tried to put a few bells and whistles on it.

00:14:44.060 --> 00:14:49.860
But the initial, I knocked it out on a Saturday afternoon, the initial capability. And it was

00:14:49.860 --> 00:14:55.720
300 lines of code. It's fairly simple. And it was fairly simple because I don't have to do all the

00:14:55.720 --> 00:15:03.440
hard work. I'm calling Mac framework API calls that are built into the Mac to do the hard work of,

00:15:03.560 --> 00:15:08.460
hey, find the screenshot, detect the text, put the text on the clipboard, those kinds of things.

00:15:08.460 --> 00:15:13.060
And so it was actually fairly easy to throw together. And then I'm using another Python

00:15:13.060 --> 00:15:19.560
package called Rumps that does all the heavy lifting of actually, hey, putting an app into the menu bar

00:15:19.560 --> 00:15:22.280
with an icon and all those kinds of things.

00:15:22.280 --> 00:15:28.640
Rumps is fantastic. It has such a ridiculous name, but it's so easy to build Mac apps with.

00:15:28.960 --> 00:15:34.120
Yeah, I've used it for quite a few different things. I've got a couple apps running right

00:15:34.120 --> 00:15:39.560
now on my Mac that I use every day that I built out of Python and Rumps. It's a really useful tool.

00:15:39.560 --> 00:15:43.700
That's cool. I'm starting to think of more that maybe I should create, but I have one as well.

00:15:43.700 --> 00:15:50.300
And maybe I'll talk about it a bit when we compare notes on yours. But yeah, super cool. This is so

00:15:50.300 --> 00:15:55.020
useful. You know, there's all these different times you're like, I really just want that text.

00:15:55.080 --> 00:16:01.000
And in macOS, you hit command shift four and you can select just a region of the screen. You don't

00:16:01.000 --> 00:16:04.940
have to screenshot the whole screen, which you'd get like, you know, the menu bar and like the

00:16:04.940 --> 00:16:10.120
navigation, like all you can just say, I want this section and it will grab that and then turn it

00:16:10.120 --> 00:16:16.220
into text, which is just amazing. I've used it for watching a video course or some kind of

00:16:16.220 --> 00:16:22.320
presentation. I'm like, I want that URL. They said, go to the huge long URL, just select snap,

00:16:22.660 --> 00:16:26.960
paste into, you know, paste the text of the URL and go, right?

00:16:26.960 --> 00:16:32.060
Yeah, exactly. It's so useful for those kinds of things. And, you know, I do want to add,

00:16:32.060 --> 00:16:37.780
this was inspired by, you mentioning text sniper, I think on the, on the Python bytes podcast,

00:16:37.780 --> 00:16:41.920
right? And text sniper is a great app. It's, you know, it's only like 10 bucks. So I mean,

00:16:41.920 --> 00:16:46.140
you should definitely check it out. But when you mentioned that I had already been working with

00:16:46.140 --> 00:16:52.540
playing around with the vision framework, which is the max ML powered computer vision framework,

00:16:52.540 --> 00:16:56.480
right? Yeah. Yeah. And so I'd been playing around with that and I knew that it would,

00:16:56.480 --> 00:17:01.680
it was fairly easy to do this optical character recognition tech, you know, and grab the text from

00:17:01.680 --> 00:17:06.720
a picture. So I thought, well, I bet you, I could recreate that. And I did play around a little bit

00:17:06.720 --> 00:17:10.840
to try to be able to actually draw the crosshairs on the screen myself and, and let you do that.

00:17:10.840 --> 00:17:16.540
And in Python, you know, Python, that was actually kind of hard to do, but then, so then it dawned on

00:17:16.540 --> 00:17:21.240
me, well, the Mac already comes with the ability to do that, the screenshot app. Then I just had to

00:17:21.240 --> 00:17:25.420
figure out, okay, how do I grab that screenshot when it happens? And it turns out, you know,

00:17:25.420 --> 00:17:29.920
there was a screenshot and then how do you get the tech? Yeah. And it turns out that that's super easy

00:17:29.920 --> 00:17:36.200
to do. If you tell the Mac, tell me when there's a new screenshot, you set up a query that basically

00:17:36.200 --> 00:17:40.580
runs just like you would, if you use spotlight on the Mac and it runs in the background,

00:17:40.580 --> 00:17:46.100
anytime there's some new thing that matches your query, it lets you know, and then it runs your

00:17:46.100 --> 00:17:51.300
code. Yeah. And that's amazing how easy it was to knock out the vision framework and some of the ML

00:17:51.300 --> 00:17:56.920
tools. They're probably using the neural engine that's built into the latest chips as well.

00:17:56.920 --> 00:18:01.300
They're probably taking advantage of that. Yeah, I'm sure they are. I've got an older MacBook and

00:18:01.300 --> 00:18:06.340
they run fine on there too, but they're, they're using the GPU on that. So that uses whatever hardware

00:18:06.340 --> 00:18:10.460
you have, but I'm sure if you've got an M1 more, you know, one of the newer Macs that's using the

00:18:10.460 --> 00:18:17.240
neural engine and it's really surprising how accurate it is and how fast it is that they've

00:18:17.240 --> 00:18:21.700
done a really good job with that. Yeah. As we look at this, we'll also see, you know,

00:18:21.700 --> 00:18:26.940
people talk about, well, Python's slow. So how could you use Python to do this thing? I mean,

00:18:26.940 --> 00:18:31.980
I think that's always a funny statement to say Python is slow because it's, it's slow until it's as fast

00:18:31.980 --> 00:18:38.060
as C or as fast as fast. You know, like it lives in those weird bimodal worlds, right? It's like

00:18:38.060 --> 00:18:42.780
really slow. They all of a sudden, oh yeah, just as fast. Yeah. All right. Yeah. I, so me, you know,

00:18:42.780 --> 00:18:47.200
I've never found Python to be slow, but I guess it depends what you're doing with it. I'm not doing

00:18:47.200 --> 00:18:52.920
really big, heavy data crunching, but you know, just as in this example, it's a Python app that's

00:18:52.920 --> 00:18:59.380
doing the text detection, but the text detection is not running in Python. That's running in objective C or

00:18:59.380 --> 00:19:04.300
Swift or whatever native language that Apple wrote it in. And we're just calling that from Python.

00:19:04.300 --> 00:19:10.420
So the only slow part of that is actually setting up that call to translate between Python types and

00:19:10.420 --> 00:19:15.180
objective C types and go back and forth. And fortunately, you know, there's a really great bridge for it

00:19:15.180 --> 00:19:22.060
called PyOBJC, which is short for PyObjective C. Objective C is one of the languages that has been

00:19:22.060 --> 00:19:26.780
around the Mac for a long time that Apple has used long time, though they're migrating a lot more to

00:19:26.780 --> 00:19:32.460
Swift now, but PyOBJC does all that sort of translation across the bridge between Python

00:19:32.460 --> 00:19:37.240
and objective C. So you don't, for the most part, don't really even really have to think about it.

00:19:37.240 --> 00:19:42.540
Yeah, exactly. So what you did is you basically saw there was a screenshot and you say, you know,

00:19:42.540 --> 00:19:47.620
send that information off to the objective C platform layer. And it just goes natively and

00:19:47.620 --> 00:19:52.840
does its thing, which is, it's a pretty interesting bit of coordination. You know, you've got rumps

00:19:52.840 --> 00:19:59.220
coordinating, working with the menu bar API and you've got these various low level OS platforms that you're

00:19:59.220 --> 00:20:04.860
talking to through high objective C and yeah, well, we'll see how you built it up. But it's there's a lot of

00:20:04.860 --> 00:20:06.140
neat moving pieces there.

00:20:14.520 --> 00:20:44.500
So what's the point of view is that users may be encountering errors, slowdowns or crashes with your app right now. Would you even know it until they sent you that support email? How much better would it be to have the error or performance details immediately sent to you, including the call stack and values of local variables and the active user recorded in the report? With Sentry, this is not only possible, it's simple. In fact, we use Sentry on all the talk Python web properties. We've actually fixed a bug triggered by a user and had the upgrade.

00:20:44.500 --> 00:20:55.500
ready to roll out as we got the support email. That was a great email to write back. Hey, we already saw your error and have already rolled out the fix. Imagine their surprise. Surprise and delight your users.

00:20:55.500 --> 00:21:20.760
Create your Sentry account at talkpython.fm/sentry. And if you sign up with the code talkpython, all one word, it's good for two free months of Sentry's business plan, which will give you up to 20 times as many monthly events as well as other features. Create better software, delight your users and support the podcast. Visit talkpython.fm/sentry and use the coupon code talkpython.

00:21:22.620 --> 00:21:28.040
Let's start with Python on the Mac because I know you have some opinions on how to get started there.

00:21:28.040 --> 00:21:49.100
Yeah. It's a good experience on the Mac, but it does take a little bit of finagling, I think, to get Python right. And the first thing you got to start with is, hey, which Python do I put on? How do I install it? And there's a number of different ways. There's a thing called Homebrew for the Mac, which is a package manager that installs apps and command line tools on the Mac. And that's great.

00:21:49.100 --> 00:21:55.520
If you need any kind of command line tool you need in the terminal, Homebrew can install it pretty fast.

00:21:55.960 --> 00:22:09.740
And so what I've seen is a lot of people are tempted just to install Python that way. Unfortunately, that can run into a number of different problems because Homebrew also uses that same Python to manage a lot of the other packages and things that it's installing.

00:22:09.960 --> 00:22:16.980
And so it might change that Python, update it, change something, and you don't realize it. And then now all of your stuff stops working.

00:22:16.980 --> 00:22:29.140
So after a few false starts with that, I decided just don't use the Homebrew Python for the Mac. And what I recommend for most people is just download it from python.org and you get the Mac installer, install it and run it.

00:22:29.400 --> 00:22:41.620
Because I develop a bunch of different open source packages and want to test them on different versions of Python, I build my own Python and I use PyE and V to manage that. And that way I can run a number of different versions of it.

00:22:41.620 --> 00:22:49.900
Anaconda is also another great one for the Mac. But you really need to understand sort of the ins and outs of which Python you picked and what it's good for.

00:22:50.140 --> 00:22:56.320
Yeah. There's an article that you linked to talking about this as Homebrew Python is not for you. We can do you as a developer.

00:22:56.320 --> 00:23:13.300
This was by Justin Mayer. It's interesting that basically the fundamental problem that he talks about is that Homebrew might change the underlying Python version, even if you don't ask it to, because you might brew install glances or some other thing that needs Python.

00:23:13.300 --> 00:23:19.640
And then it goes, well, great, we got a good new Python for that. Right. And that could potentially break your virtual environments.

00:23:19.920 --> 00:23:25.680
Which is not amazing. Yeah. So yeah. Anyway, people can check this out and see what they think about this.

00:23:25.680 --> 00:23:32.460
But yeah, I would. Yeah, I recommend that you don't use Homebrew for developing. Right. Homebrew is great for everything. I use it every day.

00:23:32.460 --> 00:23:39.300
But yeah, if you want to develop Python, get your own Python that that is only going to get updated if you decide to update it.

00:23:39.300 --> 00:23:40.200
Yeah, that makes a lot of sense.

00:23:40.200 --> 00:23:52.900
You know, for me, I'm, I'm okay. If, if my virtual environment change, I have a whole bunch of hotkeys and shortcuts and aliases that I can just recreate, reset up those things.

00:23:52.900 --> 00:23:57.340
So if something changes, I'll just drop into the terminal and blow it away and recreate it.

00:23:57.340 --> 00:23:59.660
But that's because I've been doing it for a long time.

00:23:59.660 --> 00:24:04.400
I know a lot of people, whether or not a virtual environment is active, where is Python? What is installed?

00:24:04.400 --> 00:24:07.700
All those things become very frustrating to people.

00:24:07.700 --> 00:24:10.880
Yeah. So this could help potentially avoid those, those troubles.

00:24:10.880 --> 00:24:25.920
Out of curiosity, I did a brew list. See what I got installed here. Probably got about 120 different things installed. So yeah, like Richie in the audience, which says a brew is my go-to. I'm there. I'm there with it as well.

00:24:25.920 --> 00:24:40.660
So yeah, the article, by the way, mentions this thing, which I have not used ASDF, which is, I don't even know what to make of the name. I mean, it's just like, I just, just hit left fingers, just go down and this is my app, right?

00:24:40.660 --> 00:24:56.340
But apparently this is about installing, you can use this to install different tools and frameworks. Basically it's for CLI for managing different runtimes of Ruby and Perl. And there's a plugin for Python. Have you played with this?

00:24:56.340 --> 00:25:01.600
I have not. It looks really cool, but I've got my Mac to the point where it works and I just don't mess with it.

00:25:02.340 --> 00:25:22.960
Mine is so janky, honestly, just as a sidebar talking about this is I got this, this is a Mac mini M1 and I got it in, or I ordered it right when it came out basically or a week after. And so I got it right away. And many of the things that you would pip install or certainly brew install, it would say, well, we don't have that for your platform.

00:25:22.960 --> 00:25:40.960
It took about a month before I could reliably pip install everything I needed because the wheel didn't exist for, you know, the ARM 64 of whatever. And so I've got the Intel version of brew put on here. And then I've got the C++ tools for Apple Silicon.

00:25:40.960 --> 00:26:03.100
And so when I try to do certain things that drop down to the compiler, like high MV, which I've tried and I can't get it to work. There's some mispatch about like the compiler flags and what platform it thinks it's on. I'm, I'm tempted to just reformat the whole computer now that everything's stabilized, but I am waiting until October because if they ship a new, much, much better one, I might not even go through that process.

00:26:03.100 --> 00:26:10.640
But yeah, I would love to use pie MV. I haven't been able to get it to work. Maybe ASDF is the thing to do. I'll give it a try.

00:26:10.640 --> 00:26:11.680
Yeah. Might be worth trying.

00:26:11.680 --> 00:26:18.220
Certainly worth trying. I would say now that's great for creating and getting your Python there.

00:26:18.220 --> 00:26:28.240
And one of the first things I guess that came to mind when I thought about what your app does, I'm like, oh, oh, this is going to be really tricky because I can get say a little rumps app to run.

00:26:28.240 --> 00:26:40.320
But when you really get down to it, there's a lot of things about, is it from a signed developer? You know, what permissions does it request? Like, does it request the be a server or access your

00:26:40.320 --> 00:26:48.900
file system or, you know, look at the clipboard? All these different things can get a little bit tricky. You want to talk about this permissions a bit?

00:26:48.900 --> 00:27:18.480
Yeah, sure. And that's a tricky thing for every back app, whether it's in Python or not. Right. And so there's a, I guess, you know, there's a good thing and a bad thing there. From a user's perspective, Apple's done a really good job locking down the computer in privacy. Make sure that apps cannot access your data unless you explicitly allow them to access your data. From a developer's perspective, that means apps can't access your data unless you explicitly allow it. So you've got to keep that in mind as a developer that you've got to make sure that your app has the right to access your data.

00:27:18.480 --> 00:27:31.480
And so there's a couple of different ways to do that. Every app, a .app bundle that ships on the Mac has an info.plist, a property list file that's basically just a dictionary of key values.

00:27:31.480 --> 00:27:53.540
And that tell the app, you know, various settings for the app. A couple of those things are permissions or entitlements that you need to request and say, hey, I want my app to be able to do this. For example, for Textinator, one of the things it does is request access to the desktop. And it'll pop up and then you can grant access to the desktop. But until then, it won't be able to see the desktop.

00:27:53.760 --> 00:28:01.160
Presumably because the default behavior of taking a screenshot is to drop the file onto the desktop. So you need to be able to go hunt for those files, right?

00:28:01.160 --> 00:28:14.100
Yeah. In fact, when I first got the first version of Textinator working, it wasn't doing that properly. And so I was taking screenshots and it never found them. And because it just couldn't see the disk, that the screenshots were happening.

00:28:14.100 --> 00:28:24.620
But the whole query system is smart enough to say, hey, you don't have permission to see these screenshots. So I'm not even going to tell you about them. So it was never even getting alerted that it was getting a screenshot.

00:28:24.620 --> 00:28:43.260
So I had to get that sorted out. And then if you can change the default location of a screenshot and put it somewhere else. And if you do that, you actually have to go into the system settings, the privacy settings, and actually give Textinator full disk access so that it can see where those screenshots are, you know, if they're outside of your home directory.

00:28:43.260 --> 00:28:52.540
Yeah, there is a screenshot application. And if you run it, basically, that's like your preferences. Or, you know, it's not just a hockey, but you can go and change your preferences for the screenshots.

00:28:52.540 --> 00:28:59.420
Yeah, there's a property list setting, you can do it from the command line as well. But yeah, the screenshot app is the easiest way to do that.

00:28:59.420 --> 00:29:11.920
Yeah. Okay, so basically, you go through the info.plist, and you just express, I need access to the disk? Or do you try to get there? And then macOS says, hey, this thing is trying to do it?

00:29:11.920 --> 00:29:24.320
Yeah. So what I did in Textinator is one, in the info.plist you put in, what you actually put in there is a, the message that gets shown to the user when they actually try to access the desktop or whatever it is that you're trying to do.

00:29:24.320 --> 00:29:47.600
Right. And so what I have Textinator do is when it first fires up, it tries to access the desktop to force that message to pop up so that you grant right away. Otherwise, you'll never see the screen, you'll never see the screenshots. So that's an easy way to just, you know, to force the user that way. So as they start it, they've already got their attention. It'll pop up a dialog box and say, hey, Textinator wants to access the desktop. Allow. And then now it has access.

00:29:47.600 --> 00:30:02.060
I'm always suspicious of apps that pop up these little, you know, enter your admin password. Yeah. For whatever reason. But they do it just out of the blue. You know, it's one thing, like if I launch a new app and it says, there's an update. Do you want to update? You say yes.

00:30:02.060 --> 00:30:02.300
Yes.

00:30:02.300 --> 00:30:09.540
And it'll pop up. You're like, yep, this is trying to update itself. But if, you know, it's running and it just behind the scenes doesn't update or something.

00:30:09.540 --> 00:30:09.680
Yeah.

00:30:09.680 --> 00:30:12.180
It's like, oh, we need your password. You're like, yeah, no.

00:30:12.180 --> 00:30:20.720
Yeah, I know what's going on here. So I think anyway, the reason I bring this up is I think it's a good idea to like, as they're interacting with it, just get those out of the way straight away.

00:30:20.720 --> 00:30:28.680
Yeah, exactly. Otherwise, you're right. Well, Textinator once, you know, if it pops up later on and you've forgotten all about it, it does definitely look suspicious.

00:30:28.680 --> 00:30:37.240
Yeah. Well, I'm a little unsure. I should have gone and installed this. But no, like if it's a timely one, it's okay.

00:30:37.240 --> 00:30:51.200
Well, you know, the other thing about installing apps is that they have to be signed. And you get this really scary when you try to open it, you'll get a really scary warning saying, hey, this is signed by an unknown developer. It'll destroy your computer. Don't open. Right.

00:30:51.200 --> 00:31:05.100
Kind of thing. And you actually have to right click and say open and and then say, yes, I really want to open this thing. And you only have to do that once. But the first time you do it, it does give you kind of the scary warning. So you got you do need to know where is this code coming from that I'm going to try to run.

00:31:05.100 --> 00:31:10.140
So you build it with the ultimate. You built the app bundle with Py2 app, right?

00:31:10.140 --> 00:31:11.000
Yes, that's right.

00:31:11.000 --> 00:31:16.620
Yeah. I wonder if there's a way to sign if you have a developer account, if there's a way to sign.

00:31:16.620 --> 00:31:39.960
Oh, yeah, I think you could. I don't have an Apple developer account. That's something you have to pay for. So I just then I just tinker around. So I don't haven't bothered with that. So it's mine is self signed. But yes, you can. Or you could do it after the fact. You could resign the app using the Apple signing tools, which you can run from the command line and then resign the app with your developer signature. And you could even do that and notarize it as well.

00:31:39.960 --> 00:32:05.960
Yeah, then it would it would just run with no just right. Yeah, yeah, because sometimes depending on how you run it, it won't give you the option to run anyway. Like if you click if it's in the little download section, and you just pop up that little fan and you click it sometimes it'll just go this is from an unknown developer, we won't run it. But if you right click and say run, it'll say Oh, are you sure you want to run? Yes. You know? Yeah. Yeah. So it's, it can be annoying. It can be a bit of a roadblock. But yeah, yeah.

00:32:05.960 --> 00:32:27.040
But yeah, but that's something to keep in mind if you, you know, a lot of things that Python developers do sometimes I know I do this is, is whip out a little tool to help a family member or friend or something, right? And so if you're going to send them that something to run, you've got to make sure you walk them through, okay, you've got to right click, and then say open, and then click allow access to the desktop or whatever it is that you're trying to do.

00:32:27.040 --> 00:32:53.840
Exactly. Or if you're building this app for your club or your kids football team or soccer team or whatever, right, then you're handing it off to people who don't necessarily know that it's trustworthy or how to make it go. But you kind of got to talk them through it. It's probably if you have a developer account, it probably would be cool to do. I say that as somebody who has an Apple developer account and a rumps app, and it's not signed. So they should probably figure it out.

00:32:54.040 --> 00:32:58.440
But in theory, I think it would be cool. I'm intrigued on how I might go about doing that now.

00:32:58.440 --> 00:33:04.720
Yeah, I don't think it's that hard. I think you can just run the signing tools and add your signature on there.

00:33:04.720 --> 00:33:09.080
Yeah. Well, it just has to be trusted from something that Apple trusts, right?

00:33:09.080 --> 00:33:09.360
Yeah.

00:33:09.360 --> 00:33:23.840
So let's talk about Py2App real quick. So to me, there's a bit of a danger that I might get into some ranty sidetracks here on this show. But to me, I think it's a really a big hole in Python's capabilities. Let's put it that way.

00:33:23.840 --> 00:33:39.700
That there's not a way to say, press a button or run a command. And then I have a binary thing I give to people that from their perspective, is an application with an icon that goes into their start menu or their dock, that they click it and it comes out.

00:33:40.240 --> 00:33:43.540
It does things. Or they even just run the command line and it does things, right?

00:33:43.540 --> 00:33:51.140
The ability to send somebody an application that was created with Python is super lacking.

00:33:51.520 --> 00:33:56.540
If they're not a developer on the other end receiving it or not a server admin receiving it.

00:33:56.540 --> 00:33:58.060
Yeah, that's very true.

00:33:58.060 --> 00:34:05.220
I'll add one caveat to that, Michael. If it's a command line tool, what I found is I've defaulted now just to use PipX.

00:34:05.220 --> 00:34:05.780
Yes.

00:34:05.780 --> 00:34:10.320
And they brew install PipX and then they can PipX install and they're good to go.

00:34:10.320 --> 00:34:10.500
Yeah.

00:34:10.500 --> 00:34:17.100
And it's super easy and it just works. And then inside the terminal, all your permissions are owned by the terminal.

00:34:17.100 --> 00:34:24.660
So once you've granted the terminal access to your desktop or your photos or whatever, any Python app you run will have access, no problem.

00:34:24.660 --> 00:34:26.520
That's an absolutely good point.

00:34:26.620 --> 00:34:29.740
But for a GUI app, yeah, it's just way harder than it ought to be.

00:34:29.740 --> 00:34:33.500
It is way harder than it ought to be. And I would love to see something built into Python.

00:34:33.500 --> 00:34:44.820
I mean, right now, it seems like the big focus of so many of the people doing high-end work on Python is about making it faster, do more, take more advantage of hardware.

00:34:44.820 --> 00:34:53.900
And I would not say sidetrack that work, but once that's kind of done, the low-hanging fruit is how do I get a shippable thing?

00:34:54.280 --> 00:35:01.160
Because after that, you know, you start to open up things like, well, if I could send out a binary, maybe I could send out a binary for iOS or Android.

00:35:01.160 --> 00:35:01.520
Exactly.

00:35:01.520 --> 00:35:05.100
And then all of a sudden we have mobile and like, it just, it unlocks so much.

00:35:05.100 --> 00:35:11.040
Talk Python To Me is partially supported by our training courses.

00:35:11.040 --> 00:35:15.560
Do you want to learn Python, but you can't bear to subscribe to yet another service?

00:35:15.560 --> 00:35:18.460
At Talk Python Training, we hate subscriptions too.

00:35:19.020 --> 00:35:24.320
That's why our course bundle gives you full access to the entire library of courses for just one fair price.

00:35:24.320 --> 00:35:25.180
That's right.

00:35:25.180 --> 00:35:31.680
With the Everything Bundle, you save over 80% off the full price of our courses and you own them all forever.

00:35:31.680 --> 00:35:37.920
That includes the courses published at the time of purchase, as well as courses released within about a year after the bundle.

00:35:38.760 --> 00:35:51.760
That said, what we have now are a couple of programs and tools that will bundle up the Python runtime.

00:35:51.760 --> 00:35:53.920
We're just going to scratch the surface.

00:35:53.920 --> 00:35:57.200
There's a bunch of attempts on this, like PyOxidizer, which we won't touch on.

00:35:57.300 --> 00:36:06.220
But if you're building a Mac app, probably the way to go is Py2 app because it's specifically about making macOS apps, right?

00:36:06.220 --> 00:36:06.660
Yeah.

00:36:06.660 --> 00:36:10.740
I've used PyInstaller for command line tools.

00:36:10.740 --> 00:36:12.280
It'll bundle up.

00:36:12.280 --> 00:36:17.300
Basically, it's a zip file with all your Python code in it that unzips a runtime and runs.

00:36:17.300 --> 00:36:19.440
And then Py2 app for GUI apps.

00:36:19.500 --> 00:36:29.420
There's another one called Beware, the Beware project that's really trying to solve that problem of, hey, I want to be able to run on Android and iOS and macOS and Windows.

00:36:29.420 --> 00:36:31.320
I've tried it a couple of times.

00:36:31.320 --> 00:36:33.880
It looks really promising, but I keep running into problems.

00:36:33.880 --> 00:36:37.280
And I go back to Py2 app because I know how to make it work.

00:36:37.280 --> 00:36:37.580
Yeah.

00:36:37.580 --> 00:36:40.720
The briefcase project from, I think is what it's called.

00:36:40.720 --> 00:36:41.020
Yeah.

00:36:41.020 --> 00:36:41.960
It's very interesting.

00:36:41.960 --> 00:36:45.120
But I'm holding out hope that they'll be able to solve some of these things.

00:36:45.120 --> 00:36:47.340
They seem to be doing a lot of work on it.

00:36:47.340 --> 00:36:49.340
But Py2 app, well, you run it.

00:36:49.340 --> 00:36:54.720
And it will create basically an app bundle, which is how apps are distributed on your Mac.

00:36:54.720 --> 00:37:00.260
One thing you'll notice if you go to the Textinator repo is that there's a setup.py.

00:37:00.260 --> 00:37:04.760
And on all my other projects, I've switched to poetry and pyproject.toml.

00:37:04.760 --> 00:37:05.580
Okay.

00:37:05.580 --> 00:37:09.200
But I have not figured out how to make Py2 app work with that.

00:37:09.200 --> 00:37:11.760
So Py2 app needs a setup.py.

00:37:11.760 --> 00:37:15.900
And so I use the setup.py anytime I have to use Py2 app.

00:37:15.900 --> 00:37:16.180
Okay.

00:37:16.180 --> 00:37:17.260
How cool.

00:37:17.260 --> 00:37:21.060
I'm looking at your setup.py on the Textinator repo.

00:37:21.060 --> 00:37:23.460
You've got this options here.

00:37:23.460 --> 00:37:29.000
And one of the things it has is icons, the various icons, which for your app, you actually need

00:37:29.000 --> 00:37:33.260
all these different sizes of icons, which is kind of a bit of a hassle.

00:37:33.260 --> 00:37:37.520
But then you give that file and it makes that the right, it does the right thing with it.

00:37:37.760 --> 00:37:44.080
And then you also have a plist, which has things like lsuielement is true or ns, these names,

00:37:44.080 --> 00:37:47.280
ns desktop folder usage description.

00:37:47.280 --> 00:37:48.680
That's what you're talking about, right?

00:37:48.680 --> 00:37:49.020
Yeah.

00:37:49.020 --> 00:37:53.920
Those things in the plist settings there are the things that are going to go into that info.plist

00:37:53.920 --> 00:37:54.500
for the app.

00:37:54.500 --> 00:37:55.920
And Py2 app will stick them in there.

00:37:56.280 --> 00:38:01.440
The lsuielement basically says, I want to run headless without being in the doc.

00:38:01.440 --> 00:38:04.200
And so that's how the rumps apps works.

00:38:04.200 --> 00:38:08.520
You'll get the little icon up in the status bar, but you won't see it in the doc.

00:38:08.720 --> 00:38:13.460
And then the desktop folder usage description is that's the little message that will pop up

00:38:13.460 --> 00:38:16.820
when Textinator tries to access the desktop the first time.

00:38:16.820 --> 00:38:17.060
Right.

00:38:17.060 --> 00:38:17.300
Okay.

00:38:17.300 --> 00:38:22.880
So this is the way, one of the ways in which you configure the actual creation of the Mac

00:38:22.880 --> 00:38:25.900
side of things, not just the bundling of the Python, right?

00:38:25.900 --> 00:38:26.500
Exactly.

00:38:26.500 --> 00:38:30.840
So that stuff, you know, those settings there will actually go into the app and Py2 app

00:38:30.840 --> 00:38:31.680
will stick them in there.

00:38:31.680 --> 00:38:31.960
Cool.

00:38:31.960 --> 00:38:35.700
I just want to give a quick shout out to this app here as well.

00:38:36.220 --> 00:38:41.260
I've had to create these icon sets for different apps and there's this thing called the icon

00:38:41.260 --> 00:38:45.940
set studio and you just give it one icon and it will, it doesn't have very good reviews,

00:38:45.940 --> 00:38:46.720
but it worked well for me.

00:38:46.720 --> 00:38:47.000
Yeah.

00:38:47.000 --> 00:38:47.240
Okay.

00:38:47.240 --> 00:38:47.460
Good.

00:38:47.460 --> 00:38:48.380
Yeah.

00:38:48.380 --> 00:38:53.060
So, you know, there's that, but it, you basically give it one large icon and it'll create all

00:38:53.060 --> 00:38:56.460
the different variations and one of those icon files and stuff for you.

00:38:56.460 --> 00:38:58.060
So anyway, that's, that's a pretty cool one.

00:38:58.060 --> 00:38:58.880
All right.

00:38:58.880 --> 00:39:00.680
So Py2 app.

00:39:00.680 --> 00:39:01.260
Yeah.

00:39:01.260 --> 00:39:05.900
And then you just give it a command line to build your application, right?

00:39:06.160 --> 00:39:06.280
Yeah.

00:39:06.280 --> 00:39:13.860
You run Py2 app and it will create, it'll take, read in your setup.py, read in your, all the

00:39:13.860 --> 00:39:17.900
files associated with your app and then bundle it up into the app file.

00:39:17.900 --> 00:39:18.180
Yeah.

00:39:18.180 --> 00:39:22.640
It takes a little bit of time and then there's a build and a dist folder and miraculously,

00:39:22.640 --> 00:39:24.980
it's really delightful actually.

00:39:24.980 --> 00:39:30.840
In the dist folder, I think it is, there's a, whatever you called your application.app.

00:39:31.000 --> 00:39:32.420
It has the icon.

00:39:32.420 --> 00:39:33.440
It looks like an app thing.

00:39:33.440 --> 00:39:36.480
You can drag it to your applications folder.

00:39:36.480 --> 00:39:41.000
You can put it in the doc, you know, if it's that kind of app or for the rumps one, I'm not

00:39:41.000 --> 00:39:41.160
sure.

00:39:41.160 --> 00:39:45.240
It makes a lot of sense to actually put it in the doc, but you know, it's as much an app

00:39:45.240 --> 00:39:47.660
as far as macOS is concerned as all the other ones, right?

00:39:47.820 --> 00:39:48.100
Exactly.

00:39:48.100 --> 00:39:48.360
Yeah.

00:39:48.360 --> 00:39:50.580
It's a native app and they'll run.

00:39:50.580 --> 00:39:54.900
And I, yeah, for something like Textinator, I just put it in the applications folder and

00:39:54.900 --> 00:40:00.420
then add it to the startup items or my login items so that it just starts running when I

00:40:00.420 --> 00:40:01.300
log into the computer.

00:40:01.840 --> 00:40:03.820
One thing to keep in mind is you might be tempted.

00:40:03.820 --> 00:40:07.460
You get that first time you build it, that app in your dist folder.

00:40:07.460 --> 00:40:12.500
If you run that, at least for Textinator, it won't work because it, that version of the

00:40:12.500 --> 00:40:15.560
app won't have the permissions to see your desktop.

00:40:15.560 --> 00:40:21.840
So, because it's got it, I guess the privacy settings are going to look at, hey, which app

00:40:21.840 --> 00:40:23.660
and then which signature was signed?

00:40:23.660 --> 00:40:26.700
And it's going to look for, hey, that version of it's got permission.

00:40:26.960 --> 00:40:31.980
That also means that if you upgrade Textinator, you may have to go back into your settings

00:40:31.980 --> 00:40:33.720
and give it permission again.

00:40:33.720 --> 00:40:34.100
Okay.

00:40:34.100 --> 00:40:34.720
Yeah.

00:40:34.720 --> 00:40:38.340
Permissions are always tricky, but very cool.

00:40:38.340 --> 00:40:41.520
My experience is this has worked and it's been really nice.

00:40:41.520 --> 00:40:47.260
You can build a little .app files and it's like those smart bundles, right?

00:40:47.260 --> 00:40:49.520
They're really folders, but they look like a single file.

00:40:49.520 --> 00:40:50.040
Yeah.

00:40:50.040 --> 00:40:51.040
It's a great way to handle.

00:40:51.040 --> 00:40:51.600
I do.

00:40:51.600 --> 00:40:56.940
I don't know if I agreed sufficiently enthusiastically with you, but pip,

00:40:56.940 --> 00:41:01.560
PIPX is absolutely the way to hand out command line tools these days.

00:41:01.560 --> 00:41:06.700
People can get PIPX by just reinstalling it, which brings in Python that PIPX needs.

00:41:06.700 --> 00:41:08.040
So that already is set.

00:41:08.040 --> 00:41:14.760
Then if you just type PIPX, like for example, PIPX install PLS, right?

00:41:14.760 --> 00:41:16.460
For example, that's a really nice one.

00:41:16.460 --> 00:41:17.960
It's an LS replacement.

00:41:18.120 --> 00:41:23.320
I think I've talked about it before, but it's like sort of a developer focused LS type

00:41:23.320 --> 00:41:23.980
of experience.

00:41:23.980 --> 00:41:28.700
And you can get like little icons like for readmes or license files or whatever.

00:41:28.700 --> 00:41:33.400
But if you want this, you could go through the way of setting up and running on Python,

00:41:33.400 --> 00:41:40.680
or you could just PIPX install PLS or glances or HTTP, HTTP, HTTP, IE, HTTP, right?

00:41:40.680 --> 00:41:43.320
These, all of those things should just come in with PIPX.

00:41:43.380 --> 00:41:45.580
They get automatically upgraded if you ask for it.

00:41:45.580 --> 00:41:47.640
There's a lot of good stuff going on there.

00:41:47.640 --> 00:41:54.380
But if you need to hand out an executable thing for Mac people, this Pi 2 app is really nice.

00:41:54.380 --> 00:41:55.800
Yeah, it works great.

00:41:55.800 --> 00:41:59.140
And it's, yeah, there's a few quirks to get it set up.

00:41:59.140 --> 00:42:02.860
Like you have to use the setup.py and you have to make sure you get the right things in

00:42:02.860 --> 00:42:04.360
that PLIST dictionary.

00:42:04.640 --> 00:42:07.780
But once you figure those out, it's super easy.

00:42:07.780 --> 00:42:13.620
It's also, I guess, worth pointing out, there's a Pi 2.exe for doing this for Windows folks,

00:42:13.620 --> 00:42:14.100
right?

00:42:14.100 --> 00:42:17.860
Windows people are like, well, the Pi 2 app doesn't do me any good.

00:42:17.860 --> 00:42:19.800
But there's a Pi 2.exe.

00:42:19.800 --> 00:42:22.540
And maybe more broadly, there's Pi Installer.

00:42:22.540 --> 00:42:27.860
And honestly, I don't have enough experience between Pi 2.exe versus Pi Installer, which also

00:42:27.860 --> 00:42:30.640
make Windows apps to say which you should choose.

00:42:30.780 --> 00:42:32.900
But Pi Installer has been pretty solid too.

00:42:32.900 --> 00:42:36.960
Yeah, I use Pi Installer for a different project of mine that's a command line tool.

00:42:36.960 --> 00:42:38.020
And it works great.

00:42:38.020 --> 00:42:39.680
I mean, I've got to build it.

00:42:39.680 --> 00:42:43.600
That one, I think that it doesn't, Pi Installer doesn't sign it.

00:42:43.600 --> 00:42:46.320
The Pi 2 app automatically signs it.

00:42:46.320 --> 00:42:51.460
So you have to sign it yourself, whether you use an ad hoc signature or a developer signature.

00:42:51.460 --> 00:42:52.860
But you can do that.

00:42:52.860 --> 00:42:55.100
I just have a shell script that does that for me.

00:42:55.100 --> 00:42:55.400
Okay.

00:42:55.400 --> 00:42:56.480
Interesting.

00:42:56.480 --> 00:42:59.740
I've used Pi Installer for GUI.

00:43:00.380 --> 00:43:01.500
Have you checked out GUI?

00:43:01.500 --> 00:43:02.800
G-O-O-E-Y?

00:43:02.800 --> 00:43:03.760
I have, yeah.

00:43:03.760 --> 00:43:04.620
Yeah, yeah.

00:43:04.620 --> 00:43:06.440
I don't know what to make of it.

00:43:06.440 --> 00:43:07.660
It's super interesting.

00:43:07.660 --> 00:43:14.000
And it lets you take any command line tool and turn it into, well, as the name says, like

00:43:14.000 --> 00:43:19.040
a GUI, where it takes the command line arguments and turns them into widget inputs.

00:43:19.040 --> 00:43:24.180
Like a file might be a browse for a new file, or a yes or no might be a check on or off type

00:43:24.180 --> 00:43:24.500
of thing.

00:43:24.500 --> 00:43:25.700
What's your thoughts on GUI?

00:43:25.700 --> 00:43:27.080
Will we go on for that?

00:43:27.080 --> 00:43:30.300
I think for simple CLIs, it's a great tool.

00:43:30.300 --> 00:43:33.300
I've never, I haven't used it, but I have played around with it a little bit.

00:43:33.300 --> 00:43:38.680
And it's, you know, definitely less scary, I guess, in the command line for users that aren't

00:43:38.680 --> 00:43:39.980
really comfortable in the command line.

00:43:39.980 --> 00:43:46.360
I have one app that I, a command line app that I use Pi Installer for that is fairly complex.

00:43:46.540 --> 00:43:50.580
It's got over 150 command line options and it's, it's a, it's a beast.

00:43:50.580 --> 00:43:53.020
So something like that would, would not work.

00:43:53.020 --> 00:43:55.680
I actually tried it because I was just curious to see how it would work.

00:43:55.680 --> 00:43:56.960
And it just, it kind of blew up.

00:43:56.960 --> 00:43:58.880
But it wasn't having it.

00:43:58.880 --> 00:43:59.660
What was it?

00:43:59.660 --> 00:43:59.860
Yeah.

00:43:59.860 --> 00:44:01.520
It's just like, nope, not going to do this.

00:44:01.520 --> 00:44:04.140
And I need a lot of tabs, a lot of tabs, please.

00:44:04.140 --> 00:44:06.100
For your simple command line apps.

00:44:06.180 --> 00:44:07.180
I think it's super useful.

00:44:07.180 --> 00:44:07.640
Yeah.

00:44:07.640 --> 00:44:07.900
Yeah.

00:44:07.900 --> 00:44:12.440
Because I'm sure a lot of listeners are like, it'd be fantastic to build a little Python

00:44:12.440 --> 00:44:17.940
app that just does a thing that my coworkers need instead of some manual process that they

00:44:17.940 --> 00:44:18.120
do.

00:44:18.120 --> 00:44:21.620
But then as soon as you start to say, and then you open up terminal and you start to type,

00:44:21.620 --> 00:44:22.740
they're just like, nope.

00:44:22.740 --> 00:44:23.200
Yeah.

00:44:23.200 --> 00:44:23.560
Yeah.

00:44:23.560 --> 00:44:24.060
Right.

00:44:24.060 --> 00:44:26.580
But if you give them a GUI, it's, it's pretty nice.

00:44:26.580 --> 00:44:30.200
So one thing I would love to see, and since we can't, I mean, we can't do an episode

00:44:30.200 --> 00:44:35.400
without mentioning Will McCoogan is a two-week version of GUI that would just automatically

00:44:35.400 --> 00:44:39.480
create a TUI interface for you from your command line app.

00:44:39.480 --> 00:44:39.760
Yeah.

00:44:39.760 --> 00:44:40.060
You're right.

00:44:40.060 --> 00:44:41.420
We can't do a show without mentioning him.

00:44:41.420 --> 00:44:45.740
The textual stuff, it's coming along.

00:44:45.740 --> 00:44:46.200
Yeah.

00:44:46.200 --> 00:44:48.980
It's, it's, I really impressive what he's been doing.

00:44:48.980 --> 00:44:53.580
So I have a couple of projects in mind that I, when I have to actually get sit down and

00:44:53.580 --> 00:44:57.480
have time to learn textual a little bit that I would love to play around with.

00:44:57.480 --> 00:44:57.880
Absolutely.

00:44:57.880 --> 00:45:03.580
I think it would be, you're right that that would be a pretty nice way to sort of a more

00:45:03.580 --> 00:45:06.080
more terminal native way of going.

00:45:06.080 --> 00:45:09.640
All right, we're going to build this, this UI for you, but here's how you get to it.

00:45:09.640 --> 00:45:13.360
But the one that you and I both use to create apps real.

00:45:13.360 --> 00:45:18.960
And the one that you've gone to create textinator with is rumps, which stands for ridiculously

00:45:18.960 --> 00:45:22.900
uncomplicated macOS Python status bar apps.

00:45:22.900 --> 00:45:23.740
Woo.

00:45:25.740 --> 00:45:28.780
That's probably the most complicated thing about rumps is it's name, right?

00:45:28.780 --> 00:45:30.280
Like it's amazing to use.

00:45:30.280 --> 00:45:31.420
Tell people about rumps.

00:45:31.420 --> 00:45:31.860
Yeah.

00:45:31.860 --> 00:45:32.640
It's super easy.

00:45:32.640 --> 00:45:37.720
Rumps does all the heavy lifting of creating the status bar app, which you can see if you're

00:45:37.720 --> 00:45:42.860
watching the video on the screen there, you get a, either your name of your app or an icon

00:45:42.860 --> 00:45:46.080
in this, in the status bar, and then you can click on it and you get some menus.

00:45:46.360 --> 00:45:51.000
And so, and you can do that in, I don't know, like 10 lines of code with rumps.

00:45:51.000 --> 00:45:52.500
It's just ridiculously easy.

00:45:52.500 --> 00:45:54.320
The name is accurate, right?

00:45:54.320 --> 00:45:56.140
It is ridiculously uncomplicated.

00:45:56.140 --> 00:45:56.500
Yeah.

00:45:56.500 --> 00:46:03.100
As you point out, so they've got a hello world type of thing that has four options on the menu

00:46:03.100 --> 00:46:07.100
or when you click on the thing in the menu bar, it drops down to four options.

00:46:07.100 --> 00:46:13.940
And one of the things that it does is also post a toast notification, you know, native

00:46:13.940 --> 00:46:15.800
macOS notification as well.

00:46:15.800 --> 00:46:19.340
And in order to make that happen, that really is like 10 lines of Python.

00:46:19.340 --> 00:46:20.020
Exactly.

00:46:20.020 --> 00:46:20.760
Yeah.

00:46:20.760 --> 00:46:21.680
It's super simple.

00:46:21.680 --> 00:46:26.880
And that, so that notification code isn't really hard to do, but you'd have to set it up.

00:46:26.880 --> 00:46:30.900
You'd have to set up a notification handler and, and register it.

00:46:30.900 --> 00:46:31.080
Yeah.

00:46:31.080 --> 00:46:33.900
You know, there's a, there's quite several steps to do that.

00:46:33.900 --> 00:46:34.940
And rumps does it.

00:46:34.940 --> 00:46:36.960
You just one line rumps dot notification.

00:46:36.960 --> 00:46:39.140
And boom, you can post a notification.

00:46:39.140 --> 00:46:42.240
So the basic idea is you create a class.

00:46:42.240 --> 00:46:45.100
It derives from rumps dot apps app.

00:46:45.100 --> 00:46:51.680
And then you use a bunch of decorators like clicked to say when a menu item is selected.

00:46:51.680 --> 00:46:53.660
And then there's a few other calls you can do.

00:46:53.660 --> 00:46:57.500
You can say, you know, rumps dot alert, and that pops out a notification.

00:46:57.500 --> 00:47:00.300
You can say rumps dot notification.

00:47:00.300 --> 00:47:01.300
I guess that doesn't pop it out.

00:47:01.300 --> 00:47:01.440
Sorry.

00:47:01.440 --> 00:47:02.800
That's like a modal dialogue.

00:47:02.800 --> 00:47:05.200
You can say rumps dot notification, and then it pops out the toast.

00:47:05.200 --> 00:47:06.660
And then you just say app dot run.

00:47:06.660 --> 00:47:08.800
And that's pretty much it, right?

00:47:08.800 --> 00:47:09.060
Yeah.

00:47:09.060 --> 00:47:15.540
That's, and you've got a full blown native Mac app written in Python that's up and running.

00:47:15.540 --> 00:47:15.940
Yep.

00:47:15.940 --> 00:47:21.800
The one missing element, which we've already given people the key to is this is still just

00:47:21.800 --> 00:47:23.020
a dot py file.

00:47:23.180 --> 00:47:29.000
And if you try to give this to your friends or distribute on the internet, it's going to

00:47:29.000 --> 00:47:30.180
go very badly, right?

00:47:30.180 --> 00:47:31.520
There's no app bundle.

00:47:31.520 --> 00:47:34.460
There's no dot app file package thing.

00:47:34.460 --> 00:47:36.840
There's no Python runtime, right?

00:47:36.840 --> 00:47:41.060
And so that's why you need py2app is you take your rumps app and then you py2app it.

00:47:41.060 --> 00:47:43.020
And then you have something to give out, right?

00:47:43.020 --> 00:47:43.480
Exactly.

00:47:43.600 --> 00:47:47.060
And once they get that, they don't even need Python on their machine because it comes

00:47:47.060 --> 00:47:48.520
bundled with that.

00:47:48.520 --> 00:47:50.400
And so it's, it makes, it's really nice.

00:47:50.400 --> 00:47:56.440
It actually, py2app will bundle up your Python and all the libraries packages you need in the

00:47:56.440 --> 00:47:58.780
app folder so that it's got its own copy.

00:47:58.780 --> 00:48:01.760
How do you make it auto start with system start?

00:48:01.760 --> 00:48:06.420
And I asked that as somebody who created a rumps app and it is clearly auto starting.

00:48:06.420 --> 00:48:08.040
I just don't remember how I did it.

00:48:08.040 --> 00:48:09.080
I know that you can.

00:48:09.080 --> 00:48:13.380
I don't know how to do it automatically, but if you go to your system settings and go to

00:48:13.380 --> 00:48:16.600
your login items, you can add it there, right?

00:48:16.600 --> 00:48:20.820
So it's a couple extra steps that you've got to do as a user and just say, you click, click

00:48:20.820 --> 00:48:27.200
the plus next to your login items and it'll pop up a file dialogue and you can pick the,

00:48:27.200 --> 00:48:29.920
your app to run and it'll run that when you log in.

00:48:29.920 --> 00:48:30.180
Yeah.

00:48:30.180 --> 00:48:32.540
I'm pretty sure that that's what I did to get it to auto start.

00:48:32.540 --> 00:48:36.360
I was just wondering if there might be some other clever way, but probably not.

00:48:36.360 --> 00:48:36.560
Right.

00:48:36.560 --> 00:48:36.780
Yeah.

00:48:36.780 --> 00:48:39.760
I've seen, I know it's possible for an app to do that.

00:48:39.760 --> 00:48:43.680
I've because I have apps that you run them and it says, Hey, do you want me to run that

00:48:43.680 --> 00:48:43.940
login?

00:48:43.940 --> 00:48:44.680
Yes.

00:48:44.680 --> 00:48:45.620
And it does it.

00:48:45.620 --> 00:48:49.640
And so I just have not explored doing that myself because that's not something I'm doing

00:48:49.640 --> 00:48:50.200
all the time.

00:48:50.200 --> 00:48:55.800
But there probably is a way whether you can do that from Python or the, if the permissionals

00:48:55.800 --> 00:48:57.040
will work, I'm not really sure of it.

00:48:57.040 --> 00:48:59.340
It's, it might be something worth looking into.

00:48:59.340 --> 00:48:59.960
Yeah.

00:48:59.960 --> 00:49:05.940
You know, this rumps project is super cool, but it, you know, it's, it feels like it's

00:49:05.940 --> 00:49:08.140
not quite super active these days.

00:49:08.140 --> 00:49:08.400
Right.

00:49:08.400 --> 00:49:08.840
Yeah.

00:49:08.840 --> 00:49:10.280
That is one downside.

00:49:10.280 --> 00:49:14.400
There's a fair amount of activity on the repo, but if you look at the issues, there's a number

00:49:14.400 --> 00:49:20.180
of issues that have been open a long time or PRs that haven't been merged in and it doesn't

00:49:20.180 --> 00:49:22.080
run on Python 3.10, for example.

00:49:22.080 --> 00:49:26.460
So you'll need to install Python 3.9 or earlier to use it.

00:49:26.460 --> 00:49:30.560
So there, you know, there's a, there are a few downsides to it, but it's a, you know,

00:49:30.560 --> 00:49:36.080
it's a great app and I hope that it continues to get a little bit of love because it really

00:49:36.080 --> 00:49:36.980
is super useful.

00:49:36.980 --> 00:49:38.040
It really is useful.

00:49:38.040 --> 00:49:38.980
You're, I totally agree.

00:49:38.980 --> 00:49:44.760
And I'm just thinking, you know, there could be like a startup set some preferences, right?

00:49:44.760 --> 00:49:47.700
Where you just say, I would like to request to run at startup.

00:49:47.960 --> 00:49:52.380
You know, if that's permission that already set, then just set it through whatever objective

00:49:52.380 --> 00:49:54.420
C OS level thing you've got to do.

00:49:54.420 --> 00:49:57.560
Like there's just little things like that, that would be really, really nice.

00:49:57.560 --> 00:50:03.300
You know, the menu bar, those dropdowns, like to be able to associate a hot key with them,

00:50:03.300 --> 00:50:06.820
or there's just a few things like, you know, dividers in menus, right?

00:50:06.820 --> 00:50:11.180
Like there's just these, they seem like such low hanging fruit that I think this could get

00:50:11.180 --> 00:50:12.080
a big upgrade.

00:50:12.080 --> 00:50:13.400
It's pretty popular.

00:50:13.400 --> 00:50:17.500
It has 2.8 thousand stars, but not a ton of traffic recently.

00:50:17.760 --> 00:50:23.600
And it'd be awesome if people, either Jared KS, who originally created, keeps going,

00:50:23.600 --> 00:50:27.580
or if they've lost interest, then, you know, someone else could pick it up and run with

00:50:27.580 --> 00:50:27.700
it.

00:50:27.700 --> 00:50:28.160
That'd be fun.

00:50:28.160 --> 00:50:29.140
Yeah, definitely.

00:50:29.140 --> 00:50:35.720
Oh, Richie on the audience says, creating the P list to execute your script is just having

00:50:35.720 --> 00:50:38.560
it in a tilde library launch agents.

00:50:38.560 --> 00:50:42.800
So maybe if you just copy a script to start your app over to there and it just launches.

00:50:42.800 --> 00:50:43.260
Okay.

00:50:43.260 --> 00:50:44.100
Yeah.

00:50:44.100 --> 00:50:44.620
I'll look at that.

00:50:44.720 --> 00:50:49.720
You'll have to request access to the library folder as well.

00:50:49.720 --> 00:50:51.180
So I'll have to look into that.

00:50:51.180 --> 00:50:55.860
But I've done that before the old, you know, the old fashioned way of actually creating a

00:50:55.860 --> 00:50:59.220
P list by hand and sticking it in there to get something to start.

00:50:59.220 --> 00:51:00.280
So thanks, Richie.

00:51:00.280 --> 00:51:01.120
I'll take a look at that.

00:51:01.120 --> 00:51:01.420
Yeah.

00:51:01.420 --> 00:51:02.560
That's excellent advice.

00:51:02.700 --> 00:51:04.120
This is in your user profile.

00:51:04.120 --> 00:51:06.260
So maybe you don't have to ask, but maybe you do.

00:51:06.260 --> 00:51:07.280
All right.

00:51:07.280 --> 00:51:11.560
So rumps is a very important building block here for Textinator.

00:51:11.560 --> 00:51:15.100
Pi to app, also super important.

00:51:15.100 --> 00:51:17.820
And the one that I created is called URLify.

00:51:17.820 --> 00:51:22.680
And it just does a bunch of stuff like, you know, creates URL slugs and like transforms text.

00:51:22.680 --> 00:51:24.180
That might end up doing it all the time.

00:51:24.180 --> 00:51:25.600
That's my little app.

00:51:25.660 --> 00:51:31.860
So the one we've got the ability to build the app with Pi to app, we've got a way to

00:51:31.860 --> 00:51:37.400
kind of create a shell that runs in the menu bar and constantly runs in the background, which

00:51:37.400 --> 00:51:39.860
is, you know, as long as nobody closes the app, right?

00:51:39.860 --> 00:51:40.740
It's just out of the way.

00:51:40.740 --> 00:51:42.160
So that's really fantastic.

00:51:42.160 --> 00:51:46.760
And now you need to start working with some of the OS level APIs.

00:51:46.760 --> 00:51:48.960
Like you talked about hooking the event for the screenshot.

00:51:49.260 --> 00:51:52.080
And those are Objective-C type things, right?

00:51:52.080 --> 00:51:52.780
Exactly.

00:51:52.780 --> 00:51:53.480
Yeah.

00:51:53.480 --> 00:52:00.860
And so for those, I use PyOBJC, PyObjective-C, which is a bridge between Python and Objective-C.

00:52:00.860 --> 00:52:03.960
And it's an amazing package.

00:52:03.960 --> 00:52:09.020
If you're a Python programmer and you use a Mac, you really ought to get familiar with PyOBJC,

00:52:09.020 --> 00:52:14.700
because what that does is exposes everything that Apple's built in, all the native capabilities

00:52:14.700 --> 00:52:17.220
like vision and machine learning.

00:52:17.720 --> 00:52:20.840
The most recent update to Textinator, I added QR code detection.

00:52:20.840 --> 00:52:25.140
So if you screenshot a QR code, it'll tell you what the URL is.

00:52:25.140 --> 00:52:25.780
Oh, wow.

00:52:25.780 --> 00:52:27.920
Decode the QR code?

00:52:27.920 --> 00:52:28.140
Yeah.

00:52:28.140 --> 00:52:30.460
Decode the QR code and stick it on your clipboard.

00:52:30.460 --> 00:52:31.080
Nice.

00:52:31.080 --> 00:52:37.520
But all that's built in to the Apple frameworks and PyOBJC gives you access to call those fairly

00:52:37.520 --> 00:52:37.960
easily.

00:52:37.960 --> 00:52:42.660
There are some quirks to it because Objective-C itself is kind of a quirky language.

00:52:42.660 --> 00:52:46.920
And so you've got to do the translation between Python and Objective-C.

00:52:47.360 --> 00:52:51.140
You'll see a lot of camel case that you don't normally see in Python.

00:52:51.140 --> 00:52:55.900
But once you figure those things out, it's super useful.

00:52:55.900 --> 00:52:56.160
Yeah.

00:52:56.160 --> 00:53:03.920
There's no attempt to Pythonify or make Objective-C like the low-level runtime supporting classes

00:53:03.920 --> 00:53:05.220
to make them Pythonic.

00:53:05.220 --> 00:53:05.700
Yeah.

00:53:05.700 --> 00:53:06.780
And actually, I'm glad.

00:53:06.780 --> 00:53:07.400
Yeah.

00:53:07.560 --> 00:53:10.840
Because when you have to go Google, what does this function do?

00:53:10.840 --> 00:53:13.240
Or what's this NS file handle?

00:53:13.240 --> 00:53:14.160
And how do I use it?

00:53:14.160 --> 00:53:16.240
It's a lot easier if you can just cut and paste.

00:53:16.240 --> 00:53:16.620
Yeah.

00:53:16.620 --> 00:53:17.000
For sure.

00:53:17.000 --> 00:53:18.600
One of the things I just...

00:53:18.600 --> 00:53:20.180
I'm looking at some of these examples.

00:53:20.180 --> 00:53:22.260
Maybe there's some hints in here.

00:53:22.380 --> 00:53:26.200
But one of the things that's super odd to me about Objective-C...

00:53:26.200 --> 00:53:27.460
It is one of those languages.

00:53:27.460 --> 00:53:28.820
I'm like, I'm going to try to learn this.

00:53:28.820 --> 00:53:30.000
And after a while, I'm like, no.

00:53:30.000 --> 00:53:32.700
No, it's just too weird.

00:53:33.520 --> 00:53:36.080
And how you have these named...

00:53:36.080 --> 00:53:41.020
These really oddly named methods and then variables, right?

00:53:41.020 --> 00:53:43.600
Like, you can't just say string lower.

00:53:43.600 --> 00:53:46.080
You've got to get like, you know...

00:53:46.080 --> 00:53:51.600
I forget what the API is called, but there's like a weird set of incantations to sort of invoke

00:53:51.600 --> 00:53:53.080
a lowercase type of behavior.

00:53:53.080 --> 00:53:55.400
So how does that...

00:53:55.400 --> 00:53:55.860
Is this...

00:53:55.860 --> 00:54:02.540
I see like in the example here, it looks like there's a string specifying maybe one of the

00:54:02.540 --> 00:54:07.140
arguments or something like file handle read completed colon is one of the strings last year.

00:54:07.140 --> 00:54:08.200
So the way...

00:54:08.200 --> 00:54:08.820
What's the deal with that?

00:54:08.820 --> 00:54:09.820
Yeah, this one...

00:54:09.820 --> 00:54:13.940
I'm not super familiar with this notification center API, but looking at what you have on

00:54:13.940 --> 00:54:19.140
the screen there, you've got the notification center dot add observer underscore selector

00:54:19.140 --> 00:54:23.580
underscore name underscore object underscore, which if you use my Objective-C, you're going

00:54:23.580 --> 00:54:26.840
to see a lot of underscores like that, including the trailing underscore.

00:54:26.840 --> 00:54:28.880
And what that does is the way Objective-C works.

00:54:28.880 --> 00:54:31.540
And let me caveat, I am not an Objective-C programmer.

00:54:31.560 --> 00:54:36.600
I tried a couple of times to learn and just did not grok it.

00:54:36.600 --> 00:54:42.320
But I've gotten pretty good at interpreting Objective-C through the Python lens so that I

00:54:42.320 --> 00:54:47.320
can use it in Python because I've got quite a few Python apps that use Objective-C calls.

00:54:47.320 --> 00:54:53.200
So Objective-C has this concept of selectors, which are basically sending messages to an object

00:54:53.200 --> 00:54:56.060
to call specific functions on that object.

00:54:56.060 --> 00:54:57.000
Oh, I see.

00:54:57.280 --> 00:55:00.500
And a method on an object could have different signatures.

00:55:00.500 --> 00:55:06.860
And so you might sometimes call it with a different value or a different number of parameters

00:55:06.860 --> 00:55:08.080
than you would a different time.

00:55:08.080 --> 00:55:10.380
And so the selectors handle doing that.

00:55:10.380 --> 00:55:15.200
And so what this is doing is each of those underscores is, think of it as every time you

00:55:15.200 --> 00:55:19.280
see an underscore in a PyObjective-C method call, you need to have a variable.

00:55:19.280 --> 00:55:25.120
You need to have an argument to that method call because it's passing that selector the value

00:55:25.120 --> 00:55:27.340
that you want for that particular selector.

00:55:27.340 --> 00:55:32.380
So that's why there's four underscores in this example you've got on the screen and four

00:55:32.380 --> 00:55:35.160
different values you're passing in, arguments you're passing in.

00:55:35.160 --> 00:55:39.480
So if I say add observer, underscore selector, underscore name, underscore object, I've got to

00:55:39.480 --> 00:55:43.540
give it a selector the name of the object and also itself.

00:55:43.540 --> 00:55:43.760
Yeah.

00:55:43.760 --> 00:55:48.900
And if you went and looked that up on the Apple Docs, instead of underscores, you'd see colon.

00:55:49.060 --> 00:55:52.640
So you'd see add observer, colon selector, colon name, colon object.

00:55:52.640 --> 00:55:55.520
And so that's the decoder ring there.

00:55:55.520 --> 00:56:00.020
If you're trying to translate to Python is replace the colons with underscores and then

00:56:00.020 --> 00:56:01.000
add a trailing underscore.

00:56:01.000 --> 00:56:06.040
And then you've got to figure out, okay, well, for that number of arguments, what are those

00:56:06.040 --> 00:56:07.640
arguments and what type do they need to be?

00:56:07.640 --> 00:56:09.900
Very interesting.

00:56:09.900 --> 00:56:10.720
Okay.

00:56:10.720 --> 00:56:15.180
So it's weird, but it's kind of weird in the sense of just it's mirroring Objective-C,

00:56:15.180 --> 00:56:17.600
which is itself a bit of a unique language.

00:56:18.300 --> 00:56:24.880
But that said, this is a cool library that gives you direct access to much of the operating

00:56:24.880 --> 00:56:25.880
system, right?

00:56:25.880 --> 00:56:26.700
Yeah, exactly.

00:56:26.700 --> 00:56:33.080
So Apple has built some really great frameworks like the Vision, right, that does the text

00:56:33.080 --> 00:56:35.420
detection for Textinator, which literally takes a few lines.

00:56:35.420 --> 00:56:39.160
Tell us quick about the Vision one here, because this is one of the core building blocks used,

00:56:39.160 --> 00:56:39.560
right?

00:56:39.560 --> 00:56:39.920
Exactly.

00:56:39.920 --> 00:56:40.140
Yeah.

00:56:40.140 --> 00:56:46.380
So Vision does a number of different computer vision type tasks for you and really only takes

00:56:46.380 --> 00:56:46.960
a few lines.

00:56:47.160 --> 00:56:51.080
It does text detection, find barcodes or QR codes.

00:56:51.080 --> 00:56:56.280
It'll find faces in an image, detect different objects in images, those kinds of things.

00:56:56.280 --> 00:56:58.980
And it does it really well.

00:56:58.980 --> 00:57:02.300
It runs on the neural engine, as you said at the beginning of the show.

00:57:02.300 --> 00:57:03.820
So it's super fast.

00:57:04.280 --> 00:57:07.380
And you get access to that all from Python.

00:57:07.380 --> 00:57:11.720
If you use Objective-C, you can directly access that power with just a handful of lines of

00:57:11.720 --> 00:57:11.960
code.

00:57:11.960 --> 00:57:14.760
And there are a number of Python packages for doing that.

00:57:14.760 --> 00:57:19.640
There's several different Python image-to-text packages out there, for example.

00:57:19.640 --> 00:57:24.600
But they're all fairly compute intensive, or you've got to download these big models or whatever.

00:57:24.760 --> 00:57:27.900
And if you're on a Mac, you can just use them what's already built into your Mac.

00:57:27.900 --> 00:57:28.720
Yeah, absolutely.

00:57:29.280 --> 00:57:35.380
And so, for example, in Textinator, you just say, vision.vn recognize text request.

00:57:35.380 --> 00:57:35.920
Yeah.

00:57:35.920 --> 00:57:37.520
And knit with completion handler.

00:57:37.520 --> 00:57:38.480
You give it the callback.

00:57:38.480 --> 00:57:38.740
Yeah.

00:57:38.740 --> 00:57:40.620
So one thing you'll notice, you skip past it.

00:57:40.620 --> 00:57:44.320
There was an alloc, and then it is something you've got to do in Objective-C.

00:57:44.320 --> 00:57:46.120
You've got to allocate your memory.

00:57:46.680 --> 00:57:50.880
And then there's a whole memory management piece of this you've got to keep in mind.

00:57:50.880 --> 00:57:54.160
For the most part, PyOBJC handles that for you.

00:57:54.160 --> 00:57:58.360
But every now and then, it can bite you if you have got a...

00:57:58.360 --> 00:58:01.480
You're allocating a bunch of things and never deallocating them.

00:58:01.480 --> 00:58:03.020
You can have a memory link.

00:58:03.020 --> 00:58:05.440
Back to memory management.

00:58:05.440 --> 00:58:07.860
Something you normally don't have to think about in Python.

00:58:07.860 --> 00:58:08.600
Yeah.

00:58:08.600 --> 00:58:10.160
And then maybe some of the other...

00:58:10.160 --> 00:58:14.000
You've got the natural language ML APIs as well, right?

00:58:14.000 --> 00:58:14.260
Yeah.

00:58:14.260 --> 00:58:16.260
There's natural language processing built in.

00:58:16.940 --> 00:58:21.600
All the things that the Mac does to do all those neat Mac things are, for the most part,

00:58:21.600 --> 00:58:24.640
available to the developer through these frameworks.

00:58:24.640 --> 00:58:25.340
Oh, excellent.

00:58:25.340 --> 00:58:26.200
All right.

00:58:26.200 --> 00:58:26.660
Very cool.

00:58:26.660 --> 00:58:30.420
Yeah, I guess that's probably it for the building blocks and stuff.

00:58:30.420 --> 00:58:33.600
Now that you've got this built, would you have done anything different?

00:58:33.600 --> 00:58:35.660
Or are you happy with the way it's come together?

00:58:35.660 --> 00:58:38.820
I think I was pretty happy with the way it came together.

00:58:38.820 --> 00:58:40.400
It works pretty good.

00:58:40.400 --> 00:58:46.240
The most current version will immediately ask for desktop access, like we were talking about.

00:58:46.240 --> 00:58:47.960
I didn't do that in the first version.

00:58:47.960 --> 00:58:51.540
I left it up to the user to go add that manually.

00:58:51.780 --> 00:58:57.920
When I realized, hey, the default location for screenshots is the desktop and 99% of people

00:58:57.920 --> 00:58:58.640
are going to have it there.

00:58:58.640 --> 00:59:02.840
Let's just ask right away for the desktop access and then it just works out of the box.

00:59:02.840 --> 00:59:05.480
But yeah, again, it was a fun little project.

00:59:05.480 --> 00:59:10.120
The initial version took a weekend and it's grown a little bit since then.

00:59:10.120 --> 00:59:12.940
But the whole thing is still only about 500 lines of code.

00:59:12.940 --> 00:59:16.540
And it's a fully functional, useful app that I use every day.

00:59:16.540 --> 00:59:19.560
And so it was really fun to be able to build that in Python.

00:59:19.560 --> 00:59:24.380
There's something very satisfying about having your app do the thing, you know, right?

00:59:24.380 --> 00:59:28.800
You can, of course, download, everyone downloads apps from the app store and various places.

00:59:28.800 --> 00:59:30.940
But to go, but that's my code, right?

00:59:30.940 --> 00:59:31.380
Yeah.

00:59:31.380 --> 00:59:34.420
My thing caught that text out of there or whatever it is, right?

00:59:34.420 --> 00:59:35.320
Yeah, exactly.

00:59:35.320 --> 00:59:35.880
Yeah.

00:59:36.020 --> 00:59:36.420
Excellent.

00:59:36.420 --> 00:59:42.140
I hope this is an inspiration as well as a roadmap for people that want to build interesting things

00:59:42.140 --> 00:59:47.400
for maybe for themselves, but also for end users in Python on the Mac, right?

00:59:47.400 --> 00:59:50.160
Rumps, if you want a menu bar app,

00:59:50.160 --> 00:59:54.100
PyObjective-C to get access to the framework and platform,

00:59:54.100 --> 00:59:57.940
PyToApp to build it in a way that you can hand it out.

00:59:57.940 --> 00:59:58.560
Very cool.

00:59:58.560 --> 00:59:58.960
Yeah.

00:59:58.960 --> 01:00:05.180
A few basic building blocks and you can put something pretty slick together, you know, fairly quickly.

01:00:05.180 --> 01:00:06.400
Yeah, I totally agree.

01:00:06.400 --> 01:00:12.000
The little one that I built with Rumps came together surprisingly well and it does way less

01:00:12.000 --> 01:00:13.380
than what yours does.

01:00:13.380 --> 01:00:14.480
I'm really impressed with you.

01:00:14.480 --> 01:00:14.640
Yeah.

01:00:14.640 --> 01:00:15.080
This is great.

01:00:15.080 --> 01:00:19.760
I've got another, yeah, another Rumps one that I use every day that all it does is tell

01:00:19.760 --> 01:00:24.520
me when to plug in or unplug my laptop, you know, just to try to keep the battery percentage

01:00:24.520 --> 01:00:30.720
and it has an icon of a plug and it changes color depending on whether I should plug in,

01:00:30.720 --> 01:00:35.020
it's charging or discharging and whether I should, you know, and then it pops up an alert

01:00:35.020 --> 01:00:36.500
to say, plug in your laptop now.

01:00:36.500 --> 01:00:39.260
It's super simple, but really useful.

01:00:39.260 --> 01:00:39.800
Absolutely.

01:00:39.800 --> 01:00:43.960
Keep it in the range, not always 100% charged or, but don't let it go dead.

01:00:43.960 --> 01:00:44.160
Yeah.

01:00:44.160 --> 01:00:44.960
Yeah.

01:00:44.960 --> 01:00:45.660
That's super simple.

01:00:45.660 --> 01:00:49.880
Those kinds of things are really easy to write with Rumps and the various tools we talked about.

01:00:49.880 --> 01:00:51.580
So definitely, definitely check that out.

01:00:51.580 --> 01:00:52.260
All right.

01:00:52.260 --> 01:00:56.980
Well, I think we are out of time, but super cool work on Textinator.

01:00:57.420 --> 01:01:00.760
And like I said, a great roadmap for people to follow if they want to build things like

01:01:00.760 --> 01:01:01.000
that.

01:01:01.000 --> 01:01:03.840
So, Rhett, before you get out of here, final two questions.

01:01:03.840 --> 01:01:08.780
If you're going to work on Textinator or other Python things, what editor are you using these

01:01:08.780 --> 01:01:09.000
days?

01:01:09.000 --> 01:01:10.260
I use VS Code.

01:01:10.260 --> 01:01:11.060
Right on.

01:01:11.240 --> 01:01:13.540
extensions, plugins that you're a fan of.

01:01:13.540 --> 01:01:14.360
Obviously, Python.

01:01:14.360 --> 01:01:17.640
If you don't hate yourself in one of these spaces on your own.

01:01:17.640 --> 01:01:18.060
Yeah.

01:01:18.060 --> 01:01:19.380
So I use a couple.

01:01:19.380 --> 01:01:22.380
I use GitHub Copilot, which I really enjoy.

01:01:22.380 --> 01:01:27.500
It is a solo hobbyist developer who only gets a few hours a week to write code.

01:01:27.500 --> 01:01:31.600
It's really, it's kind of like having a peer programmer and it's really helpful.

01:01:31.600 --> 01:01:32.700
Like that knowledgeable friend.

01:01:32.700 --> 01:01:35.460
You're like, how do you connect SQLAlchemy to a database again?

01:01:35.460 --> 01:01:36.280
Exactly.

01:01:36.280 --> 01:01:36.600
Yeah.

01:01:36.860 --> 01:01:38.180
It just does it for you.

01:01:38.180 --> 01:01:39.520
It's really useful.

01:01:39.520 --> 01:01:42.520
And the other one that I really like is Git Lens.

01:01:42.520 --> 01:01:43.200
Yeah.

01:01:43.200 --> 01:01:44.040
Git Lens is cool.

01:01:44.040 --> 01:01:44.840
Yeah.

01:01:44.840 --> 01:01:47.860
You can sort of, it'll put the Git history.

01:01:47.860 --> 01:01:48.400
Yeah.

01:01:48.400 --> 01:01:52.740
Right in line with the code and you can see what changed and when.

01:01:52.740 --> 01:01:57.660
And I was using that yesterday to try to debug a problem and find, when did I change this

01:01:57.660 --> 01:01:57.960
code?

01:01:57.960 --> 01:02:00.680
And yeah, it's really, that was really useful.

01:02:00.680 --> 01:02:00.920
Yeah.

01:02:00.920 --> 01:02:06.840
I was looking at the CPython source code with VS Code and I had Git Lens and stuff.

01:02:06.840 --> 01:02:13.340
And I was going through some section and I saw this change from like 1994 from Guido.

01:02:13.340 --> 01:02:15.700
And it had, why that line was changed.

01:02:15.700 --> 01:02:21.160
I'm like, okay, this is like a bit of archeology or some history going on here, you know?

01:02:21.160 --> 01:02:21.540
Yeah.

01:02:21.540 --> 01:02:22.040
That's neat.

01:02:22.040 --> 01:02:22.340
Yeah.

01:02:22.340 --> 01:02:22.760
Very neat.

01:02:22.760 --> 01:02:23.100
Okay.

01:02:23.100 --> 01:02:24.840
Well, definitely a good choice there.

01:02:24.840 --> 01:02:27.600
And then a notable PyPI package.

01:02:27.600 --> 01:02:30.900
I mean, Rumps is an obvious choice, but if you got something else that you want to throw

01:02:30.900 --> 01:02:31.580
out, go for it.

01:02:31.580 --> 01:02:31.820
Yeah.

01:02:31.820 --> 01:02:33.460
Well, let me, I've got two for you.

01:02:33.500 --> 01:02:39.000
One, if we talked about PyOVJC, if you're a Mac developer or you're a Python developer

01:02:39.000 --> 01:02:43.100
who uses a Mac, definitely take the time to learn that because it will give you, you know,

01:02:43.100 --> 01:02:44.760
give your Python superpowers.

01:02:44.760 --> 01:02:49.660
And then the other one, completely unrelated that I really like is called TextX.

01:02:49.660 --> 01:02:53.500
And it's basically a parser generator for Python.

01:02:53.500 --> 01:02:59.000
It's a peg, creates a parsing expression grammar for you out of its own little modeling language.

01:02:59.160 --> 01:03:03.100
But basically with, if you need to do, if you find that you're writing more than two

01:03:03.100 --> 01:03:09.000
regexes to parse some code, TextX might be able to help you out because it will build a

01:03:09.000 --> 01:03:14.860
parser for you out of its own sort of specification language that then gives you a Python class that

01:03:14.860 --> 01:03:15.980
parses your text for you.

01:03:15.980 --> 01:03:20.720
And I've used it in some other projects to actually create my own language to solve a specific,

01:03:20.720 --> 01:03:22.080
you know, domain problem.

01:03:22.080 --> 01:03:22.460
Yeah.

01:03:22.580 --> 01:03:24.020
And it's super useful.

01:03:24.020 --> 01:03:26.960
It's fairly easy to create a fairly powerful parser.

01:03:26.960 --> 01:03:30.800
When I hear, I don't have to write regular expressions, it already makes me happy.

01:03:30.800 --> 01:03:31.100
Yeah.

01:03:31.100 --> 01:03:32.280
It's great.

01:03:32.280 --> 01:03:38.320
Their description here is, in a nutshell, TextX will help you build a textual language in an

01:03:38.320 --> 01:03:38.800
easy way.

01:03:38.800 --> 01:03:42.120
You could invent your own language or build support for an already existing one.

01:03:42.120 --> 01:03:43.760
That sounds fantastic.

01:03:43.760 --> 01:03:44.000
Yeah.

01:03:44.000 --> 01:03:44.700
Great recommendation.

01:03:44.700 --> 01:03:45.320
All right.

01:03:45.320 --> 01:03:47.120
Well, final call to action.

01:03:47.380 --> 01:03:54.100
Before we go, people are interested maybe in building apps, Mac apps with Python in a broad

01:03:54.100 --> 01:03:54.360
sense.

01:03:54.360 --> 01:03:55.840
What advice you got for?

01:03:55.840 --> 01:04:01.120
I'd say learn PyOBJC, play with rumps and Py2 app and go.

01:04:01.120 --> 01:04:03.700
There's a lot of great projects already out there on GitHub.

01:04:03.700 --> 01:04:07.180
Go look at the textinator source code or search for some others.

01:04:07.180 --> 01:04:12.020
And I think for me, the easiest way to get started is always to look at what somebody else has done

01:04:12.020 --> 01:04:13.920
than reading through a whole bunch of docs.

01:04:14.020 --> 01:04:17.940
And so I'd say go find a project that piques your interest and go build something for your

01:04:17.940 --> 01:04:18.100
Mac.

01:04:18.100 --> 01:04:19.240
Yeah, absolutely.

01:04:19.240 --> 01:04:19.800
All right.

01:04:19.800 --> 01:04:24.560
Well, it's been great to get a look inside what you're doing with this app and how you

01:04:24.560 --> 01:04:28.060
built it for Mac and also a bit of a peek inside the Space Force.

01:04:28.060 --> 01:04:29.260
So thanks for being here.

01:04:29.260 --> 01:04:29.520
Great.

01:04:29.520 --> 01:04:29.760
Yeah.

01:04:29.760 --> 01:04:30.240
Thanks, Michael.

01:04:30.240 --> 01:04:30.920
It was a lot of fun.

01:04:30.920 --> 01:04:31.340
You bet.

01:04:31.340 --> 01:04:31.860
Bye.

01:04:31.860 --> 01:04:31.880
Bye.

01:04:31.880 --> 01:04:35.220
This has been another episode of Talk Python To Me.

01:04:35.220 --> 01:04:36.980
Thank you to our sponsors.

01:04:36.980 --> 01:04:38.640
Be sure to check out what they're offering.

01:04:38.640 --> 01:04:40.060
It really helps support the show.

01:04:40.060 --> 01:04:42.060
Take some stress out of your life.

01:04:42.400 --> 01:04:47.540
Get notified immediately about errors and performance issues in your web or mobile applications with

01:04:47.540 --> 01:04:47.840
Sentry.

01:04:47.840 --> 01:04:52.840
Just visit talkpython.fm/sentry and get started for free.

01:04:52.840 --> 01:04:56.440
And be sure to use the promo code talkpython, all one word.

01:04:56.440 --> 01:04:58.040
Want to level up your Python?

01:04:58.040 --> 01:05:02.100
We have one of the largest catalogs of Python video courses over at Talk Python.

01:05:02.100 --> 01:05:07.260
Our content ranges from true beginners to deeply advanced topics like memory and async.

01:05:07.260 --> 01:05:09.940
And best of all, there's not a subscription in sight.

01:05:10.320 --> 01:05:12.840
Check it out for yourself at training.talkpython.fm.

01:05:12.840 --> 01:05:14.740
Be sure to subscribe to the show.

01:05:14.740 --> 01:05:17.520
Open your favorite podcast app and search for Python.

01:05:17.520 --> 01:05:18.840
We should be right at the top.

01:05:18.840 --> 01:05:24.640
You can also find the iTunes feed at /itunes, the Google Play feed at /play, and the

01:05:24.640 --> 01:05:28.180
direct RSS feed at /rss on talkpython.fm.

01:05:28.180 --> 01:05:31.620
We're live streaming most of our recordings these days.

01:05:31.620 --> 01:05:35.580
If you want to be part of the show and have your comments featured on the air, be sure

01:05:35.580 --> 01:05:39.460
to subscribe to our YouTube channel at talkpython.fm/youtube.

01:05:39.460 --> 01:05:41.300
This is your host, Michael Kennedy.

01:05:41.300 --> 01:05:42.600
Thanks so much for listening.

01:05:42.600 --> 01:05:43.780
I really appreciate it.

01:05:43.780 --> 01:05:45.680
Now get out there and write some Python code.

01:05:45.680 --> 01:06:06.280
I'll see you next time.

01:06:06.280 --> 01:06:36.260
Thank you.

