metadata.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. #
  2. # Copyright 2018-2022 Elyra Authors
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. import json
  17. from typing import Any
  18. from typing import Type
  19. from typing import TypeVar
  20. from traitlets.utils.importstring import import_item
  21. from elyra.metadata.schema import SchemaManager
  22. # Setup forward reference for type hint on return from class factory method. See
  23. # https://stackoverflow.com/questions/39205527/can-you-annotate-return-type-when-value-is-instance-of-cls/39205612#39205612
  24. M = TypeVar("M", bound="Metadata")
  25. class Metadata(object):
  26. name = None
  27. resource = None
  28. display_name = None
  29. schema_name = None
  30. metadata = {}
  31. reason = None
  32. def __init__(self, **kwargs: Any) -> None:
  33. self.name = kwargs.get("name")
  34. self.display_name = kwargs.get("display_name")
  35. self.schema_name = kwargs.get("schema_name")
  36. self.metadata = kwargs.get("metadata", {})
  37. self.resource = kwargs.get("resource")
  38. self.reason = kwargs.get("reason")
  39. def on_load(self, **kwargs: Any) -> None:
  40. """Called by MetadataManager after fetching the instance and prior to validation.
  41. :param kwargs: additional arguments
  42. """
  43. pass
  44. def pre_save(self, **kwargs: Any) -> None:
  45. """Called by MetadataManager prior to saving the instance.
  46. :param kwargs: additional arguments
  47. Keyword Args:
  48. for_update (bool): indicates if this save operation if for update (True) or create (False)
  49. """
  50. pass
  51. def post_save(self, **kwargs: Any) -> None:
  52. """Called by MetadataManager following the save of the instance.
  53. :param kwargs: additional arguments
  54. Keyword Args:
  55. for_update (bool): indicates if this save operation if for update (True) or create (False)
  56. Note: Since exceptions thrown from this method can adversely affect the operation,
  57. implementations are advised to trap all exceptions unless the operation's
  58. rollback is warranted.
  59. """
  60. pass
  61. def pre_delete(self, **kwargs: Any) -> None:
  62. """Called by MetadataManager prior to deleting the instance."""
  63. pass
  64. def post_delete(self, **kwargs: Any) -> None:
  65. """Called by MetadataManager following the deletion of the instance.
  66. Note: Since exceptions thrown from this method can adversely affect the operation,
  67. implementations are advised to trap all exceptions unless the operation's
  68. rollback is warranted.
  69. """
  70. pass
  71. @classmethod
  72. def from_dict(cls: Type[M], schemaspace: str, metadata_dict: dict) -> M:
  73. """Creates an appropriate instance of Metadata from a dictionary instance"""
  74. # Get the schema and look for metadata_class entry and use that, else Metadata.
  75. metadata_class_name = "elyra.metadata.metadata.Metadata"
  76. schema_name = metadata_dict.get("schema_name")
  77. if schema_name:
  78. schema = SchemaManager.instance().get_schema(schemaspace, schema_name)
  79. metadata_class_name = schema.get("metadata_class_name", metadata_class_name)
  80. metadata_class = import_item(metadata_class_name)
  81. try:
  82. instance = metadata_class(**metadata_dict)
  83. if not isinstance(instance, Metadata):
  84. raise ValueError(
  85. f"The metadata_class_name ('{metadata_class_name}') for "
  86. f"schema '{schema_name}' must be a subclass of '{cls.__name__}'!"
  87. )
  88. except TypeError as te:
  89. raise ValueError(
  90. f"The metadata_class_name ('{metadata_class_name}') for "
  91. f"schema '{schema_name}' must be a subclass of '{cls.__name__}'!"
  92. ) from te
  93. return instance
  94. def to_dict(self, trim: bool = False) -> dict:
  95. # Exclude resource, and reason only if trim is True since we don't want to persist that information.
  96. # Method prepare_write will be used to remove name prior to writes.
  97. d = dict(name=self.name, display_name=self.display_name, metadata=self.metadata, schema_name=self.schema_name)
  98. if not trim:
  99. if self.resource:
  100. d["resource"] = self.resource
  101. if self.reason:
  102. d["reason"] = self.reason
  103. return d
  104. def to_json(self, trim: bool = False) -> str:
  105. return json.dumps(self.to_dict(trim=trim), indent=2)
  106. def prepare_write(self) -> dict:
  107. """Prepares this instance for storage, stripping name, reason, and resource and converting to a dict"""
  108. prepared = self.to_dict(trim=True) # we should also trim 'name' when storing
  109. prepared.pop("name", None)
  110. return prepared
  111. def __repr__(self):
  112. return self.to_json()