def delete(self, paths): """Delete L{Tag}s matching C{paths}. L{TagValue}s and permissions associated with the deleted L{Tag}s are removed by cascading deletes in the database schema. @param paths: A sequence of L{Tag.path}s. @return: A C{list} of C{(objectID, Tag.path)} 2-tuples representing the L{Tag}s that were removed. """ if isgenerator(paths): paths = list(paths) result = getTags(paths=paths) deletedTagPaths = list(result.values(Tag.objectID, Tag.path)) # Delete the fluiddb/tags/description tag values stored for removed # tags. Associated TagValue's are removed by an ON DELETE CASCADE # trigger. self._factory.tagValues(self._user).delete( [(objectID, path) for objectID, _ in deletedTagPaths for path in [u'fluiddb/tags/description', u'fluiddb/tags/path']]) # Touch all the objects for the given tag paths. objectIDs = list(getObjectIDs(paths)) touchObjects(objectIDs) result.remove() return deletedTagPaths
def get(self, paths, withDescriptions=None): """Get information about L{Tag}s matching C{paths}. @param paths: A sequence of L{Tag.path}s. @param withDescriptions: Optionally, a C{bool} indicating whether or not to include L{Tag} descriptions in the result. Default is C{False}. @return: A C{dict} that maps L{Tag.path}s to C{dict}s with information about matching L{Tag}s, matching the following format:: {<path>: {'id': <object-id>, 'description': <description>}} """ if not paths: raise FeatureError("Can't retrieve an empty list of tags.") result = getTags(paths=paths) values = list(result.values(Tag.path, Tag.objectID)) descriptions = ( self._getDescriptions(objectID for path, objectID in values) if withDescriptions else None) tags = {} for path, objectID in values: value = {'id': objectID} if withDescriptions: value['description'] = descriptions.get(objectID, u'') tags[path] = value return tags
def testGetTags(self): """L{getTags} returns all L{Tag}s in the database, by default.""" user = createUser(u'username', u'password', u'User', u'*****@*****.**') user.namespaceID = createNamespace(user, user.username, None).id tag = createTag(user, user.namespace, u'name') self.assertEqual(tag, getTags().one())
def testCreateStoresTagPaths(self): """ L{TagAPI.create} creates new C{fluiddb/tags/path} L{TagValue}s to store the specified L{Tag} paths. """ values = [(u'username/tag', u'A tag description')] [(objectID, path)] = self.tags.create(values) tag = getTags(paths=[u'fluiddb/tags/path']).one() value = getTagValues([(objectID, tag.id)]).one() self.assertEqual(u'username/tag', value.value)
def testCreateCreatesAboutTag(self): """ L{UserAPI.create} creates new C{fluiddb/about} L{TagValue}s when creating new L{User}s. """ values = [(u'username', u'secret', u'User', u'*****@*****.**')] [(objectID, username)] = self.users.create(values) tag = getTags(paths=[u'fluiddb/about']).one() value = getTagValues([(objectID, tag.id)]).one() self.assertEqual(u'@username', value.value)
def testCreateCreatesAboutTag(self): """ L{TagAPI.create} creates new C{fluiddb/about} L{TagValue}s when creating new L{Tag}s. """ values = [(u'username/tag', u'A tag description')] [(objectID, path)] = self.tags.create(values) tag = getTags(paths=[u'fluiddb/about']).one() value = getTagValues([(objectID, tag.id)]).one() self.assertEqual(u'Object for the attribute username/tag', value.value)
def testDeleteDoesNotDeleteOtherTags(self): """ L{TagAPI.delete} removes just the L{Tag} is is asked to and not any other tags. """ self.tags.create([(u'username/child1', u'A description')]) self.tags.create([(u'username/child2', u'A description')]) self.tags.delete([u'username/child1']) tag = getTags(paths=[u'username/child2']).one() self.assertEqual(u'username/child2', tag.path)
def testDeleteDoesNotDeleteOtherTagsWhenPassedAGenerator(self): """ L{TagAPI.delete} removes just the L{Tag} is is asked to and not any other tags when it is passed a generator (as opposed to a C{list}). """ self.tags.create([(u'username/child1', u'A description')]) self.tags.create([(u'username/child2', u'A description')]) self.tags.delete(name for name in [u'username/child1']) tag = getTags(paths=[u'username/child2']).one() self.assertEqual(u'username/child2', tag.path)
def testCreateStoresNamespacePaths(self): """ L{NamespaceAPI.create} creates new C{fluiddb/namespaces/path} L{TagValue}s to store the specified L{Namespace} paths. """ values = [(u'username/namespace', u'A namespace description')] [(objectID, path)] = self.namespaces.create(values) tag = getTags(paths=[u'fluiddb/namespaces/path']).one() value = getTagValues([(objectID, tag.id)]).one() self.assertEqual(u'username/namespace', value.value)
def testCreateStoresNamespaceDescriptions(self): """ L{NamespaceAPI.create} creates new C{fluiddb/namespaces/description} L{TagValue}s to store the specified L{Namespace} descriptions. """ values = [(u'username/namespace', u'A namespace description')] [(objectID, path)] = self.namespaces.create(values) tag = getTags(paths=[u'fluiddb/namespaces/description']).one() value = getTagValues([(objectID, tag.id)]).one() self.assertEqual(u'A namespace description', value.value)
def testGetTagsWithPaths(self): """ When L{Tag.path}s are provided L{getTags} returns matching L{Tag}s. """ user = createUser(u'username', u'password', u'User', u'*****@*****.**') user.namespaceID = createNamespace(user, user.username, None).id tag = createTag(user, user.namespace, u'name1') createTag(user, user.namespace, u'name2') result = getTags(paths=[u'username/name1']) self.assertIdentical(tag, result.one())
def testCreateCreatesAboutTag(self): """ L{NamespaceAPI.create} creates new C{fluiddb/about} L{TagValue}s when creating new L{Namespace}s. """ values = [(u'username/namespace', u'A namespace description')] [(objectID, path)] = self.namespaces.create(values) tag = getTags(paths=[u'fluiddb/about']).one() value = getTagValues([(objectID, tag.id)]).one() self.assertEqual(u'Object for the namespace username/namespace', value.value)
def testDeleteKeepsTheAboutTag(self): """ L{TagAPI.delete} keeps the C{fluiddb/about} tag value for the deleted tag. """ values = [(u'username/tag', u'A tag description')] [(objectID, path)] = self.tags.create(values) self.tags.delete([u'username/tag']) tag = getTags(paths=[u'fluiddb/about']).one() value = getTagValues([(objectID, tag.id)]).one() self.assertEqual(u'Object for the attribute username/tag', value.value)
def _getSystemTag(self, path): """Get a system L{Tag} showing an error if it doesn't exist. @param path: The path of the requested L{Tag}. @return The requested L{Tag}. """ tag = getTags(paths=[path]).one() if not tag: logging.critical( 'Fatal Error: system tag %r does not exist.' % path) sys.exit(1) return tag
def testDeleteKeepsTheAboutTag(self): """ L{NamespaceAPI.delete} keeps the C{fluiddb/about} tag value for the deleted namespace. """ values = [(u'username/namespace', u'A namespace description')] [(objectID, path)] = self.namespaces.create(values) self.namespaces.delete([u'username/namespace']) tag = getTags(paths=[u'fluiddb/about']).one() value = getTagValues([(objectID, tag.id)]).one() self.assertEqual(u'Object for the namespace username/namespace', value.value)
def _getSystemTag(self, path): """Get a system L{Tag} showing an error if it doesn't exist. @param path: The path of the requested L{Tag}. @return The requested L{Tag}. """ tag = getTags(paths=[path]).one() if not tag: logging.critical('Fatal Error: system tag %r does not exist.' % path) sys.exit(1) return tag
def testDeleteInvalidatesCachedTagPermissions(self): """ L{CachingTagAPI.delete} invalidates L{TagPermission}s to ensure the cache is always fresh. """ self.tags.create([(u'username/tag', u'A tag')]) tag = getTags(paths=[u'username/tag']).one() cache = PermissionCache() cache.saveTagPermissions({u'username/tag': tag.permission}) self.tags.delete([u'username/tag']) cached = cache.getTagPermissions([u'username/tag']) self.assertEqual({}, cached.results) self.assertEqual([u'username/tag'], cached.uncachedValues)
def testCreateTag(self): """L{FacadeTagMixin.createTag} creates a new L{Tag}.""" self.store.commit() with login(u'username', uuid4(), self.transact) as session: objectID = yield self.facade.createTag( session, u'username', u'tag', u'A tag.', 'ignored', 'ignored') self.assertNotIdentical(None, objectID) self.store.rollback() tag = getTags(paths=[u'username/tag']).one() self.assertIdentical(self.user, tag.creator) self.assertIdentical(self.user.namespace, tag.namespace) self.assertEqual(u'username/tag', tag.path) self.assertEqual(u'tag', tag.name) self.assertEqual(objectID, str(tag.objectID))
def migrate(store): # Find all the @name about values. result = store.execute('SELECT value, object_id' ' FROM about_tag_values' " WHERE value LIKE '@%'") aboutValues = dict((value, UUID(objectID)) for value, objectID in result) logging.info('Loaded %d @name about values', len(aboutValues)) # Remove all the links between comments and @name values. affectedComments = set() i = 0 for aboutValue, objectID in aboutValues.iteritems(): result = store.find( CommentObjectLink, CommentObjectLink.objectID == objectID) comments = set(result.values(CommentObjectLink.commentID)) affectedComments.update(comments) result.remove() store.commit() if len(comments): logging.info('Removed %s comment links for %d comments.', aboutValue, len(comments)) logging.info('Removed @name comment links for %d comments.', len(affectedComments)) # Find fluidinfo.com/info/about values for affected comments and remove # @names from the about lists. tagID = getTags([u'fluidinfo.com/info/about']).one().id comments = list(affectedComments) while comments: batch = comments[0:min(len(comments), BATCH_SIZE)] if not batch: break result = store.find(TagValue, TagValue.tagID == tagID, TagValue.objectID.is_in(batch)) for i, value in enumerate(result): pass value.value = [about for about in value.value if about not in aboutValues] logging.info('Updated about values for %d comments.', i + 1) store.commit() comments = comments[len(batch):] logging.info('Finished updating about values for %d comments.', len(affectedComments))
def migrate(store): # Find all the @name about values. result = store.execute('SELECT value, object_id' ' FROM about_tag_values' " WHERE value LIKE '@%'") aboutValues = dict((value, UUID(objectID)) for value, objectID in result) logging.info('Loaded %d @name about values', len(aboutValues)) # Remove all the links between comments and @name values. affectedComments = set() i = 0 for aboutValue, objectID in aboutValues.iteritems(): result = store.find(CommentObjectLink, CommentObjectLink.objectID == objectID) comments = set(result.values(CommentObjectLink.commentID)) affectedComments.update(comments) result.remove() store.commit() if len(comments): logging.info('Removed %s comment links for %d comments.', aboutValue, len(comments)) logging.info('Removed @name comment links for %d comments.', len(affectedComments)) # Find fluidinfo.com/info/about values for affected comments and remove # @names from the about lists. tagID = getTags([u'fluidinfo.com/info/about']).one().id comments = list(affectedComments) while comments: batch = comments[0:min(len(comments), BATCH_SIZE)] if not batch: break result = store.find(TagValue, TagValue.tagID == tagID, TagValue.objectID.is_in(batch)) for i, value in enumerate(result): pass value.value = [ about for about in value.value if about not in aboutValues ] logging.info('Updated about values for %d comments.', i + 1) store.commit() comments = comments[len(batch):] logging.info('Finished updating about values for %d comments.', len(affectedComments))
def create(self, values): """Create new L{Tag}s. L{Namespace}s that don't exist are created automatically before L{Tag}s are created. Associated L{NamespacePermission} and L{TagPermission}s are created automatically with the system-wide default permissions. @param values: A sequence of C{(Tag.path, description)} 2-tuples. @raise DuplicatePathError: Raised if the path for a new L{Tag} collides with an existing one. @raise FeatureError: Raised if the given list of values is empty. @raise UnknownParentPathError: Raised if the parent for a new L{Tag} can't be found. @raise MalformedPathError: Raised if one of the given paths is empty or has unacceptable characters. @return: A C{list} of C{(objectID, path)} 2-tuples for the new L{Tag}s. """ if not values: raise FeatureError("Can't create an empty list of tags.") # Make sure tag paths don't exist before trying to create new tags. paths = [path for path, _ in values] existingPaths = list(getTags(paths=paths).values(Tag.path)) if existingPaths: raise DuplicatePathError( 'Paths already exist: %s' % ', '.join(existingPaths)) # Get intermediate namespaces. If they don't exist, create them # automatically. paths = [path for (path, _) in values] parentPaths = getParentPaths(paths) missingParentPaths = self._getMissingNamespaces(parentPaths) self._factory.namespaces(self._user).create( [(path, u'Object for the namespace %s' % path) for path in missingParentPaths]) # Create the new tags. result = getNamespaces(paths=parentPaths) parentNamespaces = dict((namespace.path, namespace) for namespace in result) return self._createTags(values, parentNamespaces)
def set(self, values): """Set or update L{Tag}s. @param values: A C{dict} mapping L{Tag.path}s to descriptions. @return: A C{list} of C{(objectID, Tag.path)} 2-tuples representing the L{Tag}s that were updated. """ result = [] paths = set(values.iterkeys()) tags = dict(getTags(paths=paths).values(Tag.path, Tag.objectID)) descriptions = {} for path, description in values.iteritems(): if path not in tags: raise UnknownPathError([path]) objectID = tags[path] descriptions[objectID] = {u'fluiddb/tags/description': description} result.append((objectID, path)) admin = getUser(u'fluiddb') self._factory.tagValues(admin).set(descriptions) return result
def check(self, tagValues): """Check a given L{TagValue}s for integrity errors. @param tagValues: A sequence of L{TagValue}s to be checked. """ objectIDs = [tagValue.objectID for tagValue in tagValues] aboutTagValues = getAboutTagValues(objectIDs=objectIDs) aboutTagValues = dict((aboutTagValue.objectID, aboutTagValue) for aboutTagValue in aboutTagValues) namespaces = dict((namespace.objectID, namespace) for namespace in getNamespaces(objectIDs=objectIDs)) users = dict((user.objectID, user) for user in getUsers(objectIDs=objectIDs)) tags = dict((tag.objectID, tag) for tag in getTags(objectIDs=objectIDs)) for tagValue in tagValues: self.checkAboutTagValue(tagValue, aboutTagValues) self.checkNamespaceValues(tagValue, namespaces) self.checkTagValues(tagValue, tags) self.checkUserValues(tagValue, users)
def check(self, tagValues): """Check a given L{TagValue}s for integrity errors. @param tagValues: A sequence of L{TagValue}s to be checked. """ objectIDs = [tagValue.objectID for tagValue in tagValues] aboutTagValues = getAboutTagValues(objectIDs=objectIDs) aboutTagValues = dict((aboutTagValue.objectID, aboutTagValue) for aboutTagValue in aboutTagValues) namespaces = dict((namespace.objectID, namespace) for namespace in getNamespaces(objectIDs=objectIDs)) users = dict( (user.objectID, user) for user in getUsers(objectIDs=objectIDs)) tags = dict( (tag.objectID, tag) for tag in getTags(objectIDs=objectIDs)) for tagValue in tagValues: self.checkAboutTagValue(tagValue, aboutTagValues) self.checkNamespaceValues(tagValue, namespaces) self.checkTagValues(tagValue, tags) self.checkUserValues(tagValue, users)
def delete(self, values): """Delete L{TagValue}s. @param values: A sequence of C{(objectID, Tag.path)} 2-tuples to delete values for. @raise FeatureError: Raised if the given list of values is empty. @return: The number of values deleted. """ if isgenerator(values): values = list(values) if not values: raise FeatureError("Can't delete an empty list of tag values.") paths = set([path for objectID, path in values]) objectIDs = set([objectID for objectID, path in values]) tagIDs = dict(getTags(paths).values(Tag.path, Tag.id)) values = [(objectID, tagIDs[path]) for objectID, path in values] result = getTagValues(values).remove() if result: touchObjects(objectIDs) return result
def checkIntegrity(maxRowsPerQuery=10000): """ Check the integrity of the database for cases which the database engine can't detect. @param maxRowsPerQuery: Limit the number of rows fetched by SQL queries to avoid excessive use of memory. """ results = _splitResult(getNamespaces(), Namespace.id, maxRowsPerQuery) for result in results: namespaces = list(result) NamespaceIntegrityChecker().check(namespaces) results = _splitResult(getTags(), Tag.id, maxRowsPerQuery) for result in results: tags = list(result) TagIntegrityChecker().check(tags) results = _splitResult(getUsers(), User.id, maxRowsPerQuery) for result in results: users = list(result) UserIntegrityChecker().check(users) results = _splitResult(getAboutTagValues(), AboutTagValue.objectID, maxRowsPerQuery) for result in results: aboutTagValues = list(result) AboutTagValueIntegrityChecker().check(aboutTagValues) # In the case of TagValues we limit the query to only tag paths starting # with "fluiddb" because these are the only ones we're checking and we # don't want a huge result. store = getMainStore() result = store.find(TagValue, TagValue.tagID == Tag.id, Tag.path.startswith(u'fluiddb/')) results = _splitResult(getTagValues(), TagValue.id, maxRowsPerQuery) for result in results: tagValues = list(result) TagValueIntegrityChecker().check(tagValues)
def getUnknownPaths(self, values): """Check if the paths in a sequence of path-operation exist. @param values: A sequence of C{(path, Operation)} 2-tuples. @raise FeatureError: Raised if an invalid path or L{Operation} is given. @return: A C{set} with the unknown paths. """ tagPaths = set() namespacePaths = set() for path, operation in values: if path is None: raise FeatureError('A path must be provided.') elif operation in Operation.TAG_OPERATIONS: tagPaths.add(path) elif operation in Operation.NAMESPACE_OPERATIONS: namespacePaths.add(path) else: raise FeatureError('Invalid operation %s for the path %r' % (operation, path)) if tagPaths: existingTags = set(getTags(paths=tagPaths).values(Tag.path)) unknownTags = tagPaths - existingTags unknownTags.discard(u'fluiddb/id') else: unknownTags = set() if namespacePaths: result = getNamespaces(paths=namespacePaths).values(Namespace.path) existingNamespaces = set(result) unknownNamespaces = namespacePaths - existingNamespaces else: unknownNamespaces = set() return unknownTags.union(unknownNamespaces)
def set(self, values): """Set or update L{TagValue}s. L{Tag}s that don't exist are created automatically before L{TagValue}s are stored. Associated L{TagPermission}s are created automatically with the system-wide default permissions. @param values: A C{dict} mapping object IDs to tags and values, matching the following format:: {<object-id>: {<path>: <value>, <path>: {'mime-type': <mime-type>, 'contents': <contents>}}} A binary L{TagValue} is represented using a different layout than other values types, as shown for the second value. @raise FeatureError: Raised if the given list of values is empty. @raise MalformedPathError: Raised if one of the given paths for a nonexistent tag is empty or has unacceptable characters. """ if not values: raise FeatureError("Can't set an empty list of tag values.") objectIDs = set(values.keys()) # Implicitly create missing tags, if there are any. paths = set() for tagValues in values.itervalues(): paths.update(tagValues.iterkeys()) tagIDs = dict(getTags(paths=paths).values(Tag.path, Tag.id)) existingPaths = set(tagIDs.iterkeys()) unknownPaths = paths - existingPaths if unknownPaths: tags = [(path, u'Object for the attribute %s' % path) for path in unknownPaths] self._factory.tags(self._user).create(tags) tagIDs = dict(getTags(paths=paths).values(Tag.path, Tag.id)) # Delete all existing tag values for the specified object IDs and # paths. deleteValues = [] for objectID in values: for path in values[objectID].iterkeys(): deleteValues.append((objectID, tagIDs[path])) getTagValues(deleteValues).remove() # Set new tag values for the specified object IDs and paths. for objectID in values: tagValues = values[objectID] for path, value in tagValues.iteritems(): tagID = tagIDs[path] if isinstance(value, dict): content = value['contents'] value = createTagValue(self._user.id, tagID, objectID, { 'mime-type': value['mime-type'], 'size': len(content) }) # This is necessary to tell PostgreSQL that generates a # `value.id` immediately. value.id = AutoReload createOpaqueValue(value.id, content) else: createTagValue(self._user.id, tagID, objectID, value) touchObjects(objectIDs)
class Config(object): store = setupStore('postgres:///fluidinfo', 'main') fluidinfoUser = getUser(u'fluidinfo.com') aboutTag = getTags(paths=[u'fluiddb/about']).one() start = datetime.strptime('2012-05-27 00:01', '%Y-%m-%d %H:%M')
Change the creator id of all the fluiddb/about values to the fluiddb user. """ from fluiddb.data.tag import getTags from fluiddb.data.user import getUsers from fluiddb.scripts.commands import setupStore from fluiddb.data.value import TagValue if __name__ == '__main__': store = setupStore('postgres:///fluidinfo', 'main') print __doc__ superUser = getUsers(usernames=[u'fluiddb']).one() aboutTag = getTags(paths=[u'fluiddb/about']).one() print 'Getting value IDs. This might take some minutes.' result = store.find(TagValue, TagValue.tagID == aboutTag.id, TagValue.creatorID != superUser.id) allValueIDs = list(result.values(TagValue.id)) i = 0 valueIDs = allValueIDs[i:i + 100] while valueIDs: result = store.find(TagValue, TagValue.id.is_in(valueIDs)) result.set(creatorID=superUser.id) print 'Fixed', i + len(valueIDs), 'of', len(allValueIDs), 'tag values', print '%00.3f%%.' % (100.0 * (i + len(valueIDs)) / len(allValueIDs)) i += 100 valueIDs = allValueIDs[i:i + 100]
def set(self, values): """Set or update L{TagValue}s. L{Tag}s that don't exist are created automatically before L{TagValue}s are stored. Associated L{TagPermission}s are created automatically with the system-wide default permissions. @param values: A C{dict} mapping object IDs to tags and values, matching the following format:: {<object-id>: {<path>: <value>, <path>: {'mime-type': <mime-type>, 'contents': <contents>}}} A binary L{TagValue} is represented using a different layout than other values types, as shown for the second value. @raise FeatureError: Raised if the given list of values is empty. @raise MalformedPathError: Raised if one of the given paths for a nonexistent tag is empty or has unacceptable characters. """ if not values: raise FeatureError("Can't set an empty list of tag values.") objectIDs = set(values.keys()) # Implicitly create missing tags, if there are any. paths = set() for tagValues in values.itervalues(): paths.update(tagValues.iterkeys()) tagIDs = dict(getTags(paths=paths).values(Tag.path, Tag.id)) existingPaths = set(tagIDs.iterkeys()) unknownPaths = paths - existingPaths if unknownPaths: tags = [(path, u'Object for the attribute %s' % path) for path in unknownPaths] self._factory.tags(self._user).create(tags) tagIDs = dict(getTags(paths=paths).values(Tag.path, Tag.id)) # Delete all existing tag values for the specified object IDs and # paths. deleteValues = [] for objectID in values: for path in values[objectID].iterkeys(): deleteValues.append((objectID, tagIDs[path])) getTagValues(deleteValues).remove() # Set new tag values for the specified object IDs and paths. for objectID in values: tagValues = values[objectID] for path, value in tagValues.iteritems(): tagID = tagIDs[path] if isinstance(value, dict): content = value['contents'] value = createTagValue(self._user.id, tagID, objectID, {'mime-type': value['mime-type'], 'size': len(content)}) # This is necessary to tell PostgreSQL that generates a # `value.id` immediately. value.id = AutoReload createOpaqueValue(value.id, content) else: createTagValue(self._user.id, tagID, objectID, value) touchObjects(objectIDs)
from fluiddb.application import setConfig, setupConfig from fluiddb.data.tag import getTags from fluiddb.data.user import getUsers from fluiddb.data.value import ( TagValue, getTagValues, getAboutTagValues, AboutTagValue) from fluiddb.model.object import ObjectAPI from fluiddb.scripts.commands import setupStore if __name__ == '__main__': print __doc__ store = setupStore('postgres:///fluidinfo', 'main') setConfig(setupConfig(None)) aboutTag = getTags(paths=[u'fluiddb/about']).one() superUser = getUsers(usernames=[u'fluiddb']).one() result = store.find(AboutTagValue, AboutTagValue.value.like(u'@%')) for aboutValue in result: if aboutValue.value == aboutValue.value.lower(): continue print 'Migrating mixed cased', aboutValue.value.encode('utf-8') newAbout = u'@%s' % aboutValue.value.lower() oldObjectID = aboutValue.objectID newObjectID = ObjectAPI(superUser).create(newAbout) result = store.find(TagValue, TagValue.objectID == oldObjectID, TagValue.tagID != aboutTag.id) for tagValue in result:
def testDelete(self): """L{TagAPI.delete} removes L{Tag}s.""" self.tags.create([(u'username/child', u'A description')]) self.tags.delete([u'username/child']) self.assertIdentical(None, getTags(paths=[u'username/child']).one())
if __name__ == '__main__': store = setupStore('postgres:///fluidinfo', 'main') setConfig(setupConfig(None)) BATCH_SIZE = 50 print __doc__ superUser = getUser(u'fluiddb') tagValues = TagValueAPI(superUser) print 'Getting tags to fix.' tagsToFix = [] for tagID, path in getTags().values(Tag.id, Tag.path): root = path.split('/', 1)[0] if not root.islower(): tagsToFix.append(tagID) print 'Getting namespaces to fix.' namespacesToFix = [] for namespaceID, path in getNamespaces().values(Namespace.id, Namespace.path): root = path.split('/', 1)[0] if not root.islower(): namespacesToFix.append(namespaceID) print 'Fixing tags.' i = 0 currentIDs = tagsToFix[i:i + BATCH_SIZE]