Completed
Push — master ( 6fd567...f65269 )
by Daniel
90:27 queued 15s
created

RecipesListApplication.get()   F

Complexity

Conditions 9

Size

Total Lines 32

Duplication

Lines 32
Ratio 100 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 9
c 3
b 0
f 0
dl 32
loc 32
rs 3
1
"""
2
Groundwork recipe pattern.
3
4
Provides function to register, get and build recipes.
5
6
Recipes are used create directories and files based on a given template and some user input.
7
It is mostly used to speed up the set up of new python packages, groundwork applications or projects.
8
9
Based on cookiecutter: https://github.com/audreyr/cookiecutter/
10
"""
11
import os
12
import logging
13
from cookiecutter.main import cookiecutter
14
15
from groundwork.patterns.gw_base_pattern import GwBasePattern
16
17
18
class GwRecipesPattern(GwBasePattern):
19
20
    def __init__(self, *args, **kwargs):
21
        super().__init__(*args, **kwargs)
22
23
        if not hasattr(self.app, "recipes"):
24
            self.app.recipes = RecipesListApplication(self.app)
25
26
        #: Stores an instance of :class:`~groundwork.patterns.gw_recipes_pattern.RecipesListPlugin`
27
        self.recipes = RecipesListPlugin(self)
28
29
    # register new recipe (aka template)
30
    # get recipes
31
32
33 View Code Duplication
class RecipesListPlugin:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
34
    """
35
    Cares about the recipe management on plugin level.
36
    Allows to register, get and build recipes in the context of the current plugin.
37
38
    :param plugin: plugin, which shall be used as contxt.
39
    """
40
    def __init__(self, plugin):
41
        self._plugin = plugin
42
        self.__app = plugin.app
43
        self.__log = plugin.log
44
45
        # Let's register a receiver, which cares about the deactivation process of recipes for this plugin.
46
        # We do it after the original plugin deactivation, so we can be sure that the registered function is the last
47
        # one which cares about recipes for this plugin.
48
        self._plugin.signals.connect(receiver="%s_recipes_deactivation" % self._plugin.name,
49
                                     signal="plugin_deactivate_post",
50
                                     function=self.__deactivate_recipes,
51
                                     description="Deactivate recipes for %s" % self._plugin.name,
52
                                     sender=self._plugin)
53
        self.__log.debug("Plugin recipes initialised")
54
55
    def __deactivate_recipes(self, plugin, *args, **kwargs):
56
        """
57
        Deactivates/unregisters all recipes of the current plugin, if this plugin gets deactivated.
58
        """
59
        recipes = self.get()
60
        for recipe in recipes.keys():
61
            self.unregister(recipe)
62
63
    def register(self, name, path, description, final_words=None):
64
        """
65
        Registers a new recipe in the context of the current plugin.
66
67
        :param name: Name of the recipe
68
        :param path: Absolute path of the recipe folder
69
        :param description: A meaningful description of the recipe
70
        :param final_words: A string, which gets printed after the recipe was build.
71
        """
72
        return self.__app.recipes.register(name, path, self._plugin, description, final_words)
73
74
    def unregister(self, recipe):
75
        """
76
        Unregister a recipe of the current plugin.
77
78
        :param recipe: Name of the recipe.
79
        """
80
        return self.__app.recipes.unregister(recipe)
81
82
    def get(self, name=None):
83
        """
84
        Gets a list of all recipes, which are registered by the current plugin.
85
        If a name is provided, only the requested recipe is returned or None.
86
87
        :param: name: Name of the recipe
88
        """
89
        return self.__app.recipes.get(name, self._plugin)
90
91
    def build(self, recipe):
92
        """
93
        Builds a recipe
94
95
        :param recipe: Name of the recipe to build.
96
        """
97
        return self.__app.recipes.build(recipe, self._plugin)
98
99
100
class RecipesListApplication:
101
    """
102
    Cares about the recipe management on application level.
103
    Allows to register, get and build recipes.
104
105
    :param app: groundwork application instance
106
    """
107
    def __init__(self, app):
108
        self.__app = app
109
        self.recipes = {}
110
        self.__log = logging.getLogger(__name__)
111
        self.__log.info("Application recipes initialised")
112
113
    def register(self, name, path, plugin, description=None, final_words=None):
114
        """
115
        Registers a new recipe.
116
        """
117
        if name in self.recipes.keys():
118
            raise RecipeExistsException("Recipe %s was already registered by %s" %
119
                                        (name, self.recipes["name"].plugin.name))
120
121
        self.recipes[name] = Recipe(name, path, plugin, description, final_words)
122
        self.__log.debug("Recipe %s registered by %s" % (name, plugin.name))
123
        return self.recipes[name]
124
125
    def unregister(self, recipe):
126
        """
127
        Unregisters an existing recipe, so that this recipe is no longer available.
128
129
        This function is mainly used during plugin deactivation.
130
131
        :param recipe: Name of the recipe
132
        """
133
        if recipe not in self.recipes.keys():
134
            self.__log.warning("Can not unregister recipe %s" % recipe)
135
        else:
136
            del (self.recipes[recipe])
137
            self.__log.debug("Recipe %s got unregistered" % recipe)
138
139 View Code Duplication
    def get(self, recipe=None, plugin=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
140
        """
141
        Get one or more recipes.
142
143
        :param recipe: Name of the recipe
144
        :type recipe: str
145
        :param plugin: Plugin object, under which the recipe where registered
146
        :type plugin: GwBasePattern
147
        """
148
        if plugin is not None:
149
            if recipe is None:
150
                recipes_list = {}
151
                for key in self.recipes.keys():
152
                    if self.recipes[key].plugin == plugin:
153
                        recipes_list[key] = self.recipes[key]
154
                return recipes_list
155
            else:
156
                if recipe in self.recipes.keys():
157
                    if self.recipes[recipe].plugin == plugin:
158
                        return self.recipes[recipe]
159
                    else:
160
                        return None
161
                else:
162
                    return None
163
        else:
164
            if recipe is None:
165
                return self.recipes
166
            else:
167
                if recipe in self.recipes.keys():
168
                    return self.recipes[recipe]
169
                else:
170
                    return None
171
172
    def build(self, recipe, plugin=None):
173
        """
174
        Execute a recipe and creates new folder and files.
175
176
        :param recipe: Name of the recipe
177
        :param plugin: Name of the plugin, to which the recipe must belong.
178
        """
179
        if recipe not in self.recipes.keys():
180
            raise RecipeMissingException("Recipe %s unknown." % recipe)
181
182
        recipe_obj = self.recipes[recipe]
183
184
        if plugin is not None:
185
            if recipe_obj.plugin != plugin:
186
                raise RecipeWrongPluginException("The requested recipe does not belong to the given plugin. Use"
187
                                                 "the app object, to retrieve the requested recipe: "
188
                                                 "my_app.recipes.get(%s)" % recipe)
189
190
        recipe_obj.build()
191
192
193
class Recipe:
194
    """
195
    A recipe is an existing folder, which will be handled by the underlying cookiecutter library as template folder.
196
197
    :param name: Name of the recipe
198
    :param path: Absolute path to the recipe folder
199
    :param plugin: Plugin which registers the recipe
200
    :param description: Meaningful description of the recipe
201
    :param final_words: String, which gets printed after a recipe was successfully build.
202
    """
203
    def __init__(self, name, path, plugin, description="", final_words=""):
204
        self.name = name
205
        if os.path.isabs(path):
206
            self.path = path
207
        else:
208
            raise FileNotFoundError("Path of recipe must be absolute. Got %s" % path)
209
        self.plugin = plugin
210
        self.description = description
211
        self.final_words = final_words
212
213
    def build(self, output_dir=None, **kwargs):
214
        """
215
        Buildes the recipe and creates needed folder and files.
216
        May ask the user for some parameter inputs.
217
218
        :param output_dir: Path, where the recipe shall be build. Default is the current working directory
219
        :return: location of the installed recipe
220
        """
221
        if output_dir is None:
222
            output_dir = os.getcwd()
223
224
        target = cookiecutter(self.path, output_dir=output_dir, **kwargs)
225
226
        if self.final_words is not None and len(self.final_words) > 0:
227
            print("")
228
            print(self.final_words)
229
        return target
230
231
232
class RecipeExistsException(BaseException):
233
    pass
234
235
236
class RecipeMissingException(BaseException):
237
    pass
238
239
240
class RecipeWrongPluginException(BaseException):
241
    pass
242