 Hello, so, I'm Floris. I've been a PIDA test contributor for a couple of years now, and recently PIDA tested split off its plug-in system that it's been using, and I'm going to talk a little bit more about how that plug-in system works, how you write plug-ins with that, et cetera. So, PIDA test itself is a testing tool that hopefully all of you know and hopefully use as well. It's been around for a long time, and it actually has a lot of plug-ins itself. I think there's over 150 plug-ins, that's very unscientific, that's just the number I've quoted from someone else. But yeah, there's a lot of plug-ins, and they've been around for a long time, and its plug-in system seems to work quite well. So, recently it's been split off because it's interesting to try and it was nice to be able to use that in other projects as well, that plug-in system. And that's being called Pluggy. It's on GitHub, actually, under Holger Creckles' username. It's the repository. So, it's a standalone version of the PIDA test plug-in system. It's a little bit different than what it was originally with us, but it's very similar, like it's just a couple of details and PIDA-specific things that have been moved out of it. One note of caution though is it's still not version, we haven't been releasing it as version 1.0 yet, and it's using semantic version, so in theory you could break the API at any point, but as I said, the API is basically taken from PIDA test, so hopefully that shouldn't change too much. A little overview of basically how I'll go through the talk is basically I'll kind of, with a simple example, introduce how plug-ins work in this sort of world. I'll talk a little bit about what advantages that brings, and then towards the end of the talk I'll talk about this, basically designing your entire application consisting out of plug-ins, which is what PIDA test itself does as well actually. And I think it's an interesting way of looking at an application. So, you probably all know what a plug-in system is and why you would want plug-ins in an application, so basically it allows you, it gives you certain points in your application where you just allow all the code that you don't know to execute with some extra information etc. But people can extend it to the things that you never thought of being useful or basically make it send email or something, just everything sends email in the end. The plug-in approach to it is one of hook functions. There's several ways to writing plug-ins, I guess. And the idea is basically that you find your extension point as a hook that the application then calls, and at the point you want to extend the application you call that hook with some arguments, and any of the plug-ins that have been loaded are free to implement that hook, and they all get called. So it's a one to end kind of call mapping. A plug-in does support one to one as well in a way, I won't really go into much detail about that. It supports that sort of functionality as well. So, yeah, I'll start with basically a very simple example of writing this. This is basically just the application that I'm going to use to demonstrate it. It's very simple, just get to URL and print it to standard out. I'm using requests a little bit funny here, I'm breaking it out, so this is basically request.get, but it's spread out over four steps to sort of the slightly lower level API you can use in there. And that's because it will allow me to extend the application. So this is very straightforward, it doesn't do very much, and I'm going to basically the first extension point I'm going to create is basically allow the plug-ins to modify the headers that I send in the request. So in this case, that means that after a request that prepares being called, that object has a dictionary of headers, and I'm going to allow the plug-ins to modify that dictionary before actually sending off the request. To do that in plug-in, you define your hook points, your extension points with hook specifications. And generally, kind of eventually, we use hooktest, hookspec.py to write this in. So it's a simple module, and all you need to do is basically you have to create this mark for your application first so that the hookspec that you can use as a decorator, at hookspec decorator there. And you just name all that you really care about or all the plug you really care about here is a signature that you define, that is actually important. So the name of your hook and the arguments it takes are important. And because this is your API, it's a good idea to write a nice doc string about it and explain what it does. The sort of the name itself, again, by convention, prefix it with your application name or something. That is not strictly necessary, but that's fine. And the last thing is like, there's absolutely no code about this. This is purely about the signature, the function signature. So having defined the hook specification, let's just skip ahead and write our plug and look at what the plug-in implementation is. And that is really very straightforward. So this is a different module again, plug-in, no-py in this case. I'm using for the example. And generally, so again, to write in a hook implementation, it's sort of what it's called, you need a decorator to decorate your implementation with. So that plug-in, when it loads your plug-in, it can scan through your plug-in and find your hook implementation using that decorator. Generally, the application exposes that as an API to the plug-in writers. So I'm importing the application itself, curl in this case. And the rest of the implementation is really simple. So the argument is just a dictionary and I modify it. But do you notice that the implementation that I used only uses the one header's argument. I'll go back to the hook specification we wrote. It actually takes another session argument as well. The plug-in doesn't force you. It basically allows the hooks to only accept the arguments that they need. And plug-in will look at the signature of your hook implementation and from that will figure out basically which arguments you need. And that is kind of a good feature when your hooks evolve and you're extending them because that gives you easier backwards compatibility. To actually look at what you need to do to change application, this is still fairly similar. I mean, there's a couple of more imports. The first thing we really knew here is the hook marker to create that decorator. That was just the public API that we decided to use. And then actually the main application is like the first four lines are concerned about creating this plug-in manager. So it creates a plug-in manager. Again, give it a name. And then it's imported. So I imported the hook specification here. And the hook specification is just a module object in this case. So you register all the hooks and using that decorator in the hook specification, plug-in will scan for the hooks and find them, et cetera. In this case, I'm using just simple import lip to load dynamically load my plug-in. It's kind of hard code here. And then you just need to register the plug-in. The following is basically the same. So it's like the early request stuff, so great session, great request, et cetera. And then actually calling the hook. So I got the plug-in manager object and plug-in manager object has this hook attribute, which is a hook relay. And after you've added hook specification, for each hook that you have defined, it will create a callable in there, which allows you to call your hooks. And this is where you're actually calling the hooks from in the plug-ins. So in this case, the thing to notice also is that when you're calling the hooks, you have to provide obviously all the arguments because you don't know which arguments your plug-ins are using. And you have to specify them or give them a keyword argument as well. Because plug-in looks at the names of your arguments to know which arguments that your hook implementations need. So they need to be pasting as keyword arguments. If you forget an argument or get it wrong, unfortunately, as I discovered while writing these slides, you don't get a terribly useful error message at the moment. But hopefully that can be improved. And the rest of the application especially is just the same. So that is essentially everything that you need to do to create plug-ins and start using plug-ins. One more thing basically because the hook that I wrote didn't actually return any value. So you can have multiple plug-ins all implementing this hook and they would all have had the same dictionary. They would all have had the dictionary passed into them and they'd all be modifying the same dictionary. When your hooks want to return a value, that's also possible. And basically it returns a list of all the return values of each hook and then your application has to decide what it is going to do with the hook. As a quick example for this, I'm going to add another hook specification. This one is sort of not much special. I mean it's not the best example. I couldn't really think of anything much better. But basically the idea of this extra hook is basically it will return true or false, brilliant, whether you plug in things that you should make your request. So plug-in can deny your request or something. So hook specification service is pretty straightforward. The plug-in as well can really straightforward to implement. I don't really care. I just don't want to filter anything. So yes, I just return true. In fact I didn't even have to take the arguments in this implementation because I'm not using it. I could have skipped that as well. And this is actually the application. So it's getting a bit big but not much has changed really. So everything up until the first hook is being called, the prepare headers, hook being called is exactly the same. Now I'm doing after this basically. And this is exactly the same. So basically call the new hook. It returns a list. I just built in all plug-in here. I'll function in Python to just see if any of the hooks doesn't like it. And that's it. I just send the request, bring the response, all that stuff. So that's using return values. So that was sort of the very short introduction to how to write plug-ins. There's a lot more features actually. But essentially your application defines certain hook points. And the thing that matters there is that they are function signatures. So the function signature matters. The implementations of plug-ins. The arguments are optional so if you don't need arguments you don't have to use them. And you get the basically one call in the application resulting in lots of calls in the plug-ins. Return values as a list. There's a bunch of more advanced features that I haven't really gone into. It's just like the entry points set up to an entry points integration. So you don't have to write that all from scratch if you want to use set up tools. You can also influence the hook ordering a little bit. So sometimes that might matter. So you might want to hope to be run. Plug-in might care about it because you're being run early or late or something like that. Hook wrapping is something similar. It's just there. Your plug-in actually gets called before all the other hooks get called. And then gets the result back as well. So it gets to run code at the beginning and at the end of all the other plug-ins in a way. And doing that gives you access to, allows you plug-in to actually see the results that the other plug-ins have produced. And you could even modify them or return something else or something like that if you want to. And the last kind of interesting feature that you can use is basically writing plug-ins for your plug-in. If you just pass the plug-in manager object to your plug-in, there is nothing stopping from the plug-in from adding new hook specifications to the plug-in manager, which then can be called from other plug-ins, et cetera. So it's a slightly unique way of writing plug-ins, I think, in the Python world, compared to having static classes and your functions and signatures being strict. It allows you more flexibility because it doesn't force you. It's just a function. It can implement it as a module as it just did. You can implement your plug-in as a class as well. And the feature where basically the hook implementation doesn't have to request or doesn't have to use all the arguments, it also allows you hooks to evolve a lot easier because you can change the API by adding new arguments and all the plug-ins will keep working. And that seems to have worked quite well for Python test really. The other thing is that because there's no class or anything involved, it doesn't force any behaviour or workflow or state that you have to keep or anything like that. If you want to, plug-ins can keep states, they can implement themselves as a class as well, so you can keep state, but it also leaves if you have a very simple plug-in, it is very simple. It doesn't force anything extra on you. Next, I'll talk a bit about how you can actually use that to design your entire application out of plug-in. This is a little bit... It's an interesting way of thinking about things. So basically, at SMEs you saw, creating and setting up your plug-in manager is very small, so all you need to have is a very small bootstrap module that will import some core built-in plug-ins, and those plug-ins can then be responsible for running your entire application, and they can in fact be responsible actually doing more set-up work, so you can just use a couple of hard-coded plug-ins, and then those plug-ins will be responsible for looking at set-up tools and points, and learning more plug-ins or using namespace packages or whatever kind of mechanism you'd like to use for that. This sort of approach has been used by the testers, has been pioneering that, as far as I know, and that's sort of for command-line tools, short-tunes. I've also used it for long-running demons as well, so it scales very well for different types of applications basically, and the interesting part of that is obviously that your entire application exists out of your plug-in. If you're using your plug-in API, it kind of ensures that you make a useful plug-in API. It makes for very flexible and extendable applications as well. Basically, if I keep modifying my little toy application here to start and do that, is what I'll be doing in the next few slides. I'm replacing the main function here. I'm taking a more classical argument in this case. In this case, all it's doing is basically creating a plug-in manager. As you can see, I've actually created a list of core plug-ins, and I've just implemented this core plug-in, because I can hard-code a small set of plug-ins. I just import that already. When registering the hook, I just iterate over my core plug-ins, which is just one. Once that's done, I can just call this a new hook that I've specified. That's the end of the application in a way. There should only be one plug-in that really implements that hook. Although all the plug-ins could implement it to override it, but it's slightly more advanced. Then just use that return value to exit the application. That's all the rest of it. This is the new plug-in I wrote, so core.py in this case, implementing this workflow. This is actually now the one that drives this curl main hook. It's the one that drives the rest of the application. You'll notice I've done a little bit more here than strictly necessary. When writing your entire application this way around, inside out, as I sometimes think of it, it's useful. You'll notice after creating a plug-in manager, I create this configuration object, and then later on I create a session object. It's a nice abstraction, and it actually works quite well. Generally, I make the configuration object. I make that responsible for doing things like passing the command line, reading config files, et cetera. That provides some application states, which are essentially your static configuration. The request session that we were using earlier, I had to call it CLI session. That session object allows you to keep runtime state about your application, if you have any. It's a nice pattern. Once you're doing those two, as you can see, I call the hooks Configure and SessionStart and SessionFinish on Configure. I'm not actually going to use those hooks in these examples, because it's quite short. It's a nice pattern, and it allows you to hook in on those points and do extra static configuration, or maybe enrich a static configuration, things like that. After that's sort of done. Here you can see I've actually moved the argument parsing into the config object. I just pass in my argv. Basically, everything indicates the rest of the core of the application I decided to actually implement in just one hook. I could have split that out more, but it just makes the code to look at a bit more. This one curl make requests hook will be responsible for executing a code that we saw earlier. Because it didn't all fit on the same slide, this is still the same module. Just showing basically that config object, so I'm not doing anything clever at the moment. I just still know that I just passed in my URL. Basically, creating a little bit of stage in the CLI Session one. It seemed like a reasonable idea to put the request session in there. If my application grew some features where I would request multiple URLs or something, I could share this session, the HTTP session for that. I'll get there cookies etc. The other new hook I created was this make request hook. The implementation there is basically very similar to what we saw before. It's exactly the same as before. The only difference here is basically that now the URL is taken from the config object. The HTTP session is now taken from the CLI Session object. That's the only difference. One thing that I think is a nice example in this way of writing the application is the handling of configuration in a bit more detail. You can create your argument paths to generate minus help. You can let plugins interact with that. It's nice because in this case I'm implementing a plugin that's basically responsible for making this request. The URL argument on the command line, I'm actually making a concern of the plugin that actually wants to use this. It's a nice way of separating concerns. There is no need to have one central argument parsing thing that needs to know about what everyone wants to do. Again, it's pretty straightforward to implement. All I'm doing here is this config object that I already had. I was already responsible for parsing the argument lines. I'm actually doing it properly this time around and creating my path parser. The small trick basically to get your arguments passed by plugins is just to find another hook. Before you pass your arguments, you pass the parser around to all the plugins that want to add any arguments. In here, I've just in the same plugin directly implemented that hook as well, adding my URL argument in here. That's it. The make request changes a little bit here. Again, not really anything significant just because the URL is now in a slightly different location on my config object. Everything else is very straightforward, very similar. This way, as I was saying, the pilot test itself uses this pattern as well. This is a very simplified look of the hooks that pilot test defines and how it calls them. It doesn't cover everything slightly too short and also because it's very complicated. Oh well, reasonably complicated. You can spot the same pattern. It starts with creating its own version of the plugin manager. Then it creates this config object that helps in parsing of the command lines as the next few things. You've got the pilot test add option which adds the option then command line parsing is implemented as a hook as well. Then it basically spawns off this command line main one. That is then going to be the hook that drives the rest of the application. In there, you basically see the same pattern as I was saying earlier. You create a session, then you give all the plugins a chance to hook into those configuration and session setups. With the pilot test configure, pilot session start, then finish and then configure at the end as well. The main work that pilot test does is then split up in these two main steps which is like collection of all the tests and then running of all the tests. Inside those, that is again parsing of the work to even more and more hooks. There is more hooks than are actually shown as well. It shows that in large applications, if you think about how you structure all your hooks, etc. It's an interesting way of thinking about designing applications and it works quite nicely in some situations. To summarise a little bit, it's an interesting plugin system. It allows you to evolve your plugin API quite nicely. It forces very little on your plugin writer, so you can keep simple things simple. Likewise, if you do need to store state and all that sort of thing, you can do this as well. Despite the fact that I actually shown writing your whole application as a plugin, there is no obligation at all to do this. Like the first example, I started from a static existing application. It's very low overhead. All you need to do is create a plugin manager, so it's actually not that hard to have. If you have an existing application that you want to grow a plugin system, there is no need to go this whole everything needs to be a plugin design. You can just go very gentle, traditional kind of adding a few hooks and there's not much overhead there. I do find it an interesting way if you start from scratch. It might be interesting to consider. It's kind of a fun way to try and design your application. It works quite well in the right situations. That was what I wanted to say about Pluggy. Any questions? Thank you for the great talk. I also find this approach very interesting. When I think about it, usually I would play it with objects. One object overloads another and that way extends the functionality. This is much more dynamic in a way and it gives me the feeling that you can really plug them in at a certain time. But I was a bit missing that. How do you now configure your application together from plugins? Is that now intended in this design or not? When the user has the application, he can have a variable set of plugins. But how is that configured? How does he activate or deactivate plugins? How is that configured? That's where I was hand-waving referring to set up tools integration. It leaves it fairly open to the application itself. Typical things are set up to an entry point. If you've got any distribution, it's the right word apparently. If you've got any distribution installed at an entry point, they'll be registered. Your application can modify this. If that doesn't give you enough control, you can do command line switches, you can use a configuration file. It leaves it up to the application to select which plugins are used. Usually you'll have a set of core plugins that will provide the basic functionality. There's a new functionality in the new version of Python, which is called functools.singleDispatch. It's a generic function mechanism. It can be quite useful for some types of powering in functionality when you want to dispatch based on some type. Is there a way to integrate that with pluggy or a best practice to do that, or you just use them side by side? So generic singleDispatch is not the one you're talking about. I haven't really used that much myself. That's where it looks at the type of the first argument and then decides which. Can you register any implementations on the fly? I'm not really sure how to answer the question. I haven't really thought about how that interacts. At the end of the day, pluggy doesn't really do anything like that. You just get your arguments in the implementation. So your implementation can just say, I don't care if it's this type, I don't care, and just return none, then it doesn't influence anything. Hi. If you have multiple plugins, it's up to the application to take the results of a hook call from each different plugin and then somehow integrate them. So can you talk a bit about how... So that's then something that the application author needs to think about, I suppose, if two different plugins want to, for example, modify the headers. And also if you're passing dictionaries to your hooks, then the order really matters, right, because they could be modified in place. So the order relevant can be relevant sometimes, as you say, and that's where pluggy has... So you can, when you're implementing your hook, you can say, so the decorator, I didn't go into this, but the decorator takes... You can basically call it like a function and then give it keyword arguments. And there you can basically, you can influence the ordering a little bit so you can say, I want to be called at the beginning, I want to be called at the end. So obviously modifying, yeah, when you're modifying an object, you don't get a very strict ordering guarantee. The wrapping method was another one, basically, where you will be called at the beginning and at the end. And the other part of the question was... Well, I guess if you're the application author, you need to think about what kind of plugins people might write, I guess. Yeah, the way you return... If multiple plugins return a value, you get the list with them. Yeah, your application, I mean, there is also one of the things that you can do on the hook specification was that I refer to, you can do one-to-one calls as well. Basically, on the hook specification, you can say something like, if this list of return values is not very useful or... Basically, I skipped over this detail when I was showing the inside out architecture in a way where the main function... So if multiple plugins were to implement main, it would be kind of something funny would be happening. The way to solve that is by when you're specifying that hook, you say, I want to create a one-to-one call, really, which you do by adding the first result, I think, keyword, or use first result or something like that. At that point, you get a plugin ordering going as well. So at that point, if your plugin could go... Actually, I want to be called before the normal main one, for example, and then basically look at, do I want to... Then it can decide, do I want to return a value and actually become main or do I just return non? And if I return non, then the next hook will get a chance to be main. So, yeah, that's kind of what you can do. Thanks. Just another one about return values. Is there any way to map what return value has come from which plugin? Strictly speaking, not really, unless you happen to know the order of the plugins. So the plugin is actually... So if you tightly control the order, so when you sort of plugin manager, I was calling .register of the plugin object, that order is actually respected. So if you know exactly the order that things are registered in there, and if you know that all your plugins implement the hook, because mostly only the plugins that implement the hook will have a return value, then you can sort of... Because if you're dropping none as well, then you're going to... Is there anything to consider when testing plugins? Sorry, say that again? If you want to test a plugin, is there anything special you have to do to test it? Not really, because the way the decorators work on your hooks makes it quite nice, because it doesn't actually affect the function at all. It just marks up the function, which means it's still the same function object as you see. So in your test, you can just go and call it directly, and it actually makes testing easier in some way. So the question is, plugins can be disabled from the outside? So the question is, can you disable plugins? You can essentially, so you got in your plugin manager where you do register, you can also do unregister, and that's how you disable a plugin. So if at any point you can iterate over, you can see what plugins have been registered in plugin manager, so if you don't want to go, you can iterate over and look like, oh, I don't want to be loaded, you can go and unregister. I have a little concern in the benefits slide, you mentioned that you can basically pass any argument you want, but that's more or less the behavior you would expect from JavaScript and from Python, because this could lead to some nasty boss. So you can't pass any argument you want, it has to be, so when you, in hook specification, when you actually, those are the arguments that has to be implemented, and you can't call it with anything else if you don't, the only thing that you can do is on the next slide into implementation, you can leave out one, and that is basically just implemented as a signature inspection. So it is, it's maybe a little bit unconventional in Python, but this is what Python allows you to do, I mean it provides you all these runtime inspection things. It's, if you, if you used to Python test and it's fixtures, it will seem very natural to you as well. Yeah, it's okay, that's a little bit odd at first, but it's not really, it doesn't really violate anything terrible, I think. Yeah, it doesn't allow you to pass in on existing things either, it's still quite strict on that. It's for the recording and for the audience to be nicer. One thing is I'm thinking about, because plugins normally, they shouldn't be part of your application, and normally on plugins you have on application start, you do something on application exit, you do something else. And if everything is a plugin, how you manage what application is starting, because there is like no application if everything is plugable, you know what I mean? Because if you can not disable a plugin, it's not a plugin, so I'm a bit having a problem understanding the plugin approach. Right, this is, yeah, I mean a plugin could completely screw up your application if you really want to, but that's, you know, coding in Python, you know, you can screw up everything anyway if you really want to. I, you know, yeah, what's the term, contenting gentlemen or something like that at all. It's, yeah, in practice I think it's not really an issue, none, yeah. You know, as you said it's been in use for a long time in Python tests, and yeah, you could completely replace the main loop and go and do something else. But at the end, yeah, at the end of the day, you can't help anyone either if you do that. All right, we're out of time, but thank you Flores.