/dftimewolf/lib/recipes/manager.py

https://github.com/log2timeline/dftimewolf
Python | 129 lines | 48 code | 20 blank | 61 comment | 8 complexity | ef79057d083c12fa2c95ebb1a6d0755f MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. """Recipes manager."""
  3. import glob
  4. import io
  5. import json
  6. import os
  7. from io import StringIO, TextIOWrapper
  8. from typing import List, Union, Dict
  9. from dftimewolf.lib import errors, resources
  10. from dftimewolf.lib.resources import Recipe
  11. class RecipesManager(object):
  12. """Recipes manager."""
  13. # Allow a previously registered recipe to be overridden.
  14. ALLOW_RECIPE_OVERRIDE = False
  15. _recipes = {} # type: Dict[str, Recipe]
  16. def _ReadRecipeFromFileObject(
  17. self, file_object: Union[StringIO, TextIOWrapper]) -> Recipe:
  18. """Reads a recipe from a JSON file-like object.
  19. Args:
  20. file_object (file): JSON file-like object that contains the recipe.
  21. Returns:
  22. Recipe: recipe.
  23. """
  24. json_dict = json.load(file_object)
  25. description = json_dict['description']
  26. del json_dict['description']
  27. args = json_dict['args']
  28. del json_dict['args']
  29. return resources.Recipe(description, json_dict, args)
  30. def DeregisterRecipe(self, recipe: Recipe) -> None:
  31. """Deregisters a recipe.
  32. The recipe are identified based on their lower case name.
  33. Args:
  34. recipe (Recipe): the recipe.
  35. Raises:
  36. KeyError: if recipe is not set for the corresponding name.
  37. """
  38. recipe_name = recipe.name.lower()
  39. if recipe_name not in self._recipes:
  40. raise KeyError('Recipe not set for name: {0:s}.'.format(recipe.name))
  41. del self._recipes[recipe_name]
  42. def GetRecipes(self) -> List[Recipe]:
  43. """Retrieves the registered recipes.
  44. Returns:
  45. list[Recipe]: the recipes sorted by name.
  46. """
  47. return sorted(self._recipes.values(), key=lambda recipe: recipe.name)
  48. def ReadRecipeFromFile(self, path: str) -> None:
  49. """Reads a recipe from a JSON file.
  50. Args:
  51. path (str): path of the recipe JSON file.
  52. Raises:
  53. RecipeParseError: when the recipe cannot be parsed.
  54. """
  55. with io.open(path, 'r', encoding='utf-8') as file_object:
  56. try:
  57. recipe = self._ReadRecipeFromFileObject(file_object)
  58. except json.decoder.JSONDecodeError as exception:
  59. raise errors.RecipeParseError(
  60. 'Unable to parse recipe file: {0:s} with error: {1!s}'.format(
  61. path, exception))
  62. self.RegisterRecipe(recipe)
  63. def ReadRecipesFromDirectory(self, path: str) -> None:
  64. """Reads recipes from a directory containing JSON files.
  65. Args:
  66. path (str): path of the directory containing the recipes JSON files.
  67. """
  68. for file_path in glob.glob(os.path.join(path, '*.json')):
  69. self.ReadRecipeFromFile(file_path)
  70. def RegisterRecipe(self, recipe: Recipe) -> None:
  71. """Registers a recipe.
  72. The recipe are identified based on their lower case name.
  73. Args:
  74. recipe (Recipe): the recipe.
  75. Raises:
  76. KeyError: if recipe is already set for the corresponding name.
  77. """
  78. recipe_name = recipe.name.lower()
  79. if recipe_name in self._recipes and not self.ALLOW_RECIPE_OVERRIDE:
  80. raise KeyError('Recipe already set for name: {0:s}.'.format(recipe.name))
  81. self._recipes[recipe_name] = recipe
  82. def RegisterRecipes(self, recipes: List[Recipe]) -> None:
  83. """Registers recipes.
  84. The recipes are identified based on their lower case name.
  85. Args:
  86. recipes (list[Recipe]): the recipes.
  87. Raises:
  88. KeyError: if a recipe is already set for the corresponding name.
  89. """
  90. for recipe in recipes:
  91. self.RegisterRecipe(recipe)
  92. def Recipes(self) -> Dict[str, resources.Recipe]:
  93. """Returns recipes object."""
  94. return self._recipes