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.

89 lines
3.4 KiB

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