Brought to you by Michael and Brian - take a Talk Python course or get Brian's pytest book


Transcript #320: The Bug Is In The JavaScript

Return to episode page view on github
Recorded on Tuesday, Jan 24, 2023.

00:00 Hello and welcome to Python Bytes, where we deliver Python news and headlines directly to your earbuds. This is episode 320 recorded January 24th 2023 I am Michael Kennedy and I am Brian Okken and this episode is sponsored by us Over at Talk Python Training and testing code and patreon supporters We'll talk more about all that a little bit further into the show and also yes for those you listening live You already know this, but for most people, they may not know that we're live streaming this on YouTube.

00:30 And if you just go to pythonbytes.fm/stream/live, then you'll be able to follow over there.

00:35 Usually on Tuesdays at 11 Pacific Time, which it is right now, you can be part of the show.

00:41 That's how some of the folks are in the audience.

00:43 So hopefully you go over there and you know, subscribe on YouTube and press the notify so you know when we're posting live streams. And with that, Let's, let's get going, Brian.

00:53 Let's mark it down.

00:54 I'm going to talk about Markdown a little bit while you're queuing up your next item.

00:58 We do pay attention to Rich and Will McGugan, in the show.

01:02 And I'm not going to talk about them too much, except for that.

01:06 I noticed that Rich, updated, we got a release note that they updated their Markdown parser and, from something called common mark, which I was familiar with to mark down it pi.

01:21 I was curious about this, so I went and took a look.

01:25 The Common Mark library, if we go take a look at that, that's up on PyPI, Common Mark, no big deal.

01:34 But if we go look at the source in GitHub, it says this repository has been archived by the owner on November of 2022.

01:43 Oops. If we look down a little bit further, it says, warning, it's deprecated.

01:50 We recommend using markdownit.py.

01:53 Okay. I haven't used it, so I wanted to go take a look.

01:57 Markdownit, I think I'm saying that right, is it's built on top of another markdownit tool.

02:06 Markdownit is its own tool and the markdownit.py is a Python wrapper around it.

02:11 So great.

02:12 So it's, do we have to worry about this one going out of fashion as well?

02:18 And I think we're probably safe because the project, this is a part of executable books and executable books is a project to try to build more collaboration with Jupyter notebooks.

02:29 So I think we're safe now.

02:31 So to use Markdown for PI for a while.

02:35 And I just did a quick, there's a live demo thing.

02:38 some converting Markdown.

02:41 It's a nice package.

02:43 You can do it by itself.

02:44 You can convert from Markdown to HTML on the command line, or you can use it within your code.

02:51 It's got some nice features for in-code, pulling in different plugins and stuff if you have different specialties.

02:59 If we go back to the original release, one of the reasons why they did it wasn't just the reasons why Rich started using it, isn't just because it's been, the common mark has been deprecated, but they say it will allow us to implement a number of additional markdown features in the future.

03:17 This is pretty exciting.

03:19 I don't know a lot about markdown it, but I am going to check it out.

03:25 >> Well, if it's used by executable books and it's used by Rich, I think those are some pretty solid endorsements for it.

03:33 I saw that it got installed as well, and I wasn't sure why, but now I see that it's from Rich.

03:38 >> Yeah.

03:39 >> I use Markdown too, and I have no idea what the status of it is, but there is so much of our website that runs on Markdown.

03:48 Every episode page you visit, that's a Markdown page.

03:51 There's a bunch on Talk Python, a whole bunch of Talk Python Training.

03:54 Anything that's better for Markdown, I'd definitely be wanting to give that a look.

04:00 >> I don't want everybody using the same thing because we do want some different tools to build on each other and stuff.

04:08 >> Indeed.

04:09 >> Yeah.

04:10 >> All right. Should I sketch out the next idea for you?

04:12 >> Yeah. Let's take a look.

04:15 >> Let's take a look. Oh my God, this is fantastic.

04:19 This was sent over by Jake Furman.

04:22 Jake said, "Have you seen Sketch?" We've seen a lot of AI coding assistants.

04:29 We've talked about the potential bordering on violating license agreements and stuff of GitHub Copilot.

04:37 Everyone has surely heard about ChatGPT.

04:40 I have friends who are not even a programmer, they come, "Michael, I'm working on some programming thing because of ChatGPT." I'm like, "All right, well, interesting.

04:47 We'll see where all that stuff goes." But this thing called Sketch is like a simple view, a simple way to ask natural language questions of your data that's contained in a Pandas DataFrame.

05:01 >> Okay.

05:02 >> The way it works is you go and it's not built into the editor, it's part of monkey patches, I believe, Pandas so it adds a function to Pandas called, I think, ask.

05:17 Let's see, it's .sketch.ask or .sketch.howto or sketch.apply.

05:24 So these are the different kinds of prompts that you can give it.

05:28 >> Okay.

05:29 >> So you just pip install this, and once it's pip installed and you import it, that's all you got to do.

05:34 It lets you ask data cataloging questions like, is there any personally identifiable information PII within here?

05:43 Is there a phone number or is there an e-mail address or is there a social security number?

05:47 This could be some big large dataset and you don't want to save necessarily that or share it, you can ask it and it'll ask you how to, It'll basically tell you how to get rid of it as well.

05:56 Also, give me descriptions of this dataset, which is nuts.

06:03 Also, it does data cleaning and masking to, for example, removing the PII, and it'll extract some features and just lets you visualize stuff.

06:12 The best way to do this is to watch this one-and-a-half minute video here.

06:19 What it shows you is, here's a Pandas DataFrame read from a CSV, and it's got like an order ID and a price and a date and an address and they just say dataframe.sketch.is there any personalizing information or any personal information?

06:36 It says yes, these fields.

06:37 And it says, how can I remove it?

06:39 And so it writes out the code, you just copy and paste that, boom.

06:42 Now it's removed.

06:43 And then it says, give me a friendly, see if I can pause this, going quick.

06:47 It says, can you give me friendly names?

06:51 Stop moving.

06:52 Can you give me friendly names in a single sentence description of each column?

06:55 Format output as an HTML list and boom, it says index, numerical index, the product purchase in each order, order ID, a unique identifier for each order, purchase address, the address of the customer who placed the order, and so on and so on.

07:09 >> Nice.

07:10 >> Isn't that neat?

07:11 >> Yeah.

07:11 >> Yeah. When it spits out the answer, it puts a little copy, tags you can copy.

07:17 You can ask it, how do I do this?

07:19 how to extract city, state, and zip from the address, which is a single string, and it writes the code to do the split statements to pull it apart.

07:29 Telling you this thing is, it's not going to be perfect, but it's going to be pretty good.

07:33 It's pretty interesting. I just jumped in.

07:35 What do you think of this, Brian?

07:36 >> I think it's great.

07:37 Hey, Sketch, how do I remove the data that disproves my hypothesis?

07:42 >> Exactly. I'm going to need the data that shows a downward trend here.

07:48 Can you extract just those rows?

07:50 [LAUGHTER]

07:53 Anyway, people can check this out.

07:54 I think it's pretty neat.

07:56 It says, "In the future, we plan to update the prompt at this endpoint with our own custom foundation model built to answer questions more accurately than GPT-3 can with its minimal data context." Because it doesn't know a lot about your data.

08:12 >> Yeah.

08:13 >> You can also directly call OpenAI directly.

08:16 It says, and not use their endpoint by using your own API key.

08:22 There's that. Anyway, it talks about how it works and what it sends over.

08:26 >> This is pretty powerful though, because there's so much data for people using and different things.

08:33 We expect it now.

08:35 I was working on a project just recently where somebody did test result analysis and a question of like, Can we just put natural language strings in there to query it?

08:47 Why do we have to end?

08:49 People are expecting this all over the place now.

08:52 >> I just want to highlight just the last one there.

08:55 It says, go to the data frame, sketched on how to using Plotly, plot a map of the total sales in each state.

09:02 Boom, you get a perfect interactive map, a geographical map of the United States.

09:10 That's pretty neat.

09:12 >> Yeah.

09:12 to be able to just jump in and do that.

09:14 Like sure, you could go search and look that up, but instead of just doing df.head, df.tail, and so on, how to?

09:23 >> I could think I'm ready to hang up a shingle for data science side job.

09:29 >> I'm pretty sure that you and I could pass as at least junior data scientists now.

09:34 >> Just with this. Yeah.

09:37 >> Anyway, I think this is pretty cool.

09:41 I'm pretty psyched about it.

09:42 >> It's pretty cool. People should check it out from what I can tell it doesn't cost anything.

09:46 So just play with it, which is really nice.

09:50 Also, before we move on, I just want to tell people that this episode is brought to you by us.

09:56 So there's a whole bunch of Python courses that if you get inspired over at Talk Python Training, we've got the Python 3.11 guided tour, which a couple hours of hands-on show, that's awesome.

10:07 Python data visualization.

10:09 So if the sketch stuff was interesting, there's a bunch of that there.

10:12 Oh, there's also something on pytest, isn't there?

10:15 - Yeah, highly recommend that course.

10:17 It's great.

10:18 - I do as well.

10:19 All right, so if you want to support us, the best way you can do it is to support our other work as well as share the podcast with your friends.

10:26 So thank you, thank you, thank you for that.

10:28 - Yes, I also want to thank the Patreon people 'cause they're, it's great.

10:32 We don't really talk about it much, but you can support us through Patreon as well, and we very much appreciate it.

10:37 - Absolutely.

10:37 All right, what you got for our next one, Brian?

10:40 - Well, I wanna go in circles a little bit, actually going back to the year 2021.

10:46 No, in 2021, we covered a article by Hinnick called "Subclassing in Python Redux." And it's a great article to talk about like just how to subclassing and dealing with classes in Python and just to get your head around it.

11:04 So I did read it and I enjoyed it.

11:07 And then this year, I came across the problem I'm like, I think I can solve it with something I read in this article.

11:14 I'm going to scroll down to the thing specifically.

11:18 We have nominal subtyping with abstract-based classes.

11:22 I tried that, but I decided to go with structural subtyping.

11:26 With structural subtyping, basically, my understanding is it's like duck typing, but it's like strict duck typing.

11:36 [LAUGHTER]

11:38 And it says here, as you can see, if you've got something calling two things, two classes talking to each other, one of them doesn't have to know about the other.

11:47 So in this example, we've got a reader and a foo reader, and it's just a reader protocol.

11:55 We just need to know that there's a reader protocol here.

11:58 So I kind of like, I just knew this was here and I'm like, how do I apply this?

12:03 So I went ahead and wrote up an article called fixing circular imports in Python with protocol.

12:10 And this is directly from this.

12:11 So it's just a zoom in on one special case.

12:15 I really have, like if I've got a class, director and actor, and they talk to each other.

12:21 So director tells an actor to do an action.

12:24 No big deal.

12:25 It has to import the actor, right?

12:26 So the actor gets a director.

12:28 So it gets data from the director.

12:31 And so it has to be able to call it.

12:34 Well, I could just pass it in self.

12:36 And since we have duck typing, it all works fine.

12:39 We just call director get data.

12:41 So far, we have no types.

12:42 The problem comes in when we have types.

12:45 If we add types to the whole mix, then when we add types to the director, it's no big deal.

12:53 We just add types to everything.

12:54 But to add types to the actor, suddenly the actor needs to know what type the director is.

13:00 And that's where we get the problem.

13:02 And that's exactly the kind of problem that I ran into is I wanted to add typing to this to a similar problem and it blows up.

13:11 It actually blows up really bad.

13:12 You can't, Python just says you can't do that.

13:15 It's circular import.

13:16 So the protocol solution is really slick and I'm going to scroll down to the answer.

13:22 The answer is just, I just need a little bit of a snippet of code that says, I've got this class that is derived from protocol, but it's just got a get data function.

13:34 and that's all I know about it.

13:35 I know what it returns, and that's all I know.

13:38 I can say that my director really is that type, and that's it.

13:41 The actor has to change to say what type it is, and the rest of the code just works.

13:48 I don't have to change the director, I don't have to change the test code.

13:52 It's just this one file that I'm changing.

13:56 I like this because I'm adding typing, I'm not changing the code at all.

14:01 And the only thing is that, yeah, anyway, it's a slick way to use it.

14:06 - I think so too.

14:07 It's a little bit like interfaces, but less in the traditional Java or C#, the IEnumerable or whatever type of interfaces.

14:16 - Yeah, the first, and then Hinnick goes through abstract base classes also.

14:22 And that's one thing you could do, is you could have a base class that has this get data function, and both the director and the actor know about the base class.

14:30 but you don't really need that.

14:33 Nobody else needs to know that except for this one file.

14:36 So protocol's a nice hack.

14:38 - Interesting, yeah.

14:39 Jonathan in the audience is asking why not just use abstract base classes?

14:43 - Yeah, they both work.

14:44 So it's just a personal preference thing.

14:46 - Kim in the audience is asking, as a question of style, do you prefer an ellipsis over the keyword past to indicate an empty function?

14:57 - Well, I'm warming to the ellipsis a lot because I write a lot of test code and I write a lot of example test code.

15:07 And if I say an empty function that is a test function and I use dot, dot, dot, it's clear that I'm not finishing it.

15:18 Whereas if I say pass, I've actually had some people get confused and think that the pass keyword is how you pass a test.

15:26 >> It's not. It can be.

15:31 >> Well, you're not going to raise an error in that test method.

15:34 >> Yeah.

15:35 >> If you got a pass in there. It's a way to pass it.

15:37 >> Yeah.

15:39 >> I like it too to say, "Look, I don't want anything here.

15:43 I don't intend to come back and fill it out. This is it." >> The ellipses match how we do type stubs as well.

15:53 I think I'll use ellipses, but I don't know if they have to.

15:57 >> I think they do as well.

15:59 If you go and look at TypeShed, and you look at the stubs in there, like let me just grab one rando.

16:07 Yeah, all those have dot, dot, dots.

16:10 Dot, dot, dots for their default value, dot, dot, dot for the implementation.

16:14 Kind of gnarly looking if you open them up, but there they are.

16:17 So yeah, to me, I feel like I'm matching the stub definition.

16:22 I don't intend anything to be here.

16:23 >> Exactly.

16:25 >> Right. All right. Ready for the next one?

16:27 >> Yes.

16:28 >> This is a simple and I think will be useful to many folks.

16:32 This one comes to us via Rud Vanderham.

16:38 Rud Vanderham, thank you.

16:39 Sent it over and recommended, also created it.

16:42 We've all seen code samples that, I don't know why, but I guess it's a decent way to explore it, but I've always found it a little bit difficult to consume.

16:55 Basically, I want to show you how this bit of code works in the REPL.

17:00 Here's what happens.

17:01 Copy this and use it.

17:04 >> Yeah.

17:04 >> When you see that here, you have the triple arrow and you write some code, the triple right arrow, greater than, greater than, greater than.

17:12 You might do a for loop which then has the indents and it does a triple dot dot dot to show you that, well, now you're still in the same command.

17:20 Then eventually, a closing dot and you run it.

17:23 If you print out, you just say a variable or a response, then that gets printed without any of the dots or the greater thans or anything.

17:30 It's just straight up as if you said print that.

17:33 If I do copy this and I want to explore it, I've got to remove all the triple dot, dot, dots, unindent it correctly, remove the arrows, common, it's just like, yuck.

17:47 It's just a hassle.

17:49 So enter unravel the, the inverse of rebel, where you say, take this code and make it runnable for me.

17:59 Isn't that cool?

17:59 Yeah.

18:00 Super simple.

18:01 Like, even if you're in the rebel, you can't take what you're copying from someone else's example and put it in the rebel because then you'll have triple greater thans and so you just take this code and the way it works is you copy it to the clipboard.

18:13 Your code sample that you got from this rebel thing, and then you just call Unrapple as a CLI and it replaces the clipboard with contents of exactly what you would want, and it even comments out the output.

18:27 What wasn't code is now a code comment.

18:30 >> Wow.

18:32 >> Nice.

18:32 >> That's perfect.

18:33 >> Yeah, really perfect, right?

18:34 >> Yeah.

18:36 >> I can certainly see that this is a handy thing that people may want to install.

18:43 When you see the instructions here, it says you can just use unrapple.py, and the way that you run it, it says up here somewhere.

18:53 Okay, what you do.

18:54 What you do is you copy it and then you run.

18:58 He's updated. It just says unrapple.

19:00 It used to say run unrapple.py, but it's also on PyPI, which means that you can pip install it.

19:07 But I would not pip install.

19:09 This is not the thing that goes into a virtual environment to be part of a program.

19:13 This is a CLI tool that you just want to have.

19:16 Yeah.

19:16 Available. So I would replace pip with pipx.

19:20 And then you just globally have this command on your computer.

19:23 Just happens to be implemented with Python.

19:25 But you copy it, you know, just type unrappel.

19:28 And then, you know, whatever is in the clipboard is now better.

19:32 Okay.

19:33 It doesn't say use pipx, but I think it, I think that's the way.

19:36 That's certainly how I would do it if I'm going to install and use this thing.

19:38 There's a way to set up an auto hotkey on Windows only.

19:41 I have no idea about this, so y'all are on your own there.

19:44 Okay, so how do you do how do you use it on the command line?

19:46 You're just you just type the word Unrapple once you've installed it in the path which pip X of course does and then you paste your code or use it - yeah So you copy your code type unrapple on the command line. It looks at your clipboard Okay on rebels and then it replaces the clipboard like basically with a new copy. Oh, that's the command. Yeah, that's exactly Yeah, yeah, so really easy It also says you can import it into a program and use it if for some reason you feel like that's the thing you want and it has special handling of the underscore Which has special meaning in the rebel like it knows that about underscore and treats it. Okay. Well like for instance like Somebody could like maybe Change be Python or something like that to to allow you to paste right in. Yep. Yeah neat Yeah people out there think it's nice This is a nifty little tool says Kim And see it uses Tkinter presumably for clipboard access. I believe so as well It should use paper clip. I believe he paper paper clip rather hyper clip If if I were saying it that's like a super small simple little thing cross-platform copy and paste but you know doesn't matter Really what it uses to accomplish copy and paste the Tkinters like built-in now is just normally >> Yeah, that's true. I guess there's no dependency.

21:07 That's right, there's no dependency.

21:08 Okay, maybe I'll take that back. Maybe that is better.

21:10 Anyway, I love Paperclip.

21:12 This is cool. I don't know if you're working on a book or if you're learning a lot where you're grabbing stuff off of tutorials.

21:20 I spoke a tiny bit disparagingly of this, because the style of showing the REPL output, because I'd rather show an example that has executable code and a print statement.

21:32 I already write this output like, here's the code, comment, here's the output.

21:39 I don't know, it seems a little more reasonable to me, but whatever, people can write them in any style they want, and this Unrapple will roll it from one to the other.

21:46 >> Yeah. You can also use a doc test to check your rebel docs.

21:52 >> Yes, exactly.

21:54 While they're giving a challenge to the community, I think someone should write a PyCharm and VS Code plugins using this so it becomes part of the part of the editors as well, which would be pretty neat.

22:07 Paste as, paste from, paste from REPL.

22:11 >> Yeah, that'd be cool.

22:13 >> But it's already really handy to have it just pop over the terminal and run this.

22:18 >> Yeah. Awesome.

22:20 >> I guess that's it for all of our items, isn't it?

22:22 >> I think it is.

22:23 >> Yeah. We've come to the end of all of the main things.

22:26 What do you got for extras today?

22:28 >> Well, I was going to mention that I wrote an article, but it was my item.

22:32 >> Correct.

22:33 >> That's it. How about you?

22:34 >> There you go. I got a couple of quick ones here.

22:37 I think this happened since our last show, but pretty much right after it shipped.

22:42 If you have Git, you should update it.

22:45 There's remote code execution level vulnerabilities in the Git client that is sitting on your machine if you have less than 2.39 maybe even less than 2.39.1.

22:58 I can't remember the exact version.

22:59 But until last week, there was a problem.

23:03 I'll link to this over here, but you don't really want that.

23:07 Yeah, affects 2.39 and older.

23:09 >> I know.

23:10 >> So careful.

23:13 I mean, if you're only pulling stuff from your own repo and only you can put stuff in your repo, you're safe.

23:18 But if you're checking out someone else's repo or PRs or things, you might not like it.

23:24 Okay. Another one here.

23:27 Over on, quite on training, we, as much as I don't like to, had to add, some kind of validation.

23:36 And it used to be that dreaded recapture thing from Google.

23:40 And I would say, find all of the stoplights and you're like, well, is it the pole of the stoplight also to stop?

23:47 I mean, technically it is, but it's not the light part.

23:49 And it'll be like, try again.

23:50 You're like, no.

23:51 Right.

23:52 but because it's the internet, we can't have nice things.

23:56 Unfortunately, I had to put something there to slow people down because they were just pounding away on it in various ways that were not ideal.

24:05 We had stuff to mitigate it, but once you start encountering botnets, then you kind of get yourself into a bad place.

24:10 So I had to put the Recaptcha stuff there to keep people from messing with it.

24:15 And I hate Recaptcha, but I did it because I had to.

24:19 So then when Cloudflare came out with Turnstile, I'm like, "Yes, this is so much better.

24:24 It doesn't ask you about chimneys or fire trucks.

24:28 It just does a little like, run some code on your browser to prove that you're not just requesting and posting this from some bot, but it's a real browser, off it goes, right?

24:38 So that's great.

24:39 Eli Cobbler said, "Hey, this is awesome that you got this working.

24:44 "I can't get it working.

24:46 "How do you use it?" So I ended up posting a gist, which has an implementation of this using like a faux web framework.

24:56 Didn't really, I didn't want to tie it to Flask, but it just says like, here's your thing that handles the forum posts.

25:01 Do this.

25:02 It shows the HTML bit, which also CloudFlare would show, but they don't have the Python version.

25:07 And then it has the Pydantic based API validation stuff here, which is, this is the gnarly bit.

25:16 So he was like, I can't get it working.

25:18 Can you post this?

25:19 Boom.

25:19 And apparently he got it working as well, But I'm sure there are other people who equally hate find all the haystacks, find all the fire trucks, and would rather use churn style.

25:28 But it's kind of tricky to use.

25:29 So they can also use that gist if they so wish.

25:32 [LAUGHTER]

25:34 Yeah.

25:35 That's funny.

25:38 Yeah.

25:38 David Poole says, Google's CAPTCHAs made me doubt my own humanity.

25:43 [LAUGHTER]

25:45 So I always thought it'd be fun to do trees instead or like plant identification.

25:50 Like find the azaleas.

25:54 >> Find the azaleas.

25:55 >> What?

25:56 >> Let's see. So when I talked about getting a bunch of stickers on my computer the other day, remember that?

26:05 >> Yeah.

26:05 >> Well, one of the stickers I put is, I'm not a robot and it's like a validated reCAPTCHA.

26:10 So I just thought that was fun too.

26:12 >> But that's cool.

26:13 >> Yeah. Anyway, so if people are interested and they want to try out Turnstile, which is way more privacy oriented.

26:19 Also nicer because you don't have to interact with it.

26:21 Just a little hard to work with.

26:22 Here's some code that they can try that at least one other person has been successful in using.

26:26 So that's a tepid but somewhat good testimonial. How's that?

26:32 >> That's nice.

26:33 >> All right. Thank goodness it wasn't in the JavaScript, Brian.

26:36 >> Yeah.

26:37 >> Because if it's in the JavaScript, I don't want to have to go debug that. Do you want to debug it?

26:40 >> No, I don't want to debug it in the JavaScript.

26:42 >> It might make you so crazy as if to go and literally write a song about how much you don't want to go into the JavaScript.

26:50 >> Yeah.

26:50 >> Now, I am very unskilled at music, but Dylan Betty is actually quite skilled and has a whole bunch of really funny songs here like the big rewrite based on American Pie, when eight and a half minute long song I'll point out, and he wrote a programming song against it.

27:14 >> Awesome.

27:14 Your API is a hall of shame.

27:17 You give rest a bad name based on the Bon Jovi song.

27:20 But neither of those are the one that I'm referring to.

27:23 No, I'm referring to the song called Bug in the JavaScript based on Piano Man by Billy Joel.

27:30 This is good stuff, right?

27:32 >> Yeah.

27:33 >> Now, it's six and a half minutes, so there's no way I can play it, but it starts out like you've checked all of your database indexes, you've tuned all your API hooks.

27:41 You're starting to think that you might need a drink because there's only one place left to look.

27:46 There must be a bug in the JavaScript because everything else is built properly, but the front end is a pile of crap.

27:53 It's so fun.

27:54 >> I like it.

27:56 >> I'm starting to think that I might need a drink because there's only one place left to look. It's really good.

28:01 I recommend people put this on.

28:03 If you're working with Angular or Vue or some front-end framework, you can play it loud in the office just to share a little bit of the feeling.

28:13 Yeah, anyway, that's what I got for you this week. People can go watch the YouTube video Thanks Yeah, you bet and Thanks for being here. Thanks everyone for listening as always. Thank you. Bye. Bye y'all

Back to show page