The Power Of The Plugin Architecture In Python

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video i talk about the plugin architecture which allows you to add functionality to an application without changing a single line in the original code including the imports let's dive in if you want to learn more about software architecture and design skillshare who's the sponsor of today's video has great classes on those subjects skillshare is an online learning community with thousands of inspiring classes for creators explore new skills deepen existing passions and get lost in creativity skillshare has many classes on web development programming in python software engineering and software design at the moment i'm following frank kane's course on data science and machine learning with python it's really comprehensive it contains lessons about statistics data types clustering algorithms decision trees covers libraries such as pandas basically everything you need to know in order to get started skillshare is curated for learning there are no ads and they're always launching new premium classes so you can stay focused and follow wherever your creativity takes you the first 1000 of my subscribers to click the link in the description will get a one month free trial of skillshare so you can start exploring your creativity today in this example we have three files we have the main file which creates different types of game characters and prints out some information we have a character file which defines what a game character is and we have a level definition which is a json file so let's start with the level definition so you see that it's very basic it has an array of characters and each character has a type and a name and when you go to the character python file which is the class that represents a basic game character you see that it has a simple function called make a noise and it's a protocol class so we're going to use the python's structural typing system for this example if you look at the example main file you see that there are a couple of classes we have a source for class we have a wizard class and we have a witcher class each of them has this make a noise method so they adhere to the structure of the protocol they also have a name field which is what we're going to load from the level.json file in the main function we're going to open that json file read the data and then create a list of characters so for each item in the characters array that we read from the data what we're doing is making a copy we're retrieving the character type from that item and by using popper also removing it from the copy and that's important because the class initializes don't need it and then depending on what the character type is whether it's a sorcerer a wizard or a witcher we're creating the appropriate instance and passing along the other data that's inside of this copy once we've created all the characters we're going to do something with them namely we're going to print them and then let each character make a noise so let's run this example and see what happens so this is the output now obviously at the moment this is not very nice so we're going to create a better mechanism for dealing with this using a kind of factory pattern that's built on a plug-in architecture often you want to be able to extend code after it's been shipped for example if you build a game you might want to release an update that adds new characters or you want to allow modders to add characters or even entire levels to your game in that case you're going to need an architecture that allows you to do that without touching the original code and that architecture is called a plugin architecture in this video i'm going to show you how you can do this in python we're going to extend the original code so that you can read characters from a json file and import them dynamically using a plugin mechanism that allows to register plugins and unregister them at any time and the result is that you can write these plugins completely separate from the original code the first thing that we're going to do is fix this ugly if statement here and introducing a factory-like system to allow us to dynamically load different types of game characters so we're going into this game folder in which we also find the character definition and we're going to add another file called factory and in this file we're going to model the main registration and unregistering of different character types so one thing that we're going to need for sure is the game character so let's import that first and now what we're going to do is maintain a list of functions that allow us to create particular types of game characters and then we're going to let the system be able to register and unregister those functions so let's create a variable called character creation funcs and for now this is empty but this is going to be a dictionary that's going to map string because each character type is stored as a string in a json file and that maps it to a function that returns a game character so for that we're going to use the callable we don't know how many parameters this function takes because we're going to add these classes later on dynamically so we simply don't know so i'm writing these three dots and then the return type is a game character i'm not a big fan of this type of typing syntax i think other languages do this more clearly like typescript for example but well it is what it is so we have our character creation funcs dictionary which initially is empty and now we need to do is create register and unregister functions that allow us to do something with this dictionary so first let's create a register function and that's going to need a character type which is a string and it's also going to need a creation function and that's a callable that returns a game character now this is not a very special function is not very complicated what it's doing is register a new game character type and that's very simple so we have the creation functions and then we're going to add the function to the dictionary there we go that's all there is to it similarly we're going to have an unregister function and the only thing that needs is a character type there we go and that uses the pop function and we're going to pass that the character type i'm also going to add a default return value so that if you accidentally try to remove a character type that doesn't exist we're not getting an exception because in this case i don't really care about that so that's the on register function so the final thing we need to do to complete this is add a function that allows to create an instance of some kind of game character by looking up the creator function in the dictionary and then calling that creator function so let's add a create function for that and that's going to get some arguments which is a dictionary of string to well we don't know what type so we're just going to use any here and that function is going to return a game character something like this what we're going to do is basically copy over part of the code that is here so we're going to need to make a copy of the arguments then we retrieve the character type from the arguments and then we're going to call the appropriate creator function so first create a copy and then let's retrieve the character type and now let's try to retrieve the creator function and i'm going to put this into a try accept block so we can handle the case when a character type is not present in the dictionary so let's create a variable called creation funk and that's character creation funk and we pass it the character type there we go and then we can return calling that function with the arguments copy and let's add an accept clause key error we're going to capture key arrows here and that's going to raise a value error that we have an unknown character type and let's print out the character type here as follows there we go you see we get a warning here from pylint that we should perhaps add a context to this so we could simply do this to solve that so now that we have our factory we can start using it in the example and basically replace this if statement by using the factory instead so what we need to do first is basically register these different classes that we have here so before we can do that let's import the factory so now we have the factory and then let's register these classes so that we can create instances of them so this is how we register the sorcerer and then let's also register the wizard and the witcher there we go and now we can update this code here to actually lap the factory handle creating the characters for us and we can actually replace this whole piece of code here by a single line using a list comprehension so we're using the create function from the factory we're passing that the item that we get from the data and let's remove all this code as well so now we've changed this code and now you see that the main function actually becomes a lot shorter and let's see we're getting some kind of error here so actually now we don't need the game character anymore because we're letting the factory handle that and game character is a protocol so saucer and wizard etc don't need to know anything about it so let's try to run this code and check that this still works and it does so this was the first step in making sure that our main function the way that we create the game characters happens more dynamically and not with this fixed if statements so if you wanted to we could now add here an extra class i don't know a bart for example register it in the factory and then we don't need to change this code at all because it just relies on the factory being able to create those objects for us so that's one ingredient of the plugin architecture the second ingredient is that we're able to create those classes or insert the code that creates those that describes those classes that we can insert it dynamically into the system because at the moment if you want to extend the system we still have to write those classes here or put them in a separate file and then import that file here and then register it at the factory so what we're going to do is to add a loader module to game that allows us to load a class dynamically and that's basically the core of the plugin mechanism that we're going to use so i'm going to add an extra file here called loader and what loader is is a simple plugin loader there we go and what we're going to use to do this is import lib which allows us to import python scripts dynamically and in order to define a plugin architecture you also need some kind of entry point a function let's say that you should call when you load the plugin so that the plugin can do stuff and the whole idea is that when you load the plugin then the plugin can actually register new classes in the factory and then they can be used dynamically in your game so let's create a class that describes what a plugin should look like we're going to call this the plugin interface and a plugin has a single function called initialize and since we're not actually having an instance of a plugin interface class it's merely to define what a plugin looks like this is going to be a static method and that's called initialize it's not going to return anything and this is going to initialize the plugin so this means if we want to add a plugin to our system then we're going to need to define this initialize method otherwise it's not going to work and what we can do now that we have this setup is to add a function to load these plugins dynamically let's call that function load plugins we're assuming we're getting a list of strings for this it's not going to return anything it's simply going to call the register or sorry the initialize method on each of the different plugins and this will load the plugins defined in the plugins list so for each plugin name we're going to import the module let's actually create a separate function for that to separate things a bit more import module and that's going to get a name and now we can use our plugin interface so this is going to return a plugin interface and this is where we use import lib and that's going to get the name now i see we're getting this problem here that import module returns a something called module type which is different from our plugin interface because obviously import mobile doesn't know anything about the specific plugin interface that we expect you can't really tell import module that it's going to receive something of a given type so to avoid this problem i'm going to add a type ignore here to basically ignore this error and kind of contain it to the import module function that i defined here it's not perfect but i think it's the least horrible way of doing this now let's create the plugin by calling that import module function so now you see the proper type it's a plugin interface and of course because we now have loaded this file we need to call the initialize method plugin.initialize so this allows us to initialize the plugin when we load it and basically this is all there is to it the only thing we still need to do is to make sure that this load plugins function is actually called in the main script so i'm going to import this here and that's going to be the loader that we're going to need and then instead of simply creating the characters here we're also going to need to load the plugins and the idea is that we should be able to define what the plugins are inside this level.json object so let's say we're going to add a second array here that's called plugins and for now let's leave that empty we'll add an example later on and then what we're going to do here is after we've loaded the data from the file we're going to load the plugins before we start creating the characters so that plugins get the opportunity to introduce new characters and register them at the factory so the loader we're going to call the load plugins method and we're going to provide it the list of plugins we retrieved from the json file so this is going to load the plugins for us now if i run this code basically nothing is going to happen because there are no plugins so no plugins are being loaded at the moment but let's add an example of a particular plugin so what i'm going to do is i'm going to create a separate folder here to deal with plugins and from this point onwards we're not going to change anything in the example.pi file which is the main script file or any of the files that are inside the game folder so you could basically ship this game well game as it is and then extend it dynamically using the plugin system which is really cool let's see how that works so what i'm going to do is add another folder called plugins and i'll add an empty in a dot pi file so we can easily import them and then let's add a part because every witcher game needs a bard right bark dot pi there we go and this is where we're going to define our custom plugin so what this is is a game extension that adds a bard character there we go and what we're going to do is create a class bard that's going to be this new character that we'll add to our game and we're going to use a data class for that so we have a bard class it has a name like any of the other characters and then let's also define a function called make what am i writing here make a noise to adhere to the protocol class something like this so this is the bard class that we'd like to add to the game and in order to do that we need to register it at the factory so we're also going to need the factory there we go and then we need to implement our plugin interface class which has an initialize function so let's now define the initialize function and we're going to do one simple thing in the initialize function which is register the bar class there we go so this is our plugin we have new class and we have an initialize method and the only thing we need to do now is define it in our json file and that basically means that we have to write the proper import here and that's plugins dot part let's run the example one more time now actually nothing is going to change because we're still loading the same characters but the fun thing now is we can add an extra character type here which is of type bart and let's give this board also a name as follows and now if i run the example again and you see we have our bard appearing in the list of characters and the text is appropriately generated this is really neat we were able to add a plugin to the system by creating a new class here and registering the part at the factory we defined the plugin in our level definition which is here but we didn't change a single line of code in the main script or in any of these game factory or loader classes so this is a really neat powerful mechanism to extend your game after it's being shipped and the fun thing is we have now complete freedom to do anything we like with these classes because obviously at the moment the bar class doesn't do anything but for example you could now do specific things inside this plugin to add lots of other extensions for example you could give this part an instrument and that's a default this is a flute and then you could do stuff with that instrument for example we could write out some information about it like let's also print out the name so then basically this is what you get and now let's run the example again and so we get this and now in my level definition i could change the value of instrument i don't know what's a nice instrument banjo who doesn't love the banjo so simply define the instrument here and when i run the example there you see now jessica how do you pronounce it jessie jeske jesker i'm so bad at pronouncing witcher names i have to be really careful with this so anyway now you see that the instrument is actually a banjo and that's being used in the plugin so we can customize all these things without having to change a single line of code in the original game and that is the power of the plugin architecture so you can see that the plugin architecture is really powerful and allows for a lot of customization i've used it here in a game context but you can use it in any application context like extending guise of an existing application or adding extra data processing systems to an already existing framework thanks again to today's sponsor skillshare don't forget to check them out by clicking the link in the description below if you enjoyed this video give it a like consider subscribing to my channel thanks for watching take care and see you soon [Music]
Info
Channel: ArjanCodes
Views: 33,074
Rating: 4.9687257 out of 5
Keywords: plugin architecture, plugin architecture python, design patterns, software architecture patterns, software architecture tutorial, python tutorial 2021, learn python, python programming, python tutorial, python programming projects, software architecture, python tricks, software architecture and design, software architecture patterns for developers, software architecture patterns and styles, computer science for beginners, computer science, software architecture course
Id: iCE1bDoit9Q
Channel Id: undefined
Length: 24min 6sec (1446 seconds)
Published: Fri Sep 17 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.