 Well, today our research software engineer at Yarno will demonstrate taking some code and extracting it out from another library and publishing it on PyPI, the Python package index. So this is basically a common-ish thing we do. It's also common to do most of the steps but not publish it, but it can be installed through a repository directly. But anyway, this is hopefully a quick demo of something that basically works so you can follow the steps along later. Is that a correct introduction? Yeah, so that's what we'll be doing and it's essentially all the steps in creating an open-source publication in Python except writing the actual code. So I thought it's a pretty good example because it shows everything you need to know except the stuff that I cannot know. Should we get started? I will share my screen. Here we go. Okay, that's the right desktop. Okay, so I'm sharing my whole screen so let me know if the font is too small. Can I even increase the font in this? Oh, well, so I made a really rough plan and you can add questions and add things into the plan as we go but it's not going to be visible most of the time. I will maybe take a quick look but the first part is to quickly take a look at the code that we already have and then from there decide what we're going to do. So I will go to the actual repository. Did you see my screen before this moment? No, now I first see it. Okay, so it took a while to share it. I'll actually show the thing I told you I was showing because I guess you didn't see it. So I mean, it's not a very detailed plan but there's some notes here and we can add stuff as we go. Yeah, okay. Okay, so I already explained quickly the plan, I guess. So actually let's follow the plan and look, so the first thing is a quick look at the code and then what do we actually need to publish it? So we can think through the steps and start a repository and add what we need. So I'll use VS code to open this folder and it opens on the wrong screen. Here we are. And the file that was there are a lot of files here. This is a big project that has a specific purpose and the specific purpose importantly is not handling email and handling this big email inbox files. So one thing we want to do with this project is though to be able to open specifically email inbox files that are exported from Google and do some processing like create some sort of condensed view of email activity from that file. And for that we need to be able to read the email inbox. That's not at all core functionality here in this project but it is a useful thing you can do. There is a Python package called email which we're using but there's some things that we need to do that go beyond what email can do directly. So this is this small bit of code, this one single file that does what we need. Some passing email addresses in a list. So this is a string that is a comma separated list of email addresses and we want the actual email address not the name for example that often is contained in the string. And this is just for opening and reading specifically the Google's version of an email box file. So there is a standard but the standard is very open and there's differences between inbox files. So basically this one file is what you'd like to split out to this because it's useful alone. So this is kind of generally possibly useful for others and it is definitely not core functionality for this software that we are looking at. Okay, so to do that, what's the first thing? So we need to publish it somewhere of course. We need to put the code somewhere. This is already on GitHub, this code. So we are using GitHub so that the code itself will be public somewhere and we'll need a license and we'll need some documentation or at least a readme. It's a very short, relatively small package. So a readme is enough but I was doing it. We can of course see what comes to mind as we go. Probably dependencies, right? So we can see all the project metadata. Yeah, so what are the citations.cff file? I think these are the most important part. We didn't show it, but we do have tests for this email reader. So we will include those in the repository. Okay, well, let's go ahead and let's go to GitHub. I hope I don't need to log in. I don't. So this is the way I would now recommend actually starting. So you go do whatever online thing you want to publish your code in and create a new repository rather than creating a repository on your laptop and then pushing it to this online thing because now you don't have to worry about initializing an empty repository. So what should we call it? Because email is already a Python package and this is more specific than that. It's something like email utilities or? Google take out email or something? Well, whatever. I guess this can be changed later till it's published. A reader and some utilities or email inbox in. Now I'm actually starting to think in Google takeout. So Google takeout comes as a zip file and we do read the zip file in the code but not in this file that I'm thinking of exporting. So this is for reading files directly or files that have already been opened with zip file but it would actually be a good idea to add the ability to read the email directly from this Google takeout zip file but we're not doing it now. So it's missing a feature that I think is useful. Let's add a git ignore template for Python. This is super useful if you don't have to worry about files. And already here it's asking for or you can choose a license and I want to use the MIT license. I wrote the whole thing so it's not, I don't need to think that much about what the license is but let me still just quickly check what the license is in this original. It is MIT. So definitely taking something from published in MIT and publishing it as MIT is fine. So let's go create repo story. Okay. So here we have a small ish, well a very small initial repo story. I'll create a new terminal and clone it. So this is the address. Here we are and open it. So now I will be copying a bunch of stuff from one place to another. So I'll have two windows side by side. Start with the read me. We have this little bit of a comment text here that explains what this file does and since the file will be the entire package, this is now essentially the contents of the read me file. Oh, I don't actually want to edit the original repo story here. Okay. And probably I want to take out this underscores here and make it a proper title. Let's say this is enough for now. It could contain examples of how to use it and it could tell you how to, there's nothing specific about installing it but how to run tests, for example, but that's also just pie test. So we'll get to that. But let's say that the read me is enough for now. Now, in this file itself, I will create a new file. So this is a Python package that only contains one file. So you could still create a new folder and create an init file and create the, put the contents in, well, you need a separate files or a single file. The simplest way is to just create a file, a Python file like this. And now when you install this package, this Python file can be imported directly. Okay. So, oops, I did not copy this. So close that. Okay. So we'll need two packages, email and regular expressions. And I think neither one needs to be installed. Both of them are in the, so that would be, you can find out from looking at requirements. Yeah. So both packages exist by our in the default Python installation. So we don't actually have any requirements and dependencies for this. And then I'll just pick up the code from here. At least I think that it doesn't have any dependencies. But we'll see. There is at least one dependency that we will add in a moment. And that's PyTest, but that's only for development. Okay. So that's the main package. Now, what else do we need to do here? I guess let's go to the tests. Oh, let's actually first add the readme, commit it to the repository. Sorry. Making the initial commit. Or this is already a commit, so. Yeah, there is an initial commit because it came from GitHub. Yeah. So okay, so first you're committing the readme update. Yeah. And then I'm, oh, it shows. Interesting. Okay, this is new for me. It's just incoming and outgoing. So you can, I have changes that are not in the online repository. Okay. So Google take out email. And this is just the entire file. So. Yeah. And I guess you've made this entire file yourself. So there's no other licensing. It is published as MIT. So that's not actually a concern here anyway, but I did write the whole file myself. I mean, of course, often that can be an important thing to check. Like you need to ask all the authors before publishing stuff. Okay. So now we have all the basics. We still cannot actually, we can import this. And we should probably check that all of those imports are real. So that they are actually in this, the base Python package. So to do that, what I will do, and you can do this in many ways. In fact, there are probably these better ways that I haven't learned yet. I will use conda to create a new environment. Let's just use the name of the folder. Activate it. And what I will need to install is pip because I want to install dependencies through pip. In the end, we will be publishing this on pipi. So just to be sort of self-contained, everything, all the dependencies come from pip, or from pipi. Pip is the tool for installing things from pipi. And pipi is the Python package index. But we'll get to that. So now we have pip. I'm on one hand assuming that most of these things are familiar to anyone using Python, but on the other hand, it's always good to mention. So we're using the right pip. This is something that you have to do with conda. You have to check that you have, actually have pip in the conda environment. I don't have any dependencies listed, so I'm not going to use pip to install anything. I will just run Python and try to import. Tap completion doesn't work because I'm not using IPython. Yeah. Okay, it seems to work. Testing it interactively. Yeah. So can you, mailbox reader, actually start it? Of course, it's Google takeout email dot mailbox reader. And it's a class. And you would initialize it. So you need to give it actually a file handle to a mailbox file or a string that is a file name. Test file doesn't exist, so it will give me an error. Okay. Right, that's good. Then the next thing is that we need to create these tests. So I will put them in a separate folder, even though there is only one test file. So tests. So in this original code, there is a folder called tests with a bunch of subfolders. And here is the test email. Uh-huh. So you already have the tests also? Yes, yeah. Okay, that's good. Create a new file, test, Google takeout email dot py. Here we're importing OS. So this config file, this is the NIMPU is the package that this comes from. So this just tells where the test, where the test mailbox file is. So we'll need to change that. Yeah, I guess you need to embed the test data. Yeah, so we need, yeah. So we need to also move the test data into the package. This is a complication I didn't actually properly prepare for, but we'll have something to refer to. So the main thing I guess here is, most of these things I don't know how to do by heart, I will actually refer to something. Okay, yeah. Just like me, I mean. Yes, yeah. That's the way you should be doing it. Almost every time I make a new project, I find a recent one that is similar and copy it, copy it from myself. So now if I directly import Google takeout email, that assumes that I have used pip to install it on my system before I run the tests. So another option would be to actually do, well you could go down a level and import it from there, but I think this is common practice and it works. It just requires that you have it actually pip installed in this package. Okay, so we actually do need a new folder called data or test data. Should it be in the test folder? Guess it makes sense. Kind of makes sense. It's also easier to write out the path. Yeah. Oh, that's, okay. So I will simplify the name. So this is actually the name of the mailbox file in the Google takeout. So I will slightly simplify it. So we actually need OS, oh, we are using OS.join. This is the function we are calling here is OS.join to get the path correctly. So the folder is test data and the file is test.mbox. Here on the other hand, where is this test? So this is a test file. This is what the mailbox file looks like. There's the content of the body of the message and here's some meta data fields extender and who is it from? Who is it to? When was it received all of that? Because it looks a lot like standard email stuff. Yeah. And it's all just text. Okay. So now we have the mailbox file and we have the test and it will try to read from that file. Now we do need to install some A dependency but this is a development dependency. So I guess I will call it requirements dev.dxt. Just write down the dependencies. It is always useful to write down all the dependencies anyway. So this is PyTest. Here is a dependency and here we find the terminal window. So we in fact should install PyTest into this environment. Whoops, pip install PyTest, not just pip PyTest. Okay. Here we are. And now I think if I just run PyTest things and things will not work because I haven't installed this package. Right. I also cannot install it yet because I didn't make it a Python package yet. Okay. So let's go there next. Actually, yeah, commit this stuff first. So, okay, tests and test data. Just push them already. Okay. So again, I guess why not use this as reference? In fact, this is something I didn't write. I will tell you because this is something Richard wrote. But I will not copy this directly because this is not actually quite exactly what we want to do. Yeah. I will just use it as reference. So to make a Python package, should we actually do it the modern way and not use setup.py but use a tumult file? Yeah. I can find, do you have an example to copy from that? I have written those. While you're looking, I can explain the background. So what we saw in the old code was a setup.py file, which is probably many years old. It's the old way of storing or of declaring package metadata. It's executable Python code, which makes problems because you can't execute it until you have stuff installed already. The new method is a file called pyproject.tumble and it declaratively defines all the same things you see is here. So basically what you need, both declares what the build system is and the properties of the project. Okay. I'm unfortunately still looking. I guess I should just find the documentation rather than an example. I have a copy I can paste into those. Okay. Thanks. Okay. There. That's the chat. Now it's open on my shared screen. Oh, okay. So I guess I'll directly copy this and make changes. Yeah. It's made as a... Oh, it is an example. Okay. Yeah. It's from my own templates. Okay. So we need to create a pyproject.tumble and I've made a spelling error here. Pyproject.tumble. That's correct. Yes. Okay. So let's just look through this. So this is using flit to actually build the package, a module name. I guess we have now started using Google takeout email, which is fine. It's pip named the same. So I guess... I'm assuming this is what you... So you type pip install and this name. Yeah. So I prefer if it's the same, but people sometimes use a different name. So this is what you import. Mm-hmm. Or is it? Yeah. I guess. Yeah. And this is the name that you install. This will also be... It won't be auto-detected, but anyway. Yeah. It's better if it's like... Okay. Authors. Authors. And you can also give an email in there, but I've never done that. I generally do, but... It could be an email field, like... Oh, it's okay. This is... I'll check. Giving my work email here is not a problem because I'm already using it in the example. Oh, in the test data. Yeah. So it is email just like that. Yeah. Readme.md. So the way the PyProject file works is it automatically gets... Or the way Flit works is it automatically gets most of the metadata from the repository itself. Okay. So I guess we don't need most of these fields dependent. So I wonder if it finds dependencies directly, but we don't have dependencies. It's an empty list. Right. You know? We will actually require zip file once... I've changed it so that it can open the zip file directly, but I'm not doing that now. So right now it's 33 minutes past the hour, so maybe we won't get to that part. Yeah. We will not do that today. In any case, the point is not to write the code here. The point is to publish the package. Yeah. So this is... We don't have a script here. So we'll just get rid of that part. But here you could add a script that you can execute directly from the terminal so that you don't have to import in Python. So that's a useful thing to take a look at. Yeah. Option dependencies test. And then a PyTest. We don't have... Well, we have a repository URL, I guess. Not the separate documentation URL. And the repository URL is somewhere here. Okay. Here we are. Nope, not that one. Did I close it? That was the quickest way to find it. Okay. So it's github.com, but this is not how it works. It's name and then... Yeah. Within the L, I guess. Okay. Then there's some information here of how to build it and how to push it. Or how to publish it. We will need that in a moment. But now, since we have this, we should be able to pip install dot. The dot is this folder. So... Okay. That did not work. No doc string. Can you go down? I thought I saw. Please add a doc string to the module. So it automatically gets something from the module. Okay. So I guess copy the same thing from before. Okay. So this is the module. Yeah. And this is doc string. It needs a doc string and that, I guess it should be on top of the file. Probably, I guess. If it's automatically detecting it, then I assume it needs to be in standard place. Let's try this way. No. Need version. So... Okay. So version is not defined anywhere. Right now. And it should be defined somewhere. So is there a place in the... It should be defined in the module itself. So in Google, take an email.py. So... So it needs to be... Version. Equal. So we define a variable directly in this. And we will call it 0.0.1. Yeah. Like I usually do. Yeah. Okay. So now we have it installed. Great. I guess we can point out that where we are now, this is even useful if you never publish it to PyPI. So you have something which is now self-contained. And you can install it yourself. And you can install it from GitHub. Yeah. Which makes it easier to use things across different projects without setting Python paths and so on. So it's just... There is one more step before that. So... At... My. .string. And... Version. To the module. So I mean, we need to push it to GitHub before we can install it from GitHub. Yeah. Okay. So now we... Sync changes. Now everything's in on GitHub. And now, yeah, in principle, we could instead of pip install. We could write the repository URL here. I think it's git plus the URL with HTTPS. Yes. And then it will install it from the repository. So you don't actually need to put it on PyPI to install it from a remote. In really most of the time. I don't... If it's just for me or I don't expect it to have a big audience. Yeah. So I guess in this case, I don't really expect it to have a huge audience. But still, why not? Like pushing stuff to PyPI is also not a big step. It's... It might be useful. But it's true also. I often I would not do this next step. If I... Unless I want someone to be able to conveniently install the package without having to type in a long URL or copy it from somewhere. So that is the main purpose of PyPI. The package index, you can just type pip install package name and you get the package. Yeah. Okay. So I have actually not used flit before, but your... Basically. This template had instructions on how to do it. Yeah. We could even open the... And actually with pyproject.com, I've hardly ever used flit directly now. Oh, okay. I've basically always installed it and it's happened in the background. And the uploading, I haven't done that much. Yeah, okay. If you don't upload, then you don't need to run flit. Yeah. Okay. So... But in order to build it, do we actually need to install flit? I suppose we do because I cannot just run it from here. Yeah. If you... I could use app to install it. Or it's probably available from it. Yeah. I mean, I guess it's... It must be in flit or in pip. Yeah. It's not a built-in library, but neither is twine, which is the previous option for pushing the pypy. So this is just not a thing that's built into Python itself. Yeah. You need to install it separately. But now we have it. So we can... It tells us conveniently what we can do with it. So we can flit build. Okay. Oh, it doesn't... So the URL I wrote here doesn't start with HTTPS or HTTP. So it's not an actual URL. Yeah. Oh, that should be better. Oh, HTTPS. Let's try again. Okay. So now flit... Sorry. It built. Yeah. Now flit agrees with me and everything is fine. So it actually checks the metadata in this Tomo file, which is great. So it just checks that you didn't make weird spelling mistakes and things make sense. Okay. So what do we have now? We have this dist folder in our project. And dist folder contains essentially, in this case, a real file, which is the way you install Python packages and then a tarred version of basically the Python code, because we only have Python code. We are not actually building any C code or anything. This is relatively straightforward. So this will work with any Python tree, basically. Okay. I didn't actually... Yeah, I didn't require any specific Python version here, but it actually does only work with Python tree, I think. Okay. But yeah. So now we have a built version of it. And we can publish that on PyPy. So this command will push it to test PyPy. Most likely this will fail because I'm not logged in. Okay. So it's looking for how to log in. And there is no test PyPy logging information. So now I have to remind myself how to do this. Okay. So there should be a PyPyRC file that stores your username and password, although it didn't ask me. So I guess I have that file. Maybe I should check for that file without showing you the username and password I have. Oh, but I don't have one. Okay. So I'm slightly confused about why it's failing instead of just asking me for the username and password. But I can... Whoops. I don't want to click that one. So I can set username and password here. Can you give it the URL on the command line instead of the test PyPy instead of the name? So the full URL to the test PyPy. Okay. So it's here. Oh, actually, here's an example of the PyPyRC. So it should give you... You should add your username and the URL there. I somehow always remembered it prompting me directly. Yeah. Okay. So it does want the... Yeah. So the thing is it will prompt you for a password in any case. But you... I guess you need to create a PyPyRC file. And it needs to list all possible servers. Then it needs to define... And the username defined here is, of course, not my username. So I will quickly check it on the other screen. So copying the password in case. So the username is... It's my GitHub username as well. So it's not exactly private information. Just PyPy directly. I didn't want to copy this because I had already copied the password to PyPy, but fine. Okay. I'm not used to how Vim works because I've been using code for a while. Okay. And let's save it. So now it knows where TestPyPy is and also where normal PyPy is. And we should be able to call it this way. And now it asks for the password. I'm not sure that I have a password for TestPyPy. Let's see if this... No, this doesn't work. Username password authentication is no longer supported. Migrate to API tokens. Maybe we can stop here and leave it. Yes. Okay. So you would be here. You could upload it to PyPy following different things. You've made the package yourself. Yeah. So I do this rarely enough that I always have to... I always have to check. But I kind of did want to show it until the end or to get the package on PyPy, although that's not the most important part. So here's instructions on uploading packages from PyPy itself. And this is what we should follow to actually get it to work. Or Flit probably has updated instructions, but these instructions don't seem to work. Yeah. Yeah. So to figure that out, we'll take it a bit more. Although I think actually doing it with... Just uploading with wine is just one command. If we can figure out how the API keys work. But before we go there, there was a change in the PyProject. So fix URL in pyproject.toml. We are running out of time when we did all the important parts. So I guess one more important thing is to take a look at the repository again on GitHub. My repository is... Okay, let's look for it. So, okay, here we are. So here is my new package. And yeah, this contains everything we need to install the package and to run the tests. So I guess we didn't quite do that yet. Okay, so we can run the tests. So yeah, they don't quite work, which is understandable. So I mean, I'm wondering should we summarize now? There's a lot more debugging to do, but we've got 10 minutes. So let's not forget the main... Or what someone would be most interested in. So here we did the license documentation dependencies. This is all somehow linked through both in the repository and also declared in the pyproject.toml file. Citations. You want to comment on the citations some? Okay, we didn't create a citations file yet. This is a way especially... And also this is maybe not... This is more of a utility project. But if someone uses your code specifically to... In some scientific publication, for example, they should be able to cite your code, right? Because it is a scientific output, kind of like a paper. So the citations.cff file tells how you cite a project. I can show an example of one relatively quickly. So this would be... Now I find the rest of repositories. Somehow I didn't previously find it. Okay, sorry. How did I not find it? Right, so here's an example of a citation.cff file. This is also a Python project. And it lists essentially a message. Please cite the software as below. And then lists the authors. And you can add a DOI which you have from Zenodo and a URL to the publication, the software. And importantly, you can also cite things here. So if you use something, if you use a scientific publication of software that you can cite, you can then list them below in this file. And there's tools for tracking citations using this, including in GitHub itself. So that's just useful for you, of course, to track citations to your project. But also it's important for others when they use your code for doing some science, they can then actually cite your work. It kind of allows them to use it in a scientific publication. Okay. So we didn't quite get it on PyPy, but that only makes it slightly easier to install. Do you want to demonstrate installing from GitHub with the URL? Yeah, that's a good ending point. I actually just thought about something. We didn't quite add the data file into the package yet, and that is why the tests are not working. So adding a data file to the package is something that you don't... You sometimes need to do, but most of the time not. And that's a separate issue. So this is a Google takeout email. So what I'm looking for is the URL, correct URL to the repository. And now we can install it from PyPy, using install it with PyPy. So PyPy install as usual, and then we say git plus, and then the HTTPS address. We often make a mistake here, but I think this is correct. Let's see. Looks good. Okay, so it clones. Okay, so yeah. So it first clones the git repository. Okay, it says cloning. And then it does some filtering of the branches and also it doesn't want all of the data. But the important thing that is, it's getting the repository from online. And then it's following as usual to installing dependencies, getting requirements to building a wheel file, and then installing it and getting metadata. So this is exactly the same thing that happens if you install from PyPy. So if you type pip install numpy, it just gets the file from a different location. And you have to specify the full URL here too, so that it knows the location. Okay, well, is there anything else to comment before we go? Yeah, it would be nice to get it to PyPy and to get the data stuff to work, but that would have taken more than an hour. I mean, we focused on the important parts. We can have the people can look at the repo later for the full thing. So I have shown the URL, but let's actually copy the URL here. And this is basically, this is not that hard. Yeah, that's the main thing. The code yourself. You can easily find a PyProject tunnel file, copy it, make it semi-installable, and add stuff later. And I really do this for most of my projects these days. Yeah. So for license, we just selected it when creating the repo story. And there's some differences, but they're not huge anyway. Well, read me a documentation is a part that you have to write yourself, but it is a description of the project. You just describe what you're doing. Some examples are nice, of course. Listing dependencies is maybe the, well, it's something you should do anyway, because it is very useful. But it is kind of also the thing that is non-trivial here. Then citations, which I showed. So you can copy a template again from anywhere. And tests are their Python functions. So there's nothing that, nothing especially intimidating here. And it only took us an hour to do the main steps. Maybe in another 10 minutes or so, we could add the data file to correct place. And in another 10 minutes or so, we could figure out how PyPy has changed so that Flit no longer works as instructed. But that stuff happens. Yeah, so those are the important parts. Yeah, well, thanks for watching. And yeah, thanks for watching. Thanks. We'll update the repository and video description with anything that's been missing. Yeah. Okay, with that, bye. Bye. Hello, so we are back later in the afternoon and we will finish the demo now that we've figured it out. It wasn't that hard. Anyway, Jarno, so now we will try to complete the publishing to test PyPy. Yeah, so there were two issues. One was the data file and the other one was that publishing didn't work as it seemed from the documentation. So I figured both things out. So for the data file, we don't actually need to have it in the package because we will only use it for testing. And when you're running the tests, you will clone the repository. So we will just have the data file in the repository. The problem was that this is not the correct path depending on where you're running the test from. This is not the path to the data file if you're running it from the main folder of the repository. We'll make it even better than that because we can get the full path to the test folder because I just copied this from my notes but I will quickly explain. So this file contains these test functions. We take the absolute path to it and then we take the directory name of that which gives us the full path to the folder where the tests are. And then we just join it here in the beginning. So now we have the directory where the tests are and from there we go to the test data folder and there we take the mailbox file. So yeah, a little bit complicated but we always get the correct file no matter where we are on the file system. And that in fact did work the last time I tried. So the tests run correctly now. I run PyTest and there's three tests that it runs and they all work. So that's one thing. The other one is then publishing it. So that is an example of what the PyPy RC file should look like. So what I needed to do is go to the pypy.org and test.pypy.org separately and for both of them create an API token. And then authentication happens so that the password is the token that you generate and the username is underscore token. So it looks similar to before but really what you need is this authentication token and not 100% sure what the purpose of the change is but it is more secure with the token. You could change the structure of the file to make it clearer but in any case this is what it looks like. So let's just close that example. That was of course an example because I'm not showing my actual token here. So otherwise there's no change and we can go to the terminal and run flit publish. So in fact let's find the yes. So flit publish repository test pypy. Oh great, a new one. So flit wants you to have everything tracked with git so there's no risk of unintended stuff being published. Okay so what is it called? What is it saying it's not tracked? The bottom one. Can you do git status and maybe? Okay. Oh it is actually complaining probably about this example file that I created. So this is not the real pypy rc file. This is the example I just showed you. So let's just remove it and try again. Okay, so now it works. So we already ran flit build. So I will come back to that error in a second. We already ran flit build. Everything's fine. And then you run flit publish. It is now complaining because the file already exists. I have already uploaded it. I can probably get around this and demonstrate this by updating the version number. So let's do flit build. Okay fine. Flit build publish. Okay. And now to publish in actual pypy you just remove this part. So just flit publish and it goes to the actual Python package index. Here we go. Okay. And now that it's in the actual Python package index you can install it with the pip install package name. And it worked. So now we upgraded from, oh already satisfied point also. So yeah, I didn't ask it to upgrade. It just, it is already installed. Yeah. Yeah. All right. So that is everything. Now the project does have its own page on pypy if you want to take a look. This is maybe the last interesting thing to demonstrate. So all of this information comes from the Tomo file and read me. So it found to read me in the repository. And in Tomo file it has a, the URL to the GitHub page. Well, this is my pypy account but authors are listed here. License is here. The license file and so on. So yeah, it's relatively smart. It found a lot of things just from the files in the project. But yeah, so there it is. People can now install this package and use it. Yeah. I guess all our other talking was done before. So thanks a lot. Thanks. Bye. Bye.