Transcript #195: Runtime type checking for Python type hints
Return to episode page view on github00:00 Hello and welcome to Python Bytes, where we deliver Python news and headlines directly to your earbuds.
00:04 This is episode 195, recorded August 12th, 2020.
00:10 I'm Michael Kennedy.
00:11 And I am Brian Okken.
00:12 And this episode is brought to you by all the cool work that we're doing.
00:15 Tell you more about this.
00:16 Right now, I kind of just want to think about paying attention to stuff, Brian.
00:20 Maybe watching things.
00:22 Oh, like look closer.
00:24 Yes, watch carefully.
00:25 So actually this thing sent over by Preston Daniel, he sends us a bunch of good topics and links.
00:32 And this one's called Watchdog, and maybe you've heard of it.
00:34 It's the foundation of some web frameworks, for example, and things like that to know if, say, a file has changed that you're editing.
00:42 Does it need to auto restart the website?
00:44 So when you refresh it, it actually reruns it, things like that.
00:47 But Watchdog is a cool little library that you can use just on your own to know if something has changed.
00:55 So basically it's a simple little API.
00:57 You create this thing called an observer and you tell it to just.
01:01 Start watching in some way.
01:03 You give it a path.
01:04 It can recursively look, you can give it like a pattern or whatnot.
01:07 And it will say, just basically start firing events back on this observer.
01:12 Thing when stuff happens, files created, files, deleted, files, modified, and so on.
01:18 Nice.
01:18 This is cool.
01:19 Yeah.
01:19 Isn't that cool?
01:20 It also comes with a CLI script called watch me do.
01:25 And what you can do with watch me do is you just type watch me do, all one word, log.
01:32 And wherever your current working directory is when you type that, it will just start watching for files that change right there, just like on the command line.
01:40 - Oh, okay, cool.
01:41 - So if you're, you don't wanna write a program, but you just happen to be somewhere and you're like, what is happening?
01:46 Like what files are being modified or being touched or being changed here?
01:49 And you can just type that and boom, there it goes.
01:51 That's cool.
01:52 Right?
01:52 That actually is really cool.
01:54 Yeah.
01:54 We have a build process that part of it is mucking up some directories and it kind of cool to use something like this.
02:00 Yeah, absolutely.
02:02 And so you could just pip install watchdog and then just type watch me do space log wherever you want it to know.
02:08 There's a bunch of other things it does that you can pass commands and stuff.
02:13 I suppose like you could ask it to recursively watch or whatever, but yeah, I haven't had a use case for that.
02:19 So, but yeah, this is a cool recommendation from Preston.
02:22 - Yeah, nice.
02:22 - The example that I think I could possibly use it for is, you know, one of the things that we do that makes pythonbytes.fm, as well as all the Talk Python, Talk Python Training sites, ridiculously fast as a user, is we don't require you to re-download almost any of the static content.
02:42 Anything JavaScript, any image in the entire site, you know, CSS, all of those things, they're cached for a year, right?
02:50 And so a problem with caching stuff for a long time is if you redeploy a new version, something's wacky, right?
02:58 Something is really weird.
02:59 Like I have this problem with Twitter and Firefox.
03:01 I'll go there and periodically it'll just say something went wrong, can't load it.
03:05 And a command R, a hard refresh always fixes it because there's probably some JavaScript file that's like out of sequence, some other part of some API or some random thing like that.
03:15 And so what we do to make sure that never happens is we look at every one of those static files that we have a year long cache on, and we read it and we create the hash and we put question mark hash equals the hash of the file on the end of it.
03:29 So that, you know, that's a separate file.
03:32 If it ever changes, one character of it changes, the hash changes and it's now a totally separate thing in the web browser cache, right?
03:40 Although this is perfect.
03:41 The one drawback is to make things fast, you're not rehashing all the images on every request, is it just says, have I seen this file before?
03:50 Do I already compute the hash?
03:51 Just use it, right?
03:52 So that's fine, but that means that little hash refresh trick only really works if I restart the website.
03:59 If like say only a CSS file changed.
04:02 I could use this watchdog to watch all the static files that we're hashing.
04:06 If any of them change, just instantly recompute the hash.
04:09 That's it.
04:10 So that would allow me to do like push deploys of just static content changes without kicking the website at all.
04:18 And it would just magically like redetect those and start rolling.
04:21 I think I might start using Watchdog for that.
04:23 Anyway, that's my use case that makes me excited.
04:25 - And I'm glad you pronounced Watch-Me-Do because I looked at it and I went, Watch-Meadow, that's a weird name.
04:30 - That is a weird, I think it's Watch-Me-Do.
04:33 - Watch-Me-Do is better, yeah.
04:34 - Yeah, you know what else is weird?
04:37 Weird HTTP status codes.
04:40 Like I'm very familiar with 400 bad requests.
04:43 That's when somebody's created an API and I talked to it badly and they've created the API correctly.
04:49 Or I get a 500 where their stuff has crashed because I sent them something bad and they've written their API badly.
04:56 But somewhere in between there lives some odd thing.
05:00 Right?
05:01 - Yes. Have you heard of error code 418 before?
05:04 - I'm a teapot.
05:05 (laughing)
05:07 - This is just great.
05:08 - I love it, I love it.
05:10 - Status code 418, I'm a teapot.
05:13 Any attempt to brew a coffee with a teapot should result in an error code 418, I'm a teapot.
05:19 Resulting entity may be short and stout.
05:22 (laughing)
05:23 - So resulting entity may be short and stout, I love it.
05:26 - This actually got brought up in a conversation I was having this morning.
05:30 A colleague of mine, Andy Howe, suggested it.
05:33 He said, so when is Python 3.9 gonna come out?
05:36 Because hasn't it been a while?
05:38 And as a reminder to everybody, Python 3.9 release, or the release candidate one, or RCCR, or whatever, is out now.
05:46 So you can play with it.
05:47 It's probably, I've been using it, it's safe to use, but the schedule for the release, final release is in October, I believe.
05:58 And then after that, bug fixes releases every four months are planned, if there are any.
06:03 Andy said, you know, my favorite enhancement for Python 3.9 is this 4.0.1.8 ima-tpot.
06:11 This is new in Python 3.9.
06:13 HTTP library was missing the status code 4.0.1.8 ima-tpot and now it has it in there.
06:21 But wow, I've got some links for you because in the show notes, this is referenced from HTCPCP, which is Hypertext Coffee Pot Control Protocol.
06:33 And this came out in 1998 as an April Fool's joke.
06:38 And then it's kind of part of it now.
06:40 So it's part of HTTP, sort of.
06:43 Another fun error code.
06:45 So HT, the document, the entire document for HTCPCP says that most error codes share the same status codes as HTTP.
06:55 However, 4018 is separate, but also now HTTP can use that also.
07:00 but also 406 not acceptable.
07:03 So 406 is this response code may be returned if the operator of the coffee pot cannot comply with the accept addition request unless the request was in the head.
07:15 So, and it should return the list of possible coffee additions.
07:21 This is bizarre that this is in the world.
07:23 - This is awesome.
07:25 I really love it.
07:26 I don't realize, I didn't realize it had been taken so far as this.
07:29 This is great.
07:30 And if you go and look at the thing you linked to, the Hypertext Coffee Pot Control Protocol, HTC PCP/1.0, the whole memo describes so much of this thing.
07:41 And it even talks about things like crossing firewalls.
07:47 Like modern coffee pots do not use fire, however, a firewall is useful in protecting it.
07:52 It also addresses the security considerations.
07:55 Anyone who gets between me and my morning coffee should be insecure.
07:58 Unmoderated access to unprotected coffee pots from internet users might lead to several kinds of denial of coffee service attacks.
08:05 The improper use of filtration devices might admit Trojan grounds.
08:10 Filtration is not a good virus protection method.
08:12 I mean, it just goes on and on.
08:13 It's beautiful.
08:14 - Yes, and there's a security layer that I forgot about that's funny too, that's part of this, but I can't find it right now.
08:23 - Yeah, no worries.
08:24 I threw in a quick link also to this place called htpstatuses.com, which this is a slightly more serious take, although it does of course include the coffee pot.
08:36 But it's just, if you were like, what the heck should I be doing in this situation?
08:41 Like here's the error codes here, so on if you just click on them, it gives you a like a really nice summary.
08:46 Like if you click on 403, for example, it talks about when it's used and when it's like how it compares to 404.
08:52 And then also how that reference code is set up as an enumeration in different languages.
08:59 So for example, in Go, it's HTTP.status forbidden.
09:02 In Rails, it's colon forbidden.
09:04 And in Python 3, it's HTTP.httpstatus.forbidden and things like that.
09:08 So it tells you like the language version instead of typing it all in yourself and having magic strings and numbers everywhere.
09:16 - Yeah, so it does look like, I don't know what Symfony is, but both Symfony and Go do also support 418 status.
09:26 - Beautiful, we're on par with Go in terms of the internet now.
09:29 Very good, that was a good one.
09:31 I almost could have been the joke, but I like it.
09:33 All right, also things that I like is that we get a chance to create amazing stuff to help people get better with Python out there.
09:40 So over at Talk Python Training, we have three new courses coming very soon.
09:45 We have Getting Started with Data Science, we have Moving from Excel to Python, and we have Diving into Python Memory Management and Optimizing Your Programs around that.
09:55 All three of those are coming within probably a month or so.
09:57 So if you want to get notified about that, just visit training.talkpython.fm.
10:03 And right at the front, there's a little email place you can enter your email address to get notified.
10:08 How about you?
10:08 - I wanted to highlight Test Encode.
10:10 So pytest 6 is out.
10:13 And so Test Encode is the other podcast I do at testencode.com.
10:18 And I had talked last week that there was going to be a pytest 6 episode.
10:24 And instead of doing it just by myself, I contacted Anthony Sotilli and had him come on the show.
10:30 And so we BS about stuff for about an hour, some of its actual content.
10:35 So I'm really trying to get, I'd really like to have the shorter episodes on there, but you know, I get to talking with somebody and it's just fun, so.
10:43 - Just keeps going, awesome.
10:45 Yeah, it's definitely a good one.
10:46 People should check it out.
10:46 - What do we got next?
10:48 - We talked about Pydantic, right?
10:50 So Pydantic is a cool library.
10:52 you know, it's something I really just need to start using.
10:54 Like I haven't created any new projects that probably really deserve this, but I think it's definitely one of those things that I wanna start using 'cause it's just so slick.
11:04 The ability to say, I have these models, they have these types, you can validate them, you can auto convert between them and so on, right?
11:12 So we talked about that not too long ago actually.
11:14 However, Andy Shapiro sent over a heads up about a new feature that's in the beta version of PyDandic that's coming that makes me pretty excited, actually.
11:25 I don't know, we'll see how people feel about this, but I'm excited about it.
11:29 I'm excited because I'm a fan of type hints.
11:31 - Yeah, me too. - I love type hints.
11:32 Yeah, I love them mostly because the way they make the editor help me write code.
11:40 Instead of going and saying, oh, how do I do this thing with this library, right?
11:45 If I just say the type of this thing is one of the objects out of that library and I hit dot, boom.
11:51 the editor shows me all the stuff I can do.
11:53 It just keeps me in flow and working on what I need to.
11:55 So I love type hints mostly for that, but also for the validation.
12:00 One of the things they are like, but they do not do, is they are not like static typing in other programming languages where somehow that verifies what's being passed, right?
12:11 So like, for example, if I have a function, it says it takes an integer, an age colon int, and that's what it says it takes.
12:19 But then I go and I write code and I give it quote seven instead of the number seven, that's fine.
12:23 Python's like, yeah, that's cool.
12:25 You probably don't know what you're doing, but whatever.
12:27 We're just going to let it fly.
12:28 Right?
12:28 Well, with Pydantic, there's this new type annotation validator.
12:33 And so what you can do is you can say for this function, it has type annotations and I want those to mean something.
12:39 Okay.
12:40 So it gives it basically that it's not runtime behavior in the static languages, it's a compile error, right?
12:46 you argument string cannot be converted to enter whatever the compilation error happens to be, but it would give you the runtime equivalent.
12:53 So all you have to do is you have a regular Python function and it has regular types, just like it would, you know, S colon, stir count, colon, int, that type of thing.
13:03 And you can just work with it.
13:06 It's a little bit smarter than the compiler.
13:09 Maybe of say like C++ or C# though, in that, what do you do?
13:13 you just say at validate arguments you give a decorator that validates the arguments.
13:18 And not only does it validate but it will convert if it can. So for example, I'll put an example in our show notes, there's a function called repeat it takes a string and a number. And it's just going to echo out that string, however many times that number exists, right? So you can say repeat, quote, hello, comma three, and it'll print hello three times. Super simple. That passes the the validation precisely because it takes a string and an integer.
13:43 But you can also say repeat hello, comma quote for, and it'll still print out hello four times because it can take the string for and make it an integer.
13:52 For that's cool.
13:53 Right.
13:54 But if I say repeat, hello, comma, goodbye, boom, exception, validation error, you know, the value is not a valid integer type equals type error integer, whatever, like some message there.
14:07 Right.
14:08 So it says the count parameter is not and cannot be converted to the type specified by the type annotation.
14:14 Oh, yeah, this is cool.
14:15 How do you feel about this?
14:16 I obviously don't want runtime typing for all of Python code. It's not...
14:23 I don't want it, but for special places, like maybe around your API?
14:28 Exactly, the boundary of libraries, like stuff going in there.
14:32 Yeah.
14:32 You're like, please don't send me the wrong data.
14:35 Like, what is your other alternative, right?
14:37 if you really want to validate that, you have to do is instance of an int or try to cast it to an int and you've got to raise your own exception type and all that kind of stuff.
14:47 You're already going to have to do that work if you're writing a good API, or you're going to send out weird errors like int does not have, type int does not have function split or some weird thing.
15:00 And people have to realize like, oh, what that means is you type some kind of, you send a number where a string value is accepted, right?
15:07 So this, if you really do depend on the types that you specify in your type annotation, this seems like a good idea to me.
15:14 The one drawback is this cannot make your code faster.
15:17 (laughing)
15:18 Right?
15:19 If it's doing both validation and type conversion in front of your code, it can't be faster than just not doing that.
15:25 But you know.
15:26 - But it might make your development faster.
15:27 - It's not always about speed.
15:28 Exactly, and may make your sanity.
15:30 - Yeah, so one of the ways people have gotten around this in the past is instead of, is like for an example, doing instead of trying to validate parameters to a function at an API level, I have the parameters bundled into like an adders object and have adders validators written because adders does have.
15:52 - Right, right.
15:53 Put it into a validated object type and then pass that thing over.
15:56 Yeah, gotcha.
15:57 Which is kind of what this is, but without doing it.
16:00 - This is cool.
16:01 - Yeah. - I like it.
16:02 - Yeah, very cool.
16:03 I was leaning Pydantically previously.
16:06 This makes me lean more that way.
16:07 I like it.
16:08 All right.
16:09 So one of the things that's really cool is, you know, we've had a lot of conversation about is Python fast, is it slow?
16:14 Is it fast for coming up with programs that work?
16:18 Does it execute fast?
16:19 Should you use libraries like Cython or rewrite some stuff in Rust?
16:24 But Anthony Shah, he took it to another level, like further down than even something like rewrite bits in C or compile Python to C or something. Yeah, I think this is maybe an example of Anthony just having too much time in his hands.
16:39 I think maybe. So Anthony wrote a, I just tried it this morning, a project called PyMult, P-Y-M-U-L-T. You can pip install it and it just multiplies numbers and it only works for positive integers or negative integers. It just doesn't do, it doesn't ever display negative numbers.
17:00 Anyway, regardless of that, it's an extension for Python written in assembly.
17:06 Because why not?
17:08 And because of Anthony.
17:09 As in like MV and you know, add like the assembly language.
17:14 That's the foundation of basically every other programming language.
17:18 Yeah.
17:19 In Anthony's Twitter announcement, he says after a series of highly questionable life decisions, my Python extension written in pure assembly is now on PyPI.
17:28 It required an assembly extension for distutils.
17:32 He also wrote a GitHub action support.
17:34 So it's running CI/CD and testing with pytest.
17:38 Above and beyond, over the top.
17:40 But there is some coolness of it.
17:41 So it's a proof of concept to demonstrate how to create a Python extension in 100% assembly.
17:48 And then how to write a, you know, how to link those up, how to call a C API and create a Py object and parse PyTubal and stuff like that.
17:57 basically all the stuff you have to do to get parameters back and values out of an extension written in an assembly.
18:06 And yeah, it's interesting.
18:09 So I like it actually.
18:10 Yeah.
18:11 And anyone wants to know what the code looks like, it's like move racks, x, I'm all keyword of why move result to racks, move Eddie to result.
18:23 - Yeah, but then you get a call, PyLong from long, which is kind of a cool thing.
18:28 So there's this like interesting mix between the just pure assembly language and the C data types of CPython.
18:35 - On a serious note though, anybody wanting to learn a little bit of assembly or something, it's not like often you need some sort of environment to try it out and having some way to link Python and assembly is kind of neat actually.
18:49 So I'm grateful for that.
18:50 Well, you're right, some of these commands, I mean, I wrote some assembly in college.
18:55 That was a long time ago.
18:56 - Yeah, well, you had to tip to Anthony 'cause that's some impressive stuff.
19:00 Would you say it's easy though?
19:02 - No, that's hard mode.
19:04 - Yeah, you know what actually is not as easy, I think, as it should be.
19:07 And it's honestly just bizarre, are properties in Python.
19:11 As in at property, some function, return some value, converge what looks like a function call over to something that looks like field access, right?
19:19 But why does it have to have this like bizarre sequence of conventions where I have an app property and then a function that has a name and okay, that defines the name of the property, that makes sense.
19:32 But then the subsequent thing, if I wanna be able to set that, I have to say, you know, if the property was A, then I have to say decorator A.setter and also have a function that is called A.
19:44 Like the A.setter stuff is weird.
19:46 And then the order of the setter varies as well, right?
19:50 It has to be, or is constrained.
19:52 It has to be after the property A.
19:54 And what I've found also is that you can run into issues with inheritance.
19:59 I think if the property is defined in the base class, you try to create a setter on a drive class, things don't work right as well, which is all kinds of weird, right?
20:06 So there's just like, this is supposed to make life easy.
20:09 Why is this so complicated?
20:11 Like surely there was just another alternative that was like easier to do, to implement as well.
20:18 Anyway, it's always kind of been one of the bizarro things of Python.
20:22 That said, I love properties.
20:23 I love that you can do like lazy calculation with them.
20:27 You don't have to store them.
20:28 It's part of the memory.
20:30 If they can be recomputed from the values, there's a lot of good reasons to have them.
20:33 So Rude Vanderham sent over this cool project, which he created called EasyProperty.
20:40 And do you know what?
20:41 It's easier than regular properties.
20:43 I think that's why he named it that.
20:45 So the idea is, you know what you could do?
20:48 Instead of have this property of then at a.setter or a.deleter is you could just say, there's a at getter, at setter, at deleter on the thing.
21:01 And it doesn't matter what order they are or if they're all defined.
21:04 Like for example, you can't have a setter without a getter.
21:07 Now conceptually, you should never really do that.
21:09 But just, you know, syntactically, Like those things have to be defined there.
21:14 And like I said, the sort of base class, drive class type of thing, it gets wonky as well.
21:19 So with this, you just create a function.
21:22 The function name is the name of the property, and then you put @getter on it.
21:27 And that makes it the kind that is, you know, the getter property.
21:30 If you want to have one, you can set, you say @setter.
21:33 The order doesn't matter or anything like that.
21:36 - Nice. - Isn't that nice?
21:37 - Yeah.
21:38 There's a cool little example that he wrote.
21:42 You can do them separately, which is probably what I would recommend and do, but you can also have an @getter/setter.
21:48 So you can have one function whose job is to both be the getter and the setter operation of the property.
21:56 And then the way it works is just the value is set to be like some default value, which is what happens in the get version.
22:04 - You can have an @documenter.
22:06 - You can have an @documenter.
22:07 So you can also have documentation for your property.
22:12 And there's a little function that will come up and do that.
22:16 So anyway, it looks pretty nice to me.
22:19 It's not one of those things that's gonna be, you're taking a heavy dependency upon to give it a try.
22:24 It's not like a runtime thing.
22:26 For example, that validate arguments, that's in between every time you call a function all the time, all the time.
22:31 So this is the type of thing, if it's gonna work, it's gonna work clearly, or it's just not gonna work at all.
22:36 So give it a try and see if it makes you happy.
22:38 And if it sparks joy, you can have an at getter.
22:42 - And I like the syntax better than properties.
22:44 Just more sense.
22:45 - Yeah, and you know, I think at property is just fine.
22:48 Like, okay, that totally, but you know, at property name dot setter, like that just drives me, it's just so bizarre and weird in the ordering and the dependencies of it existing in the same class hierarchy level.
23:01 Yeah, don't get me started.
23:02 So this is cool.
23:03 I like it.
23:04 Thanks for developing that and sending that over, Rude.
23:07 - So we talked about assembly and properties and decorators and stuff.
23:11 So if anybody's left listening to this podcast, we do have more to talk about.
23:16 - Let's talk about recursion.
23:17 No, just kidding.
23:18 - I couldn't resist highlighting the last topic.
23:22 So there was an article by Ryan Howard that was on the testproject.io blog called "Non-blocking Assertion Failures with pytest-check." And I had to highlight it because it's really the first time anybody's ever written an article about something I wrote.
23:37 So that's neat.
23:38 So pytestCheck is a library that allows you to do multiple failures within a test, mostly because assert, normally you use asserts, but asserts stop after the first failure, and sometimes you want more.
23:52 And I never really thought about the different use cases, but so Orion has the use case of using it with Selenium, because sometimes when you're testing a page, coming back you might have to test multiple aspects of it.
24:04 What's the, his example was, what's the content of some field and also if the URL is correct or something. And I think with like a Selenium or some sort of web test that totally makes sense because you'd have things like you want to check the error code and content and whether somebody's name shows up then a whole bunch of stuff you could check and having multiple things. Right, you don't necessarily have just one assertion that captures all of the essence of what you're trying to determine, right?
24:35 Yeah. And so I also linked to the pytest-check Library and then, or plugin, and then also an article that I wrote back in way back in 2015 where I started thinking about this and thinking of how to solve it. So. Okay. Yeah. Yeah. Very cool. Nice article, right?
24:52 And then I also wanted to do a public service announcement that because both Ryan and Anthony got this wrong, there are no capital letters in pytest.
25:01 - Hold on, hold on, hold on, let me try this.
25:03 Let me just, yeah, okay, on this one, but if I go over to Word and I type pytest is a testing framework, I'll bet you that Word makes it capital.
25:12 - Yeah. - Doesn't that change, doesn't that make it real?
25:14 - (laughs) No.
25:16 - I know PowerPoint well.
25:17 - So. - No, yeah, that's always tricky, right, when you have sort of a formal name, but the formal name is, doesn't have spaces or it's lowercase, but I'm fine with it.
25:25 - We got used to it with the iPhone, So there's no capital, I mean, the P is capitalized in iPhone, but--
25:31 >> Yeah.
25:32 >> Will Word correct you, I wonder, if you try to type iPhone?
25:35 >> I would doubt it.
25:36 I mean, but that's Apple, and they think differently.
25:39 That's what they tell me.
25:40 >> [LAUGH] But there's others.
25:42 I mean, so there's some, I guess it's a weird thing with tools and stuff.
25:46 Some tools don't care whether you capitalize or not.
25:51 The people in pytest like it not capitalized.
25:54 So anyway.
25:54 - Yeah, let's respect their lowercase p's.
25:59 All right, so I have three really quick things.
26:02 They're all kind of fun.
26:03 So PyMC is a cool library for Bayesian analysis and probabilistic programming in Python.
26:12 So Alex, one of the core developers there, sent me a message, said, "Hey, we're planning the first ever PyMC on." I pronounce PyMC on because it's so on.
26:24 they say, and it's the first Bayesian community online conference around PyMC.
26:30 So if you're into probabilistic programming in Python, check it out.
26:34 There are a bunch of cool stuff they got going on.
26:37 They're also, I think, have a call for paper.
26:40 So if you wanna do a presentation, shoot them a note and yeah, I'll link to that in the show notes.
26:47 Second, a while ago, we talked about RUMS, quite a while ago.
26:51 this ridiculously uncomplicated Mac, I don't know, something, menu programs or something like that.
27:00 And so I ended up, I was sitting around, I have this little library that I run because so many things in my life require taking a title or taking some words and turning them into a file name or a URL name, all right?
27:15 So suppose I've got the title of a video and I want to name an MP4 file that, and it's got like a colon in it, or like a forward slash, or some random thing that shouldn't be in there.
27:27 I don't want spaces, I want...
27:29 So I wrote this little command line utility called urllify that I would just run, it would take whatever's in the clipboard and replace it with that canonicalized version that would work well as a file name.
27:39 Yeah, and that was cool, but then I got tired of like always firing up the command line, the terminal, maybe it's busy doing something else, so I got into a new window, run that thing, and then close it down.
27:49 And I was like, I just wanna click something once.
27:52 All right, I don't wanna click the terminal and then start a new terminal.
27:56 And it was like, not that much work, but you can imagine the way I'm going on and on about this, I must be doing it more than is reasonable.
28:03 So what I did is in like 45 minutes, I converted that command line app to a Mac.app file, like a full on.app file that I could just ship to any Mac user.
28:13 They have no idea it uses multiple Python packages, a code that I wrote, and it runs as a GUI, auto-starting with my macOS when I log in in my menu bar.
28:25 Is that cool?
28:25 And so I linked to a little tweet that has a screenshot just says convert text, but actually I changed it to URL-fi text, trim text, and now I can just click up there and any text that I have.
28:35 You know, if you copy something and you want to like paste an email, but maybe it's got a bunch of white space, you're like, why is this all here?
28:41 Right, instead of putting it in text editor and just getting the little bit out.
28:44 I just hit that and it'll trim it or like a good lowercase text.
28:47 So like have these like little cool like clipboard text transforms and it all took like 45 minutes to turn it into a Mac app with pie to app and rumps.
28:56 - Oh wow.
28:57 So are you sharing this somewhere?
28:58 - I will happily share it.
29:02 - Okay.
29:02 - But I don't have the GitHub repo public yet.
29:04 So let me just like put like a screenshot and what the heck it is and then I can make it public.
29:09 But yeah, so I'll put it in the show notes.
29:11 - Neat.
29:12 - Super cool, and finally, I just want to let people know who are taking courses over at Talk Python, I was sitting around and I decided, I want, as you hover over the scrubber, I want it to show a little thumbnail of where you would go in the video if you were to click the scrubber in that location.
29:30 - That's a scrubber.
29:31 - We're not, like the little time thing where you can click around and zoom ahead and whatever, we're not reusing Vimeo or YouTube or something that might potentially do that.
29:41 I think YouTube does it, I don't know that Vimeo does.
29:44 Anyway, I recently figured out how to do that.
29:48 So if people are interested, they can check it out.
29:52 But there's a cool little trick where you can take a second hidden video player and point it at either the same video or a smaller version of the video, and then as the mouse moves, just seek it to that time and show it above where the mouse is.
30:08 Isn't that crazy?
30:09 - Yeah, that's cool.
30:09 - Yeah, so if people need something like that, - Yeah, a little cool trick you might consider.
30:13 Not that often in Python, but if you happen to make your way to the JavaScript side of things, you'll do a lot of that, I guess.
30:18 - So is that in your training site?
30:20 Has it been updated then?
30:21 - Yeah, exactly.
30:22 It's not updated as we speak, because I'm still busy transcoding 200 hours of video to 500 by 200 size.
30:32 So I'm about halfway through that.
30:35 Yesterday, I uploaded 200 gigs of data.
30:39 I'm so going over my data limit this month.
30:42 (laughing)
30:44 That was 20% of my data limit in one day.
30:47 I got more to send.
30:48 So, but yeah, it'll be there as soon as the videos are done.
30:50 It'll turn it on.
30:51 - I'm gonna back up a little bit.
30:52 This IMC, probabilistic programming.
30:55 Can I use this to make a probability drive?
30:58 - A probability drive?
30:59 - Yeah.
31:00 - Is this from science fiction?
31:01 I don't know it.
31:02 - Like Hitchhiker's Guide to the Galaxy.
31:03 - Oh, yeah, I would think so.
31:05 I would begin by feeding it the number 42 and see what comes out.
31:09 - Maybe, yeah.
31:09 - That's probably the natural first step, yeah.
31:12 All right, speaking of jokes, we've got two.
31:14 You wanna go first?
31:15 - Yes, this was submitted by Reuven Lerner, inspired by Anthony Shaw.
31:20 I used to do low-level programming, then a product I bought told me no assembly required.
31:25 Since then, I've been coding in Python.
31:27 - Nice.
31:29 - Except for Anthony.
31:30 Anthony's coding in assembly.
31:31 - He codes in both, yeah.
31:33 His Python is assembly code or something like that.
31:37 So this one, like last week we talked about little Bobby Tables, who's beautiful, over at XKCD.
31:42 And I've got another XKCD for us.
31:44 But this one is not about databases.
31:46 No, it's about source control and Git.
31:48 All right, you want to be the woman developer that asks the question?
31:52 I'll start us off.
31:54 All right, so a couple developers speaking.
31:56 First one is talking about this new source control.
31:59 This is Git.
32:00 It tracks collaborative work on projects through a beautiful distributed graph theory tree model.
32:05 Cool, how do we use it?
32:06 - No idea, just memorize these shell commands and type them to sync up.
32:09 If you get errors, save your work elsewhere, delete the project and download a fresh copy.
32:13 (laughing)
32:16 It's funny because it happens too often, I think.
32:22 - Yeah, I think most people have like the four, five, or six git commands that they use all the time and everything else they have to look up if they need it.
32:29 - Yeah, like merge conflict, all right, I'm deleting it, I'm gonna copy this back over.
32:35 - Yep.
32:36 - All right.
32:37 Funny indeed.
32:38 Well, thanks for being here as always, and thanks everyone for listening.
32:41 - Thank you.
32:41 Bye. - Yep, bye.
32:42 Thank you for listening to Python Bytes.
32:44 Follow the show on Twitter via @pythonbytes.
32:46 That's Python Bytes as in B-Y-T-E-S.
32:49 And get the full show notes at pythonbytes.fm.
32:53 If you have a news item you want featured, just visit pythonbytes.fm and send it our way.
32:57 We're always on the lookout for sharing something cool.
33:00 On behalf of myself and Brian Okken, this is Michael Kennedy.
33:03 Thank you for listening and sharing this podcast with your friends and colleagues.