Source code for tdw_catalog.metadata.editor

from datetime import date, datetime
import mimetypes
import os
from typing import List, Optional, Union
import tdw_catalog.dataset as dataset
from tdw_catalog.errors import CatalogFailedPreconditionException, CatalogInvalidArgumentException, CatalogNotFoundException, CatalogPermissionDeniedException, _convert_error
from tdw_catalog.team import Team
import tdw_catalog.metadata.contract_owner as contract_owner
import tdw_catalog.metadata.currency as currency_field
import tdw_catalog.metadata.data_cost as data_cost
import tdw_catalog.metadata.date_field as date_field
import tdw_catalog.metadata.decimal as decimal
import tdw_catalog.metadata.license_expiry as license_expiry
import tdw_catalog.metadata.link as link
import tdw_catalog.metadata.list_field as list_field
import tdw_catalog.metadata.field as field
import tdw_catalog.metadata.number as number
import tdw_catalog.metadata.organization_member as organization_member
import tdw_catalog.metadata.organization_team as organization_team
import tdw_catalog.metadata.point_in_time as point_in_time
import tdw_catalog.metadata.point_of_contact as point_of_contact
import tdw_catalog.metadata.text as text
import tdw_catalog.metadata_template as metadata_template
import tdw_catalog.metadata.alias as alias
from tdw_catalog.organization_member import OrganizationMember
from tdw_catalog.team import Team


[docs]class TemplatedMetadataEditor(): """ A :class:`.TemplatedMetadataEditor` assists with the alteration and updating of :class:`.MetadataField` values in a :class:`.Dataset` """ fields: List['field.MetadataField'] _dataset: 'dataset.Dataset' template: 'metadata_template.MetadataTemplate' def __init__( self, fields: List['field.MetadataField'], dataset: 'dataset.Dataset', template: Optional['metadata_template.MetadataTemplate'] = None ) -> None: """ Initializes a :class:`.TemplatedMetadataEditor` for the alteration and updating of metadata fields in a :class:`.Dataset` Parameters ---------- fields : List[MetadataField] | List[MetadataTemplateField] The list of metadata fields to be added or updated """ self.fields = fields if fields is not None else [] self._dataset = dataset self.template = template
[docs] def get_field( self, key: str, cls: 'field.MetadataField[field.T]' = None ) -> 'Union[field.MetadataField[field.T],field.TemplatedMetadataField[field.T]]': """ Get a specific metadata field belonging to this :class:`.Dataset`. The field's properties can then be changed directly Parameters ---------- key : str The key of the desired field Returns --------- MetadataField[T] | TemplatedMetadataField[T] The specificed field whose key matches the provided key Raises ------ CatalogNotFoundException: If no :class:`.MetadataField` with a matching key can be found """ f = next((field for field in self.fields if field.key == key), None) if f is None: raise CatalogNotFoundException( message="No metadata field with that key was found") return field.TemplatedMetadataField( f) if self.template is not None else f
[docs] def list_fields(self) -> 'List[field.MetadataField]': """ Return the list of fields currently being edited by this :class:`.MetadataEditor` Parameters ---------- None Returns ------- List[MetadataField] The list of :class:`.MetadataField`\\ s currently being edited by this :class:`.MetadataEditor` """ return self.fields
[docs] def keys(self) -> List[str]: """ Return a list of keys for each field in this :class:`.Dataset`'s custom or templated fields list Parameters ---------- None Returns -------- List[str] A list of strings representing the keys for each field in this list """ return list(map(lambda field: field["key"], self.fields))
[docs] def save(self) -> None: """ Save and persist all changes made to the metadata of the :class:`.Dataset` Parameters ---------- None Returns ------- None Raises ------ CatalogInternalException If the call to the :class:`.Catalog` server fails CatalogPermissionDeniedException If the user does not have permission to make changes to this :class:`.Dataset` """ if self.template is not None: untouched_fields = self._dataset.custom_metadata if self._dataset.custom_metadata else [] res = self.fields + untouched_fields self._dataset._custom_fields = res else: untouched_fields = self._dataset.templated_metadata if self._dataset.templated_metadata else [] res = untouched_fields + self.fields self._dataset._custom_fields = res self._dataset.save()
[docs]class MetadataEditor(TemplatedMetadataEditor): """ A :class:`.MetadataEditor` assists with the alteration and updating of :class:`.MetadataField`\\ s in a :class:`.Dataset` """
[docs] def add_text_field( self, key: str, value: Optional[str], ) -> 'MetadataEditor': """ Any additional text-based information you want to add to the :class:`.Dataset` Parameters ---------- key : str An identifying key for the this field value : Optional[str] An optional `str` value for the text this field represents Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing """ f = text.Field(key, value=value) self.fields.append(f) return self
[docs] def add_attachment_field( self, key: str, value: str, ) -> 'MetadataEditor': """ Attach any metadata files directly to the :class:`.Dataset` Parameters ---------- key : str A path for the file to be attached with this field Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing Raises ------ CatalogInternalException If the call to the :class:`.Catalog` server fails when uploading the file to be attached """ try: file_size = os.path.getsize(value) file_name = os.path.basename(value) (mime_type, encoding) = mimetypes.guess_type(value) if mime_type is None: mime_type = 'text/csv' res = self._dataset._client._create_upload( resource_type="dataset", resource_id=self._dataset.id, content_length=file_size, content_type="csv", filename=file_name) import tdw_catalog.metadata.attachment as attachment f = attachment.Field( key=key, value=res["upload"]["id"], ) self.fields.append(f) return self except Exception as e: raise _convert_error(e)
[docs] def add_linked_dataset_field( self, key: str, value: Optional['dataset.Dataset'], ) -> 'MetadataEditor': """ Connect :class:`.Dataset`\\ s together to maintain lineage or increase discoverability Parameters ---------- key : str An identifying key for this field value : Optional[Dataset] An optional object representing a :class:`.Dataset` that has been semantically linked to this field Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing """ import tdw_catalog.metadata.linked_dataset as linked_dataset f = linked_dataset.Field(key, value=value) self.fields.append(f) return self
[docs] def add_organization_member_field( self, key: str, value: Optional[OrganizationMember], ) -> 'MetadataEditor': """ Someone who is associated with the data Parameters ---------- key : str An identifying key for this field value : Optional[OrganizationMember] An optional :class:`.OrganizationMember` value representing a member of this :class:`.Dataset`'s :class:`.Organization` Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing """ f = organization_member.Field( key, value=value, ) self.fields.append(f) return self
[docs] def add_organization_team_field( self, key: str, value: Optional[Team], ) -> 'MetadataEditor': """ A :class:`.Team` who is associated with the data Parameters ---------- key : str An identifying key for this field value : Optional[Team] An optional :class:`.Team` value representing a team in this :class:`.Dataset`'s :class:`.Organization` Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing """ f = organization_team.Field(key, value=value) self.fields.append(f) return self
[docs] def add_date_field( self, key: str, value: Optional[date], ) -> 'MetadataEditor': """ Add dates to the :class:`.Dataset` to keep track of timelines or events Parameters ---------- key : str An identifying key for this field value : Optional[date] An optional `date` object for the date this field represents Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing """ f = date_field.Field(key, value=value) self.fields.append(f) return self
[docs] def add_point_in_time_field( self, key: str, value: Optional[datetime], ) -> 'MetadataEditor': """ A specific time and date associated with the :class:`.Dataset` Parameters ---------- key : str An identifying key for this field value : Optional[datetime] An optional `datetime` value for the datetime this field represents Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing """ f = point_in_time.Field(key, value=value) self.fields.append(f) return self
[docs] def add_number_field( self, key: str, value: Optional[int], ) -> 'MetadataEditor': """ Track numbers associated with the :class:`.Dataset`, like total number of allowed users Parameters ---------- key : str An identifying key for this field value : Optional[int] An optional `int` value for the number this field represents Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing """ f = number.Field(key, value=value) self.fields.append(f) return self
[docs] def add_currency_field( self, key: str, currency: Optional[str], amount: Optional[float], ) -> 'MetadataEditor': """ A field for currency values Parameters ---------- key : str An identifying key for this field currency : Optional[str] An optional three character string representation for this currency type (e.g. USD) amount: Optional[float] An optional fractional amount of currency for this currency field Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing """ f = currency_field.Field(key, currency, amount) self.fields.append(f) return self
[docs] def add_decimal_field( self, key: str, value: Optional[float], ) -> 'MetadataEditor': """ A field for confidence values or other fractional information Parameters ---------- key : str An identifying key for this field value : Optional[float] An optional `float` value for the decimal this field represents Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing """ f = decimal.Field(key, value=value) self.fields.append(f) return self
[docs] def add_alias_field( self, key: str, value: Optional[str], ) -> 'MetadataEditor': """ An alternative unique identifier for this :class:`.Dataset`\\ , for integrating with external systems Parameters ---------- key : str An identifying key for the this field value : Optional[str] An optional `str` value for the alias this field represents Returns ------- MetadataEditor Returns this :class:`.MetadataEditor` for further metadata editing """ f = alias.Field(key, value=value) self.fields.append(f) return self
[docs] def remove_field(self, key: str) -> 'MetadataEditor': """ Remove a metadata field from the :class:`.Dataset` Parameters ---------- key : str The key of the field to be removed Returns ------- MetadataEditor This :class:`.MetadataEditor` with the given field removed from its fields list """ self.fields = list( filter(lambda field: field["key"] != key, self.fields)) return self
[docs] def reorder( self, new_field_order: 'List[field.MetadataField]') -> 'MetadataEditor': """ Reorder the fields list of this :class:`.MetadataEditor` Parameters ---------- new_field_order : List[MetadataField] The newly ordered list of :class:`.MetadataTemplateField`\\ s to replace the old list on this :class:`.MetadataTemplate`. Must contain the same fields as the original list, with the only allowed change being the list's order Returns ------- MetadataEditor This :class:`.MetadataEditor` with a reordered fields list Raises ------ CatalogInvalidArgumentException If the user supplied fields list contains a field with a key that does not exist on this :class:`.Dataset`, the number of fields in the list provided does not match the number of fields on the :class:`.Dataset`, or if a provided field matches the key of a field in the original list but is of a different type """ if len(self.fields) != len(new_field_order): raise CatalogInvalidArgumentException( message= "The number of fields provided must equal the number of custom fields on the Dataset" ) if set(new_field_order).issubset(self.fields) == False: raise CatalogInvalidArgumentException( message= "One or more of the fields provided does not match the original custom fields list of this Dataset" ) keys = self.keys() for f in new_field_order: if f.key not in keys: raise CatalogInvalidArgumentException( message= f"No custom field with key {f.key} exists on this Dataset") self.fields = new_field_order return self