An ebook/comic library service and web client
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
4.2 KiB

  1. """Patching support for db.Model objects."""
  2. from typing import Type, Set, Optional, Any, Dict
  3. from atheneum import db
  4. from atheneum.model import User
  5. from atheneum.service import transformation_service
  6. from atheneum.service import validation_service
  7. def get_patch_fields(patch_json: Dict[str, Any]) -> Set[str]:
  8. """Convert json fields to python fields."""
  9. return {
  10. transformation_service.convert_key_from_json(key) for key in
  11. patch_json.keys()}
  12. def perform_patch(request_user: User,
  13. original_model: Type[db.Model],
  14. patch_model: Type[db.Model],
  15. model_attributes: Set[str],
  16. patched_fields: Optional[Set[str]]) \
  17. -> Type[db.Model]:
  18. """
  19. Patch changed attributes onto original model.
  20. :param request_user:
  21. :param original_model: The model to apply the patches to
  22. :param patch_model: The model to pull the patch information from
  23. :param model_attributes: The attributes that are valid for patching
  24. :param patched_fields: The explicitly passed fields for patching
  25. :return: Thd patched original_model
  26. """
  27. change_set = validation_service.determine_change_set(
  28. original_model, patch_model, model_attributes, patched_fields)
  29. model_validation = validation_service.validate_model(
  30. request_user, original_model, change_set)
  31. if model_validation.success:
  32. for attribute, value in change_set.items():
  33. setattr(original_model, attribute, value)
  34. db.session.commit()
  35. else:
  36. raise ValueError(
  37. 'Restricted attributes modified. Invalid Patch Set.')
  38. return original_model
  39. def versioning_aware_patch(request_user: User,
  40. original_model: Type[db.Model],
  41. patch_model: Type[db.Model],
  42. model_attributes: Set[str],
  43. patched_fields: Optional[Set[str]]) \
  44. -> Type[db.Model]:
  45. """
  46. Account for version numbers in the model.
  47. Versions must match to perform the patching. Otherwise a simultaneous edit
  48. error has occurred. If the versions match and the patch moves forward, bump
  49. the version on the model by 1 to prevent other reads from performing a
  50. simultaneous edit.
  51. :param patched_fields:
  52. :param request_user:
  53. :param original_model: The model to apply the patches to
  54. :param patch_model: The model to pull the patch information from
  55. :param model_attributes: The attributes that are valid for patching
  56. :return: Thd patched original_model
  57. """
  58. if original_model.version == patch_model.version:
  59. patch_model.version = patch_model.version + 1
  60. return perform_patch(
  61. request_user,
  62. original_model,
  63. patch_model,
  64. model_attributes,
  65. patched_fields)
  66. raise ValueError('Versions do not match. Concurrent edit in progress.')
  67. def patch(
  68. request_user: User,
  69. original_model: Type[db.Model],
  70. patch_model: Type[db.Model],
  71. patched_fields: Optional[Set[str]] = None) -> Type[db.Model]:
  72. """
  73. Patch the original model with the patch model data.
  74. :param request_user:
  75. :param original_model: The model to apply the patches to
  76. :param patch_model: The model to pull the patch information from
  77. :param patched_fields:
  78. :return: The patched original_model
  79. """
  80. if type(original_model) is type(patch_model):
  81. model_attributes = validation_service.get_changable_attribute_names(
  82. original_model)
  83. if patch_model.id is not None and original_model.id != patch_model.id:
  84. raise ValueError('Cannot change ids through patching')
  85. if 'version' in model_attributes:
  86. return versioning_aware_patch(
  87. request_user,
  88. original_model,
  89. patch_model,
  90. model_attributes,
  91. patched_fields)
  92. return perform_patch(
  93. request_user,
  94. original_model,
  95. patch_model,
  96. model_attributes,
  97. patched_fields)
  98. raise ValueError(
  99. 'Model types "{}" and "{}" do not match'.format(
  100. original_model.__class__.__name__,
  101. patch_model.__class__.__name__
  102. ))