Skip to content

Commit

Permalink
Add 'include recent shared links' support (#582)
Browse files Browse the repository at this point in the history
* Add include recent shared links support

* no longer run functional tests

* change some comments

* fix comments

* Support search param for shared link items

* Update search docs

* Update search test

Co-authored-by: Sujay Garlanka <sujay.garlanka@gmail.com>
Co-authored-by: Skye Free <sfree@box.com>
  • Loading branch information
3 people committed Mar 24, 2021
1 parent 589b49d commit f14fbdd
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 1 deletion.
1 change: 1 addition & 0 deletions HISTORY.rst
Expand Up @@ -10,6 +10,7 @@ Next Release

- Add metadata query functionality (`#574 <https://github.com/box/box-python-sdk/pull/574>`_)
- Add folder lock functionality (`#581 <https://github.com/box/box-python-sdk/pull/581>`_)
- Add search query support for the `include_recent_shared_links` field (`#582 <https://github.com/box/box-python-sdk/pull/582>`_)
- Update `get_groups()` to use documented parameter to filter by name (`#586 <https://github.com/box/box-python-sdk/pull/586>`_)

2.11.0 (2021-01-11)
Expand Down
140 changes: 140 additions & 0 deletions boxsdk/object/search.py
Expand Up @@ -383,3 +383,143 @@ def metadata_query(self, from_template, ancestor_folder_id, query=None, query_pa
return_full_pages=False,
use_post=True
)

@api_call
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches
def query_with_shared_links(
self,
query,
limit=None,
offset=0,
ancestor_folders=None,
file_extensions=None,
metadata_filters=None,
result_type=None,
content_types=None,
scope=None,
created_at_range=None,
updated_at_range=None,
size_range=None,
owner_users=None,
trash_content=None,
fields=None,
sort=None,
direction=None,
**kwargs
):
"""
Search Box for items matching the given query. May also include items that are only accessible via recently used shared links.
: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`
:param scope:
The scope of content to search over
:type scope:
`unicode` or None
:param created_at_range:
A tuple of the form (lower_bound, upper_bound) for the creation datetime of items to search.
:type created_at_range:
(`unicode` or None, `unicode` or None)
:param updated_at_range:
A tuple of the form (lower_bound, upper_bound) for the update datetime of items to search.
:type updated_at_range:
(`unicode` or None, `unicode` or None)
:param size_range:
A tuple of the form (lower_bound, upper_bound) for the size in bytes of items to search.
:type size_range:
(`int` or None, `int` or None)
:param owner_users:
Owner users to filter content by; only content belonging to these users will be returned.
:type owner_users:
`iterable` of :class:`User`
:param trash_content:
Whether to search trashed or non-trashed content.
:type trash_content:
`unicode` or None
:param fields:
Fields to include on the returned items.
:type fields:
`Iterable` of `unicode`
:param sort:
What to sort the search results by. Currently `modified_at`
:type sort:
`unicode` or None
:param direction:
The direction to display the sorted search results. Can be set to `DESC` for descending or `ASC` for ascending.
:type direction:
`unicode` or None
:return:
The collection of items that match the search query.
:rtype:
`Iterable` of :class:`Item`
"""
url = self.get_url()
additional_params = {'query': query, 'include_recent_shared_links': True}
if ancestor_folders is not None:
additional_params['ancestor_folder_ids'] = ','.join([folder.object_id for folder in ancestor_folders])
if file_extensions is not None:
additional_params['file_extensions'] = ','.join(file_extensions)
if metadata_filters is not None:
additional_params['mdfilters'] = json.dumps(metadata_filters.as_list())
if content_types is not None:
additional_params['content_types'] = ','.join(content_types)
if result_type is not None:
additional_params['type'] = result_type
if scope is not None:
additional_params['scope'] = scope
if created_at_range is not None:
additional_params['created_at_range'] = '{},{}'.format(created_at_range[0] or '', created_at_range[1] or '')
if updated_at_range is not None:
additional_params['updated_at_range'] = '{},{}'.format(updated_at_range[0] or '', updated_at_range[1] or '')
if size_range is not None:
additional_params['size_range'] = '{},{}'.format(size_range[0] or '', size_range[1] or '')
if owner_users is not None:
additional_params['owner_user_ids'] = ','.join([user.object_id for user in owner_users])
if trash_content is not None:
additional_params['trash_content'] = trash_content
if sort is not None:
additional_params['sort'] = sort
if direction is not None:
additional_params['direction'] = direction

additional_params.update(kwargs)

return LimitOffsetBasedObjectCollection(
self._session,
url,
limit=limit,
offset=offset,
fields=fields,
additional_params=additional_params,
return_full_pages=False,
)
16 changes: 15 additions & 1 deletion docs/usage/search.md
Expand Up @@ -10,7 +10,7 @@ enterprise.
Search for Content
------------------

To get a list of items matching a search query, call [`search.query(query, limit=None, offset=0, **kwargs)`][query] will return an `Iterable` that allows you
To get a list of items matching a search query, call [`search.query(query, limit=None, offset=0, ancestor_folders=None, file_extensions=None, metadata_filters=None, result_type=None, content_types=None, scope=None, created_at_range=None, updated_at_range=None, size_range=None, owner_users=None, trash_content=None, fields=None, sort=None, direction=None, **kwargs)`][query] will return an `Iterable` that allows you
to iterate over the [`Item`][item_class] objects in the collection.

<!-- sample get_search -->
Expand Down Expand Up @@ -50,6 +50,20 @@ client.search().query(None, limit=100, offset=0, metadata_filters=metadata_searc
[add_value_based_filter]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.search.MetadataSearchFilter.add_value_based_filter
[add_filter]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.search.MetadataSearchFilters.add_filter

### Search with Shared Link Items

To get a list of items matching a search query, including items that a user might have accessed recently through a shared link, call [`search.query_with_shared_links(query, limit=None, offset=0, ancestor_folders=None, file_extensions=None, metadata_filters=None, result_type=None, content_types=None, scope=None, created_at_range=None, updated_at_range=None, size_range=None, owner_users=None, trash_content=None, fields=None, sort=None, direction=None, **kwargs)`][query_with_shared_links]. This method will return an `Iterable` that allows you
to iterate over the search result objects in the collection.

<!-- sample get_search_with_shared_links -->
```python
search_results = client.search().query_with_shared_links(query='TEST QUERY', limit=100, file_extensions=['pdf', 'doc'])
for search_result in search_results:
print('The item ID is {0} and the item name is {1}'.format(search_result.item.id, search_result.item.name))
```

[query_with_shared_links]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.search.Search.query_with_shared_links

Metadata Query
--------------
To search using SQL-like syntax to return items that match specific metadata, call `search.metadata_query(from_template, ancestor_folder_id, query=None, query_params=None, use_index=None, order_by=None, marker=None, limit=None, fields=None)`
Expand Down
68 changes: 68 additions & 0 deletions test/unit/object/test_search.py
Expand Up @@ -121,6 +121,33 @@ def metadata_query_response():
}


@pytest.fixture
def search_with_shared_links_entries():
return [
{
'accessible_via_shared_link': 'https://www.box.com/s/vspke7y05sb214wjokpk',
'item': {'id': '1234', 'type': 'file'},
'type': 'search_result'
},
{
'accessible_via_shared_link': None,
'item': {'id': '1234', 'type': 'file'},
'type': 'search_result'
}
]


@pytest.fixture
def search_with_shared_links_response():
entries = search_with_shared_links_entries()
return {
'entries': entries,
'total_count': len(entries),
'limit': 20,
'offset': 0
}


class Matcher(object):
def __init__(self, compare, some_obj):
self.compare = compare
Expand Down Expand Up @@ -374,3 +401,44 @@ def test_make_single_metadata_filter(test_search):
filter_as_dict = metadata_filter.as_dict()
assert filter_as_dict['templateKey'] == template_key
assert filter_as_dict['scope'] == scope


def test_query_with_shared_links(
mock_box_session,
make_mock_box_request,
search_content_types,
search_limit,
search_offset,
search_query,
search_result_type,
search_value_based_filters,
search_with_shared_links_entries,
search_with_shared_links_response,
test_search,
):
# pylint:disable=redefined-outer-name
mock_box_session.get.return_value, _ = make_mock_box_request(response=search_with_shared_links_response)
response = test_search.query_with_shared_links(
search_query,
limit=search_limit,
offset=search_offset,
metadata_filters=search_value_based_filters,
result_type=search_result_type,
content_types=search_content_types,
)

for actual, expected in zip(response, [File(mock_box_session, entry['item']['id'], entry['item']) for entry in search_with_shared_links_entries]):
assert actual.item == expected

mock_box_session.get.assert_called_once_with(
test_search.get_url(),
params=Matcher(compare_params, {
'query': search_query,
'include_recent_shared_links': True,
'limit': search_limit,
'mdfilters': json.dumps(search_value_based_filters.as_list()),
'offset': search_offset,
'type': search_result_type,
'content_types': ','.join(search_content_types) if search_content_types else search_content_types,
})
)

0 comments on commit f14fbdd

Please sign in to comment.