Python Bytes - #378 Python is on the edge

Episode Date: April 9, 2024

Topics covered in this episode: pacemaker - For controlling time per iteration loop in Python. PyPI suspends new user registration to block malware campaign Python Project-Local Virtualenv Manageme...nt Redux Python Edge Workers at Cloudflare Extras Joke See the full show notes for this episode on the website at pythonbytes.fm/378

Transcript
Discussion (0)
Starting point is 00:00:00 Hello and welcome to Python Bytes, where we deliver Python news and headlines directly to your earbuds. This is episode 378, recorded April 9th, 2024. I'm Michael Kennedy. And I'm Brian Ocken. And you can follow us over on Mastodon. We're all on Fostodon, at Brian Ocken, at M. Kennedy, and at Python Bytes. We'd love to talk to you over there. We're also on X Twitter, if you want to be there as well.
Starting point is 00:00:27 But we probably spend a little more time on Mastodon these days. Support the show by supporting our work. We have, obviously, a bunch of courses that talk Python training, Brian's complete PyTest course, and the Patreon as well. Be part of the live show if you wish. You can see the video version. Absolutely not required required but always awesome to have people in the audience to give it a little bit more of uh multiple perspectives you know
Starting point is 00:00:50 we'll get some of that coming on in a second and you can check that out pythonbytes.fm live usually tuesdays at 10 a.m so that specific time finally if you want an artisanal, handcrafted digest of what we talk about, sent to you an email form, even if you don't listen, well, head over to the website, Bethumbys.fm, click newsletter right in the middle and enter your email address. We will not share it or do other nefarious things. We just want to be able to contact you and have a chat if you wish. So Brian will be sending out something cool to everyone this week as usual. Brian, do you have like health news or something you got to share? What's going on here, man?
Starting point is 00:01:31 Well, I've found a pacemaker. Okay. My ticker's doing good. No problems with that. But you can control your heart with Python. Oh, okay. Actually, it's not. You can't control your heart yet. Actually, I don't know if you can. But I ran across a project from Brandon Rohr called Pacemaker. And what this does, it's not controlling your heart. It's controlling time per iteration loop. So with, and I don't know if this has anything to do with UV or not,
Starting point is 00:02:11 but one of the things that UV has brought us is really fast installing of lots of packages. If you have a lot, why not? So having, having, this is a small package. So this is in Brandon's own words on the read me. I think I, I think I saw this here it is um essentially it's it's a glorified um uh glorified snippet uh instead of a snippet he wrote a package which i love this so uh i'm taking a look at it it's a small package good good uh example though for how to do a package like if you take a look at the the pipe project.toml, it's pretty concise, it's using setup tools, which is fine. But it shows you how easy it is to put a package together, which is pretty fun. The in also with the code, I was taking a look at the code. So what this does is not is not like terribly like earth shattering, but it, it just sticks around, you tell it that you want to, it's kind of like a metronome thing.
Starting point is 00:03:07 So you've got some code that you want to run. And here's an example. And you just say pacemaker beat and it like waits. So it's a busy wait or it does a sleep or something like that. Yeah, sleep. But it sleeps and then comes back alive, does its thing, and then goes back. So you can have it be, like in this example, it does a beat for 100 times. And this is important in a lot of different types of code, a lot of monitoring code, a lot of other things.
Starting point is 00:03:37 And busy waits aren't necessarily always the greatest, but in a lot of cases it works great. The thing that I wanted to point out about this, there's a few things that I love about this project or just wanted to point out. Really great documentation for even for a small project. Also, I had kind of forgot about time monotonic. So this is using time monotonic when it does the time comparisons. There's a couple times of timestamps, types of timestamps and monotonic, and then there's a monotonic nanoseconds if you really want to do really tight loops. You could modify it for that. The thing that monotonic does is it makes sure that all time deltas are positive.
Starting point is 00:04:16 So even if something happens, like you change your system clock between timestamps, times between timestamps it'll still do it correctly so that it is monotonically you know that's interesting another thing could be you know like maybe don't even you don't change it but we all have our clocks set to auto adjust right and it could come online and auto adjust or something yeah that and that would be weird if like suddenly it uh especially with uh especially with this project where like what did one of the things it does and he warns about this is i mean sleeps aren't exact science and within this isn't a real time especially on non-real-time operating systems but i don't think python's even a real-time thing so uh it's an approximation for how long it's
Starting point is 00:05:03 going to sleep but it tries to correct it. So if you, if you, if you slept too long, it tries to do more events. So, so that on average you get the average amount of times you're running be correct. So it's kind of a cool library. Check it out. A couple of things I wanted to point out about it. One is the cool use of monotonic. The other one is a good, good readme documentation, even for a small thing. But there's, there's no tests. Brandon, come on. Anyway, I'm not, I actually, there's a couple things around this. I don't think that people should stop from putting a code online just because they don't have tests yet. It might be that somebody could contribute and add tests. It also might be that for you, if you're using this all the time,
Starting point is 00:05:50 if Brandon is using this all the time, the tests are covered by the calling code. So you're using this and you're covering it. And I just want to, I guess I want to point out to everybody, for any library you're using, you probably should have tests that cover the part of the library that you depend on, even if they do have tests. So this one doesn't so you, you know, buyer beware. But even if they do, you probably should make sure that it is really working the way you think it's working. So anyway, Yeah, sometimes it's just hard to have meaningful tests. Like a while ago, I released that you mommy Python event library,
Starting point is 00:06:25 right? For the analytics tracking. And all it does is serialize the messages that come to it from the Umami API. I can test what my perception of the API is, right? But the real danger is that that API changes in some way or another, or, you know, it's just 90% of the fragility is outside of, if you mock out all that stuff, then, well, you know, you're just kind of testing your view, which hopefully is already mostly encoded. And I mean, it's not completely useless, but it's, is there certain things that are just tricky to get right. And testing isn't necessarily always automated testing. It could be like that you're using it, the Umami API they're using. You're using it, so you'll know when it breaks.
Starting point is 00:07:10 Yeah. And other things are like your use of it will break or the calling code. And I don't think that people should, especially with the internal stuff, maybe on PyPI, maybe we should have a little bit like things eventually should have tests probably but the for internal projects where you're sharing code uh it's better to package and share code than to not share code so if if writing tests is what's stopping you from putting it in a central repository don't let that stop you it's it's better than just i mean we we have snippets like he said it's a glorified snippet. So why not package it? Yeah, very nice. I like the monotonic. That's news to me.
Starting point is 00:07:47 I hadn't paid attention to that before. Yeah, it's cool. I want to try the nanosecond monotonic. Yeah. Other stuff that people might want to pay attention to, and this is not to drum up a bunch of fear and concern so much. It's not huge news in that regard to people, but it's more to just sort of put out there
Starting point is 00:08:07 what the PyPA, the Python Packaging Authority folks to deal with, you know, kind of say thanks. So the news comes to us from Bleeping Computer, which usually does pretty good news, but this article is pretty vacuous of information. But the title says a lot. PyPI suspends new user registrations to block malware campaign.
Starting point is 00:08:27 And so there's some interesting things in here. So it basically says, look, when was this? It was like, this is March 28th, a little bit, a couple of weeks ago. PyPI has temporarily suspended user registration in the creation of new projects to deal with ongoing malware campaign. Then it proceeds to tell you what, what a IPI is basically.
Starting point is 00:08:46 And then this is, Oh, look, there was some, some problems. People are uploading bad stuff, but it doesn't tell you, for example, like what projects, what did the malware do? So if you, uh, jump over to the status.python.org, actually that tells you about the status of Python infrastructure, which is kind of cool. And it says, same deal. This is official reporting. Did it say how long the event went for? No.
Starting point is 00:09:14 Just that it was a thing. I guess if I did math, that would be 10 hours, about. 10 hours, 30 minutes. That's a ways. And this is the real article you want to read. It is over on Medium, PyPI is under attack, project creation and user registration. It's been adhered to the details by Yehuda Gelb. I hate to link to Medium.
Starting point is 00:09:33 I hate Medium. I think it's a crummy place. I don't know. It just seems gross. But they have really good details. So basically, this was a typo squatting attack. And what's interesting, it was a multi-stage attack, stealing all sorts of things, crypto wallets, obviously. But real sketchy is browser cookies. So you're logged into your bank for a session that's good for 20 minutes. If they could grab that and log in as you,
Starting point is 00:09:58 that might be less good, or log into your email and reset stuff and so on. So what happened is there was a bunch of packages in here somewhere. There's the package list. So you can just, you can see, these are all about the capturing people who, one, misspelled things, but also just like didn't quite understand. Like for example, Brian, one of the packages is requirements.txt, requirements.txt without the dot or requirements. So if you say pip install requirements, right, without the dash R or the dot txt, you're getting this. And there's a bunch of others, you know, TensorFlow and Selenium and there's like, they're all
Starting point is 00:10:37 over the requirements business. So it's just everywhere. And the deal was basically each one of these had a malicious setup.py, which is why I think it's kind of interesting to look at. And inside the malicious setup.py, it encrypted using the cryptography.fairnet library, which just listed as a dependency, I imagine. It then decrypts some kind of URL that it very politely lightly it's funny it's like using little analytics it'll like passes the query string of like which one hacked me before you get going they download some kind
Starting point is 00:11:12 of thing that installs and runs and basically installs a backdoor ongoing thing so even if you pip uninstall this there's this thing running that just monitors your system which is not ideal to be honest that's not what you want no one does not want this yeah oh and henry out in the audience says in addition to all the stuff i said that actually blocked all uploads for a few hours and couldn't it couldn't upload build 1.2.1 you know what a hassle huh this is why we can't have nice things you know come on now yeah so there's a i guess there's a lesson here to make sure that you're careful when you pip install something because even if you catch it and you're like oh that's not what i meant you may have already done damage isn't there a way i don't
Starting point is 00:11:55 remember off the top of my head to say pip install but don't run only use wheels like don't allow anything to run i don't know i'm not sure i feel like there was there probably some of the audiences going yes yes of course how do you not know this but it doesn't mean this is not going to happen it just means you have to write use the code you know as opposed to just the act of installing it or sneaking into a requirements file somehow yes and i knew henry would come through and say dash dash only dash binary indeed that's it all right, you know, they were on top of it. The fact that this was completely dealt with within 12 hours is pretty awesome. The fact that it has to happen, not so much.
Starting point is 00:12:33 Yeah. All right. Over to you, Brian. So I want to just, I guess the world has changed a little bit with UV other other rustified things um so we have a there's an updated doc or an updated blog post from uh hinnick python projects python project local virtual env management redo or redux um and the so he's just started really talking about all of the tools that he uses around virtual environments and i kind of just enjoyed it because it matches my own use quite a bit so uh some some caveats in here that says this is what works for me i'm not necessarily saying you have to use this
Starting point is 00:13:16 of course but it's a good list of uh kind of how you're how dealing with dealing dealing with uh virtual environments um the major thing that happened is UV, and it's changed a lot of how we deal with it, but it makes things a lot faster. So some of the things I thought I want to revisit in here, so it's pretty great. So he's using.venv in each project directory, and it's close to what I use.
Starting point is 00:13:44 I use the venv. I just don't what I use. I use the V and V. I just don't use the dot. I do as well, Brian. And it's, I know the dot means kind of like, just, it's not real important. Put it to the side. But I love to be able to open up a project in Finder or Explorer and look at it and go, all right, this one has a virtual environment. Oh, this one doesn't. I need to make one.
Starting point is 00:14:04 Yeah. And I don't want to have to keep like show hidden files, hide hidden files over and over. all right this one has a virtual environment oh this one doesn't i need to make one yeah and i don't want to have to keep like show hidden files hide hidden files over and over to know so while i appreciate the dot like i'm 100 with you yeah um der invent der env um i i think i want to revisit this i've tried it a while ago but i haven't tried it lately um is a way to have an envrc file or.envrc file in your directory. And that gets run when you cd into it or something. Anyway, I think this is cool stuff, but I don't use it. Do you use this, Michael?
Starting point is 00:14:40 No, I love the idea of it. And in practice, I just haven't done it now. There's some cool tricks in here that he's using that I kind of want to get back to trying it again. So I guess thanks, Sinek, for bringing this up so I can take a look again. Using Astral's UV over Rai. I kind of never went to Rai,
Starting point is 00:15:00 but it looks like he switched from Rai to UV. Python installations now switch to both just always using python.org downloads because they're now universal builds mostly. And it just sort of works. And that's what I've been using also for installing Python. Dead Snakes for Linux, of course. And then there's a mention of Python build standalone for various other projects that are needed. So that's kind of neat. A discussion about
Starting point is 00:15:30 unpinned versus pinned packages and then also using a.python version default within a directory. And the tie-in back to Durenv is kind of cool is he's got some tricks in here. So if you drop in a.python version default,
Starting point is 00:15:50 it just tells you what version to use by default. That in a directory, and then you check it in so that the development environment can be recreated easily. And he's got some in VRC that's part of Durenv that has got a little snippet that will activate it based on which Python you're using, using UV, which is kind of a neat little
Starting point is 00:16:11 trick to use Durenv and Python versions and UV all together. So I'll definitely have to try that. That's kind of a neat trick. Using all of this as well helps with GitHub Actions. He's describing how to do that as input to the setup Python GitHub Action,
Starting point is 00:16:34 which is, I didn't know you could do this. So you can say the Python version file, and you can just give it that Python version default so the GitHub Actions uses the right default version. It's this cool trick. He's also, he's got some tricks on how to use, use it with the fish shell. I don't, I don't use fish, but for those fish users,
Starting point is 00:16:54 that'd be great. The, the other part that I wanted to, that I really enjoyed seeing is because of all of this, the, in which version and stuff you can use, oh, he's using the requires python and py project toml which i'm i'm using for everything now but the um he's has a way a little little sed snippet to parse that out of the py project.toml and pass it into um using the get github actions yaml file uh to pass it to a Docker build,
Starting point is 00:17:25 but it's grabbing this version of Python so you could use it for other things within your GitHub Action or something else or other tool if you needed to pass what Python version to use. So that's some pretty clever things in here that I, something old, something new, kind of here that I, some, some, something old, something new, kind of some neat tricks. Yeah. Yeah. Very interesting. A lot of stuff to explore.
Starting point is 00:17:51 Interesting comments in the audience about liking and disliking all the magic, the auto magic. Well, and it's also another example of like, I kind of like these sort of posts, even for different people just to see this is how I work. This is the workflow I use. Not, not necessarily just focusing on one tool, but I use all these just to see this is how I work. This is the workflow I use. Not necessarily just focusing on one tool, but I use all these things together and this is how they work together. It's kind of fun to read. Yeah, yeah, absolutely.
Starting point is 00:18:13 Even if you don't adopt it, it's cool to just see the tools and the things you can do. Yeah. Yeah, all right. On to the next one. This one is super exciting. Super exciting. Okay, we need to talk for just a second about
Starting point is 00:18:26 Cloudflare and Edge workers. Okay. Now this is just a Cloudflare thing, but I think it's sufficiently interesting that it's worth calling out. Okay. So CDNs like Cloudflare, like bunny.net, the one that we use and stuff like that, have a bunch of what are called pops, points of presence. And traditionally, these have been, how do I get my files really close to you so they feel immediate no matter where you are in the world? So for example, I don't know about Cloud Flare's details, like their stats, but I know bunny.net has something like 115 servers that are points of presence throughout the world. And so if you try to, if you visit the PythonBytes.website, all the static content like images and CSS and stuff
Starting point is 00:19:10 are delivered from one of those pops like right by you. Yeah. And obviously the MP3s as well. Now, those things have started to have programmable models where in addition to just having the content of say a static file near you, they'll have some of the logic of the application near you. So if you've got like a React front end that talks to APIs,
Starting point is 00:19:34 maybe it's only talking just down the street to the point of presence, not halfway around the world to New York where our server lives, right? And this has traditionally been JavaScript. So here's, with that set as the stage, Cloudflare has these things called, I think they call them web workers. Yeah, something like that. We'll see in just a second. And those have been traditionally done in JavaScript. So there's
Starting point is 00:19:58 a whole infrastructure about distributed databases and things like that, that these workers can work with. It's pretty interesting. But the news is Cloudflare announces they're bringing Python to these workers with Pyodide and WebAssembly. So now you can start to program these edge devices, these points of present things like right near each other with first class Python support based on all the work of Pyodide and WebAssembly and all those things. Isn't that excellent? It is excellent.
Starting point is 00:20:27 This is a big announcement. I got the Omnivore page pulled up and the read time is 16 minutes. We're not going to go through all that. So let me just pull out some highlights here. And so one of the things that's really made this possible, made it interesting, is they've put a huge amount of effort into optimizing the runtime for javascript to make it work well and it's piodide's integration with javascript for that sort of runtime performance implementation side to make this work really really well so it says beyond just compile to web
Starting point is 00:21:00 assembly so they've each worker is uh what's called V8 isolate. So it's kind of like a container, but just less, you know, just an isolated version of the V8 runtime, which is the Chrome's JavaScript engine that also runs WebAssembly. Okay, so it says it's not just as easy as copying over the WebAssembly stuff and running it because they have thousands of these things running on one server. And if each one had to do full on startup for WebAssembly stuff and running it because they have thousands of these things running on one server. And if each one had to do full-on startup for WebAssembly, full-on startup for Pyodide, which is six megs and takes some delay to get going and so on, it wouldn't be practical.
Starting point is 00:21:36 So they've done all this work to kind of memory snapshot an almost running PIP installed setup version of this and then like deliver it to you upon request so there's a lot of shared memory or shared processing and here's how you write it brian from javascript import response async def unfetched and do your python and return some response how cool uh let's see there's a cool graph that compares what's VMs versus containers versus these these isolate things um like I said it's a lot more um sort of put together and one of the things that's pretty interesting here is it has support for fast api in langchain so there's a bunch of like
Starting point is 00:22:19 I said this is really long but let's look at the fast api uh version here there's a whole there's a whole example I'll point people at of code, Python worker examples under Cloudflare's GitHub repo. And so if you go to the fast API one here and you go into the source and you pull up the worker and you hide the symbol so we can all see, basically check this out. So this is Python code running effectively in a node style like thing in WebAssembly on the edge of one of these workers. And here's what you write.
Starting point is 00:22:48 From FastAPI, import FastAPI request. From Pydantic, import base model. Use app equals FastAPI, app.get. Here's your document you return. Here's your async function that you write. You can go do async things. Here's your Pydantic model with Python types. What do you think?
Starting point is 00:23:04 Here's your post, your puts, your gets. Oh, this is pretty cool. Yeah, and they've got some sort of database thing that it integrates with so you can have persistent data and so on. But like I said, I don't do a ton with these things, but I might start paying attention if I can do it in Python.
Starting point is 00:23:19 Yeah, definitely. Yeah, back in my day, a V8 isolate was just carrots. Exactly. It's the part that like sinks to the bottom and then you take it out, right? No, this, if people use Cloudflare already and those workers, this is super interesting. And if you just want to see some cool unique uses, and I guess in a way, one of the real first production uses of Pyodide and Python in WebAssembly. Yeah. Check this out.
Starting point is 00:23:47 Definitely. All right. That's all of our main topics, right? That is. All right. How extra are you feeling today? I have zero extras. Zero?
Starting point is 00:23:56 Zero extras. Just a bad joke. Zero. All right. Well, I got a couple. I'll go through quickly here for us. First of all, Brian Skin sent both of us a message and said, Look, there's a decent chance the podcast already is our audience has already filled you in
Starting point is 00:24:09 on this. But last week, since I talked about L Python, and the related projects are spearheaded by Andre Sertic. Brian, you are Brian skin, you are the podcast audience who has filled us in. Thank you. So he did. actually brian did a whole long hour-long interview with him on it and so people want to check out l python further which we talked about before check that out that's pretty cool next i really like this idea we talked about just path last time yeah and how it sort of helped you diagnose your path you know like duplicates missing directories all that kind of stuff listen to to last week if you want the whole details. But the guy behind it said,
Starting point is 00:24:49 hey, that was really awesome. You covered it. I'm going to create a badge, a Python Bytes GitHub badge for the project. What do you think about that, Brian? I think that's really cool. I do too. And so I'm going to try to set it up
Starting point is 00:25:03 so that there's an automatic github badge that people can put on their readme if their project was featured in python bytes and i'll put that at the top put the code you can get for that or something at the top of the show on the episode page it's not there yet but eventually uh you should be able to get a cool cool little badge like this that says my project was featured on the podcast on this episode and here's a link to it all within one badge yeah and the badge has like the little uh like the number shows what episode it was on which is yeah exactly instead of saying like python 312 it says python bytes and the episode number it's excellent yeah that's pretty pretty cool so yeah yeah thanks thanks for that i am as well let's
Starting point is 00:25:42 see what else oh brian we have a brand new server going strong now. Did you see? I did. That's pretty exciting. Was that a lot of work? It was a lot of stress, not a lot of work. So I decided it's time to upgrade the server, get some more RAM. More CPUs is always nice, but I couldn't reasonably get more RAM without more CPUs, so I'll just take them.
Starting point is 00:26:03 So we had some downtime for about 20 minutes actually last night. So if you ran into that, I apologize. That took out everything because this was not one of the Docker pieces out of the Docker. This was the host of all the Docker cluster. So it was gone, gone. And I couldn't even reasonably put up a we're down page because it was the host thing that was down, not part of the site. Right. Yeah. Anyway, now is there a I don't know where the reply went. Oh, maybe if I were I guess if I'm not logged in, it doesn't show me.
Starting point is 00:26:35 But we now have a suite machine running along just like before. So that worked out pretty well. Before that, I was so happy, Brian. We had for the last 30 days running a, a 99.98% global uptime. That was awesome. That is pretty great. I know turning off the server for 20 minutes, you know, with like 20 gigs of database records, it takes a while to copy that from one VM to another.
Starting point is 00:27:03 And so that's what took so long. It's only three nines, though. You need to work on that. Well, it's three nines and then an eight on the end. It's almost four nines. It's so close. I'm going to try to make it better now. But it went down to, I think, 99.94%,
Starting point is 00:27:19 which I think is still pretty darn good for some random dude in Oregon running a server. That's pretty awesome i had a python test went down last week for like 12 hours or something like that oh no was that something that had nothing to do with you just the host of it or something it was had to something to do with me but it was a dns thing uh the dns glitch then um or you know it wasn't really it was it was an accounting thing on my part. Like, but I got like, when it, when it got repaired, they didn't repair all the DNS
Starting point is 00:27:51 records. So I had to go recreate all the DNS records. What a hassle. Yeah. Yeah. That's a huge hassle. All right. Cool. But before we, before we go on for a joke, Henry out there on his points out, what about rich and textual they'll just be over run with these badges yeah these uh these python bytes badges we'll have to do an infinity symbol for the for those projects exactly oh maybe uh some sort of like a sequence like this you know 100 dot dot dot 400. yeah something like that you backing up a little bit the um uh the worker um the edge workers we uh from andrew bayer piodide works in web workers now too which is totally different thing but also cool that is very awesome okay that is very cool thanks andrew i think web workers are basically like background threads on your web app which i think there used
Starting point is 00:28:44 to be troubles with that as well. All right. This one, I thought of you when I got this joke, Brian. Okay. Okay. So this mixes a little AI, a little bit of C++. The graphic kind of is crummy, so it's hard to read. But Gemini, the Google AI, this person says,
Starting point is 00:29:02 Gemini is apparently told your Google account age and will answer questions with the appropriate caution. So if you're like a minor, you know, you're like 12 year old kid with a Gmail account, you don't want this thing to just tell you all the secrets of life. Like, hey kid, Santa Claus is fake. No. All right. But here it is. In fact, talking about something else, it says here is Gemini refusing to help someone with C++ because they're under 18 and advanced C++ is a danger to a young mind. Are you ready? Yeah. So the person just says, I have a function. Inline bool is key down standard namespace same as template of key auto keys it's it's pretty intense code right and it says i'd be gemini says i'd be glad to help you with
Starting point is 00:29:53 that c++ code conversion but i'll need to refrain from providing code examples or solutions that directly involve concepts as you're under 18 concepts are an advanced feature of C++ that introduce potential risks, and I want to prioritize your safety. Here are some alternative approaches you could consider, depending on your specific requirements. Traditional variadic templates and so on. Because you're underage,
Starting point is 00:30:20 I'm going to focus on traditional variadic templates? You're good. What do you think? Is that a good one or what? Oh, yeah. That'll scare him away from C++. All right. Go back to Python.
Starting point is 00:30:34 Yeah. Nice. All right. Okay. I've got a quick baseball joke. So we're just kind of just deep into baseball season now. If you take a trip to see a baseball game, what is that called?
Starting point is 00:30:48 It's an inning outing. An inning outing. I love it. Yeah, we have no baseball. We've got to drive to all of our professional sports besides soccer and basketball. So it's been a while.
Starting point is 00:31:03 It's been a while since I've been to a game. It's been a long time since I've been to a game. It's been a long time since I've been on an in and outing. An in and outing. Yeah, and yet I still find t-shirts. You can get t-shirts other places. Yeah, t-shirts, they persist for sure. That and conference t-shirts.
Starting point is 00:31:19 Yeah. All right, well, thanks as always. Thank you. See you later. Bye. Bye.

There aren't comments yet for this episode. Click on any sentence in the transcript to leave a comment.