Package: generic-design-patterns
Python Generic Design PatternsPython package implements design patterns in generic way. Its can be used in a wide range of projects.
Some of these patterns are slightly improved for efficient use in real-world projects.Installationpipinstallgeneric-design-patternsOverviewImplemented PatternsChain of responsibilityEvent ProviderSpecificationOther parts of packagePluginChain Of ResponsibilityThe purpose of this text is not to explain the principles of chain of responsibility. For example, source describing CoR isrefactoring.guru.
This package implements node of chain as plugin. For more information about plugin in this package visitplugin chapter.How it works in few stepsUser create chain node pluginUser set collectors which collect all chain nodes (plugins)User call build functionChain NodeChain node have to inherit fromgdp.chain.ChainNodePlugin, which inherit formyapsy.IPlugin.IPlugin.Each node of chain have to implement these methods:check()- It detects that the request is handleable by the node. The method has to return bool value.handle()- It is performing method which processes the request. It returns result.description()- It returns string or any other class which describes the node/plugin.All nodes/plugins (in one chain) have to implementcheck()andhandle()with same arguments.ExamplesHere is a short minimum example. It implements chain nodes for pseudo handling different text formats.TXT Node Pluginimportgeneric_design_patternsasgdpclassTxtChainPlugin(gdp.chain.ChainNodePlugin):answer="txt successfully handled"defcheck(self,input_string):return"txt"==input_string.strip()defhandle(self,input_string):returnself.answerdefdescription(self):return"txt"JSON Node Pluginimportgeneric_design_patternsasgdpclassJsonChainPlugin(gdp.chain.ChainNodePlugin):answer="json successfully handled"defcheck(self,input_string):return"json"==input_string.strip()defhandle(self,input_string):returnself.answerdefdescription(self):return"json"Build chainimportgeneric_design_patternsasgdpcollectors=[gdp.plugin.SubclassPluginCollector(gdp.chain.ChainNodePlugin)]chain=gdp.chain.build(collectors)This example usesgdp.plugin.SubclassPluginCollector. This package implements more plugin collectors, which are described in partplugin collectors.Handle request by chainforrequestin["txt","json","yaml"]:result=chain.handle(request)print(result)>>>txtsuccessfullyhandled>>>jsonsuccessfullyhandled>>>NoneGet description of chain nodesThe chain is dynamically build by collected plugins. Generally we do not know which nodes chain will contain (before build).
However assembled chain should offer information about its nodes. It other words chain should describe which request is able handle.
This feature cover chain methoddescription().descriptions=chain.description()print(descriptions)>>>["txt","json"]Input valueyamlhas not handler in the chain. In that case return value isNone.Event ProviderThis standard implementation of publisher-subscriber design pattern. There are not any improvements. Note that current implementation is only for single thread/process usage.How it worksMain part is event provider, which store subscriptions. On the basis of subscriptions provider directs notifications to right subscribers.Subscribers can register at provider.Publishers can send notification via provider.ExamplesThe code shows minimum example. Note:The subscriber has to implementupdate()method. The package containsAdvancedSubscriberclass which add methods for subscribe and unsubscribe itself.The publisher is created only for this example. Important is line wherenotify()method is called.The example shows how to make subscription. It has to part stringmessageandsubscriberobject.Use notification class from this package or your custom class which should inherit from it. The most import is that notification has to contain message attribute.importgeneric_design_patternsasgdpdummy_message="dummy message"classDummySubscriber(gdp.event.Subscriber):def__init__(self):self.notification=Nonedefupdate(self,notification):print(notification.message)classDummyPublisher:def__init__(self,provider):self.provider=providerdefpublish(self):dummy_notification=gdp.event.Notification(dummy_message)self.provider.notify(dummy_notification)provider=gdp.event.Provider()subscriber=DummySubscriber()provider.subscribe(dummy_message,subscriber)publisher=DummyPublisher(provider)publisher.publish()print(subscriber.notification.message)>>>dummymessageSpecificationThe purpose of this text is not to explain the principles of specification pattern. For detail information visitwiki - specification pattern.However it is useful to describe the most important aspects of this pattern:This pattern encapsulates condition to class.This pattern enables compose condition together and create more complex conditions. All this without losing readability and clarity. In other words, it allows to avoid an endless cascade of if-else cascades or some very long condition.This pattern allows to create the composite conditions dynamically.ExamplesThe example is intended to demonstrate the creation of a complex condition.First of all define list to work with.alphabet_list=["Alpha","Bravo","Charlie","Delta","Echo","Foxtrot","Golf","Hotel"]Now define rules for selecting items:Select items its index is lower or equal to 2 and not start with char "b" (case insensitive).Select items its index is higher than 2 and it contains "e" or "a" but not both.  (case insensitive)Create single conditions by specification patternimportgeneric_design_patternsasgdpclassContainChar(gdp.specification.Condition):required_char=""defis_satisfied(self,index,item):returnself.required_charinitem.lower()classContainCharA(ContainChar):required_char="a"classContainCharE(ContainChar):required_char="e"classIsIndexHigherThanTwo(gdp.specification.Condition):defis_satisfied(self,index,item):returnindex>2classFirstCharIsB(gdp.specification.Condition):defis_satisfied(self,index,item):returnitem[0].lower()=="b"Note that input arguments of methodis_satisfied(), it depends only on the user's requirements. But it is necessary that the arguments of all conditions are the same.Put single conditions togethercondition=(~IndexHigherThanTwo()&~FirstCharIsB())condition|=(IndexHigherThanTwo()&(ContainCharA()^ContainCharE()))Apply conditionIterate over the list and filter items which meet condition.forindex,iteminenumerate(alphabet_list):ifcondition(index,item):print(item)>>>Alpha>>>Charlie>>>Echo>>>HotelPluginHere is not implement some plugin system. Plugin module only encapsulates existing systems
and makes it easier to use. Current version of the package uses plugin only forchain of responsibility.CollectorsIn the context of this package, plugin can be average class orYapsyplugin. For more information about Yapsy plugin system visitYapsy documentationpages.Collectors are intended for find plugin and make it accessible. This package contains three basic plugin collectors:gdp.plugin.YapsyPluginCollectorgdp.plugin.YapsyRegExPluginCollectorgdp.plugin.SubclassPluginCollectorAll examples in this chapter follow the example in chapterchain of responsibility.YapsyPluginCollectorIn the default setting, this collector find standardYapsyplugins by.yapsy-plugininfo file.Assume this directory structure:+- plugins/
   +- toml.py
   +- toml.yapsy-plugin
   +- yaml.py
   +- yaml.yapsy-plugintoml.pyimportgeneric_design_patternsasgdpclassTomlChainPlugin(gdp.chain.ChainNodePlugin):answer="toml successfully handled"defcheck(self,input_string):return"toml"==input_string.strip()defhandle(self,input_string):returnself.answerdefdescription(self):return"toml"toml.yapsy-plugin[Core]
Name = toml
Module = toml

[Documentation]
Author = ShadowCodeCz
Version = 0.1
Description = Test Toml PluginToml and Yaml plugins are similar.Collector constructionimportgeneric_design_patternsasgdpcollector=gdp.plugin.YapsyPluginCollector(["./plugins"])If you are experienced withYapsy, you can use attributeplugin_managerofgdp.plugin.YapsyPluginCollectorclass. It is instance ofyapsy.PluginManager.PluginManager.YapsyRegExPluginCollectorThis collector is child ofYapsyPluginCollector, which bring some improvements:plugins are located in destination by regular expression.yapsy-pluginare not requiredAssume this directory structure which is similar to previous one only without.yapsy-plugin. Contents of.pyfile are same.+- plugins/
   +- t_plugin_toml.py
   +- t_plugin_yaml.pyCollector constructionimportgeneric_design_patternsasgdpcollector=gdp.plugin.YapsyRegExPluginCollector(["./plugins"],"t_plugin_.+.py$")Be careful about regular expression. Especially about ending symbol$. It will find also.pycfiles without$at the end of re. It will causes problems.SubclassPluginCollectorIt is collecting all child of selected class. The example of usage the collectorSubclassPluginCollectoris inchain of responsibilitychapter.