Transcript #157: Oh hai Pandas, hold my hand?
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:05 This is episode 157, recorded November 14, 2019.
00:10 I'm Brian Okken.
00:11 And I'm Michael Kennedy.
00:12 And this episode is brought to you by DigitalOcean.
00:15 So, Michael, we're going to cover a topic that we've covered a little bit before, I think.
00:20 We covered Cerebrus, right? Or Cerberus?
00:22 Cerberus. Yeah, we covered Cerberus, which is like a validation layer for unstructured data.
00:29 So, this is as built as part of the Eve framework by Nicola, who runs both of those projects.
00:35 And it's really nice, right?
00:36 So, I get like some JSON posts back to my REST.
00:39 It's a REST framework.
00:40 I get a JSON post for some data.
00:42 And I have some models to find.
00:44 It can tell you whether they're a fit or not.
00:47 It can tell you what's required.
00:49 I do think the way you set it up is a little bit of out of band.
00:53 So, Colin Sullivan shot us a note after that.
00:56 Said, hey, that's really cool.
00:57 You should also talk about Pydantic.
01:00 Had you heard of Pydantic?
01:00 I think so.
01:01 But, yeah.
01:02 Tell me more.
01:03 And it's got a great name.
01:04 Yeah, it definitely has it.
01:06 Yeah, yeah.
01:06 It's got a super name.
01:07 I believe I've heard of it, but I didn't do anything with it.
01:10 So, on Colin's suggestion, I checked it out.
01:12 And, yeah, this is a sweet, simple framework that solves some really nice problems.
01:17 And a lot of times with these frameworks, I'm like, yeah, I would love to use this.
01:20 But at the same time, like, it's not that helpful.
01:24 And so, I'm not sure I'm actually going to use it.
01:26 I could just put a little test in my class to make sure this file, like, this thing parses an int or this name is here or whatever.
01:32 But this one, like, might convince me to do it because, yeah, this is super, super cool.
01:36 All right.
01:37 Let me tell you what it is.
01:38 So, it's data validations and settings managements for using Python type annotations.
01:43 And it's the type annotations that make me really extra happy.
01:46 Oh, really?
01:46 Okay.
01:47 Yeah.
01:47 So, you know how we've got data classes and you can have, like, annotated values there and you get a little validation and whatnot.
01:55 But this is super cool.
01:57 So, I can just take a create class and say it has things like an ID, which is integer, a name, which equals a default string, a date time, which has a default of none, things like that.
02:09 Right.
02:09 So, you basically, either you have type annotations or the thing has a default value, which implies the type.
02:16 Okay.
02:16 Yeah.
02:17 And this probably represents some data that's exchanged over REST or something like that.
02:22 Right.
02:22 Some sort of dictionary.
02:23 So, if I get a dictionary back, then what I can do is I can just star, star, unpack that dictionary into the object, the class that I've defined.
02:31 Right.
02:32 So, basically, keyword arguments, ID equals whatever the value is, name equals whatever the name and the dictionary is and so on.
02:38 And it will validate all that using some really simple rules that you follow along.
02:43 So, we've got a class and it has an ID, which is an integer, but has no default value.
02:48 It has no none.
02:49 That means the ID has to obviously be an integer, but it's also required.
02:52 If it's not there, an error will be raised.
02:55 The name is a string, so it has to be a string.
02:58 But because it has a default value, it's optional to pass it.
03:01 Oh, okay.
03:02 That's cool, right?
03:03 The date time, which is a date time field, is not required because it has none as a value.
03:10 But if nothing's passed, it's just going to be none.
03:12 So, it's an optional date time.
03:14 That's pretty cool.
03:15 So, some of the reasons that I think this is cool and they call out in their webpage is that it works automatically with all the IDEs that you already have.
03:23 Right?
03:23 There's no, like, special, oh, yeah, there's a YAML file that tells me what this schema looks like for this.
03:29 Or there's a JSON schema that comes back and, like, no, it's standard Python with type annotations.
03:35 So, your IDE knows already what all those things are and you don't have to, like, backfill that, right?
03:41 So, the validation also works for just working with the classes.
03:44 That's pretty cool, right?
03:45 Yeah, that's cool.
03:46 Yeah, it's supposed to be faster than all the other libraries they tested and they have a link to the ones that they did.
03:50 It also supports really rich recursive validation.
03:55 So, if you've got, like, a list or a tuple and maybe, like, stuff is inside there, right?
04:03 Or something like that, right?
04:04 You've got some nested types.
04:06 So, it'll actually recursively traverse the stuff that you're looking for, right?
04:11 So, it doesn't just test the top-level things.
04:13 It tests, like, the entire object graph.
04:15 And by default, the way it works is you derive from the Pydanic base model, which is cool.
04:20 But you can also use a decorator on a data class, which we talked about.
04:24 It's very similar because of the type annotations.
04:26 And it'll actually put parsing and validation on there for you.
04:29 Oh, that's neat.
04:30 Yeah.
04:30 So, if you really want to use data classes, you can make them better with Pydanic as well.
04:33 Okay.
04:33 Yeah, simple, right?
04:34 Yeah.
04:35 So, where do you put it in your...
04:36 So, do you get it, like, when you get data in, you validate the data with Pydanic then?
04:40 That's the thing is you don't even validate it.
04:42 That's what's so sweet about it.
04:43 There's not even a validation step.
04:45 You have the class.
04:46 And in the notes, I'm putting a user class.
04:49 So, I'll maybe reference that.
04:50 And then you get some external data, which is a dictionary.
04:53 And then you just create the user object.
04:55 It's a user of star star dictionary.
04:57 And it'll unpack it keyword-wise.
04:59 The validation happens in the fact that you don't have a dunder init on your class.
05:04 And it derives from the Pydanic base model.
05:06 So, it uses its dunder init, which theoretically does the validation.
05:09 Oh, okay.
05:09 So, you can't not validate then.
05:11 Yes.
05:12 Exactly.
05:12 Yeah.
05:13 You just basically...
05:13 I tried to create the class.
05:14 And either that worked really well or not so much.
05:16 Oh, that's actually pretty cool.
05:17 And you get like a JSON response of all the things that were wrong with the validation as part of the exception, I believe.
05:22 So, you can actually go, no, there's actually three things wrong here.
05:25 Not just, well, the first thing it hit made it crash.
05:27 So, this is obviously useful for like REST APIs and stuff like that or grabbing external data.
05:33 But there's a lot of times where we're passing dictionaries around between components.
05:37 And it'd be good to have some, if there's less trusted components, to have some sort of validation.
05:42 So, this is pretty cool.
05:43 Yeah.
05:43 Even web forms that get posted back, a lot of times those come back in Pyramid or Flask as dictionaries.
05:49 Right?
05:50 If you wanted to map that to a class, right, you could get validation.
05:52 There's a lot of places.
05:53 Yeah.
05:53 Yeah.
05:54 Even settings files, right?
05:55 Yeah.
05:55 Yeah.
05:56 There's a lot of people that just throw stuff that gets adjacent or something that gets thrown in a file.
06:01 And it's user editable also.
06:03 Yeah.
06:05 So, you have to validate it because who knows what somebody edited it to.
06:09 Yeah.
06:10 Absolutely.
06:10 All right.
06:11 What you got next for us?
06:12 I am doing, hopefully doing a favor, adding work to Ned Batchelder.
06:17 So, he posted on Twitter recently that there is changes afoot in coverage.py.
06:23 So, coverage is, hopefully everybody knows, coverage is great for using to tell you how much of your code base, your test suites are covering.
06:32 I mean, that's how it's usually used.
06:34 So, you could potentially do anything to try to measure coverage, but usually it's around a test suite or something.
06:40 Anyway, so, the change is they've added measurement contexts.
06:44 So, allowing you, while it's collecting data for coverage, it collects what was the context of what it was doing while it was covering certain bits of code.
06:55 Now, that seems a little, the obvious use model of that, there's lots of use models.
07:00 The obvious use model is which test covered which line of code.
07:05 And to have that, and that's a lot of data.
07:07 So, he's changed the way the data for coverage is being stored.
07:12 And it's pretty cool.
07:13 So, I'm going to jump to the conclusion.
07:14 There's this cool feature.
07:17 The context feature is very cool.
07:18 I want to talk about that.
07:19 But first of all, it is a little bit of a break in the coverage, use of coverage.
07:24 I think the reason is just because there's a, the way the data is stored, there's a little local database stored.
07:30 So, there's another dependency that isn't an external dependency.
07:35 It's a built-in dependency.
07:37 But it's something that some versions of Python don't always have, I guess.
07:43 So, for that reason, he's asking everybody, please try out the beta 1 coverage 5, 5.0 beta 1, and try it out and let them know if there's any issues.
07:52 Right.
07:52 So, basically, the idea is go try it and see if what you're doing before still works.
07:56 If not, let them know real quick before it becomes permanent, right?
07:59 Right.
08:00 Exactly.
08:00 And I really want this to become permanent because measurement context is so cool.
08:05 I tried it out this morning.
08:06 I'm going to put in show notes.
08:08 I wasn't really clear on how to download, how to install a beta version of something.
08:12 So, you just do the, like for this, it's pip install coverage double equal 5.0b1.
08:20 Okay.
08:21 So, we'll put that in the show notes.
08:22 It's not too bad to install it.
08:24 And then also, I didn't put this in the show notes, but one of the other tricks I found out is if you want to know what versions are available to pip install,
08:31 you can just do the coverage equal equal and then don't list a version.
08:36 And you'll get an error message that says, I don't know what you're talking about, but here's all of the versions that are available.
08:41 That's pretty awesome.
08:42 I didn't know that.
08:43 Yeah, that's pretty cool.
08:44 So, I tried it out, a few lines of code to, or a few lines of command line stuff to run coverage on a little dummy file.
08:50 And sure enough, if I generate the HTML report, on the right-hand side of the window or the screen, I've got little drop-downs on every line of code to tell me which test covered which line of code.
09:03 I like that a lot.
09:04 That's cool.
09:04 Yeah.
09:05 Very neat.
09:06 Yeah, super nice.
09:06 I look forward to it.
09:07 Okay.
09:08 I don't know why I think this is funny.
09:10 My brain's just not working, man.
09:11 Will you do the ad read?
09:12 Got it.
09:13 Now, this episode is brought to us by DigitalOcean.
09:15 And I just want to tell you about something brand new that's gone from beta to general availability, memory-optimized droplets.
09:24 Droplets are DigitalOcean's words for virtual machines, right?
09:28 Goes to the cloud, clouds full of rain, rain droplets, that sort of thing.
09:32 And if you have some sort of workload that requires a lot of memory, well, then these things are like super optimized that.
09:38 So it has eight gigs of RAM for each dedicated virtual CPU.
09:43 You can get them with two or many, many more, right?
09:47 Multi-core systems.
09:48 So basically, you can go all the way from 16 gigs to 256 gigs of RAM, which is like a ridiculous amount of RAM.
09:58 One thing you can do to make your app run faster is to make sure it never touches the disk, right?
10:02 So if it could just cache everything, that would be great.
10:05 So they're really good for things like high-performance SQL or NoSQL databases, large memory caches and indices, indexes, things like that.
10:14 And just lots of big data and stuff running with large runtime requirements.
10:17 So if you need between 16 to 256 gigs of RAM and you want to just pay mostly for the memory, right?
10:26 The pricing is optimized around that use case.
10:28 Then check them out at pythonbytes.fm/DigitalOcean.
10:32 They're a big supporter of the show.
10:33 Speaking of cool stuff, the PSF and the Python Software Foundation Packaging Working Group, actually, that group of the PSF,
10:43 they're looking to hire some folks.
10:45 They're looking for, I think, three developers and maybe a project manager.
10:50 I can't remember exactly all the details, but quite a few number of people to make pip better.
10:55 Like you just said, if you said, you know, pip install coverage double equals, it'll help you, right?
11:00 So this is supposed to be a much better setup.
11:03 So the idea is that one of the things that could be improved in pip is its dependency resolver, right?
11:12 So it's, you know, this package depends on this thing, but other package also maybe depends on that, but a different version.
11:17 Or, you know, I don't know how often it's happened to you, but I've had the order in which I list stuff in the requirements causing issues.
11:25 Because one requires, I don't know, doc opt of this version.
11:29 The other one requires doc opt of another version.
11:31 And how can you possibly install them both at the same time, right?
11:35 Weird stuff like that.
11:36 Poetry has noticed this problem and it has a solution to it, but it's around poetry.
11:41 And it'd be really cool if that sort of dependency resolution was built in to PIP.
11:46 That'd be great.
11:47 Yeah.
11:47 The underlying idea is to make distributing and installing Python software just more reliable and easier.
11:53 So funding has been allocated to two contractors, a senior developer, an intermediate and an intermediate developer.
12:01 That's what it is.
12:01 To work on developing, testing and building this feature, the test infrastructure, code review, bug triage, all that kind of stuff.
12:09 And this is a non-trivial offering.
12:11 So I believe the senior developer will end up getting $116,000 out of this based on the time they're estimating and the rate.
12:20 And then the either senior developer or the contractors, I can't remember, get $103,000 each.
12:26 This is quite significant.
12:28 Not too shabby.
12:29 Yeah.
12:29 That's like a, not just a, hey, I need somebody to work on this for a couple of weeks.
12:33 That's like a legit thing.
12:34 So if you'd like to contribute to Python, work on PIP, things like that, just, you know, go check out this link.
12:39 It shows you how to apply.
12:41 Very cool.
12:41 Yeah.
12:41 So when I work on Pandas, Brian, I kind of feel a little bit lost.
12:46 There's all these operations and I don't use Pandas enough to kind of actually know what I should be doing.
12:50 Often it's in the context of Jupyter Notebooks where the autocomplete's slightly less good than PyCharm or VS Code.
12:56 I could always use some help when I'm working on Pandas.
12:59 How about you?
12:59 Yeah, I could.
13:00 And I know people that, there's a lot of people that work in it all the time,
13:03 but I usually just jumping in for some particular use.
13:07 And I know I don't know the best way to do things.
13:10 There's a thing called DovePanda.
13:12 I think I'm saying that right.
13:14 D-O-V Panda.
13:15 And this was submitted by Dean Langstrom, Langsom, sorry.
13:20 I think that it's his project, but essentially it's an overlay on, I'm just going to read his thing.
13:27 He says directions.
13:28 So DovePanda has directions and are hints and tips for using Pandas in an analysis environment.
13:36 DovePanda is an overlay for working with Pandas.
13:39 So the idea is, like, if you have this installed also, you're working in a Jupyter Notebook and you start typing stuff, you start doing Pandas operations.
13:49 It looks at what you did and provides hints and it pops up in little windows in your notebook to give you hints on, I think you're doing this, but there's a better way to do it or giving you tips.
14:01 So it's like Clippy for Pandas in Jupyter.
14:05 Yeah, but it's definitely, sort of.
14:08 But instead of having just one Clippy that pops down, they're in your notebook so you don't have to deal with them right away, but you can go back and improve your use of Pandas within the notebook.
14:20 It's pretty cool.
14:21 Yeah, it actually looks really helpful.
14:23 So the example they have, they've got a bunch of pictures on the GitHub repo you all can check out.
14:28 But, like, for example, there's one where someone's calling pd.concat and taking two data frames and specifying the axes equals one.
14:35 And then the little panda pops up and says, all data frames have the same columns, which hints for concat on axis zero.
14:44 You specified axis one, which may result in unwanted behavior, and it'll show you the code.
14:49 Or after concatenation, you're going to have duplicate column names, pay attention, and things like that.
14:55 It's got a bunch of great little tricks.
14:57 And then, you know how you mentioned Kevin Markham from Dataschool.io and his tips?
15:02 You can type DovePanda.tip, and it'll pull up a Kevin Markham tweet.
15:06 That's pretty cool.
15:08 Like, inside your notebook, it'll pull up, like, some random tip.
15:11 Yeah, that's pretty cool.
15:12 A whole circle there.
15:13 And, like, you can use it, apparently you can use it, not even just in notebooks.
15:17 So there's a command line mode where you can set the output to be, you know, there's no inline output to go to.
15:23 So you can tell it to print the output to just standard out or to a display or to somewhere else.
15:30 That's nice.
15:31 So if you're using, you want to have these sort of tips, but you're not using notebooks, you can still get them.
15:36 Yeah.
15:36 Very cool.
15:38 This next one is really simple, but I think some folks will find it super useful.
15:42 You know, maybe you picked up that project from someone else at work, and they're not following all the best Python practices.
15:50 You see a bunch of import stars all over the place.
15:54 And you're like, man, didn't somebody tell these people that import star is not worth it, right?
16:00 That there's all these potential drawbacks.
16:02 So enter remove star.
16:04 Remove star is a command line app you can run or command you can run.
16:10 And you point it at either a module, a file, a directory, something like that.
16:15 It will go through, and by default, it'll just find the issues where import star is done.
16:19 And then it will look at the actual files and say, well, you said import star, but you're actually just, you know, like from collections import star.
16:29 Maybe you're actually just using named collections encounter or something like that.
16:34 Yeah.
16:35 Maybe that's it or tools.
16:35 Anyway, you're just using one or two things.
16:37 And it'll say, you know what?
16:39 You could replace that line with from collections import named tuple, right?
16:44 And it could suggest that, or you could actually give it a command to say, no, just change all my files.
16:49 Fix it.
16:50 Yeah.
16:50 This is very cool.
16:52 Yeah, it's great.
16:52 So it's not that it just says import star is bad.
16:55 It actually figures out what of that star is being used and what you should actually write, and then it will write it for you.
17:01 Yeah, so my normal operation when I see something like this is just to comment out the import statement and see what breaks.
17:08 And that's not the best way to do things.
17:12 So this is way better.
17:13 I like it.
17:14 Yeah, yeah.
17:14 It reminds me a little bit of Flint, F-L-Y-N-T, which will take all your strings and rewrite them as f-strings.
17:21 This will take all your import stars and rewrite them as proper specific imports.
17:25 OMG, I totally forgot about Flint.
17:27 We've got a whole bunch of code that we wrote for 3.5 that still has all the old stuff in it.
17:35 So yeah, I got to use that.
17:36 Well, it's about to get a whole lot better.
17:38 Hit it with Flint.
17:38 It's so good.
17:39 Yeah, definitely.
17:40 Awesome.
17:41 All right.
17:41 Well, that's it.
17:42 Remove stars.
17:42 There's not a whole lot to it.
17:43 It's just a great little command line tool you can use to make your Python code better.
17:47 Yeah.
17:48 So the last thing I want to talk about today, actually, oddly enough, we didn't plan this, is another.
17:54 It came from Brian Rutledge, too.
17:56 So the PSF thing that we talked about, the hiring developers came from him, too.
18:01 So we've got two stories from Brian.
18:03 So thanks, Brian, for helping us out.
18:04 Yeah, absolutely.
18:05 Thanks, Brian.
18:06 Double thanks.
18:07 Well, so one of the things that Brian's working on is a pytest plugin called pytest Quarantine.
18:12 This is so cool.
18:14 Hopefully all your tests pass.
18:15 But let's say you've got a, you just implemented, you got really fantastic, you got into testing, and you started writing a bunch of tests, and you put it on a code base, and you got a bunch of failures.
18:27 You know you're going to fix them, but you're not going to fix them right away.
18:31 So what do you do?
18:31 And the idea with pytest Quarantine is it saves a list.
18:35 So you run it once, and you tell it to save a list of all the failing tests.
18:39 And it saves it somewhere, and you can throw it in Git or something, store it.
18:44 And then you run it again with that list, and it automatically marks all of the tests that have failed in the past as X fails.
18:54 Now this is something you can do manually to say, I know this is going to fail, just run it as an X fail instead of, it separates it from a failure.
19:03 You know, there's arguments of whether that's good or bad, but it's very useful so that you can still use your suite to find new failures while you're working on the old ones.
19:12 Anyway, this is a nice little extra tool.
19:15 I think it's super cool.
19:16 I also wanted to bring this up because he sent me this really nice email.
19:21 So apparently I met Brian a couple times at PyCon in Cleveland, and he said he started out as a complete pytest newbie and bought my book, started working through it, loved pytest, and then helped his company to adopt pytest, and then wrote this plugin.
19:39 And he wrote it at work and convinced his company to be able to release it as open source.
19:44 So that's super cool.
19:46 Yeah, that's really great.
19:47 Yeah, good work, Brian.
19:47 This sounds like super useful.
19:49 You know, you've got to make some huge change.
19:51 If it breaks 50 tests, you can't start solving all 50 at once, right?
19:55 You've got to like chop your way out of them.
19:57 So yeah.
19:58 Chip away at it.
19:59 Yeah, exactly.
19:59 Quarantine them and then just, you know, take them one at a time.
20:02 So yeah, I like it.
20:03 I mean, there are ways in which you can deal with this.
20:05 Like in PyCharm, you can say run only this test or run certain ones.
20:11 But you know, like it doesn't help you on continuous integration or something like that, right?
20:16 So yeah, I think this is great.
20:17 And one of the things I wanted to bring up also is I've dealt with this in the past on a temporary basis, of course, where you've got for some reason a breaking change that fails some things.
20:27 You're working through them.
20:28 And we have occasionally, if there's like a known failure, that the fix is scheduled, right?
20:34 It's a, we know about it.
20:35 We're going to fix it, but it's not going to be fixed for like three weeks.
20:38 You can add X fail to the test itself.
20:41 But one of the issues with that is you're to add the X fail mark, you edit the test file.
20:46 So one of the benefits of this is you're not actually editing the test file.
20:50 You're editing a different file that marks those.
20:53 So that's kind of right.
20:54 You don't want those changes to show up and get saying, well, we've made all these changes to these tests, but actually, no, we're just trying to fix something else and get them out of our way.
21:01 Yeah, I like it.
21:02 Yeah.
21:02 All right.
21:02 Well, that's it for all of our main items.
21:03 Brian, you got anything extra you want to throw out there?
21:05 I do not.
21:06 How about you?
21:07 I've got some pretty cool news.
21:09 So I recently decided to go through the effort of figuring out how much energy all of our services and servers use, right?
21:18 So for like delivering Python bytes and talk Python and Talk Python Training courses and all that stuff.
21:24 And I figured out how much that was and went out and bought renewable energy credits to offset all the carbon from all of our infrastructure.
21:32 Wow.
21:32 That's neat.
21:33 Yeah.
21:33 Yeah.
21:34 So I'm going to keep doing that going forward.
21:36 So not a huge, huge amount, but it's, you know, I think a good signal for all the other companies out there as well to say, look, if this podcast or these podcasts can be carbon neutral for their server structure, why can't we?
21:50 Right?
21:50 Yeah.
21:51 Yeah.
21:51 Cool.
21:51 So anyway, small, but hopefully can trigger some good change.
21:54 All right.
21:55 Ready for a joke?
21:56 I am so ready for a joke.
21:57 I need it this week.
21:59 Well, it's more science than it is programming.
22:03 But I think our audience will generally, generally like it.
22:07 So I'm going to tell the joke and then explain the joke because I'm not sure everyone will know, but I think a lot of us will get it.
22:13 And jokes are so much more funny if you explain them.
22:15 I know.
22:15 Absolutely.
22:16 They are.
22:17 So imagine a time not too long ago.
22:20 Dr. Heisenberg from quantum mechanics fame.
22:23 He's driving down the highway and he gets pulled over for speeding.
22:27 The policeman comes over.
22:29 The officer says, excuse me, sir.
22:31 Do you know how fast you were going?
22:33 Heisenberg pauses for a moment and then answers, no, but I do know where I am.
22:39 I love that.
22:39 That's so funny.
22:40 Yeah.
22:41 Thanks.
22:41 Yeah.
22:42 So the Heisenberg uncertainty principle basically says that the position and velocity of an object cannot both be measured exactly at the same time.
22:50 Not even theoretically.
22:52 You can know one or the other, but not both.
22:54 So, yeah.
22:54 He knows where he is.
22:55 Yeah.
22:56 Funny.
22:57 Pretty good.
22:57 All right.
22:57 Well, thanks for being here.
22:58 Good to be back together.
23:00 After taking off and hiding in Florida for a while.
23:03 Now we're back on the usual track.
23:04 Yeah.
23:05 Yeah.
23:05 All right.
23:05 Have a good one.
23:06 You too.
23:06 Bye.
23:06 Bye.
23:06 Thank you for listening to Python Bytes.
23:08 Follow the show on Twitter at Python Bytes.
23:11 That's Python Bytes as in B-Y-T-E-S.
23:14 And get the full show notes at Pythonbytes.fm.
23:17 If you have a news item you want featured, just visit Pythonbytes.fm and send it our way.
23:22 We're always on the lookout for sharing something cool.
23:24 This is Brian Okken, and on behalf of myself and Michael Kennedy, thank you for listening
23:28 and sharing this podcast with your friends and colleagues.
23:30 Thanks.