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.

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