WEBVTT

00:00:00.001 --> 00:00:05.060
Hello and welcome to Python Bytes, where we deliver Python news and headlines directly to your earbuds.

00:00:05.060 --> 00:00:10.400
This is episode 194, recorded August 5th, 2020.

00:00:10.400 --> 00:00:11.880
I'm Brian Okken.

00:00:11.880 --> 00:00:12.860
And I'm Michael Kennedy.

00:00:12.860 --> 00:00:18.760
And this episode is brought to you by us, and we'll tell you more about what we're shilling later in the day.

00:00:18.760 --> 00:00:21.400
I want to talk to you about mutants.

00:00:21.400 --> 00:00:26.160
Mutants? Like mutant ninja turtle type things? Or what are we looking at here?

00:00:26.160 --> 00:00:27.860
Sure, mutant ninja turtles.

00:00:27.860 --> 00:00:29.740
No. So mutation testing.

00:00:29.740 --> 00:00:35.380
So I really kind of, I think, I'm warming to mutation testing, and it's kind of a neat thing.

00:00:35.380 --> 00:00:42.860
And I think we've covered it before, but this article is from Mosh Zadka, and it's called An Introduction to Mutation Testing in Python.

00:00:42.860 --> 00:00:48.180
There are a few, a handful of, I think there's like two or three different mutation testing libraries.

00:00:48.180 --> 00:00:52.440
MuttMutt is one of them, and that's what this article uses.

00:00:52.440 --> 00:00:57.020
And so if people are not familiar with mutation testing, here's the problem.

00:00:57.160 --> 00:01:04.760
So you can use code coverage tools like coverage.py to show how much of your code your tests are covering.

00:01:04.760 --> 00:01:09.560
But even if you get to 100% coverage, it doesn't mean that you're really testing everything.

00:01:09.560 --> 00:01:16.980
And so mutation testing, what it does is it takes your code under test, and it does some modifications.

00:01:16.980 --> 00:01:21.480
So it modifies portions of your source code to simulate potential bugs.

00:01:21.480 --> 00:01:30.800
Like, for example, it'll replace the greater than comparison with greater than equal, or replacing it with those sorts of edge cases and stuff are often where we muck up.

00:01:30.800 --> 00:01:35.100
If there's no boundary test around the boundary condition, you know, there'll be a problem.

00:01:35.360 --> 00:01:38.180
So every little change is considered a mutant.

00:01:38.180 --> 00:01:43.160
And it generates all these different mutants, and it does it in a fairly smart way.

00:01:43.160 --> 00:01:47.340
It can test your code fairly quickly with not too many mutants.

00:01:47.340 --> 00:01:50.420
So, and then it runs your test suite on the mutant.

00:01:50.420 --> 00:01:54.280
And the idea is your test suite should kill all of the mutants.

00:01:54.640 --> 00:02:01.760
So in this article, he shows an example of three methods and one test case and 100% code coverage.

00:02:01.760 --> 00:02:08.920
But he runs mutt-mutt, and 16 of them survive, and then talks about how to fix that.

00:02:08.920 --> 00:02:11.300
So it's a really good, quick article.

00:02:11.300 --> 00:02:12.480
Yeah, this is interesting.

00:02:12.480 --> 00:02:17.380
And I like the emoji legend use for the output.

00:02:17.380 --> 00:02:19.480
Yeah, it's a cute library.

00:02:19.480 --> 00:02:19.960
Yeah, it is.

00:02:19.960 --> 00:02:24.040
You know, one thing that I don't understand about mutation testing is, like, I understand, okay, well,

00:02:24.580 --> 00:02:30.360
we're going to change, like, a value of a variable, or, like, the way if we're doing a test,

00:02:30.360 --> 00:02:34.140
make it, it was less than, we're going to make it greater than, and see if your tests still pass,

00:02:34.140 --> 00:02:35.280
and, like, those kind of things.

00:02:35.280 --> 00:02:36.500
That totally seems reasonable.

00:02:36.500 --> 00:02:40.740
But if it goes in, I don't know if it does, maybe, you know, if it goes and, like, says,

00:02:40.740 --> 00:02:44.100
well, you're doing a print statement, so we changed part of the print string.

00:02:44.100 --> 00:02:46.580
Like, who's testing for that, right?

00:02:46.580 --> 00:02:48.880
Like, that seems like it would survive.

00:02:48.880 --> 00:02:50.160
Yeah, I'm not sure.

00:02:50.160 --> 00:02:54.520
So it seems like there's certain things, like, I would just never care to test for the output

00:02:54.520 --> 00:02:58.180
of the print statement where the static string changes.

00:02:58.180 --> 00:03:01.260
Like, to me, that just is not something I care to test, right?

00:03:01.260 --> 00:03:02.400
Yeah.

00:03:02.400 --> 00:03:07.180
But I feel like the sort of general case of mutation testing, you go, well, here's a piece of variable

00:03:07.180 --> 00:03:08.520
that I need to change around.

00:03:08.520 --> 00:03:11.240
Let's change a string and see if the tests still pass.

00:03:11.240 --> 00:03:14.880
So, I don't know, maybe it's just inappropriate for those types of scenarios.

00:03:14.880 --> 00:03:18.760
Maybe you only test stuff, like, at a lower level where you don't have, like, a bunch of print statements.

00:03:18.760 --> 00:03:20.860
But, you know, you've got logging and all kinds of things.

00:03:20.860 --> 00:03:21.520
So, I don't know.

00:03:21.520 --> 00:03:23.000
But still, I do like the idea.

00:03:23.000 --> 00:03:29.620
I think MutMut and some of the others have ways to specify which kinds of mutants to generate.

00:03:29.620 --> 00:03:30.460
I see.

00:03:30.460 --> 00:03:34.580
So, I don't know if it does the print statement sort of example, but I'm sure that there's ways

00:03:34.580 --> 00:03:37.220
to say, yeah, I don't really care about.

00:03:37.220 --> 00:03:39.680
Don't modify string values, for instance.

00:03:39.680 --> 00:03:40.040
Yeah.

00:03:40.040 --> 00:03:40.500
Yeah.

00:03:40.500 --> 00:03:40.540
Yeah.

00:03:41.120 --> 00:03:43.940
Like, don't modify constants or something, maybe.

00:03:43.940 --> 00:03:44.400
Who knows?

00:03:44.400 --> 00:03:44.720
Yeah.

00:03:44.720 --> 00:03:45.440
Yeah.

00:03:45.440 --> 00:03:45.680
Cool.

00:03:45.680 --> 00:03:46.560
All right.

00:03:46.560 --> 00:03:47.720
Well, you know, that looks really interesting.

00:03:47.720 --> 00:03:50.640
And Masha does a great job writing up these types of things.

00:03:50.640 --> 00:03:51.840
We feature him a lot.

00:03:51.840 --> 00:03:52.260
Very cool.

00:03:52.260 --> 00:03:56.800
Next up, I want to talk about asynchronous programming.

00:03:56.800 --> 00:03:57.440
Oh, nice.

00:03:57.440 --> 00:03:57.740
Yeah.

00:03:57.740 --> 00:03:59.640
So, maybe we've covered this before.

00:03:59.640 --> 00:04:03.860
No, we've covered this a lot, but I don't believe we've covered async Q.

00:04:03.860 --> 00:04:04.780
I don't think so.

00:04:04.780 --> 00:04:05.620
I don't think so either.

00:04:05.620 --> 00:04:10.220
So, this is from Quora, and it is not brand new.

00:04:10.520 --> 00:04:11.960
So, I just want to be really upfront.

00:04:11.960 --> 00:04:16.320
Like, this has been around since 2016, but it's pretty interesting.

00:04:16.320 --> 00:04:22.740
And the idea is, so much of what asynchronous programming, especially asyncio type of async

00:04:22.740 --> 00:04:26.940
and await programming is about, is scaling while you're waiting.

00:04:26.940 --> 00:04:28.640
Scaling the latencies, right?

00:04:28.640 --> 00:04:29.540
Okay.

00:04:29.540 --> 00:04:34.060
So, you know, like, I'm going to call the Stripe service, and it's going to take, you

00:04:34.060 --> 00:04:35.800
know, half a second to return.

00:04:35.800 --> 00:04:39.220
And so, I want my web server to be able to go and just do stuff.

00:04:39.920 --> 00:04:43.240
You know, other requests instead of waiting for half a second while we're checking out

00:04:43.240 --> 00:04:44.560
some person or whatever, right?

00:04:44.560 --> 00:04:46.720
But they've got a different use case.

00:04:46.720 --> 00:04:50.520
What they're doing is they're running, I don't know if I said this is from Quora.

00:04:50.520 --> 00:04:54.420
They're running Quora.com, which is a really cool Q&A site.

00:04:54.420 --> 00:04:59.380
I actually think Quora does a great job of having, like, solid, thoughtful answers.

00:04:59.380 --> 00:05:02.280
Not always right, but thoughtful at least, which is pretty cool.

00:05:02.600 --> 00:05:07.140
But what they do is they don't talk directly to their database because that would be too

00:05:07.140 --> 00:05:07.520
slow.

00:05:07.520 --> 00:05:09.300
Don't give me started on that.

00:05:09.960 --> 00:05:15.400
But what they're doing is they're talking to Memcached or Redis or whatever, but they're

00:05:15.400 --> 00:05:22.920
using Memcached to store a bunch of pre-computed query results so they don't have to keep going

00:05:22.920 --> 00:05:23.600
back to the database.

00:05:23.600 --> 00:05:29.240
Like, for example, when you go view a question, they want to see the names of the people who

00:05:29.240 --> 00:05:30.940
upvoted the question, right?

00:05:31.420 --> 00:05:33.580
So it's kind of a complicated query, right?

00:05:33.580 --> 00:05:35.380
I need to go, here's the IDs.

00:05:35.380 --> 00:05:37.660
We store the IDs of the upvoter.

00:05:37.660 --> 00:05:42.580
Then we're going to do a query, a join over on the user table and get their names back.

00:05:42.580 --> 00:05:43.420
And then we're going to show it.

00:05:43.420 --> 00:05:45.440
Like, that sounds expensive for lots of data.

00:05:45.440 --> 00:05:48.260
So what they do is they basically store those answers.

00:05:48.260 --> 00:05:52.000
Like, this user goes to this thing in Memcached.

00:05:52.000 --> 00:05:56.660
But a lot of the latency around this has to do actually with the network call.

00:05:56.660 --> 00:05:58.040
Like, it's pretty close.

00:05:58.180 --> 00:06:01.980
It's like, you know, one millisecond or something, but they've got to go get those

00:06:01.980 --> 00:06:03.460
names over and over, right?

00:06:03.460 --> 00:06:07.700
Because the way that you store stuff in Memcached is this ID has this name.

00:06:07.700 --> 00:06:09.480
You've got 50 upvoters.

00:06:09.480 --> 00:06:11.660
It's like, give me the name of this person, give me the name of that person.

00:06:11.660 --> 00:06:15.760
So there's a way to do like a, you know, a batch get like, here's all these IDs.

00:06:15.760 --> 00:06:17.540
Go get me all the associated names.

00:06:17.540 --> 00:06:22.920
And they've got like this dependency tree of these sorts of questions they have to answer.

00:06:22.920 --> 00:06:25.600
So what they've done is they've come up with this thing called AsyncQ.

00:06:25.720 --> 00:06:30.880
And it's all about batching asynchronous requests and converting them from a bunch of individual

00:06:30.880 --> 00:06:32.940
calls into like one massive call.

00:06:32.940 --> 00:06:33.660
Oh, okay.

00:06:33.660 --> 00:06:37.140
So they can like do what looks like asynchronous programming.

00:06:37.140 --> 00:06:38.680
Say, go get me all these things.

00:06:38.680 --> 00:06:43.400
And instead of doing a bunch of individual async and await type calls, the system looks that

00:06:43.400 --> 00:06:48.280
goes, okay, what that means is turn that into one giant query where it's like all of these

00:06:48.280 --> 00:06:51.120
IDs go to all those things and then return them back.

00:06:51.120 --> 00:06:51.620
Oh, that's right.

00:06:51.700 --> 00:06:51.820
Yeah.

00:06:51.820 --> 00:06:52.240
It's pretty neat.

00:06:52.240 --> 00:06:57.380
So it's basically this way to write code that will take a, what would be a bunch of small

00:06:57.380 --> 00:07:03.500
independent requests and turn it into like a one shot request for talking to things like

00:07:03.500 --> 00:07:04.860
caching servers and whatnot.

00:07:04.860 --> 00:07:05.260
Yeah.

00:07:05.260 --> 00:07:05.640
Yeah.

00:07:05.640 --> 00:07:11.480
So apparently this is like a core component of core's architecture and yeah, it's all about

00:07:11.480 --> 00:07:13.000
batching up these calls.

00:07:13.000 --> 00:07:15.540
I didn't know core was Python on the backend.

00:07:15.540 --> 00:07:16.140
Oh yeah.

00:07:16.140 --> 00:07:16.580
Yeah.

00:07:16.580 --> 00:07:20.680
They've got a really interesting Python blog where they, like an engineering blog where they

00:07:20.680 --> 00:07:21.780
talk about all sorts of stuff.

00:07:21.780 --> 00:07:26.880
So this was like written up on their engineering blog about sort of how they went from what they

00:07:26.880 --> 00:07:30.700
were doing before this, which was, you'd have to like write several functions that would

00:07:30.700 --> 00:07:31.760
prepare the things.

00:07:31.760 --> 00:07:35.180
And then you could ask for it because they would be cached locally and all sorts of funky

00:07:35.180 --> 00:07:35.460
stuff.

00:07:35.620 --> 00:07:39.240
So there's a great writeup on sort of the whole use case of this.

00:07:39.240 --> 00:07:41.340
So this, like I said, is from 2016.

00:07:41.340 --> 00:07:43.560
So it predates async and await.

00:07:43.560 --> 00:07:50.840
And so they use the yield keyword, which is a sort of a more foundational way to get, to

00:07:50.840 --> 00:07:52.960
break up functions into parts that run.

00:07:52.960 --> 00:07:57.500
So basically you decorate a function and then you yield out the various steps.

00:07:57.500 --> 00:08:01.600
And then before it executes all those, it looks at them, figures out what it has to do.

00:08:01.600 --> 00:08:04.000
And then it like batches it up and then does it all at once.

00:08:04.000 --> 00:08:04.640
Pretty wild.

00:08:04.640 --> 00:08:05.080
Yeah.

00:08:05.080 --> 00:08:05.840
Yeah.

00:08:05.840 --> 00:08:08.620
So I thought this was, you know, kind of interesting.

00:08:08.620 --> 00:08:12.520
I think it's a little bit just looking at the patterns here.

00:08:12.520 --> 00:08:19.220
I feel like it's a little tiny bit limited because it's targeted at, I believe they're still at

00:08:19.220 --> 00:08:20.240
least then, right?

00:08:20.240 --> 00:08:25.280
So when they came up with it and it's still active, I think they built it for running a

00:08:25.280 --> 00:08:26.340
Python two, right?

00:08:26.340 --> 00:08:28.860
Remember this 2016, they'd been running for a while.

00:08:28.860 --> 00:08:34.300
So some of their APIs, I don't think like, for example, they don't use the async and await

00:08:34.300 --> 00:08:35.300
keyword.

00:08:35.300 --> 00:08:38.140
I think because that didn't exist.

00:08:38.140 --> 00:08:43.300
Like they supported Python three, four where asyncio was, but async and await didn't come

00:08:43.300 --> 00:08:44.820
along until just a tiny bit later.

00:08:44.820 --> 00:08:45.440
I don't think so.

00:08:45.440 --> 00:08:50.580
Anyway, a bit of a grain of salt, but I think, you know, this will be a pretty interesting thing

00:08:50.580 --> 00:08:53.740
that people can adopt and use for these types of scenarios.

00:08:53.740 --> 00:08:57.140
Certainly if it powers Quora, it's probably pretty good.

00:08:57.140 --> 00:08:57.480
Yeah.

00:08:57.480 --> 00:08:58.240
Neat.

00:08:58.240 --> 00:08:58.800
Cool.

00:08:58.800 --> 00:08:59.360
Absolutely.

00:08:59.360 --> 00:08:59.720
Yep.

00:08:59.720 --> 00:09:02.500
Another thing that's cool is Talk Python Training.

00:09:02.500 --> 00:09:03.020
Thank you.

00:09:03.020 --> 00:09:04.540
We got a lot of stuff going on over there.

00:09:04.540 --> 00:09:06.240
Actually, we've got a ton of new courses coming.

00:09:06.740 --> 00:09:11.300
course for people who generally live in Excel and should be adopting the Python data science

00:09:11.300 --> 00:09:11.680
tools.

00:09:11.680 --> 00:09:13.680
So that's coming really soon.

00:09:13.680 --> 00:09:15.160
We've got a getting started with data science.

00:09:15.160 --> 00:09:18.460
I just actually, last time we spoke, I said, hey, I'm writing this course.

00:09:18.460 --> 00:09:21.340
I started writing a course called Python Memory Management.

00:09:21.900 --> 00:09:22.760
I finished it.

00:09:22.760 --> 00:09:23.500
I recorded it.

00:09:23.500 --> 00:09:24.420
It's like a five hour course.

00:09:24.420 --> 00:09:24.920
It's going to be awesome.

00:09:24.920 --> 00:09:28.420
So now I'm on to writing a new course, Python design patterns.

00:09:28.420 --> 00:09:29.080
Oh, nice.

00:09:29.080 --> 00:09:29.360
Yeah.

00:09:29.360 --> 00:09:31.040
So that'll be out in a few weeks as well.

00:09:31.040 --> 00:09:32.100
How about you?

00:09:32.100 --> 00:09:32.460
Yeah.

00:09:32.460 --> 00:09:35.040
I just wanted to highlight again.

00:09:35.040 --> 00:09:42.420
I have the URL pytestbook.com set up to go directly to Arata because I get a lot of people

00:09:42.420 --> 00:09:48.780
asking, hey, your pytest book, is it still good for, it was like in 2017, still valid?

00:09:48.780 --> 00:09:50.160
Yes, it's still valid.

00:09:50.160 --> 00:09:53.720
But there's a few gotchas and I list them out.

00:09:53.720 --> 00:09:56.420
Very easy to read at pytestbook.com.

00:09:56.420 --> 00:09:59.180
It directs you to an Arata page to show you.

00:09:59.180 --> 00:10:02.820
It's just a couple tweaks to the source code you got to make.

00:10:02.820 --> 00:10:05.440
You got to pin TinyDB and a couple other things.

00:10:05.440 --> 00:10:11.660
And we'll try to get those changes out to the download link on Pragmatic as soon as possible.

00:10:11.660 --> 00:10:13.060
That's still in the works.

00:10:13.060 --> 00:10:18.420
But there's also a link to, if you have any issues, there's a link to the official Pragmatic

00:10:18.420 --> 00:10:21.000
Arata page where you can ask questions.

00:10:21.000 --> 00:10:24.120
And if you haven't run into anything, I'd love to hear about it.

00:10:24.120 --> 00:10:26.240
And I'm excited to get a lot.

00:10:26.240 --> 00:10:31.420
Lately, a lot of the people that have been contacting me said they're excited about reading the book

00:10:31.420 --> 00:10:32.540
are machine learning people.

00:10:32.820 --> 00:10:38.240
So it's kind of neat to see data science and machine learning people add testing to their workflows.

00:10:38.240 --> 00:10:39.020
That's exciting.

00:10:39.020 --> 00:10:39.520
Absolutely.

00:10:39.520 --> 00:10:42.020
So I have a final call to action for people out there.

00:10:42.020 --> 00:10:48.200
If you want to make sure that we have the time and energy to keep creating stuff like this podcast and the other things we're doing,

00:10:48.460 --> 00:10:52.600
you don't necessarily have to get our stuff, but how about recommending it, right?

00:10:52.600 --> 00:10:59.960
If your company needs to get up to speed on Python, recommend that your company buy the courses for that team.

00:10:59.960 --> 00:11:06.700
Or if a company is doing a bunch of testing, have everyone on that team or the engineering group get Brian's book.

00:11:06.700 --> 00:11:07.660
That would be great.

00:11:07.660 --> 00:11:12.880
Yeah, and then individually to remind people that we do have a Patreon campaign going.

00:11:12.880 --> 00:11:16.480
So people can contribute a buck or two a month.

00:11:16.480 --> 00:11:17.120
That would be great.

00:11:17.120 --> 00:11:17.440
Yeah.

00:11:17.440 --> 00:11:19.460
Now that we go anywhere, we don't buy coffee.

00:11:19.460 --> 00:11:20.580
Yeah.

00:11:20.580 --> 00:11:21.980
Next, I want to talk.

00:11:21.980 --> 00:11:24.140
This sort of ties into your async thing.

00:11:24.140 --> 00:11:25.400
Yeah, yeah, for sure.

00:11:25.400 --> 00:11:26.560
That's interesting.

00:11:26.560 --> 00:11:28.340
But they use Memcached.

00:11:28.340 --> 00:11:30.780
But I wanted to talk about Redis.

00:11:30.780 --> 00:11:37.420
So I've not used Redis myself, but I know that a lot of people do for caching and for other things.

00:11:38.240 --> 00:11:39.760
And so this is an article.

00:11:39.760 --> 00:11:48.340
It's actually on the Redis site, but it's an article called Redis Beyond the Cache in Python by Guy Royce, I think.

00:11:48.340 --> 00:11:53.600
I knew that Redis did more than just a cache for a backend database.

00:11:53.600 --> 00:11:55.160
But this is kind of neat.

00:11:55.160 --> 00:12:00.980
So these are good, clear examples of Python code using Redis for more than just caching.

00:12:00.980 --> 00:12:04.680
So the first example talks about how to use it as a queue.

00:12:04.840 --> 00:12:07.780
So you can set it up as a fast queuing system.

00:12:07.780 --> 00:12:12.400
And apparently there's a couple calls called rpush and blpop.

00:12:12.400 --> 00:12:16.440
And actually, to tell you the truth, I picked this article because of blpop.

00:12:16.440 --> 00:12:19.060
I think that's one of the best function names ever.

00:12:19.060 --> 00:12:22.300
I don't know what it means, but maybe back of the list pop?

00:12:22.300 --> 00:12:23.280
Not sure.

00:12:23.280 --> 00:12:24.360
But it's good.

00:12:24.360 --> 00:12:31.840
I thought you picked it because of the various, from the code example about putting stuff into queues here.

00:12:31.840 --> 00:12:34.020
That felt close to home.

00:12:34.020 --> 00:12:34.420
Did it?

00:12:34.420 --> 00:12:34.760
Yeah.

00:12:34.760 --> 00:12:36.160
It's about Bigfoot sightings.

00:12:36.160 --> 00:12:42.180
We've got a sighting near the Columbia River and people were chased by a tall, hairy creature and so on.

00:12:42.180 --> 00:12:42.220
Oh, yeah.

00:12:42.220 --> 00:12:46.900
So like asynchronously adding Bigfoot sightings from the general Pacific Northwest.

00:12:46.900 --> 00:12:48.800
Yeah, that's good.

00:12:48.800 --> 00:12:50.760
Sorry, carry on.

00:12:50.760 --> 00:12:51.660
Didn't mean to derail you.

00:12:51.660 --> 00:12:52.380
No, no.

00:12:52.380 --> 00:12:52.840
It's good.

00:12:53.040 --> 00:12:56.740
So using it as a queue, using it in a PubSub model.

00:12:56.740 --> 00:13:00.440
Apparently there's functions like publish and psubscribe.

00:13:00.440 --> 00:13:03.020
So you can do publish and subscribe models.

00:13:03.020 --> 00:13:06.440
Data streaming, using it as a search engine.

00:13:06.440 --> 00:13:12.940
The search engine seems like a little more hardcore because it looks like they're, it's almost like SQL queries that you're using.

00:13:12.940 --> 00:13:14.220
But apparently you can do that.

00:13:14.220 --> 00:13:21.500
And of course, you can also use it as your primary in-memory database if you want to, as long as you don't need to store it somewhere.

00:13:21.920 --> 00:13:24.860
So, or use some later thing.

00:13:24.860 --> 00:13:26.700
You know, I guess I'm just winging it here.

00:13:26.700 --> 00:13:31.200
I don't know how you get, how you hook up a Redis database to a normal database.

00:13:31.200 --> 00:13:34.440
But I know you database people know how to do that.

00:13:34.440 --> 00:13:42.780
But I probably use, I like the idea of using it as a queue system for like multi-threads and multi-processes.

00:13:42.780 --> 00:13:43.660
That sounds kind of fun.

00:13:43.660 --> 00:13:47.960
This is a really cool article because I just often think of Redis as cache, right?

00:13:47.960 --> 00:13:49.920
But yeah, there's a bunch of neat stuff here.

00:13:49.920 --> 00:13:51.060
And yeah.

00:13:51.060 --> 00:13:55.060
So often you think like, oh, I'll just write this cool data structure.

00:13:55.060 --> 00:13:55.980
We'll just do this thing.

00:13:55.980 --> 00:13:56.660
And it's great.

00:13:56.660 --> 00:13:59.140
And you're like, oh, wait, but hold on.

00:13:59.140 --> 00:14:03.820
When I deploy that to the web server, it forks off like 10 copies of micro whiskey.

00:14:04.120 --> 00:14:08.840
And so I'm going to have like 10 separate DB copies and all this, like, there's just certain

00:14:08.840 --> 00:14:10.960
times you're like, I just need a thing to hold this stuff.

00:14:10.960 --> 00:14:12.480
And like Redis seems pretty cool for that.

00:14:12.480 --> 00:14:12.680
Yeah.

00:14:12.680 --> 00:14:17.260
And the examples use, apparently there's a bunch of different Python libraries to access Redis.

00:14:17.260 --> 00:14:23.680
And this one uses AIO Redis because there's async and await calls to access everything.

00:14:23.680 --> 00:14:23.980
Yeah.

00:14:23.980 --> 00:14:24.360
It's beautiful.

00:14:24.360 --> 00:14:26.860
It's a real nice example of async and await as well there.

00:14:26.860 --> 00:14:27.140
Yeah.

00:14:27.200 --> 00:14:30.700
So I'm sure, Brian, you've heard of little Bobby tables.

00:14:30.700 --> 00:14:33.380
Yeah, of course.

00:14:33.380 --> 00:14:34.380
I don't know.

00:14:34.380 --> 00:14:35.540
I think we've brought it up on the show.

00:14:35.540 --> 00:14:35.920
Yeah.

00:14:35.920 --> 00:14:38.860
I don't know if we've actually, have we featured it as a proper joke?

00:14:38.860 --> 00:14:40.100
I don't know what we have.

00:14:40.100 --> 00:14:42.740
Nonetheless, this one is no joke.

00:14:42.740 --> 00:14:44.400
This is just little table.

00:14:44.400 --> 00:14:45.780
I don't know what I was thinking.

00:14:45.780 --> 00:14:46.900
I know what I was thinking.

00:14:46.900 --> 00:14:47.560
I was curious.

00:14:47.720 --> 00:14:53.520
I didn't want to commit as much effort as it turned out to be into having like a broad discussion about this.

00:14:53.520 --> 00:14:55.920
But I thought, okay, well, we have dictionaries.

00:14:55.920 --> 00:15:01.960
And so I can go and find a single key, pass in a certain key, and then get the thing back or not.

00:15:01.960 --> 00:15:02.180
Right?

00:15:02.180 --> 00:15:07.080
So if I've got like, I don't know, users, I could have the user ID.

00:15:07.080 --> 00:15:11.980
And then the user object comes back if I index the dictionary like that.

00:15:11.980 --> 00:15:12.740
Totally simple, right?

00:15:12.740 --> 00:15:13.040
Yeah.

00:15:13.040 --> 00:15:16.540
What if we wanted to ask that question two ways on the same data structure?

00:15:16.700 --> 00:15:22.180
What if I wanted to say, give me the user by ID and give me the user by email?

00:15:22.180 --> 00:15:29.680
So one possible way, I guess, you could just cram all the IDs and all the emails into the dictionary.

00:15:29.680 --> 00:15:38.820
But then things like, you know, enumerate over dict.items breaks because you get, you know, every now and then it's integers or it's strings.

00:15:38.820 --> 00:15:43.080
And then it's a duplicate of the users like in .items or .values.

00:15:43.080 --> 00:15:45.000
So it's not really a great one.

00:15:45.020 --> 00:15:49.500
So I said, does Python have like a structure that is not a database?

00:15:49.500 --> 00:15:52.700
Because I do not want to do database stuff.

00:15:52.700 --> 00:15:54.960
Like if I wanted to do that, I would just use a database.

00:15:54.960 --> 00:16:05.220
A thing that is lightweight and in memory and easy to use that lets me put something like a user in there, but then be able to ask, give me the user by ID.

00:16:05.220 --> 00:16:06.600
Give me the user by email.

00:16:07.120 --> 00:16:08.160
That is fast.

00:16:08.160 --> 00:16:08.760
Right.

00:16:08.760 --> 00:16:18.460
So dictionaries work because they're indexed and they're insanely like near, you know, oh, one type of performance on getting back the content that's in there.

00:16:18.460 --> 00:16:18.660
Right.

00:16:18.660 --> 00:16:18.920
Yeah.

00:16:18.980 --> 00:16:21.900
So I want to be able to do that both with email and ID not.

00:16:21.900 --> 00:16:24.580
I'm going to go on this rant some more later.

00:16:24.580 --> 00:16:29.060
I'm actually trying to pull together all the responses I got because I got a bunch of things given back to me.

00:16:29.460 --> 00:16:33.120
A lot of people suggested pandas, but I want to store non-tabular data.

00:16:33.120 --> 00:16:37.420
So I'm not sure pandas, which is tabular-ish makes sense.

00:16:37.420 --> 00:16:45.560
Nonetheless, one thing I did come up with that's probably the closest to what I was asking for without me doing any work, which I'm not against doing work.

00:16:45.620 --> 00:16:47.760
But if something exists, you know, let me pip install it.

00:16:47.760 --> 00:16:47.940
Right.

00:16:47.940 --> 00:16:50.580
Is this thing called light table by Paul McGuire.

00:16:50.580 --> 00:16:52.140
Sorry, not light table.

00:16:52.140 --> 00:16:52.740
Little table.

00:16:52.740 --> 00:16:54.580
Little table.

00:16:54.580 --> 00:17:04.320
And it gives you a schema-less in-memory thing that's kind of like a dictionary, but gives you ORM-like access to the objects.

00:17:04.320 --> 00:17:04.840
Okay.

00:17:04.840 --> 00:17:17.500
Okay, so it's like, think of like an in-memory database, basically, that you don't have to go create table, you know, set column type, name this to, you know, var char 16 type.

00:17:17.500 --> 00:17:22.040
And like, you don't have to actually define the table like a full-on database, right?

00:17:22.040 --> 00:17:28.280
You just say it has, you know, put these things in it like you would a dictionary, and then you can access all the elements.

00:17:28.280 --> 00:17:29.140
What do you think?

00:17:29.140 --> 00:17:31.320
I think I'd like to try to solve your problem also.

00:17:31.320 --> 00:17:33.880
It's a fun programming problem, right?

00:17:34.220 --> 00:17:38.060
But this thing is pretty cool because it lets you do like greater than queries.

00:17:38.060 --> 00:17:42.680
It has indexes on all of the columns or the columns that you say you want them on.

00:17:42.680 --> 00:17:46.780
Like, all you do is say, it's like create a dictionary and say, I'm going to put in a thing by ID.

00:17:46.780 --> 00:17:47.940
I'm going to put in a thing by email.

00:17:47.940 --> 00:17:49.220
I'm going to put in a thing by city.

00:17:49.220 --> 00:17:51.600
And I want to index for all of those.

00:17:51.600 --> 00:17:54.840
So it's like dictionary-like speed, which is pretty cool.

00:17:54.840 --> 00:17:59.160
It even does like in-memory joins and all sorts of stuff.

00:17:59.160 --> 00:18:01.040
So, yeah.

00:18:01.040 --> 00:18:01.500
Okay.

00:18:01.500 --> 00:18:02.440
That's neat.

00:18:02.440 --> 00:18:05.600
And the result of like a query can be like another little table.

00:18:05.600 --> 00:18:09.840
So I could like do a filter and select only a couple of columns.

00:18:09.840 --> 00:18:11.780
And then out comes a little baby little table.

00:18:11.780 --> 00:18:13.580
A little, even littler little table.

00:18:13.580 --> 00:18:21.780
Anyway, I thought this was a pretty cool thing because it lets you kind of do database-like stuff without the effort.

00:18:21.780 --> 00:18:22.340
Right?

00:18:22.340 --> 00:18:23.340
Do it dynamically.

00:18:23.580 --> 00:18:26.300
Some people said, hey, you should just use SQLite.

00:18:26.300 --> 00:18:28.000
I'm like, yeah, SQLite's cool.

00:18:28.000 --> 00:18:32.400
But then I've got to come up with a full-on schema for defining the thing.

00:18:32.400 --> 00:18:34.220
And that gets to be a pain.

00:18:34.220 --> 00:18:36.220
There's also some other options.

00:18:36.220 --> 00:18:38.020
But little table looks good.

00:18:38.020 --> 00:18:38.360
Yeah.

00:18:38.360 --> 00:18:40.240
I'll have to get an example.

00:18:40.240 --> 00:18:43.160
Get your actual problem statement again and try it.

00:18:43.160 --> 00:18:44.000
But this looks neat.

00:18:44.000 --> 00:18:44.340
Yeah.

00:18:44.340 --> 00:18:45.040
Yeah, absolutely.

00:18:45.040 --> 00:18:47.780
Yeah, well, I'll come back to that for sure as well.

00:18:47.780 --> 00:18:54.520
So because I want to bring together, like I got so many good recommendations and ideas that I think is probably worth just doing a segment on that.

00:18:54.520 --> 00:18:55.340
But little table.

00:18:55.340 --> 00:18:55.740
Nice.

00:18:56.040 --> 00:18:58.780
This is something I'm surprised we didn't talk about already.

00:18:58.780 --> 00:19:00.500
Maybe we have, but I've forgotten.

00:19:00.500 --> 00:19:02.180
pytest Timeout.

00:19:02.180 --> 00:19:03.880
This was a listener's suggestion.

00:19:03.880 --> 00:19:14.360
And I think it's pretty much an essential plugin for any test suite that you're running, especially if it's not something you're running where you're watching it.

00:19:14.360 --> 00:19:23.800
So if it's something running on a server or continuous integration or something, or if it's a long-running test suite, it's a very simple-to-use plugin.

00:19:23.800 --> 00:19:29.080
And what you want to make sure is that none of your tests run longer than a certain number of seconds.

00:19:29.080 --> 00:19:35.980
All the people out there that are, like, scratching their head thinking, wow, there's a test that runs longer than a second.

00:19:35.980 --> 00:19:38.360
Yes, there are tests that run longer than a second.

00:19:38.360 --> 00:19:42.100
Especially if they're trying to talk to hardware or external things.

00:19:42.100 --> 00:19:42.460
Yeah.

00:19:42.460 --> 00:19:44.520
And that thing might not be there and it's just waiting.

00:19:44.520 --> 00:19:46.660
Yeah, there's more to testing than unit testing.

00:19:46.660 --> 00:19:48.040
There's also system testing.

00:19:48.040 --> 00:19:53.020
But anyway, this one's great because you can set up a configuration in the config file.

00:19:53.160 --> 00:19:58.680
You can throw one number in to say, like, say you have, like, you know, five minutes or something like that.

00:19:58.680 --> 00:20:01.020
Or even just down to, like, three minutes.

00:20:01.020 --> 00:20:05.100
I want to make sure nothing runs longer than this.

00:20:05.100 --> 00:20:09.520
And just to make sure that the server doesn't just sit spinning all night long.

00:20:10.080 --> 00:20:16.180
And then, well, let's say you even tighten it closer to try to kill off a test if it's running longer than a certain amount.

00:20:16.180 --> 00:20:21.000
But there's, like, maybe two of your tests that are longer or a few of them that are longer.

00:20:21.000 --> 00:20:25.200
You can put a decorator on those particular tests and give them more time.

00:20:25.200 --> 00:20:27.000
And then the rest of them shorter.

00:20:27.520 --> 00:20:32.480
It's very easy to operate and just kind of a must-have for long test suites.

00:20:32.480 --> 00:20:33.380
Yeah, that's super cool.

00:20:33.380 --> 00:20:39.100
Yeah, I mean, sometimes you'd just rather have the test fail if it's taking way, way, way too long.

00:20:39.100 --> 00:20:41.820
And you're like, I'm pretty sure this is going to fail, but not right away.

00:20:41.920 --> 00:20:46.860
I would recommend just trying it out and kind of, like, look at the time of your tests and stuff.

00:20:46.860 --> 00:20:51.600
And then set it so that it actually kills one of your tests in the middle.

00:20:51.600 --> 00:20:54.360
Or stick a spin in there or something like that.

00:20:54.360 --> 00:20:55.920
Just to verify it does.

00:20:55.920 --> 00:20:58.020
Because it is sort of operating system dependent.

00:20:58.020 --> 00:21:05.960
And there's some configuration allowed in the plug-in to be able to use either signals or kill commands or process killing.

00:21:05.960 --> 00:21:10.980
There's different ways to stop a test that's going too long.

00:21:11.420 --> 00:21:13.780
So test it before you deploy it.

00:21:13.780 --> 00:21:15.040
But it's a good thing.

00:21:15.040 --> 00:21:15.820
Do a meta test.

00:21:15.820 --> 00:21:16.160
Yeah.

00:21:16.160 --> 00:21:17.480
Test of your test.

00:21:17.480 --> 00:21:17.900
Exactly.

00:21:17.900 --> 00:21:19.140
Super cool.

00:21:19.140 --> 00:21:20.220
Okay, that's a great one.

00:21:20.220 --> 00:21:22.720
And, you know, use case is super straightforward.

00:21:22.720 --> 00:21:26.280
I have got one for you that has got me really, really excited.

00:21:26.280 --> 00:21:27.520
It's called events.

00:21:27.520 --> 00:21:32.020
So in Python, we have functions as first class objects, right?

00:21:32.020 --> 00:21:35.620
You can pass a function around super easy, right?

00:21:35.620 --> 00:21:41.180
Like if there's some part of your program that's going to run and you want to get a function called when it's done,

00:21:41.180 --> 00:21:44.400
you can pass that function, it'll do its work, you can call it, right?

00:21:44.400 --> 00:21:48.300
You have this kind of observer style programming, right?

00:21:48.300 --> 00:21:48.560
Yeah.

00:21:48.560 --> 00:21:53.920
What requires programming on your behalf is to have that happen for more than one thing.

00:21:53.920 --> 00:22:03.240
Like I would like parts of my program to subscribe to being notified about events and one or more of them get called when this thing happens.

00:22:03.800 --> 00:22:10.400
So a friend of mine, Nicola Aroshi put together a really cool project called events.

00:22:10.920 --> 00:22:15.540
And the idea is that it adds event subscription and callback to the Python language.

00:22:15.540 --> 00:22:16.160
Oh, cool.

00:22:16.160 --> 00:22:17.560
In like a super simple way.

00:22:17.560 --> 00:22:20.500
So go to a function that is an event.

00:22:20.700 --> 00:22:32.000
If I want my function to be called by it, I would say like, like if I want the event on change, I would say my class dot on change plus equals some function to call.

00:22:32.000 --> 00:22:37.300
And if there's already one there, it's just going to add it to the list of all the functions that will be called when that event fires.

00:22:37.700 --> 00:22:47.000
And if at some point I decide I don't want to hear about it anymore, I just go to my class dot or my object dot on change minus equals the function I want to take out of that subscription list.

00:22:47.000 --> 00:22:48.040
And that's it.

00:22:48.040 --> 00:22:48.920
Oh, that's neat.

00:22:48.920 --> 00:22:49.560
Isn't that slick?

00:22:49.560 --> 00:22:56.500
And then to call it, you just say object dot on change and you pass the arguments and then all those functions get called in order.

00:22:56.500 --> 00:22:57.400
Oh, this is cool.

00:22:57.400 --> 00:22:57.720
Yeah.

00:22:57.720 --> 00:23:05.380
So it's if you have to do any sort of observer design pattern event subscription stuff like this is super, super nice.

00:23:05.520 --> 00:23:11.480
And it's inspired on the C# language base event keyword, which is based on delegates, basically function pointers.

00:23:11.480 --> 00:23:14.400
It doesn't really matter if you know about that or care about it.

00:23:14.400 --> 00:23:18.120
But if you know about the C# version, this basically brings that to the Python language.

00:23:18.120 --> 00:23:18.380
Yeah.

00:23:18.380 --> 00:23:21.640
I kind of want to build up a finite state machine using this.

00:23:21.640 --> 00:23:22.340
It's cool, right?

00:23:22.340 --> 00:23:22.800
Yeah.

00:23:22.800 --> 00:23:25.340
I mean, it could make it really readable.

00:23:25.340 --> 00:23:25.660
Yeah.

00:23:25.660 --> 00:23:29.540
I have a gist that I'm working on or I have some code I'm working on.

00:23:29.540 --> 00:23:35.100
I'll post as a gist that people can check out that is like a lot better than what they have in the documentation.

00:23:35.740 --> 00:23:40.880
So the documentation takes like this raw event source and shows you how you can subscribe and unsubscribe to it.

00:23:40.880 --> 00:23:44.880
But what I've got is something that's like here's how you have a class, right?

00:23:44.880 --> 00:23:47.040
Like, you know, a thing on the screen.

00:23:47.040 --> 00:23:52.820
And then you could have like subscribe to when the location changes or the size changes or, you know, those kinds of things.

00:23:52.820 --> 00:23:56.500
And it's more of like a natural programming analogy.

00:23:56.500 --> 00:23:57.820
So I'll put up the gist for that.

00:23:57.820 --> 00:24:01.020
I'm just working on a few things to see if I can make it even slightly better.

00:24:01.020 --> 00:24:08.320
I'm seeing if I can use descriptors so that the event triggering happens behind the scenes without you even have to program it as well.

00:24:08.320 --> 00:24:12.200
So like right now the from the outside using it is really easy.

00:24:12.200 --> 00:24:17.100
But you do have to sort of like know when something's changed and then call that raise that event.

00:24:17.100 --> 00:24:20.520
I think I can use descriptors to maybe make it seamless on both sides.

00:24:20.520 --> 00:24:21.880
But I'm still playing with that.

00:24:22.820 --> 00:24:29.440
Now, do you know if this if all of the events get called by the thing changing the making the event happen?

00:24:29.440 --> 00:24:29.800
Yes.

00:24:29.800 --> 00:24:30.080
They do?

00:24:30.080 --> 00:24:30.340
Yeah.

00:24:30.340 --> 00:24:30.560
Okay.

00:24:30.560 --> 00:24:30.780
Yeah.

00:24:30.780 --> 00:24:30.960
Yeah.

00:24:30.960 --> 00:24:35.680
So they get called by the thing that whatever decides to raise the event.

00:24:35.680 --> 00:24:37.900
That's the thing that's doing the calling.

00:24:37.900 --> 00:24:43.320
The events just basically manage what are the functions to be called in what order.

00:24:43.320 --> 00:24:46.560
And then like you call it and it just like delegates onto them.

00:24:46.560 --> 00:24:51.540
Also, you get to just arbitrarily pick the parameters that gets passed that get passed along.

00:24:51.940 --> 00:24:56.980
But it seems like a good idea to say this event always takes these kinds of arguments and whatever.

00:24:56.980 --> 00:24:58.960
There's not a lot of structure there.

00:24:58.960 --> 00:25:09.000
You do get the only real safety is you can say when you create it, you can say these are the only allowed events because it's kind of just full on dynamic programming.

00:25:09.000 --> 00:25:14.800
But you can say these three things you can subscribe and unsubscribe and call anything else.

00:25:14.800 --> 00:25:16.000
We're going to say it doesn't exist.

00:25:16.000 --> 00:25:17.460
So that's pretty nice.

00:25:17.460 --> 00:25:17.820
Yeah.

00:25:17.820 --> 00:25:18.380
Yeah.

00:25:18.380 --> 00:25:18.860
Yeah.

00:25:18.860 --> 00:25:19.760
It provides a little safety.

00:25:19.760 --> 00:25:20.040
Cool.

00:25:20.040 --> 00:25:20.380
Yeah.

00:25:20.620 --> 00:25:22.040
Well, that's our six items.

00:25:22.040 --> 00:25:23.660
Do you have any extras for us?

00:25:23.660 --> 00:25:24.200
Not really.

00:25:24.200 --> 00:25:25.660
Just I sort of talked about it.

00:25:25.660 --> 00:25:26.760
I was going to talk about it here.

00:25:26.760 --> 00:25:30.300
But I talked about it in the we talked about what we're doing, how people can support us.

00:25:30.300 --> 00:25:32.560
I finished the Python memory management course.

00:25:32.560 --> 00:25:34.000
The thing is so cool.

00:25:34.000 --> 00:25:39.360
It's a five hour course just diving into the internals of like Python memory management algorithms.

00:25:39.360 --> 00:25:44.740
And what I thought I would create was something that was like understanding Python memory management.

00:25:44.740 --> 00:25:54.020
But there's actually a ton of techniques I discovered that actually let you run your code in ways that are like, well, now it uses half as much memory and it's 30% faster and stuff like that.

00:25:54.020 --> 00:25:56.940
So I didn't think there would be a lot of actionable stuff coming out of it.

00:25:56.940 --> 00:25:59.680
But there is, which I think is pretty cool, actually.

00:25:59.680 --> 00:26:00.240
Oh, nice.

00:26:00.240 --> 00:26:00.560
Yeah.

00:26:00.560 --> 00:26:01.160
How about you?

00:26:01.160 --> 00:26:04.060
I'm pretty excited that pytest 6 is out.

00:26:04.060 --> 00:26:10.380
A couple weeks ago, we talked about the 6 being in sort of a beta release, but it's out now.

00:26:10.560 --> 00:26:16.820
And I wanted to mention that episode 125 of Testing Code walks through those changes.

00:26:16.820 --> 00:26:19.900
This is due to the miracles of time travel.

00:26:19.900 --> 00:26:25.160
This has not been recorded yet, but it will be recorded and released by last week.

00:26:25.160 --> 00:26:27.060
Perfect.

00:26:27.060 --> 00:26:28.160
Time travel.

00:26:28.160 --> 00:26:28.700
I love it.

00:26:28.700 --> 00:26:31.620
You've chosen the perfect joke.

00:26:31.620 --> 00:26:38.900
So the only question I have for you before we do the joke is, am I the school administrator IT person or am I the mom?

00:26:38.900 --> 00:26:40.120
Oh, you be the mom.

00:26:40.120 --> 00:26:40.560
Okay.

00:26:40.560 --> 00:26:40.960
Okay.

00:26:40.960 --> 00:26:41.920
So the phone rings.

00:26:41.920 --> 00:26:42.520
I pick it up.

00:26:42.520 --> 00:26:42.740
Yeah.

00:26:42.740 --> 00:26:44.620
Hi, this is your son's school.

00:26:44.620 --> 00:26:46.520
We're having some computer trouble.

00:26:46.520 --> 00:26:47.380
Oh, dear.

00:26:47.380 --> 00:26:48.680
Did he break something?

00:26:48.680 --> 00:26:49.460
In a way.

00:26:49.460 --> 00:26:52.560
Did you really name your son Robert?

00:26:52.560 --> 00:27:01.120
Robert single quote, parentheses, semicolon, drop table, students, semicolon, minus, minus?

00:27:01.120 --> 00:27:01.920
Oh, yes.

00:27:01.920 --> 00:27:03.400
Little Bobby Tables, we call them.

00:27:03.400 --> 00:27:05.960
Well, we've lost this year's student records.

00:27:05.960 --> 00:27:06.880
I hope you're happy.

00:27:06.880 --> 00:27:09.680
And I hope you've learned to sanitize your database inputs.

00:27:10.400 --> 00:27:12.300
Be on the lookout for that sequel injection, baby.

00:27:12.300 --> 00:27:14.580
I love it.

00:27:14.580 --> 00:27:15.420
This is so good.

00:27:15.420 --> 00:27:18.520
This is absolutely one of the most classic computer jokes there is.

00:27:18.520 --> 00:27:19.600
Yeah.

00:27:19.600 --> 00:27:20.340
I love it.

00:27:20.340 --> 00:27:22.500
Because it probably would actually work.

00:27:22.500 --> 00:27:32.240
It reminds me of the guy who said that he got his license plate to be the characters N-U-L-L,

00:27:32.240 --> 00:27:32.800
null.

00:27:32.800 --> 00:27:33.240
Yeah.

00:27:33.400 --> 00:27:34.240
I heard about that.

00:27:34.240 --> 00:27:34.540
Yeah.

00:27:34.540 --> 00:27:43.380
And he ended up getting all the like automated, you know, you drove through a traffic light sort of thing, tickets for all the records that were null.

00:27:43.380 --> 00:27:44.740
Yeah.

00:27:44.740 --> 00:27:46.580
Anytime they didn't have data, it went to him.

00:27:47.080 --> 00:27:51.080
Any police officer that forgot to enter the license plate, it would go to him.

00:27:53.080 --> 00:27:55.640
He thought he would get out of it because they wouldn't be able to send it to him.

00:27:55.640 --> 00:27:56.280
But oh, no.

00:27:56.280 --> 00:27:59.500
That's hilarious.

00:27:59.500 --> 00:28:00.080
Awesome.

00:28:00.080 --> 00:28:00.380
Awesome.

00:28:00.380 --> 00:28:01.220
All right.

00:28:01.220 --> 00:28:02.820
Well, great to chat with you as always.

00:28:02.820 --> 00:28:03.280
All right.

00:28:03.280 --> 00:28:03.700
You too.

00:28:03.700 --> 00:28:04.000
Bye.

00:28:04.000 --> 00:28:04.340
Bye.

00:28:04.800 --> 00:28:06.460
Thank you for listening to Python Bytes.

00:28:06.460 --> 00:28:08.920
Follow the show on Twitter at Python Bytes.

00:28:08.920 --> 00:28:11.960
That's Python Bytes as in B-Y-T-E-S.

00:28:11.960 --> 00:28:14.840
And get the full show notes at Python Bytes.fm.

00:28:14.840 --> 00:28:19.900
If you have a news item you want featured, just visit Python Bytes.fm and send it our way.

00:28:19.900 --> 00:28:21.960
We're always on the lookout for sharing something cool.

00:28:21.960 --> 00:28:23.120
This is Brian Okken.

00:28:23.120 --> 00:28:28.440
And on behalf of myself and Michael Kennedy, thank you for listening and sharing this podcast with your friends and colleagues.

00:28:28.440 --> 00:28:29.440
Thanks.

