def test_with_new_child(new_child, extend_default_translator): item_type = 'foo' class Foo(object): pass mapping = {item_type: Foo} kwargs = {} if new_child is not None: kwargs['new_child'] = new_child translator = Translator( mapping, extend_default_translator=extend_default_translator, **kwargs) assert item_type in translator assert translator.maps[0] == {} with pytest.raises(KeyError): del translator[item_type] class Bar(Foo): pass translator.register(item_type, Bar) assert translator.get(item_type) is Bar assert mapping == {item_type: Foo}
def _init_user_and_group_instances(session, response_object, user, group): """ Initialize User & Group instances based on the passed in parameters. This allows the GroupMembership instance to maintain a 'has-a' relationship with a corresponding User and Group instance. :param session: The Box session used to make requests. :type session: :class:`BoxSession` :param response_object: The Box API response representing the object. :type response_object: :class:`BoxResponse` :param user: The User object, if any, to associate with this group_membership instance. :type user: :class:`User` :param group: The Group object, if any, to associate with this group_membership instance. :type group: :class:`Group` """ if not response_object: return user, group user_info = response_object.get('user') group_info = response_object.get('group') user = user or Translator().translate(user_info['type'])( session, user_info['id'], user_info) group = group or Translator().translate(group_info['type'])( session, group_info['id'], group_info) return user, group
def test_with_extend_default_translator(default_translator, extend_default_translator, new_child): item_type = 'foo' class Foo(object): pass kwargs = {} if extend_default_translator is not None: kwargs['extend_default_translator'] = extend_default_translator translator = Translator({item_type: Foo}, new_child=new_child, **kwargs) assert set(translator.items()).issuperset(default_translator.items())
def get_items(self, limit, offset=0, fields=None): """Get the items in a folder. :param limit: The maximum number of items to return. :type limit: `int` :param offset: The index at which to start returning items. :type offset: `int` :param fields: List of fields to request. :type fields: `Iterable` of `unicode` :returns: A list of items in the folder. :rtype: `list` of :class:`Item` """ url = self.get_url('items') params = { 'limit': limit, 'offset': offset, } if fields: params['fields'] = ','.join(fields) box_response = self._session.get(url, params=params) response = box_response.json() return [ Translator().translate(item['type'])(self._session, item['id'], item) for item in response['entries'] ]
def _paging_wrapper(self, url, starting_index, limit, factory=None): """ Helper function that turns any paging API into a generator that transparently implements the paging for the caller. A caller wanting to implement their own paging can do so by managing the starting_index & limit params, and never iterating over more than 'limit' items per call. For example: first_ten = list(itertools.islice(_paging_wrapper(..., 0, 10, ...), 10)) second_ten = list(itertools.islice(_paging_wrapper(..., 10, 10, ...), 10)) third_ten = list(itertools.islice(_paging_wrapper(..., 20, 10, ...), 10)) ... When one of the lists has less than 10 items... the end has been reached. Caveat: any hidden items (see the Box Developer API for more details) will render the above inaccurate. Hidden results will lead the above get_slice() code to trigger API calls at non-expected places. :param starting_index: The index at which to begin. :type starting_index: `int` :param limit: The maximum number of items to return in a page. :type limit: `int` :param factory: A callable factory method which creates the object instances. Signature should match the __init__ signature of BaseObject. If no factory is given then the Translator factory is used. :type factory: `callable` or None :returns: A generator of 3-tuples. Each tuple contains: 1) An instance returned by the given factory callable. 2) The number of objects in the current page. 3) Index the current instance in the current page. :rtype: `generator` of `tuple` of (varies, `int`, `int`) """ current_index = starting_index while True: params = {'limit': limit, 'offset': current_index} box_response = self._session.get(url, params=params) response = box_response.json() current_page_size = len(response['entries']) for index_in_current_page, item in enumerate(response['entries']): instance_factory = factory if not instance_factory: instance_factory = Translator().translate(item['type']) instance = instance_factory(self._session, item['id'], item) yield instance, current_page_size, index_in_current_page current_index += limit if current_index >= response['total_count']: break
def search(self, query, limit, offset, ancestor_folders=None, file_extensions=None): """ Search Box for items matching the given query. :param query: The string to search for. :type query: `unicode` :param limit: The maximum number of items to return. :type limit: `int` :param offset: The search result at which to start the response. :type offset: `int` :param ancestor_folders: Folder ids to limit the search to. :type ancestor_folders: `list` of `unicode` :param file_extensions: File extensions to limit the search to. :type file_extensions: `list` of `unicode` :return: A list of items that match the search query. :rtype: `list` of :class:`Item` """ url = '{0}/search'.format(API.BASE_API_URL) params = { 'query': query, 'limit': limit, 'offset': offset, } if ancestor_folders: params.update({ 'ancestor_folder_ids': ','.join([folder.object_id for folder in ancestor_folders]) }) if file_extensions: params.update({'file_extensions': ','.join(file_extensions)}) box_response = self._session.get(url, params=params) response = box_response.json() return [ Translator().translate(item['type'])(self._session, item['id'], item) for item in response['entries'] ]
def test_without_extend_default_translator(new_child): item_type = 'foo' class Foo(object): pass mapping = {item_type: Foo} translator = Translator(mapping, extend_default_translator=False, new_child=new_child) assert translator == mapping
def test_without_new_child(extend_default_translator): item_type = 'foo' class Foo(object): pass mapping = {item_type: Foo} translator = Translator(mapping, new_child=False, extend_default_translator=extend_default_translator) assert item_type in translator assert translator.maps[0] is mapping class Bar(Foo): pass translator.register(item_type, Bar) assert translator.translate(item_type) is Bar assert mapping == {item_type: Bar} del translator[item_type] assert not mapping
def default_translator(original_default_translator): """The default translator to use during the test. We don't want global state to mutate across tests. So before each test (because of autouse=True), we make a copy of the default translator, and assign this copy to Translator._default_translator. At the end of the test, we reset the reference. """ try: translator = Translator(dict(copy.deepcopy(original_default_translator)), extend_default_translator=False, new_child=False) Translator._default_translator = translator # pylint:disable=protected-access yield translator finally: Translator._default_translator = original_default_translator # pylint:disable=protected-access
def test_without_new_child(extend_default_translator): item_type = 'foo' class Foo(object): pass mapping = {item_type: Foo} translator = Translator( mapping, new_child=False, extend_default_translator=extend_default_translator) assert item_type in translator assert translator.maps[0] is mapping class Bar(Foo): pass translator.register(item_type, Bar) assert translator.get(item_type) is Bar assert mapping == {item_type: Bar} del translator[item_type] assert not mapping
def test_with_new_child(new_child, extend_default_translator): item_type = 'foo' class Foo(object): pass mapping = {item_type: Foo} kwargs = {} if new_child is not None: kwargs['new_child'] = new_child translator = Translator(mapping, extend_default_translator=extend_default_translator, **kwargs) assert item_type in translator assert translator.maps[0] == {} with pytest.raises(KeyError): del translator[item_type] class Bar(Foo): pass translator.register(item_type, Bar) assert translator.translate(item_type) is Bar assert mapping == {item_type: Foo}
def add_collaborator(self, collaborator, role, notify=False): """Add a collaborator to the folder :param collaborator: collaborator to add. May be a User, Group, or email address (unicode string) :type collaborator: :class:`User` or :class:`Group` or `unicode` :param role: The collaboration role :type role: :class:`CollaboratorRole` :param notify: Whether to send a notification email to the collaborator :type notify: `bool` :return: The new collaboration :rtype: :class:`Collaboration` """ collaborator_helper = _Collaborator(collaborator) url = API.BASE_API_URL + '/collaborations' item = {'id': self._object_id, 'type': 'folder'} access_key, access_value = collaborator_helper.access accessible_by = { access_key: access_value, 'type': collaborator_helper.type } data = json.dumps({ 'item': item, 'accessible_by': accessible_by, 'role': role, }) params = {'notify': notify} box_response = self._session.post(url, expect_json_response=True, data=data, params=params) collaboration_response = box_response.json() collab_id = collaboration_response['id'] return Translator().translate(collaboration_response['type'])( session=self._session, object_id=collab_id, response_object=collaboration_response, )
def search(self, query, limit=100, offset=0, ancestor_folders=None, file_extensions=None, metadata_filters=None, result_type=None, content_types=None, **kwargs): """ Search Box for items matching the given query. :param query: The string to search for. :type query: `unicode` :param limit: The maximum number of items to return. :type limit: `int` :param offset: The search result at which to start the response. :type offset: `int` :param ancestor_folders: Folder ids to limit the search to. :type ancestor_folders: `Iterable` of :class:`Folder` :param file_extensions: File extensions to limit the search to. :type file_extensions: `iterable` of `unicode` :param metadata_filters: Filters used for metadata search :type metadata_filters: :class:`MetadataSearchFilters` :param result_type: Which type of result you want. Can be file or folder. :type result_type: `unicode` :param content_types: Which content types to search. Valid types include name, description, file_content, comments, and tags. :type content_types: `Iterable` of `unicode` :return: A list of items that match the search query. :rtype: `list` of :class:`Item` """ url = self.get_url() params = { 'query': query, 'limit': limit, 'offset': offset, } if ancestor_folders: params.update({ 'ancestor_folder_ids': ','.join([folder.object_id for folder in ancestor_folders]) }) if file_extensions: params.update({'file_extensions': ','.join(file_extensions)}) if metadata_filters: params.update( {'mdfilters': json.dumps(metadata_filters.as_list())}) if content_types: params.update({'content_types': ','.join(content_types)}) if result_type: params.update({'type': result_type}) params.update(kwargs) box_response = self._session.get(url, params=params) response = box_response.json() return [ Translator().translate(item['type'])(self._session, item['id'], item) for item in response['entries'] ]
def translator(): return Translator()
def test_translator_converts_response_to_correct_type(response_type): response, object_class = _response_to_class_mapping[response_type] assert type(Translator().get(response.json()['type']) == object_class)
def run_collab(box_client): global migration_folder, collaboration migration_folder = client.folder(folder_id=config.base_folder_id).get() users = migration_folder.get_items(limit=1000, offset=0) # get the items from inside for user in users: # go through the folders in migration new_name = config.new_folder_prefix + user.name collab_folder = user.rename(new_name) # rename the folder f.write('\nFolder {0} renamed'.format(collab_folder.get()['name'])) print('\nFolder {0} renamed'.format(collab_folder.get()['name'])) box_users = box_client.users(filter_term=user.name + "@") if len(box_users) == 0: f.write('\nNo Box users found for ' + user.name) print('No Box users found for ' + user.name) continue if len(box_users) > 1: f.write('\nMultiple Box users found for ' + user.name) print('Multiple Box users found for ' + user.name) continue first_user = box_users[0] f.write('\nCreating a collaboration with ' + user.name + ' (login: '******')...') print('Creating a collaboration with ' + user.name + ' (login: '******')...') try: collaboration = collab_folder.add_collaborator(first_user.login, CollaborationRole.CO_OWNER) # add a # collaborator using the folder name except BoxAPIException as box: if box.status == 204: # success f.write('\nCreated a collaboration with ' + user.name + '\n') print('Created a collaboration with ' + user.name) pass elif box.status == 400: # auto-accept is off failed_users.append(user.name) f.write('\nFAILED to create the collaboration with ' + user.name + ': ' + box.message + '\n') print('FAILED to create the collaboration with ' + user.name + ': ' + box.message) collab_folder.rename(user.name) continue pass elif box.status == 409: # auto-accept is off failed_users.append(user.name) f.write('\nFAILED to create the collaboration with ' + user.name + ': ' + box.message + '\n') print('FAILED to create the collaboration with ' + user.name + ': ' + box.message) collab_folder.rename(user.name) continue pass else: raise try: collaboration.update_info(role=CollaborationRole.OWNER) # make the collaborator the owner except BoxAPIException as box: if box.status == 204: # success f.write('\nModified the collaboration to OWNER\n') print('Modified the collaboration to OWNER') pass elif box.status == 400: # auto-accept collaboration is likely off failed_users.append(user.name) f.write('\nFAILED to modify the collaboration to OWNER for ' + user.name + ': ' + box.message + '\n') print('FAILED to modify the collaboration to OWNER for ' + user.name + ': ' + box.message) collab_folder.rename(user.name) pass else: raise json_response = box_client.make_request( # get the collaboration id 'GET', box_client.get_url('folders', collab_folder.id, 'collaborations'), ).json() id_num = [Translator().translate(item['type'])(None, item['id'], item) for item in json_response['entries']] # translate the id for the machine collab = id_num[0] # grab the ID try: json_send = box_client.make_request( # delete the collab_id 'DELETE', box_client.get_url('collaborations', collab.id), ) pprint.pprint(json_send) except BoxAPIException as box: if box.status == 204: # success pass else: raise print("\n")
def __init__(cls, name, bases, attrs): super(ObjectMeta, cls).__init__(name, bases, attrs) item_type = attrs.get('_item_type', None) if item_type is not None: Translator().register(item_type, cls)
def translator(default_translator): # pylint:disable=unused-argument return Translator(extend_default_translator=True, new_child=True)
def upload_stream( self, file_stream, file_name, preflight_check=False, preflight_expected_size=0, upload_using_accelerator=False, ): """ Upload a file to the folder. The contents are taken from the given file stream, and it will have the given name. :param file_stream: The file-like object containing the bytes :type file_stream: `file` :param file_name: The name to give the file on Box. :type file_name: `unicode` :param preflight_check: If specified, preflight check will be performed before actually uploading the file. :type preflight_check: `bool` :param preflight_expected_size: The size of the file to be uploaded in bytes, which is used for preflight check. The default value is '0', which means the file size is unknown. :type preflight_expected_size: `int` :param upload_using_accelerator: If specified, the upload will try to use Box Accelerator to speed up the uploads for big files. It will make an extra API call before the actual upload to get the Accelerator upload url, and then make a POST request to that url instead of the default Box upload url. It falls back to normal upload endpoint, if cannot get the Accelerator upload url. Please notice that this is a premium feature, which might not be available to your app. :type upload_using_accelerator: `bool` :returns: The newly uploaded file. :rtype: :class:`File` """ if preflight_check: self.preflight_check(size=preflight_expected_size, name=file_name) url = '{0}/files/content'.format(API.UPLOAD_URL) if upload_using_accelerator: accelerator_upload_url = self._get_accelerator_upload_url_fow_new_uploads() if accelerator_upload_url: url = accelerator_upload_url data = {'attributes': json.dumps({ 'name': file_name, 'parent': {'id': self._object_id}, })} files = { 'file': ('unused', file_stream), } box_response = self._session.post(url, data=data, files=files, expect_json_response=False) file_response = box_response.json()['entries'][0] file_id = file_response['id'] return Translator().translate(file_response['type'])( session=self._session, object_id=file_id, response_object=file_response, )