# HG changeset patch # User Marcin Kuzminski # Date 2019-10-17 11:26:31 # Node ID 823cbf311eaac23b706dc8d79faf08712f82b684 # Parent 276ef6b7bc2831406187e2ca80611c1f039cb424 artifacts: refactor metadata code - fix type conversions - split some code so we can re-use it - add specific validations of duplicates and wrong types diff --git a/rhodecode/lib/exceptions.py b/rhodecode/lib/exceptions.py --- a/rhodecode/lib/exceptions.py +++ b/rhodecode/lib/exceptions.py @@ -157,3 +157,15 @@ class VCSServerUnavailable(HTTPBadGatewa if message: self.explanation += ': ' + message super(VCSServerUnavailable, self).__init__() + + +class ArtifactMetadataDuplicate(ValueError): + + def __init__(self, *args, **kwargs): + self.err_section = kwargs.pop('err_section', None) + self.err_key = kwargs.pop('err_key', None) + super(ArtifactMetadataDuplicate, self).__init__(*args, **kwargs) + + +class ArtifactMetadataBadValueType(ValueError): + pass diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -67,6 +67,8 @@ from rhodecode.lib.ext_json import json from rhodecode.lib.caching_query import FromCache from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data from rhodecode.lib.encrypt2 import Encryptor +from rhodecode.lib.exceptions import ( + ArtifactMetadataDuplicate, ArtifactMetadataBadValueType) from rhodecode.model.meta import Base, Session URL_SEP = '/' @@ -5146,6 +5148,10 @@ class FileStore(Base, BaseModel): repo_group = relationship('RepoGroup', lazy='joined') @classmethod + def get_by_store_uid(cls, file_store_uid): + return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar() + + @classmethod def create(cls, file_uid, filename, file_hash, file_size, file_display_name='', file_description='', enabled=True, hidden=False, check_acl=True, user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None): @@ -5176,6 +5182,19 @@ class FileStore(Base, BaseModel): return for section, key, value, value_type in args: + has_key = FileStoreMetadata().query() \ + .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \ + .filter(FileStoreMetadata.file_store_meta_section == section) \ + .filter(FileStoreMetadata.file_store_meta_key == key) \ + .scalar() + if has_key: + msg = 'key `{}` already defined under section `{}` for this file.'\ + .format(key, section) + raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key) + + # NOTE(marcink): raises ArtifactMetadataBadValueType + FileStoreMetadata.valid_value_type(value_type) + meta_entry = FileStoreMetadata() meta_entry.file_store = file_store meta_entry.file_store_meta_section = section @@ -5185,8 +5204,12 @@ class FileStore(Base, BaseModel): Session().add(meta_entry) - if commit: - Session().commit() + try: + if commit: + Session().commit() + except IntegrityError: + Session().rollback() + raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.') @classmethod def bump_access_counter(cls, file_uid, commit=True): @@ -5239,20 +5262,28 @@ class FileStoreMetadata(Base, BaseModel) file_store = relationship('FileStore', lazy='joined') + @classmethod + def valid_value_type(cls, value): + if value.split('.')[0] not in cls.SETTINGS_TYPES: + raise ArtifactMetadataBadValueType( + 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value)) + @hybrid_property def file_store_meta_value(self): - v = self._file_store_meta_value - _type = self._file_store_meta_value - if _type: + val = self._file_store_meta_value + + if self._file_store_meta_value_type: # e.g unicode.encrypted == unicode - _type = self._file_store_meta_value.split('.')[0] - # decode the encrypted value + _type = self._file_store_meta_value_type.split('.')[0] + # decode the encrypted value if it's encrypted field type if '.encrypted' in self._file_store_meta_value_type: cipher = EncryptedTextValue() - v = safe_unicode(cipher.process_result_value(v, None)) - - converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] - return converter(v) + val = safe_unicode(cipher.process_result_value(val, None)) + # do final type conversion + converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] + val = converter(val) + + return val @file_store_meta_value.setter def file_store_meta_value(self, val): @@ -5270,11 +5301,19 @@ class FileStoreMetadata(Base, BaseModel) @file_store_meta_value_type.setter def file_store_meta_value_type(self, val): # e.g unicode.encrypted - if val.split('.')[0] not in self.SETTINGS_TYPES: - raise Exception('type must be one of %s got %s' - % (self.SETTINGS_TYPES.keys(), val)) + self.valid_value_type(val) self._file_store_meta_value_type = val + def __json__(self): + data = { + 'artifact': self.file_store.file_uid, + 'section': self.file_store_meta_section, + 'key': self.file_store_meta_key, + 'value': self.file_store_meta_value, + } + + return data + def __repr__(self): return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section, self.file_store_meta_key, self.file_store_meta_value)