Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Object tagging updates #353

Merged
merged 8 commits into from
Jul 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions solvebio/resource/object.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Solvebio Object API resource"""
import collections
import os
import re
import base64
import binascii
import mimetypes

import requests
import six
from requests.packages.urllib3.util.retry import Retry

from solvebio.errors import SolveError
Expand Down Expand Up @@ -484,24 +486,36 @@ def is_file(self):
return self.object_type == 'file'

def has_tag(self, tag):
"""Return True if object contains tags"""
"""Return True if object contains tag"""

def lowercase(x):
return x.lower()

return lowercase(tag) in map(lowercase, self.tags)
return lowercase(str(tag)) in map(lowercase, self.tags)

def tag(self, tags, remove=False, dry_run=False, apply_save=False):
def tag(self, tags, remove=False, dry_run=False, apply_save=True):
nikolamaric marked this conversation as resolved.
Show resolved Hide resolved
"""Add or remove tags on an object"""

def is_iterable_non_string(arg):
"""python2/python3 compatible way to check if arg is an iterable but not string"""

return (isinstance(arg, collections.Iterable) and
not isinstance(arg, six.string_types))

if not is_iterable_non_string(tags):
tags = [str(tags)]
else:
tags = [str(tag) for tag in tags]

if remove:
removal_tags = [tag for tag in tags if self.has_tag(tag)]
if removal_tags:
print('{}Notice: Removing tags: {} from object: {}'
.format('[Dry Run] ' if dry_run else '',
', '.join(removal_tags), self.full_path))

updated_tags = [tag for tag in tags if not self.has_tag(tag)]
tags_for_removal = [tag for tag in tags if self.has_tag(tag)]
updated_tags = [tag for tag in self.tags if tag not in tags_for_removal]
else:
print('{}Notice: Object {} does not contain any of the '
'following tags: {}'.format(
Expand All @@ -526,3 +540,8 @@ def tag(self, tags, remove=False, dry_run=False, apply_save=False):
self.save()

return True

def untag(self, tags, dry_run=False, apply_save=True):
"""Remove tags on an object"""

return self.tag(tags=tags, remove=True, dry_run=dry_run, apply_save=apply_save)
9 changes: 8 additions & 1 deletion solvebio/test/client_mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Fake201Response(object):

def __init__(self, data):
self.object = {
'id': 100,
'id': data.get('id', 100),
'class_name': self.class_name
}

Expand All @@ -20,6 +20,9 @@ def json(self):
def create(self, *args, **kwargs):
return convert_to_solve_object(self.object)

def save(self, *args, **kwargs):
return self.create(*args, **kwargs)

def retrieve(self, r_id, *args, **kwargs):
obj = self.create()
obj.id = r_id
Expand Down Expand Up @@ -249,6 +252,10 @@ def fake_object_create(*args, **kwargs):
return FakeObjectResponse(kwargs).create()


def fake_object_save(*args, **kwargs):
return FakeObjectResponse(kwargs).save()


def fake_object_retrieve(*args, **kwargs):
return FakeObjectResponse(kwargs)._retrieve_helper(
'LogicalObject', *args, **kwargs)
Expand Down
72 changes: 71 additions & 1 deletion solvebio/test/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import mock

from .helper import SolveBioTestCase
from solvebio.test.client_mocks import fake_object_create
from solvebio.test.client_mocks import fake_object_create, fake_object_save
from solvebio.test.client_mocks import fake_dataset_create


Expand Down Expand Up @@ -166,3 +166,73 @@ def test_object_dataset_getattr(self, ObjectCreate, DatasetCreate):
for obj in [file_, folder_, ds, ds_obj]:
with self.assertRaises(AttributeError):
getattr(obj, fake_attr)

@mock.patch('solvebio.resource.Object.create')
@mock.patch('solvebio.resource.Object.save')
def test_object_remove_tag(self, ObjectCreate, ObjectSave):
ObjectCreate.side_effect = fake_object_create
ObjectSave.side_effect = fake_object_save

tags = ['tag1', 'tag2', 'tag3']
file_ = self.client.Object.create(filename='foo_file',
object_type='file',
tags=tags)
for tag in tags:
self.assertTrue(file_.has_tag(tag))

tags_for_removal = ['tag1', 'tag2']
file_.tag(tags=tags_for_removal, remove=True, apply_save=True)

# test that given tags are removed
for tag in tags_for_removal:
self.assertFalse(file_.has_tag(tag))

updated_tags = [tag for tag in tags if tag not in tags_for_removal]

# test that tags which have not been removed are still there
for tag in updated_tags:
self.assertTrue(file_.has_tag(tag))

@mock.patch('solvebio.resource.Object.create')
@mock.patch('solvebio.resource.Object.save')
def test_object_untag(self, ObjectCreate, ObjectSave):
ObjectCreate.side_effect = fake_object_create
ObjectSave.side_effect = fake_object_save

tags = ['tag1', 'tag2', 'tag3']
file_ = self.client.Object.create(filename='foo_file',
object_type='file',
tags=tags)
for tag in tags:
self.assertTrue(file_.has_tag(tag))

tags_for_untagging = ['tag1', 'tag2']
file_.untag(tags=tags_for_untagging, apply_save=True)

# test that given tags are untagged
for tag in tags_for_untagging:
self.assertFalse(file_.has_tag(tag))

updated_tags = [tag for tag in tags if tag not in tags_for_untagging]

# test that tags which have not been untagged are still there
for tag in updated_tags:
self.assertTrue(file_.has_tag(tag))

@mock.patch('solvebio.resource.Object.create')
@mock.patch('solvebio.resource.Object.save')
def test_object_add_tag_non_iterable_or_string(self, ObjectCreate, ObjectSave):
ObjectCreate.side_effect = fake_object_create
ObjectSave.side_effect = fake_object_save

# test that a string is added propery
tags = 'tag1'
file_ = self.client.Object.create(filename='foo_file',
object_type='file')
file_.tag(tags)
self.assertTrue(file_.has_tag(tags))

# test that non iterable (e.g. integer) added properly
tags = 1
file_.tag(tags)
self.assertTrue(file_.has_tag(tags))