How to build a cross-platform graphical user interface with Python

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
foreign like to introduce Russell Keith McGee Russell is a um is an important contributor in many ways to to Python and the python sort of ecosystem and Community Russell is a fellow of the Python software Foundation um has has been very involved in the Django project and is the author of The beware Suite of the sort of original author of The beware Suite which is now growing and uh um so Russell's going to talk about the beware suite and uh in particular about cross-platform user interfaces so please give a warm welcome to Russell Keith McGee foreign as he said my name is Russell Keith McGee and today I'm going to show you how to build a cross-platform GUI with python a little bit about me I am from otherwise known as Perth Western Australia I am a principal engineer in the open source group at Anaconda not the camping store the data science and python people Anaconda pays me to attend conferences like this one to talk to people like you so I'd like to thank them for that support they also support the python open source ecosystem more broadly by providing direct financial support to organizations like num focus and by employing developers to work full-time on open source projects projects like Jupiter number and the open source project that I founded beware for those who haven't come across it before beware is not a single product or library or tool it is a collection of Open Source tools and libraries for creating native user interfaces in Python for desktop but also for iOS for Android for single page web apps and in Alpha form for console apps uh in every tool in the suite solves one very specific part of the overall problem of writing a GUI app in Python and distributing that app to other people on various platforms Anaconda supports beware to ensure that python remains a viable programming language on the platforms that people are increasingly using as part of their day-to-day Computing life phones tablets and so on however it is important to note that beware is not an anaconda product or anything like that it is a fully open source project with independent governance and funding I just happen to be paid by Anaconda to work on it full time one of the components of Beware is toga a cross-platform GUI toolkit this talk is not a toga talk at least not directly the Practical examples are going to lean toga but that's just because I've got to show something however the ideas and problems I'm going to be discussing are very generic you will see the same problems and broadly speaking the same Solutions just with different spelling no matter which GUI toolkit you use but before we start building our GUI app first thing we have to ask do you need a GUI at all in many cases the answer is no you do not you know the adage this meeting should have been an email this app should have been a mailing list as software professionals we owe it to our clients to interrogate why they want an app do they actually have a use case that requires an app or does an executive just want to be able to say we've got an app this is on us as developers to push back against clients that ask for things to interrogate why a client wants an app and if their use case doesn't actually add up convince them that their development time is better spent elsewhere sometimes our website is the right answer to a problem especially if you're able to take the effort that you would have spilled would have spent trying to build an app and direct that towards improving the web user experience for example websites do have distinct advantages over apps features like deep linking are in the blood of how websites work you can deep link into an app but it isn't immediately discoverable websites are single deployments where a deployment has immediate and Universal effect if you find a bug in a website and you need to deploy a fix urgently you can push an update and everyone has that update pretty much immediately that is effectively impossible to do with an app and apps especially mobile apps are distributed through the wall Gardens of app stores your ability to play in that Garden is 100 at the whim and mercy at the owners of those Gardens if an app store rejects your app or doesn't approve it in a timely fashion you are pretty much out of luck and then there's the mandatory profit sharing that app stores in force so when is an app the right solution well native apps allow you to work with local data if your data files are multiple gigabytes uploading to a web app probably isn't a viable option if the data is sensitive for any reason you need to be very careful about where you are uploading your data local data storage and manipulation may be preferable or even required in those cases remember your attack surface is only ever as big as the data that you actually hold you can't leak data that never left the user's device in the first place local data also matters if users are going to be working offline Outback locations have bad to non-existent mobile data coverage and unless you've got access to a satellite some inner city locations have bad coverage on some mobile networks if I can't get a mobile data signal I can't use a web app but I can still use native apps on my phone native apps also allow you as a developer to deeply integrate the user experience or with the user experience of the device integration with task managers notifications taskbars application lifecycle and more these Integrations are difficult or impossible to do in a web context because the entire web experience is delivered through a the lens of a browser tab native apps have almost unlimited access to device Hardware things like cameras and peripheral ports and GPS readers web apis for these exist and they are getting better but they're also heavily sandboxed by the browser native apps have much lower level access to those services and when I say device Hardware this includes the CPU and GPU you can do some very impressive things with webassembly and related Technologies but if you need to do some computational heavy lifting a native app is going to give you much better results because you have direct access to multi-core CPU and GPU acceleration now some of these properties can be true of some web apps but when compared to a vanilla website these are areas where having a native app will be an advantage okay so you've decided your a GUI app is appropriate for your use case you know you want to use Python because why wouldn't you so how do you build your app well if you want to build a GUI app you need a GUI toolkit a GUI toolkit is the API that lets you put buttons and text inputs on the screen so your end user can interact with them the good news is that there's a whole lot of options for GUI toolkits in Python the bad news is that there's a whole lot of options for GUI toolkits in Python the python standard library has a GUI toolkit baked in called TK enter TK enter is tickle TK wrapped in Python now ten years ago I stood on the pycon Australia stage and gave an impassioned defense of TK inter I now run my own GUI Project Read into that what you will QT is the big name in cross-platform GUI space not just in Python it's the GUI toolkit used by KDE on Linux but it's also used by a lot of commercial apps that need to have both Windows and Mac and sometimes Linux clients virtualbox caliber Mathematica these are all apps that use QT for their GUI those apps aren't written in Python but QT has python bindings actually two of them for historical reasons gtk is the most popular GUI toolkit for Linux because it's the GUI toolkit for The Gnome desktop environment and essentially every other desktop environment that isn't KDE gtk is available on Macos and windows but it doesn't really work well there 20 years ago WX widgets was a moderately popular option it's still around and is being maintained it's a lot less popular than it once was all these toolkits will produce an app that looks native sometimes that's done by actually wrapping native implementations sometimes it's a theme that tries to fake looking native that theme may or may not be convincing depending upon the implementation Kitty is a cross-platform toolkit that takes a completely different approach rather than try to make a KIVI app look native Kitty takes complete control of the rendering process a kitty button looks the same on every platform it never looks like a native button then there are gaming toolkits there are some slight diff these are slightly different to you know traditional buttons and text input GUI Frameworks because they're targeting building games but they're graphical and you can use them to build a user interface so depending upon your needs they might be the right way to go uh Pi game and pursued by bear are two examples in the python space and then there's electron electron isn't really a GUI toolkit per se it's a website wearing a trench coat uh you can build any user experience you'll want it's it's up to your HTML styling and JavaScript that you've got on your web page this can be a viable way to rapidly develop a cross-platform user interface especially if you've already got a team with web skills being a hybrid you get better access to Native capabilities than a raw web app would have but it also has its own trade-offs memory use is a big one electron apps also have some very distinct tells from a user interface point of view that can be very difficult to mask but it works whether it works for you that's a decision you have to make this is not an exhaustive list there are others not the least of which is toga if we've got so many choices you might ask why did I bother building another one well firstly platform support of all the options that I listed previously QT and kitty are the only two that have any solution for mobile and at the time I started toga QT didn't support mobile at all to the best of my knowledge it still doesn't have python support for mobile QT has a webassembly tech demo none of them have a terminal a console mode back end toga supports all of those as well as the desktop platforms secondly toga is 100 platform native if you are on Macos and you write an app in Kitty you can tell it was written in KIVI because it doesn't look like a Macos app it looks like a KIVI app if you write a toga app on Macos it is indistinguishable from a native app because you are using the same apis that you would have used if you'd actually built a native app in objective-c or or Swift why do you care about that well aside from the Cosmetics you get things like accessibility for free a toga app will adapt to Native font sizes color choices and we'll work out of the box with screen readers and other assistive Technologies because the underlying platform apis support screen readers and assistive Technologies Kitty doesn't thirdly it is written in pure python that means there are no compiled components this means it's easier to deploy because there's no binary wheels or compilation step and it is a lot smaller the binary wheels for the pi side Essentials on Macros clocking in at 144 megabytes if you want a web browser in your app you're also going to need the add-ons package and that's an additional 255 megabytes the whole of toga on Macos including the web widget is a little over 500 kilobytes lastly it is an Unapologetic unapologetically python toolkit written for python developers that means it uses python linguistic idiom async await context managers done to methods the developer experience has been designed to be familiar to python developers not a c plus experience crammed into a python development environment and there's no additional domain-specific UI markup languages or yaml or XML which you have to learn to make your GUI work Okay so we've picked a GUI toolkit let's build an app you might be thinking that with so many options I couldn't possibly say anything both meaningful and generic but the thing is for the variety all the variety of options most GUI tool kits are very similar when it comes to basic workflow here is the code for a simple toga app we subclass and app class that app class has a startup method that runs when the app starts that startup mechanism creates a main window you add content to the main window somehow and then you show that window the main method of your app creates an instance of your app and then starts What's called the main Loop which runs the applications event Loop we'll come back to the event Loop but for now all you need to know is the event Loop is what runs the app the app will be displayed when main Loop is invoked when you exit the app the main Loop method returns now that's toga but the code is very similar for almost every other GUI toolkit here is the same app written in QT with pi side all of the widget names start with a queue the startup method is the dunder in it of your application application class and you set Central widget rather than assigning content and you call xec rather than main Loop but structurally it's exactly the same and here's the same app in TK enter TK inter convolves the main window and the app has a single thing so you don't have to explicitly create the main window but you create an app you pack content into the main Window app hybrid and then you start the main loop again modulo some fairly minor discrepancies it's the same code game toolkits are a little different because they tend not to have widgets like button but the basic start app create Windows start main Loop workflow definitely exists electron is the odd one out here it still has a main Loop but you really don't ever see it electron is really just starting a web server and the GUI is rendered pages from that web server all right so that's how you set up the skeleton for an app how does that app actually you know work well what is an app an app is a layout of graphical elements that respond when the user interacts with those graphical elements interacting with resources that are provided by the host computer your experience with any given GUI framework will essentially be governed by how simple or difficult the framework makes those three tasks so first off we need a layout of graphical elements you need to describe what widgets are visible and where they are visible every toolkit has a very slightly way of doing this but there are some very common General themes most GUI toolkits when you do their you know hello World tutorial will introduce you to some sort of box packing model whether or not they call it that this is some container and you pack content into that container that packing process will usually have some sort of controller whether you're packing horizontally or vertically whether a new object is packed at the start or the end of the container of the packing content the containers are usually hierarchical so you can pack a horizontal container inside a vertical container inside a horizontal container and so on as you need to go Dom based layout is somewhat like a box packing model except that instead of explicitly putting a widget inside a horizontal box inside a vertical box you build a hierarchy of content and then apply layout rules to that content or to that hierarchy this will often involve designated some content as horizontal or vertical boxes but it doesn't require it css3 flexbox and grid layouts for example are both layered over the top of a Dom the hierarchical definition of content but the specifics of where children of that tree that hierarchy live appear where they appear relative to their parents is determined by styling rules electron being a website in a trench coat uses a non-model because it's literally HTML and CSS so does toga toga doesn't literally use CSS it uses a reduced CSS subset called Pac but pack has a direct mapping to css3 flexbox rules and toga has been designed in such a way that the intention is in the future we will be able to drop in an actual full css3 engine into toga another common approach is a grid layout this is essentially the same as HTML tables you describe how many columns and rows exist in your layout you place widgets in that layout at a specific row or column depending on your GUI framework widgets maybe how to span multiple columns or multiple rows and then you define how the grid is allowed to resize are all columns the same fixed width or is column one exactly 100 pixels wide and column two is twice the width of column three things like that a less common approach is something called constraint based layout in a constraint-based layout all the content is thrown into a big unsorted bag but then you specify mathematical constraints on the final layout button one must be 100 pixels wide the left of button one must be 10 pixels from the side of the window the left of button two must be 20 pixels from the right side of button one the layout algorithm is then literally told sulfur X in that case it would mean or in this case here it would mean that button one is at x equals 10 and button two is at x equals 130. constraint based layouts are incredibly flexible and very responsive but it's also easy to end up with an over or under constrained layout where a layout isn't working it's nearly inscrutable to work out why you are literally trying to debug a complex linear algebra problem where all you've got is the wrong answer and a layout that doesn't work on your GUI toolkit you can mirror grid inside your box packing layout for example but it's not the only layout consideration you also need to think about how you're going to adapt to different sizing for anyone with a web design background this won't be a new idea what if any constraints are you going to impose on window size and how will content in the window adapt to sizes in window size this is an area where Dom and constraint based layouts have an advantage because you can Define different CSS rules for different widgets window sizes or rely on loosely defined constraint rules to find different solutions to the layout depending upon the input window size but completely aside from layout you need to think about look and feel if you are building a cross-platform app are you going to try to try to make that app look the same everywhere or make the app look as native as possible everywhere now this is an area where I have opinions uh I despise personally apps that decide that they know better than the operating system what a button should look like my experience has been that apps that have a common look and feel everywhere are the Hallmark of either lazy or under resourced developers to me commonality in parent in appearance is a form of implicit documentation if your apps buttons look like every other button on every other app in my operating system then I know when and how they can be pressed and I know how they will respond when they are pressed if your UI designer has come up with a whole new visual concept for pressing this I need to learn what that is and that slows me down and when it comes to things like how keyboard shortcuts work in a text area it can be a major impediment to productivity the one case I will begrudgingly make for guis being visually consistent across platforms is in settings like education where you want screenshots of your app to be consistent with exactly what the user sees on every platform however even then I would argue that Beyond basic introductory material you are doing your users a disservice by trying to mask platform conventions under a facade and inevitably when you hide platform defaults you also hide platform accessibility affordances and all the little details that make computers easy to use whether you agree with me or not this is an area where your GUI toolkit will constrain your options KIVI is all in on making your GUI look the same on every platform gtk has a default look and feel but it can be extensively themed toga has very limited styling options some basic size and color controls but you can't make a button look rounded if the Platinum platform doesn't give you rounded buttons you don't get rounded buttons so that's layout going back to our description of an app our app responds to user interactions with elements in our layout now earlier when I spoke about starting the app I mentioned the event loop as being what runs the app let's peel back the lid on an ISO standard GUI app and see what we find inside no matter the GUI framework what you will essentially find is this when you invoke the main Loop the startup method is invoked which creates the GUI then you start a literal loop the loop is essentially while not quit process events redraw every time you click on a button type a character drag the corner of a window to resize it that's an event and the app code will respond to each of those events in turn redrawing any part of the window that requires a visual change now this isn't actually the code of any GUI framework but it's also not that far off the important thing to note is that when an app is redrawing it's not processing events and when it's processing events it's not redrawing who has ever seen an app beach ball yeah all right ever wondered what's happening this is what's happening your app is trying to process an event and it's taking too long and it hasn't redrawn in a while for some measure of a while and so the operating system tells you there's a problem by showing you a little beach ball icon beach ball is very easy to generate let's add a button to our GUI and say that when the Press event occurs we want to download a movie and in our event handler we use requests to download the URL the movie photo was large so that download is going to take a minute or two we run our app we push the button and we get a beach ball why because requests.get is a blocking method the event handler has been called but it won't return until the download has finished and until the event handler is returned the GUI can't redraw so obviously we need to do something different here First Option use threads now as soon as the threads get involved the gods have a little chuckle threat execution can be unpredictable and therefore very hard to debug the old joke is that if you're a developer with a problem and you solve your problem with threads problems now too have you the other thing to consider is that a lot of GUI toolkits have a very complex relationship with threats the thread that runs the event Loop is called the GUI thread so something simple like when the down sorry the many GUI Frameworks will only allow you to invoke GUI actions on a GUI thread so something simple like when the download is complete update the status bar becomes a lot more complicated because you can't update the GUI from the thread that is doing the download the usual solution for this is the GUI toolkit will provide some way to post an event to the main thread so you wrap up your update the status bar logic into a function of some kind and then post that function to the GUI thread so it can be executed there almost as if it was a user generated event the second approach is to break whatever you're doing down into chunks and then explicitly Release Control back to the main event loop on a regular basis there's no problem doing complex calculations in the GUI thread you just can't monopolize the GUI threads time you've got a release control back so it can redraw so most GUI toolkits will have some sort of mechanism to either explicitly explicitly run an iteration of the main Loop or Release Control back to the main Loop in some way for example in toga because it's a python first toolkit you do this by turning your event handler into a generator and yielding it tells the event handler you can give control back to the event Loop now and then resume processing as soon as you as soon as you can as long as the gap between yields is relatively short sort of 0.1 of a second There's A good rule of thumb is an upper limit this can work really well better approach still go asynchronous guis are almost literally the poster child for why you care about asynchronous behavior in your app while you're waiting for something to happen you don't have to block the app for doing something else like redrawing Release Control back to the GUI app let me resume when the result is ready most python GUI toolkits don't support async Behavior natively either for historical reasons or because the languages they're built in usually C or C plus plus or derivatives don't have native asynchronous language constructs but python is a language a toga is a python first toolkit so any event handler can be an asynchronous Co routine you need to Source your async libraries for any blocking Behavior so you can't use requests for HTTP retrieval my suggestion would be use either hdbx or AIO HTTP but as long as you can find an async library to do your busy work this works really well so that's event handlers and the event Loop they're responding to explicit interactions from a user what about background workers I want something to run in the background upgrading the GUI once a second well same rules apply you can't put a while true sleep One update into your code because that will block the event Loop so you need a different approach again you can use threads with the same caveats as before another common approach is something called a timeout function you invoke the function on the main Loop that says call this other function in one second and when that other function finishes the last thing it does is say call me again in one second it's a bit like the way you post methods from a worker thread uh from the for the main Loop when the GUI updates except that there's a delay between invocation and execution or if your GUI toolkit allows you can also Define a Cooperative or asynchronous background Handler after all what is a background worker but a persistent button Handler where the button is pressed for you when you start the app the only cut only caveat you can't use time.sleep because that's a blocking method but you can use a weight async io.sleep because that's non-blocking all right sorry we've got a layout we're doing work in response to user interactions but we're going to need to interact with the resources on the host computer there's really only one thing you need to know when it comes to dealing with app resources there is no current working directory when you write a Python program that says open myfile.txt myfile.txt is being specified as a relative path not an absolute path it's implied that any relative path will be resolved relative to your current working directory which is basically the directory from which you started your python interpreter when you start an app from your taskbar what's the current working directory is it the location of the app is it the user's home directory the last folder you would open your file manager the root of the file system who knows essentially if you do anything with past in your app you must use absolute Parts worst case you can use done to file on your python source code but most GUI GUI toolkits will give you some way to get an absolute path anchor of some kind a no known path that is associated with the app a resource folder or something like that toga for example provides app.parts that gives you a whole bunch of known paths that you can use as anchors as path live objects so you've built your app but any good engineering is not just about building it's about testing as well how do you test a GUI app the bad news here is that GUI testing is a bit of a nightmare there is essentially no analog of selenium or plainright for poking a GUI in a cross-platform way there are platform specific Solutions but some of them cost a lot of money and they're all a bit messy so your best bet is to clearly separate GUI logic from business logic use automated testing for all the business logic interface and then use manual testing as a final integration or acceptance test there are many application design patterns for this model view controller model model view view model model view presenter picking one of those is a talk unto itself and your choice of GUI toolkit might influence your choice the key thing is that you have to have a clear testable interface so that you can test assuming the button is pushed what logic will be executed and what changes might be affected in the GUI this is also an area where your GUI toolkit might be able to help you toga for example is a cross-platform toolkit and one of those platforms is a dummy a back end that satisfies the API contract of a back end but doesn't actually display anything it but what it does do is expose an auditable log of all the GUI actions that have been performed toga guarantees that the real back ends will do the same thing as the dummy back end as part of its own testing so if you can validate that your app works with a dummy backend you should only require some light final acceptance testing also remember that you should probably be running your tests on the platform where the apps will run so if you're building a mobile app that means you need to run your tests on a phone if you're building a mobile sorry beware can be some help here briefcase which is beware's app deployment tool has a testing mode that can be used to run apps on app tests on mobile devices and mobile device simulators including NCI and briefcase isn't limited to working with toga all right so what if you're elbow deep in your cross-platform app and you discover that there's some capability that your platform has but your GUI toolkit doesn't well any cross-platform to a toolkit is a layer on top of system native libraries at some level if your toolkit doesn't expose a feature you need go straight to the source call the native system libraries to do whatever you need to do there is always a way to get a handle to those native system libraries sometimes multiple ways and in the worst case you can almost always fall back to raw ffi and C types to invoke functions in a binary library that code that you write won't be cross-platform so you'll need likely need to add your ifs dot platform equals macros type guards you also need to get familiar with reading Objective C documentation and converting it into the equivalent python or you know whatever platform language you need to know but it's also not as hard as you might think for what it's worth toga Express provides explicit access to these layers if you've got a toga widget you can always get a handle to the native widgets that are being used to implement the toga widget this is true to varying degrees of other cross-platform toolkits check local guide for details but as we did in the beginning sometimes it's important to interrogate your original premise maybe a cross-platform toolkit isn't what you need a cross-platform toolkit is by its very nature and abstraction this means fine details get lost if you're reaching a point where you care about those fine details maybe it's time to consider whether you should abandon your cross-platform toolkit and see whether you can build a common logic core that's then shared between specific front-ends this also doesn't mean you have to leave python toga is a cross-platform toolkit but you could build a native platform-specific app using Rubicon objective-c tracker pythg object to python.net toolkits that at over users to access its local systems so that's a lightning tour of the things you need to consider when building a cross-platform GUI app in Python if you want to know more you want to try this out for yourself I recommend going through the beware tutorial.com that will take you from a clean sheet of paper to a running app on desktop mobile and web other GUI toolkits have tutorials as well if you want to go further token has its own docs the GitHub repo has lots of examples briefcase which is beware's tool for packaging apps and distribution is also well documented and isn't toga specific if you like what you've seen here and you'd like to support this work on toga and the beware project you can join the project as a financial member we also you can also back me on GitHub sponsors the income we use is raised to cover stickers hosting things like that as I said earlier I am paid by Anaconda to work on beware but the project itself is independently managed and financed so this funding is an important part of the project we've also got a Discord plus discussion forums and ticket trackers for the projects details on the website or if working kind is more your thing we're always looking for new contributors we have a long list of features we'd like to add we have challenge coins for people who contribute I will be here for the full Sprints I've got a long list of issues for first-time contributors and there's still plenty of low hanging fruit even if your own experience as as a python developer thank you all very much thank you very much thank you Russell thank you for a fabulous talk and thank you for your work on this uh um important talk about what's been declaring Gap in Python for a long time so are there any questions yes Eleanor Mike uh thanks Russ it was great talk really informative um I know from the testing slide you use the term dummy instead of mock was there something important uh no just a choice of words it is not really a mock in that it's not it's not pretending to be an underlying Library it it is a GUI toolkit it just doesn't draw anything and it's output it's GUI output so it's fundamentally like the same as a mock um maybe yeah I guess the difference the discrepant the difference I would draw is that it's not injecting itself in and pretending to be something that's not it is a legitimate Target in itself it's just not a useful Target we have an online question from Leo Gaggle asking what's the support of third-party widgets in togra specifically data visualization widgets um so it depends there is a matplotlib back end for example where like we've basically written a charting library that uses toga's native apis to drop in the issue is that those libraries like they need to draw something and they need to use a language to draw in there is almost no reason why those libraries couldn't be wrapped into a toga wrapper and it wouldn't actually be that much work like the matplotlib plugin is like a couple hundred lines of code and most of that is like transfer trying to transform what drawing a line matplotlid's drawing a line means in drawing a line in in toga if you've got a toolkit that exposes a native like on Macos an NS view wrapping that as a toga widget that you can just drop into a toga layout it's not that hard so it could be done are there any more questions yes microphone hi so I assume that the toga programs are compiled into uh whatever uh executable uh is necessary for the no but they're not they are interpreted at runtime so when when you ship to iOS for example what you get is a c python uh like straight up C python interpreter running as lib python uh there is a compiled component but it's basically a 100 line bootstrap that starts a python interpreter that runs your code and it is being interpreted on the phone as python code at runtime oh because my question was going to be how reverse engineerable is is your code from your binary and so it's all there it's in a directory as python code so okay if you can get access to the IPA you could get the source code out like you then you fall into the usual app obsidication type stuff of only shipping the pycs and things thank you any more questions in time for one more occurring once going twice yes um he kind of mentioned how a lot of these GUI apps uh have a very similar type structure how difficult would it be to go from say a decanter to a gtk to a toga is it fairly transferable if you've written a GUI in one to go to the other uh depends how much you're leaning on the toolkit uh so like if you are using the vanilla I'm just putting widgets in buttons and things like blah blah blah the the top level surface apis very immediately transferable there are some local variations but not that hard if you are like a TK enter app where you are internally poking TK into configuration strings to make it do one particular thing yeah that's not going to transfer because you're programming TK into there you're not programming a GUI so you know it depends on the problem but at least at the surface level shouldn't be that hard like a a tutorial how to build a QT app you could probably translate it to a how to build a toga app you know in in a couple of hours it's just a matter of knowing what what the spelling changes are effectively beautiful thanks so much thank you so much we have a small gift uh to present to you Visa except that thank you very much [Applause]
Info
Channel: PyCon AU
Views: 4,742
Rating: undefined out of 5
Keywords: RussellKeith-Magee, pyconau, pyconau_2023
Id: DH6rkImiZtc
Channel Id: undefined
Length: 33min 38sec (2018 seconds)
Published: Tue Aug 22 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.