 All right, good afternoon and happy Friday to everyone. Just a little bit smaller. All right, there we are. How's it going, Beata over in YouTube? And yes, good afternoon to everyone. Thanks for tuning in. For anybody that might be new, my name is Tim, I go by FOMIGuy on GitHub and Discord. This is the DeepDive, which is a weekly live stream that occurs here on YouTube and Twitch and other various streaming platforms. Most typically, this stream has got Scott Shawcroft, lead developer of CircuitPython. But when Scott is not around and I am, then I will typically fill in, such as we're doing this week. If you are new to this, if you haven't watched the program before, it is all kind of centered around CircuitPython. And as the name DeepDive would suggest, it is kind of very technical in nature. We're usually just jumping right in. Scott, when he's on, usually he's working on something in the core or something on the kind of like cutting edge of CircuitPython. I don't tend to work quite as low level as him. So I'm usually either working on libraries or projects or some other user code or some other related infrastructure and things like circuit and the build tools and stuff like that that aren't necessarily fitting into other categories. But anyway, for those that might not know what it is, CircuitPython, the main website for it here is circuitpython.org. That's where you can head if you'd like to learn more about it. It is basically a version of Python that runs on tiny computers called microcontrollers, which we can see a bunch of over here on the downloads page. Today I have hooked up a Feather TFT, ESP32S3 Feather like that. So this little microcontroller device runs CircuitPython and what that means is that it can plug into the computer. This one with a USB cable, it shows up like a thumb drive and then I'm gonna download this while we're here because I don't think I'm on 904. It shows up like a thumb drive. There's a Python code file on that thumb drive. You can edit that Python code file. And then when you save it, the computer that's actually on this little microcontroller in this case it's on the back, we can't actually see it. But the computer on here will notice that you modified that file and it will execute the new one. It will kind of restart itself and execute the new version of that file. So that is kind of the like 50,000 foot view of this is writing Python code that runs on microcontrollers. But it goes a lot deeper than that as well. If anyone does have questions or comments or anything that they like to ask about, feel free to drop those in the chat. I can see specifically the Discord and the YouTube chats which are the same two that are on the screen there. I know we're streaming in a couple other places but I do not have those other chats pulled up. So if you do have questions or comments or anything you wanna get my attention, Discord or YouTube are the places to do it. Let me fix this thing, there we go. Good afternoon to Zarnalyn and Sam Blenny. Thanks for tuning in folks as well as Mark Gambler. Can you say how happy that I am for Friday? Yeah, I'm with you there as well. Definitely ready for the weekend. All right, as always, to learn more about CircuitPython CircuitPython.org, that's the main website where you can go. You can also join us on the Discord which is linked down below, ADAFRU.IT slash Discord. There's a CircuitPython dev channel and a help with CircuitPython channel there where you can either get help with your CircuitPython project or if you are trying to contribute to CircuitPython or the libraries or some other bit of the surrounding infrastructure, you can get help on that over on the Discord. So join us there, as always, thanks to ADAFRU. ADAFRU is the company that makes CircuitPython possible by paying the folks who work on it. Scott, the one who is normally doing this stream is the lead developer. He's him as well as a couple other folks are paid to work on the project full-time. Myself and some other folks are paid to work on the project part-time. ADAFRU is the one that's paying for that. So huge thanks to them as always. And if you wanna help support CircuitPython, one of the easiest ways you can do that is just heading over to ADAFRU.com which is their website and purchase hardware from them. Got some groovy 420 banner going on here. It looks like today, which is kinda cool looking. So they sell the microcontrollers as well as all sorts of other doodads that you can hook up to your microcontrollers such as the ones in this picture here. We actually have like a battery and I don't know, temperature sensor or some kind of thing like that are hooked up here. So they sell all sorts of little bits and bobs and things that you can do with your microcontroller as well as the microcontrollers themselves. Jumping right in today, what I would like to work on is inside of the ADAFRU requests library. ADAFRU requests CircuitPython. Inside of the request library, I would like to add the capability for uploading files. So ADAFRU CircuitPython requests right here. We have this request. This is meant to be like a subset of the CPython library requests, you know, the sort of regular Python requests. But one of the things that CPython requests can do that CircuitPython requests cannot is uploading files. And I shouldn't say that it cannot do it because it can, you just have to kind of like do a bunch of it manually. This code right here is for the CPython requests library. Or even I'm gonna drop down to this one here because it's even simpler. It only does one thing. So, oh yeah, this was my question as well. Actually, what's up with that? Anybody answer? Yeah, okay, that was the conclusion I came to as well. Felt odd, but I guess that's how it works. So with CPython requests, you can just go request.post, you give it your URL just like normal. And then you just have this handy argument right there, files, you just set that equal to a dictionary. You can have one or more files in that dictionary. Each one gets a name and an open file handle or open file reference. But you open the file you want, you do need to open it as read bytes. And then that gets put on the dictionary. The dictionary gets passed into files and then request handles everything else for you. Gets uploaded as a multi-part form data. Multi-part form data gets uploaded like that for you. You don't have to worry about anything else really beyond just doing that, which is super duper nice. The circuit Python request does not currently have that, but that's what we're trying to add. If we go and look, we get the docs, as well as the code real quick. Docs, best.post, make your request even better. That's the hope, that's the hope. So post, so right here, these are all just quarks. So it just says URL and then key W. So there are a bunch of these we can send. There's like method URL data. JSON headers stream. Oh, but that's just Boolean to our false. Timeout, allow, redirects. Okay, so yeah, there's no files there. No files there on that one. And if we go and look inside of the code here as well, we'll see the same thing. We have URL, we have all the rest of the quarks. We pass those up to request. Jump in the actual code here. Did I commit that? I don't know. I didn't, hold on, I need to go back and commit actually. I'm just gonna commit but not push on that. That's something else that I'm working on is unrelated. Let's see, making simple test example. Ultimately, I will aim to make a simple test example but the thing, like I'm trying to make the argument work more than I am the example. The example will follow because it will be good to illustrate how the argument works, the new argument that doesn't exist today in the circuit Python one. But the actual functionality in the request is definitely my primary target, the example is kind of a byproduct in my mind. So I go along with what I said in the meeting that there's just a lot of simple test gaps. To an extent, yeah, I mean, there's not one for uploading. So I mean, there's definitely opportunity there for this one, at the very least an opportunity for all kinds of stuff for sure. I would say the C Python request can do all kinds of things that we still don't have access to yet. So I think as we add those things one at a time or however fast we add them, it's like all of those is basically one, one or more new examples each. So there's definitely lots of example or to mine in those fields, in those fields, in those mines, I don't know, in those mountains. Never used them. Yeah, so it's actually, it's not easy today. It's difficult to do it today. So hopefully we're gonna make that easier. I have a couple of things set up to help me do this. I have a simple Flask server, which is running on my PC. This is not circuit Python. This is actually C Python. It's a Flask server. It's very, very, very basic. It just has one route that is get or post. This is the one where you get to if you load the index, which we could as well just do right now. I think if we hit that. So I did change this. I guess we need to do this this way now, maybe. There we go. Okay, so it has, when you do a get request, you end up with this HTML page right here, which comes from the index template. When you do a post request, that's when you're actually uploading the file. So we could go and select a file here. We could click upload, and then it would upload that file for us. We'll be doing that a bunch later. So I'm not gonna do it right this second. The only other route that we have is the kind of like access your files route. So slash upload slash name. This will try to find a file with that name inside the upload folder. If it finds it, then it will return it to the user. So if it's like an image or whatever, they'll just get that image back that they can download or do whatever they want with. And that is it. We have this thing. I don't even really know what this does. If I'm honest, I copied it out of the requests out of the Flask example, but it's there. And then we got a line down here to start the server. And that is it. Very, very basic Flask server. And then I have some CPython requests code that we're aiming for, right? So this is what we want to be able to write, roughly more or less, in Circuit Python. This is the CPython version. So it's like import requests. I open up that file there. Again, read bytes, that's important. So we open up that file. We put that onto a dictionary here. So we say file is the key. File handle is the value. I did switch it up and use the context manager. So we go with request.post. We give it the URL, files equals, our dictionary right there as response in our case. And then we can just print out the response. So I will close that without meaning to, which is awesome. Let me go reopen it. I will run this. One thing is this isn't on localhost anymore. I mean, it is kind of, it depends on how you think about it, but it's not gonna work on localhost. And that's the important part. So there we go. We can upload that. In this case, I just uploaded a picture of the Raspberry Pi Pico, which does in fact show up over here inside of uploads. And it does return us a JSON object that says, hey, that was successful. And then it says, here's the URL where you can access that file that you just uploaded. So if we wanted to, we could grab this. We could go back to the browser. We drop that on there and we'll see the file that we just uploaded. All right, so this we wanna be able to do from CircuitPython. What you would have to do today, if you wanted to do this from CircuitPython is you would need to basically do it like this file does. This is now into CircuitPython rather than CPython. So this bit down here is where the magic is basically happening. So in CPython, when you put this files thing right here, what it's actually doing internally is this kind of thing all down here. It's creating a boundary. And it's putting these boundaries in between all those different, you know, multi-parts because it's a multi-part form. So it's putting those boundaries between the parts. The actual file part gets this content disposition thing. It gets the name, it gets the file name. Then this is the actual content right here, which would be the content written as bytes. That's why that read bytes is important. Then in the case of this example, it's actually uploading some other fields also, some other form fields, but we're not really worried about those right now. And then in this case, all that stuff was getting kind of created into this body, which was then getting passed as data. So it is possible with CircuitPython, but you have to build this kind of gnarly thing and include all of these slash Rs at the end of everything. And it's just not the best, right? It'd be a lot easier if we could just, just like in CPython, right? You know, files equals files, boom, done, nice and easy. All right, so with the setup out of the way. How's it going, Tyeth? Thanks for tuning in notifications are slow on the phone today. Ah, no worries. See, multi-part attachments, MIME types for the win. Yep, I have to figure out about, I don't know how the MIME types will have to do some kind of automatic MIME typing or something. I always thought curl was just an OS thing, not specifically a Python thing. Curl, I believe, is an OS thing. Let me catch up on the chat here. Most API examples have curl, examples more than getPost, with REST APIs getting all the methods. It will in curl should be the goal. I mean, yes and no. Like in my mind, like curl is a thing that sends HTTP requests, but in my mind, the goal for the CircuitPython library is not to match curl. The goal for the CircuitPython library is to match the CPython library. The CPython library and curl are like two tools that do a lot of the same things. So like if you are targeting to match one, you're gonna end up kind of matching the other a fair amount, but in terms of like syntax and functionality and like argument names and all of that kind of stuff, like the CPython version of requests, I think should be what we're trying to copy most closely more so than curl itself or anything like that. Maybe on Blinka, curl is a Python thing. Yeah, I think, I'm pretty sure curl is an OS, like a Linux thing. It might be written in Python, that part I don't know, but I'm pretty sure it's like a Linux program at least. You install it with like sudo apt install curl if you're on Debian or whatever. Yeah, and I do not believe it exists in CircuitPython. However, it's kind of the same like I was saying, like requests, you know, kind of does a lot of the same stuff. So I don't know what you asked about response text. This is not what you was saying. This is why you asked about response.text recently. It is not why I asked about that. Nope, that's a separate thing. This was something else. Trying to change my resolution on my new Raspberry Pi 4 Model B. How do I do that? I tried screen configuration, but every time I click apply, then okay, it resets to 1200 by 720. Unfortunately, I don't know hardly anything really about the Raspberry Pi 4. I've got one I think I have powered it on about half a dozen times or so. I'm just now kind of getting back into the swing of looking into that Raspberry Pi 4 stuff, but unfortunately I have very little experience. I can't really help you on that. Zarnalyn here, though, has got something for you. Check in boot config. Maybe see if there's a size over write or something going on in there potentially. All right, so let's write the CircuitPython code we wanna end up with. This was some CircuitPython code, but you can see there's read, there's extra stuff we don't really care about. So I don't really wanna get this working because it has more stuff than we need. I will call this example or like a prior. It's like prior art basically. This is kind of what I'm copying from. Pi there, we'll call this CircuitPy. I will call this CircuitPy upload because this is kind of our target. This is like our goal. We want this code to work when we're done. And it's gonna be a lot like this. So I'm just gonna start by plopping that right in there, but it will be a bit different because ours is, of course, CircuitPython, not CPython, so we'll do this. We will do all of this stuff like this. Actually, we don't need that part, do we? Yeah, I don't think we need that part. We, yeah, in response, that'll get closed there, file handle, that'll get closed there. That's what we want, right there, okay. That's actually pretty straightforward. I'm gonna copy that to the device. It's obviously, it can't run right now, but I'm just gonna put that there. Oh, me, are you still using PyCharm? I am, yep, this is PyCharm that we are in here. PyCharm is my daily driver for work and also the thing that I do most of my CircuitPython work. I will say when I do venture into the core, I use C-Line, which is the, you know, IntelliJ is the same brand, basically, it's just the C code version rather than Python version, but yeah, I'm definitely pretty firm into the PyCharm way, so I've gotten to the point where all the tweaks and stuff that I do and I get everything set up how I want and I'm very like, I like how everything works within it. All right, CircuitPy upload is there. We wanna get the one on the device and then we want to get requests and what I'm gonna do is actually just grab the current copy of requests from main. That's what we will start modifying from. What setting am I looking for? Oh, for the Raspberry Pi thing, yeah, I would not know. Something about the size is my best guess, but you have an explicit open and then if you do an explicit open, then it needs to close. We've been doing that wrong. To the best of my knowledge, that's true. Yeah, for the file like that. But I honestly, it's weird to me that this doesn't just take a string. I think this would be cooler or better. It's off the top of my head if you just put this file path right here, but it's not the way that the C-Python one works, so it's not the way that I'm gonna make this one work. All right, so we, sorry. I'm not sure if this can technically be only posts. Like I'm not sure if posts are the only thing that allow files or not. See if we can find that. That real fast is post. Only method that can use multi-art form upload. I mean, that's looking like a yes. I'm not gonna go dig that much farther, but that's looking like a yes. But anyway, we would still need to add it here because post is just calling request, right? So request is gonna have to accept files if we want to not have to rewrite it. So we'll say files. We will say that is a optional dictionary, a dictionary which is strings to, I don't think it's actually called a file handle. What is the type of a open file? F equals open boot out text. In our case, it's read-by, so I don't think that matters. Type F, that's a file IO. The default is none. I don't know if we're gonna be able to use this. I don't know if that's like, I don't know about that type. It'll keep working or not, but high screen resolution, yeah, there's a good link potentially. Okay, so one thing we would need to rectify is that would kind of overwrite that, right? I don't know what the CPython one does if you pass both data and files or like data and JSON or JSON and files or whatever. Like those are kind of, you're meant to do one or the other type thing. Okay, send request, host method, headers, data. This is basically the meat of this. It's like, let's say you pass JSON. What does it do with it? Oh, interesting. If your last response was open, it could get closed here too. It just gets passed through, I guess. It doesn't actually do anything with it while it's inside of here. So that says, that doesn't even return anything either. It just says self.send request socket, host method, path headers, data JSON. Then, so it starts with okay is true. It calls this, if OS error, put that, put that to false. So if that succeeds, then it will still be true. But has receive, otherwise use receive into closed socket. This looks like this is closing the socket here, actually. Our response, allow redirects, starts with HTTP, add split redirect. So this is all redirecting. Actually, all of that all the way down to there is redirecting. Last response equals response and a return response. Okay, so that's gonna follow those. So there's not redirects. Send request right here is where it must be and more stuff. I think put can support multi-products also. Okay, let's get to know. Okay, here we go. So if JSON is none, assert data is none. Okay, so there are tests for this actually. If JSON is sent, so we could say assert files is none also. Asser? But we need to pass that through to here. Files, optional dictionary string. Okay, there we go. They pass JSON, then they're not allowed to have data or files. And that says if JSON is not none, so we only get to here if they did, well, no, because this doesn't return though. Set a content header and convert to string. Module dumps. If data is sent in as a dictionary, set content type header, convert it to a string. URL forming coded. Okay, so that's gonna take your dictionary, turn it into a forming coded like blank equals blank, you know, key equals value, key equals value, whatever. Convert that to bytes, send as bytes, send space in a slash and as bytes path. So method, I guess that is the slash that goes after the method. There is slash after the method, there must be. Then send as bytes the path and then send HTTP 1.1. And then now we go into handling the headers. So these are all convenience arguments. You can pass those and they all equal a header. So this will send them one at a time if they were set. And then at the end of all of that, we're also still gonna loop over all of the headers because you can also pass headers as a dictionary. After that, we're gonna be sending empty line. Then we're gonna be sending data. So in the case of files, so at a high level, we need to be basically making data. We need to, we can keep all the rest of this code and then we're just gonna be saying like, if they passed files, we wanna put a bunch of stuff into this data string. There's actually a byte string ultimately. Although it gets passed to bytes here. You know, it's already set to bytes here. So I think we should assert here that files is none because if they pass data and files, that's not good, I don't think. Is it? No, I guess, I mean, maybe that's not bad. I mean, you can. So I think actually that CPython might be able to support that. We'll have to look later at how it works exactly. Building a files option, or are you building files option mystery in the editor? She's not in files, it's on check block. I don't think there should be any JSON block since you're not sending JSON, but the files in the check should not occur since it's not JSON. Not sure if I follow, sorry. I'm also not entirely sure when you typed, I wasn't watching very close and there's also a delay. So it's probably some context from a minute or two ago. Maybe a web form would do exactly that. A web form, I think, yeah, a web form. Also, I mean, a web form, I'm pretty sure a web form will work like this manual one that I have here. So like this manual prior, this one has extra arguments. So in my case, it had an argument called zip name, which was a string, and then it had an argument called query channel, which was also a string. And then it had the argument called my file, which was the actual file data. And I'm pretty sure if you had a form with a file and an input that's text for the zip name and an input that's text for the query name, I believe this is kind of what it would generate for you. But I don't know the equivalent in CPython requests upload, but I'm gonna handle file only first. I'm not gonna worry too much about this until we have the file only here. So if data in is instance dictionary, for right now, I'm just gonna assume right now that, I'm just gonna assume right now that data did not get sent. I'm just gonna come in here. We're gonna say if files is not none and files is instance dictionary. Oh, that's not how that works. Then we basically want to generate data. I did not mean to close that. I really wish the right click would stop doing that. Let's see the if JSON, this part here. You have the files assert. You're not hitting that block since you're not sending JSON. I mean, when I get to my test, I won't be, but what this part's doing is it's saying, if you set JSON and you set files, you're not allowed to do that. So this is not for the happy path necessarily. Like I consider the happy path here, the actual uploading of a single file. That's what I'm considering kind of like my first level of happy path. This one is not on that happy path, but it is on the path where the person uploads JSON, but they also include a files and that shouldn't be allowed. Technically data and files also shouldn't be, but I also just went through that a little bit and I'm not gonna worry about it right this second, but I'm pretty sure we do wanna, be sure we do wanna outlaw that one. Again, it's not in the, it won't be in the first test, in case if it were to happen. All right, so if that is not none and it's a dictionary, then we basically wanna be building one of these things. One thing that we need to have is, we need to make a header, which is a multi-part form type header. So we should not really allow them to set content type maybe. Where does that come from? Okay, well, they don't actually pass that in, I guess. Okay. Supplied header, so we're gonna have to send this header, so I'm actually tempted to grab this and let's move it down to here. So one thing we're gonna do is we're gonna be setting that to this. Ah, this is weird though, because we, okay, so we actually, we need to set it to this whole thing, and then we need to generate the boundary still. Content type header equals this. You don't actually wanna do this. Maybe boundary string, boundary string. And the boundary string, as far as I understand it, it's arbitrary and it can be generated randomly, which it seems like is what requests does the CPython one. So what we will do is, we don't have that, I don't think, right? Random after choice. How do you get a random string? The way I would do this in CPython is using the secrets module. Okay. Do these things exist? String.asci uppercase? So we have random choice. I guess we'll use that. So I'm pretty sure the CPython one was hex, because I captured this one that CPython generated. Yeah, and that definitely looks like hex. How long is that as well? That is 32 characters. Okay, so that's our hex characters. And then we can say like, boundary is empty for i in range 32. And we can say boundary plus equals, plus equals random dot choice. Can you random dot choice a string? Yes, random dot choice of hex characters. We generate 32 of those. We put them into a string. We're gonna be returning that. Turn that one down to here. We will be generating that boundary string equals generate self dot generate. Time to use Beneski. Does Beneski have random? Two upper, two lower. Oh, two lower, yeah. Beneski, maybe Beneski does, I don't remember. It has hexalify, unhexalify, and then base 64, and unbase 64, whatever this is, which I don't actually know. I think that's some kind of checks on, but I'm not sure. Hexalify, this could somehow be used for our purposes, but I don't know how off the top of my head, somehow we could generate a value and then pass it to this, and that would give us a string that looks pretty much like what we want. I'm not sure what we would need to generate, maybe just a number or something. That's the content type header, and then I think we can just go ahead and send it so we can just say self dot send header socket. Oh, no, no, no, no, no, we don't wanna send it. We just wanna set it because it's gonna get sent down here. Okay, we should make this out here. I don't know if this actually matters, but it feels more correct. Set that to none, then we generate it there. Not used. Yeah, see, that's not working. This is like a different scope or something, is it? Doesn't realize this, does it realize that's the same variable? Maybe not. Should we put it on self? Maybe we should put it on self. Self is what? This self is the, no, we can't put it on self because self is like the amorphous requests thing. Not really like an individual request. Okay, we'll try it like this and see how it goes. I think it should be fine. All right, we don't actually need the cookie, so that should do that. Okay, so now we need to create the body, but the body is actually just called data in our case. So we don't need to create it, we just need to put stuff into it, and we're gonna just put a string into it because it's gonna get turned into bytes here. So we will, we technically would wanna loop for file in files, but for file, let's say file name, yeah, for file name. It's technically not even actually the file name, it's like the form field name. Let's say for field name, field name in files. I'm not sure if you have to do that, but I kind of like doing it this way better. So field name in files.keys, and then for each one of these, we're basically gonna be generating this kind of thing here. So data plus equals, but we don't wanna use boundary, we wanna use the boundary string. Do we wanna do multi-line? Should we do multi, yeah, I think let's do multi-line, I think. We need to then pull this all the way back so we don't end up with, this is one of the weirdest parts about Python syntax, I think, is the fact that, I feel like these should discount the tabs, but it'd be a lot harder, probably. It's like the only part where white space suddenly gets all weird on you. Love S-Treams now, yeah, I'm with you on that one. What are you gonna do? It's basically, that's pretty much what it is, random hex token, that's exactly what it is. I don't know why they choose it by random, but it seems like that's what they do. Yeah, I mean maybe, it seemed random to me. It was different each time, so it's not like a hash or something, but technically it could be anything. So like the original code, like this was just using the word boundary as like a test or whatever, it just has to be whatever it is, you just can't have it in the rest of your body. That's like the most important part, you're not allowed to have it in the rest of your body, so I'm not sure why they don't use a standard instead of a random one though, probably a reason for it. So then we will have this content disposition, content disposition, form data, name equals, that says my file, do we wanna, I think we wanna call it, we want that to be field name, whoops, no, form data, name, field name, file name. Yeah, and then in my case, I was doing some dot zip, dot text kind of stuff, which we won't bother with for right now. Really what we want is, we really want the name of the open file, which like, can you get that from a file IO? Is that different in CPython? That is quite unfortunate. I'm not sure where that gets actually declared. I don't know, these seem like the actual place where it would be declared. Hmm, BFS open. So we're where, we're in EXT mod. Not sure what exactly that's doing, somehow that's like calling open through the queue string, can you do one that's not open? I really wish it would open it for you inside of here, that would solve our problem, and it would make it so that you don't have to remember to do this, which seems better CPython upload. Wasn't the wrong file, should have named these more different. I don't think it worked right because, let's delete this and see if it gets re-uploaded. I don't think it worked right though, because we didn't get the JSON response. We should have got success true in the URL. That's not what we got. I mean, it looks to me like it actually sent a get request, but I guess it technically was a post, but it didn't actually work. All right, well, I wish we could match the CPython API, but we can't unless we had a way to get the file name. Let's try to super set it. So my initial inclination was to change it to work like this, but that's like diverging the APIs at that point. Then ours works differently than CPython, which we're gonna end up with no matter what, but we should make it to where we end up with a difference that still uses this. I really wish we could see the name. Okay, we can't do it the same. So how do we make it different? This I think is the most convenient, but I don't wanna do it because it makes it so we're not compatible even further. What I would like to do is something additive. I wanna add something to this and let it keep this. Man, I wish we could make this a dictionary, but then we're still kinda breaking the API. Sorry, this kinda ground us to a halt here. I'm just really judging a lot of different ways we could do it. None of them I really like that much. I guess I should talk them out loud, I guess. So one idea is maybe we do file name, but then our for loop is looping over all the keys, so it's gonna see that. We'd have to write some extra logic that says if it's a name, maybe then ignore it. I mean, I guess we could do that if field name, name.nzwith, name if not. Okay, so then we know now, I mean not know, but we are gonna assume it's gonna be an error if it's not the case, but we're gonna say the file name is files and then it is field name and then it is dash name. Name, so field name dash name, that will store the file name. You'll have to pass both in order for it to work in CircuitPython and then when you go back to Cpython, you could just delete that one. You have to delete that one because it will fail, I assume. Actually it succeeded, that's just pretty convenient. Okay, that's actually quite convenient. That's basically what I wanted to end up with was something that worked on both and that works on both. So somehow miraculously that does not interrupt the other one. I wonder if we're getting some very weird, I wonder if we're getting some very weird version of it. Let me see if we can watch this real fast. Okay, I got wrecked by the filter thing in Wireshark for about the millionth time today. I don't have any display filter. I don't have any TCP traffic. Sorry, I know this isn't very interesting, but I don't really wanna be shown Wireshark on my screen. Okay, this is not being very useful because that's not running on that anymore. I really wish I could just see it. I don't wanna change all this stuff again. Run this on zeros and that succeeds. Okay, this is our request. This is our TCP stream. So we posted that. That's the random boundary that it came up with. It does say name, file name. So I'm trying to make sure here is that we don't end up. Ah, yeah, see, we got this. We don't really want this. This could potentially mess up a server. I'm actually a little surprised that didn't create this file. Did it not create this file? I guess it failed because we have a limitation on it. Well, let's just roll with it for now. Can't repeat the boundary names. My understanding is the boundary name is supposed to be the same the whole way. Like whatever you put in the header you need to use throughout the whole body. So this says boundary equals this. Therefore, my understanding is all the boundaries need to be equal to this in this request. Now like next request, it could be different. You wanted them to be. Although I don't think they have to be either. Yeah, that's my understanding though. They need to be all the same. Yeah, the filters. So what kept wrecking me is that I emptied it out but I guess I just didn't press enter. And so like it was just filtered on nothing or it was still filtered on the old thing I guess but it was not showing me everything like it would if it was actually empty. You got to empty it out and then press enter and I keep wrecking that. That one was only start, one was only end. So there is a difference on the end. The end one uses the dashes at the end also. So that is the one difference. At the beginning, at the beginning you get dashes at the beginning right there with none at the end. And then in between each of your parts because it's multi-part remember in between each of your parts you get the boundary with the dashes on the beginning and then at the very end of your body that's where you get the boundary with the actual dashes on the end too. All right, so we will do that. Content disposition is that form data. Then in our case we are gonna be having new line dash our new line, the data. So that is gonna be read from the file. So that's gonna be files. This is gonna be bytes, is that gonna work? Sorry, I don't know if that was loud. Files, field name. So we know field name. That's gonna be the open file handle. So that will just be read R on there. Need an R on there as well. And then yeah, I think that's what we want our for loop because that will make a boundary at the beginning of everything. And then at the end once we're actually done with our for loop so we actually finish all of that then we will add the ending boundary. So we go data plus equals. We need a dash R, I don't think so. So we would go dash dash boundary string dash dash. Probably won't work but I have syntax errors I think. I actually still have circuit pi. No, that's the one that's open, okay. So we do want this we do not really want that. We do want to close that. Yes, yeah, we'll do that. Files, file handle, file name. Bam, bam, boom. Files, files are spots, okay. Save that and then I'm just gonna run it by valid syntax line 478. Circuit pi line three. Import 78, we have syntax errors. Is that not supposed to be here? Does circuit Python have a problem with the multi-line strings? Why is it invalid? Maybe you are not allowed to double those up. Okay, more syntax error but the import did not work for file IO. I don't think we actually need to import it though. I think that's like only for types basically so we could put it in here. Okay, now we're getting somewhere. I did not copy that to my device so that's expected. You grab a copy of that. Let me delete it from the uploads folder too. Actually let me, if I have an extra copy of it there. Okay, yeah, let's delete it from the uploads folder. Let's delete that too, we don't need this one. Let's copy that onto our device. Let's run that again. It's not failing, eventually failed. Unsupported types for add and none type. So probably when we were plusing our stuff together we got a none or 78. All right, I had to run to the restroom as well real quick. Let's get to 478. All right, BRB. Must have been a none. Plusing something in there, must have been none. All right, BRB. Thanks for taking us slash me through, yeah, for sure. Thanks for being interested and asking questions. I'm always happy to help walk through whatever I'm doing. But yeah, for anybody else that's watching anywhere if you ever have questions about what I'm working on or want clarifications or anything like that please feel free to shout. I'm more than happy to go two-way here. You know, sometimes I get in the zone a bit and just get head down to programming but yeah, I'm super, super happy to go back and forth with questions and stuff so. Unsupported types for add and none. So where, so that says 478. Data starts as none. Okay, that's fair. Unless we pass it. Okay, so I guess, let's say if data is none then data is empty string. I have a ping somewhere else. Let me see. Wait as long as enough people helping know about the change, it should be okay. All right, nice. All right, that's over in the other chat. It's Justin talking about something else that I was asking about earlier. Okay, so let's see data. All right, that should give it to an empty string. I think that should at least get us past that error. String argument without encoding. So I'm wondering if we are gonna be getting wrecked by the fact that we open that thing as read bytes but now we're just trying to plop it into a string which I'm not sure you're allowed to do. String argument without encoding. This is 498. Do we wanna make that a byte string maybe? This is made as bytes earlier. Where's that data none? What if we do that? Did that change anything? Did it actually work though? Okay, let's check the logs from the server wherever they may be. 1718, so that looks successful from there. It uploaded but it's corrupted. Does it have content? It has content. It's not empty so that's a good sign. Does, how big is this? So that is 72K but that is 206. Let's delete that. Okay, so I mean this time we are having a byte string. Can you do a byte F string? Is that a thing? So my next hunch is that we are having a string problem somehow. Like the fact that that's read bytes and the fact that this is a string, regular string. Although we're plusing it onto a byte string now. I don't even know at that point. If we have S equals a byte string, hello. And then we go S plus equals that one. World next line, close that. S is still a byte string and it picked up the whole thing. Okay, so that should be fine really. Okay, can we see the traffic? Can we wire shark that? I don't know, let's see. Let's close that so that we don't get confused looking at an old one. That would be terrible. Let's restart this. I don't know if this is gonna work. Let's clear the filter, escape. No, backspace, enter. Clear the filter, now run this again. Let's take a second, got success. Only traffic here is TCP integrated WebSocket client. I don't know what that is, but it's randomly sending WebSocket pings. It's not related to our thing though, so this is not helping us. I think I can't be on this one anymore. I think I need to go. So this is where I was getting kind of messed up with the, first of all, why is this unsaved? So we'll save it. So that's hitting 197. So I guess I can't do loop back. I'm not very experienced with wire shark. I'm gonna just check and see if it works and hope it does, and if not, try something else and hope for the best. It's about my level of experience. Okay, there we go. See that traffic coming I think. So we'll wait for that to finish, and then I will grab that stream, follow TCP. All right, let's take a look, what do we got here? All right, maybe I should have kept that other one open. I can always rerun it again. But I will say right away, this stuff doesn't look right. In the other one, we had weird stuff, not a bunch of these slashes, I'm pretty sure. So right away, I think that is our problem. Well, let's check some other stuff here. We got a boundary, we started it, looks pretty good. Dash is on the beginning, content disposition, form, data, name, file, file name, we got that, came through, okay, and then in Circuit Python, we don't actually have that file dash name one, and then we did get our dashes, so I mean, that looks good to me. One thing I noticed is wire shark really does not wanna show me the packets going back from the server to the microcontroller. I don't know why, but yeah, doesn't wanna do that. Like, I mean, obviously it sent it, right? Like we received it here, so like that data passed through there, but apparently just not through this interface, I guess. I'm not sure how that works without having gone through this interface, but all right, let me do one, can I keep that open if I stop this? Yeah, let me do one back on loopback, loopback it, let me run this one again on the Cpython, and then we grab that stream, then we compare. So yeah, this one that worked is on the right, that's the one we uploaded from Cpython, the one on the left is from Circuit Python, and yeah, totally, the difference is like in here we just have weird dots and all kinds of stuff. My guess is that these dots are un-renderable or whatever, right? It's like a character that's not a visible character or whatever, and in this one it's not doing those, it's not converting it to like ASCII, it's converting it to like escaped Python string. So how do we get the raw bytes? How do we get the raw bytes? All the fun of reading data strings, making use of the outputs, people's are so much fun to work with, work out in Python, left side showing you hex values of the bytes, yeah. Yeah, which I think those are like automatically, like those are like automatically getting converted to the Python representations. I think that X slash is like a Python thing. Maybe it's a deeper level than that, I always assumed it was Python. We have FF, we could count these and we could try to figure out what the ASCII for C is. We're right in there, I guess, is it's giving us the ASCII for C or maybe that's the actual C right there, yeah. It's like we got the string representation of the byte converted to hex basically rather than the actual kind of like raw literal byte. Testing it with a file to start, text file to start, we could do that. That one succeeded, although it has bytes in it which is interesting. He's got converted to slashes also. So I think there's like an open thing for this stuff. This stuff is weird but it's unrelated. I really think we are somehow inverting awkwardly. I think the way this is working is those are kind of like getting passed through two string essentially. We will try this. It's gonna be kind of painstaking but, well, but can you do a byte string with F string? It's Python byte string F string. Pass it to bytes, no. Okay, maybe, maybe we actually just need to call in code on this and this. Though, I'm not sure about this one seemed to be fine I thought but just plain text I guess. I'll delete that. I delete that as well actually. I will stick with the text file I guess because that's actually a lot faster. Still actually says byte string. That doesn't seem to change anything. So we're getting like a two string representation of a byte string and it's actually, it's just all in one big line. So it doesn't contain any literal new lines. It only contains escaped slash r slash ns. Ah, I feel like we're so close. It has to be something to do with the conversions and stuff here. Let me print data I guess. Well, we actually see if we do this print data. And print the type of data also. Print type type data. Is that a string or a byte string? Should I have that changed with the use of width and the examples or keep it separate? The change to remove close? Change to remove close. Jason? At the same, to do that at the same time as updating the examples. Yeah, what do we get for that? Save that. So but some of those are actual new lines. So okay, so what happened is this, these ones are actually literal new lines. These ones in here, okay, you can see right there. They got doubled up. I mean, this has ends with, should it not be? Should we decode it back? That's kind of a, out in the dark, but maybe it's getting doubled up. It's getting encoded twice. Actually, that might have worked. It looks better here. We don't have the B, we don't have the B single quote. Is that the same? Yes. Okay, let's try the, let's try the JPEG. Yeah, good call by the way. Test user 123011 on YouTube. Thanks for asking me to switch to boot out because that actually did make this go a lot faster, I think. Cause I was taking like a noticeable second or two when we were doing the JPEG one. So we would have had to do that a lot of times in order to figure out what we just figured out. That's weird then that we open it as bytes, but then we have to turn it back to a string so that we can turn it back to bytes. We could open it as a string instead of bytes, but then it's like different from, different from CPyvon again. Cause that one has to be bytes. I don't know, we'll try it. Let's see. Here's cross. Let's grab this. Our server. Oh, Unicode decoder. So that works for text files. Doesn't work for JPEG binary data. It's not UTF-8, makes sense. But it didn't work when we didn't have that either because it's still like it's double encoded or whatever. It gave us the slash X instead of the raw eta. We'd be encoding UTF, I'm pretty sure UTF-8 is the default. I should make a smaller JPEG, but I can already tell it's not gonna work. So I actually was doing text. Python, insert into string, string. I mean, that doesn't seem like it's gonna work, right? I mean, I would assume that's getting called automatically anyway. No, same thing, we got all the slashes. I mean, maybe that's what it would look like in Python. The fact that they're double slashes, though, makes me think it's not gonna work. I think on the Python side, we should see those as single slash Xs, but we're getting slash slash X, which means we're escaping the first slash, which means we're not a slash X anymore. We're like a literal slash and then an X or whatever. See, I think if everywhere where we see double slash X, if we only saw a single slash X, I think it might actually work. I think that's how it would output to the Python terminal. In Wireshark, there's a bunch of just dots that we can't see, but I think in Python, those would end up being slash X, whatever, six. Is it because we call bytes again here? If we don't call bytes here, does it work? Yeah, well, that's working. Now those are still doubled up, it looks like. Should we not read it as bytes, maybe? But then we're back to being different from, I don't even know if that would work, honestly, if we didn't do read bytes. Let's see if that's faster. Yeah, I guess maybe, but not that much. Yeah, we don't wanna do that then. Why are we getting doubled up on those? Should we get rid of this? I don't even know if these, I don't think these ended up helping, did they? I guess we would have to go back and do the text one to make sure. So leave them. I guess let's try just read. I don't really know what we'd do with that if it does work though. A little lost in thought about, different ways it could potentially work. This code doesn't work, okay. That has to be bytes, there's no way around that. How do we get it to not double escape? Why is it getting double escaped? On byte string concatenation plus double escape. Okay, so far so good. That's basically what we got. Ours is a byte string. I mean, that's basically what we have. That's basically what we're getting. I'm not using Benasky currently. And yeah, theoretically we could, but CPython doesn't. So I would prefer to be the same. So because the problem with that is if we do that by default inside of request, that makes it so if we're uploading to the same server, it's no longer gonna work because that server would have to unbase64 it. Whereas right now the server just grabs the bytes and puts them in a file. And that works when I send it from CPython. And CPython doesn't base64 it. We know because we saw in here. So I think that that would probably be a way to let's say work around this problem. Not necessarily a solution, but it's a workaround I think that would work. But it's not necessarily what I wanna do. So this feels very close to where we are at, but I'm not sure, there's not really anything. It doesn't seem like the exact same problem because it sounds like they have a text file. What if we don't print data? We don't print data. What if we do, what if we do I guess print this? Like maybe we could be not doing this up here and putting it into a string, but instead getting down to here and just like send the top part and then send the next part, then send the bottom part. Kind of then breaking the way this flow works honestly. That doesn't feel great either. And then what is the type of that also? Did we put that back to read bytes? We're not field name yet. Okay, yeah, so that's class bytes and they all have single ones and they're all good. I don't have Wireshark running, but I'm pretty certain that this basically got the doubles. Actually they did not get doubles, but they do have a B in the front. Gosh, I feel so close, but I'm so clueless about the right way to handle these different, I mean it's kind of down to encodings or whatever I guess, right? But that's weird though. Why do we see one slash there? Those are not doubled up, but so at this point the B, that might be the only thing messing it up. If we didn't have that B at the beginning and this quote at the end, part of me thinks this PNG might actually just work. I don't think I have an easy way to edit that really, but how did we get rid of the doubled up? What did I change since then? Well, we were printing data, but we saw them doubled up in, no, okay, they were not doubled up. Okay, they're not doubled up. They were doubled up when we saw them in the Python console. When we printed data, they're not doubled up here, but they do have the byte thing at the beginning. So it totally is calling two string, converting that to like a B single quote, all that stuff inside, that's my place now. And we can't decode this because it's not gonna work. We get that Unicode error. Should we decode it to what was that format I saw a minute ago that was like raw or something? What other thing we can do? Unicode error, bytes aren't encompassed in the B. Yeah, I don't know how to get them out of there though, or I don't know why they're bytes showing up that way. I mean, it's calling two string here, I think, when it gets substituted in, but I don't know how to get it to not do that, basically. So maybe there's like a decode we could pass, but it's not Unicode escape. I don't, I assume that this is not what we need. So there was like a raw something, wasn't there one of these I just saw, Python? I had no idea what page we were on, decode. Types, can I see the, is there like a full list of these? None of those mean anything to me though. That's 2.3, let's get something. I know that's like just like raw bytes or whatever, like unencoded or something, I don't know. Raw Unicode escape, undefined. Can I copy this, annoying? I don't think this will work either. I don't think Unicode is what we want, really. Are you serious right now? How many times are we gonna do this? All right, I think that's gonna do it for the night. I just stick this into my back of my head and I didn't think of another way to do it. So I cannot actually say that I ever tested the code that I got working was for text files. And we did actually succeed with the text files with boot out, but I never did actually test a non-text file, come to think of it. Like my best idea right now I think is, it's really annoying to do it that way though. I don't even wanna say it because I don't think it's a good idea. I don't know. The circuit python have a string format method. It does, decimal value format, it does, I believe. Well, no, I don't know about that. I've never seen format work like that. Format I've always seen on a string is undefined, but you can say like S equals whatever. And if you have one of these in there, you can say format like that. That's what you're talking about. Is there a bytes one of those or something? It's there to tell the page that it's a blob of binary data. Yeah, I mean, we're trying to use F strings right now it's not working. So F strings is not the answer unfortunately as much as I would love to use them as much as I love them, but it's clearly not working with them. Yeah, I mean, I think the core crux of the problem is we are receiving the Python like terminal string representation of bytes, not literal bytes. We're seeing a string of how Python outputs bytes. We're not seeing literal bytes. And so we are getting this, which is in fact signifying just what you say the blob of binary data is inside of that. It's a byte string or it's bytes or however you wanna call it. That is what we're getting, but we wanna be getting without it. We wanna be getting no B and we wanna be getting literal actual raw bytes, not Python's string representation of bytes. We've got like an extra conversion happening. But I don't know why or how to stop it necessarily. Can you pack it with a byte array? Maybe. Well, maybe. I don't know if we need a byte array though. I think a byte string is what we need. I thought data was a byte string, but I'm not sure. Okay, one thing actually, if I say BS for byte string equals a byte string with something and then if I say LS for a long string, long string. Okay, and then if I say BS equals BS plus or actually BS plus equals LS, then I say BS. If I say BS plus equals BS, okay, we do not end up with an extra B in the middle there. Okay, so now what I'm thinking is maybe we end the string there and then we use plus and then we use plus and then we'll have to start a new string down here. I think that's gonna be an exercise for tomorrow though rather than right now. I'm gonna put that back how it was. Can you convert your B to actual, I mean, yeah, like presumably yes somehow, but I don't know how. So I mean, that's kind of the million dollar question here. I assume there is some way, but I'm not sure the correct way. I mean, it is, the thing is like it is bytes already. We read it out of the file as bytes, but then when we put it into this data variable, we are, like I think at that point it's getting converted to the string representation of the bytes, but I'm pretty sure like what comes out of here should be bytes already because we are opening it as read bytes right there, RB. So I think it is like raw bytes there. Now we can't, like I don't know how to confirm that. We can't print it to try to confirm because if we print it, then Python is gonna show us the string representation of those bytes, but I think that is the raw bytes. And if we could get that to get substituted in here like straight up raw rather than the string representation and then substitute that in there, then I think we'd be good to go, but regarding the Wireshark format, I found this online. If you click a packet, right click a packet, choose show packet bytes, you can choose the encoding at the bottom. Show data as, and doesn't let me change that one down there. Go packet bytes, that's different though. You said what now? Right click a packet, select show packet bytes. I don't see show packet bytes. Right click a packet, click a packet. If you click a packet, then right click a packet, then click show packet bytes. Click a packet, right click a packet. Ah, that right click drives me insane. It's decode as, I guess would let me choose some stuff, but not sure. That decode ascii from your byte string? I don't think we have ascii data though, do we? Because it's not really ascii inside the file, is it? I guess that's what we need though. No, we got Unicode error. Hold on though, where do we get that to? That's 478. That's maybe we're not allowed to re-encode that now or something. Yeah, I don't know. It doesn't like that decode ascii. What about AST literal eval? I'm a little scared of anything with eval in the name. I don't know what that does, I'll look into it. I'm gonna wrap it up for the night now though. I do appreciate all of the help, all the folks giving ideas and stuff that's super duper helpful, I appreciate you all. If you're interested and you happen to be around tomorrow morning, I will be back around 10 a.m. central time tomorrow, and I think I will probably sit back down and start working on this again. So if you are interested in the round two, so to speak, to see if we managed to get this working or not. Tune in tomorrow morning, 10 a.m. central time. It's over on my channel instead of the Adafruit channel and I will drop links in the live broadcast chat here on Discord when I get started. So if you just pop in around 10 a.m. central time, you can click the link if you want. Thanks to everybody for watching. I hope everyone has a good rest of your evening and a good weekend and all that stuff. I'm pretty sure Scott's back next week. I haven't talked to him about that. So as far as I know, Scott's back next week. But again, I haven't talked to him, so maybe that could change. But yeah, I'll be around tomorrow. I'll be around next Saturday. Yeah, that's all. Have a good night. Thanks for watching.