Skip to content

Commit f14fbdd

Browse files
PJSimonsujaygarlankaswfree
authored
Add 'include recent shared links' support (#582)
* 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 <[email protected]> Co-authored-by: Skye Free <[email protected]>
1 parent 589b49d commit f14fbdd

File tree

4 files changed

+224
-1
lines changed

4 files changed

+224
-1
lines changed

HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Next Release
1010

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

1516
2.11.0 (2021-01-11)

boxsdk/object/search.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,3 +383,143 @@ def metadata_query(self, from_template, ancestor_folder_id, query=None, query_pa
383383
return_full_pages=False,
384384
use_post=True
385385
)
386+
387+
@api_call
388+
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches
389+
def query_with_shared_links(
390+
self,
391+
query,
392+
limit=None,
393+
offset=0,
394+
ancestor_folders=None,
395+
file_extensions=None,
396+
metadata_filters=None,
397+
result_type=None,
398+
content_types=None,
399+
scope=None,
400+
created_at_range=None,
401+
updated_at_range=None,
402+
size_range=None,
403+
owner_users=None,
404+
trash_content=None,
405+
fields=None,
406+
sort=None,
407+
direction=None,
408+
**kwargs
409+
):
410+
"""
411+
Search Box for items matching the given query. May also include items that are only accessible via recently used shared links.
412+
413+
:param query:
414+
The string to search for.
415+
:type query:
416+
`unicode`
417+
:param limit:
418+
The maximum number of items to return.
419+
:type limit:
420+
`int`
421+
:param offset:
422+
The search result at which to start the response.
423+
:type offset:
424+
`int`
425+
:param ancestor_folders:
426+
Folder ids to limit the search to.
427+
:type ancestor_folders:
428+
`Iterable` of :class:`Folder`
429+
:param file_extensions:
430+
File extensions to limit the search to.
431+
:type file_extensions:
432+
`iterable` of `unicode`
433+
:param metadata_filters:
434+
Filters used for metadata search
435+
:type metadata_filters:
436+
:class:`MetadataSearchFilters`
437+
:param result_type:
438+
Which type of result you want. Can be file or folder.
439+
:type result_type:
440+
`unicode`
441+
:param content_types:
442+
Which content types to search. Valid types include name, description, file_content, comments, and tags.
443+
:type content_types:
444+
`Iterable` of `unicode`
445+
:param scope:
446+
The scope of content to search over
447+
:type scope:
448+
`unicode` or None
449+
:param created_at_range:
450+
A tuple of the form (lower_bound, upper_bound) for the creation datetime of items to search.
451+
:type created_at_range:
452+
(`unicode` or None, `unicode` or None)
453+
:param updated_at_range:
454+
A tuple of the form (lower_bound, upper_bound) for the update datetime of items to search.
455+
:type updated_at_range:
456+
(`unicode` or None, `unicode` or None)
457+
:param size_range:
458+
A tuple of the form (lower_bound, upper_bound) for the size in bytes of items to search.
459+
:type size_range:
460+
(`int` or None, `int` or None)
461+
:param owner_users:
462+
Owner users to filter content by; only content belonging to these users will be returned.
463+
:type owner_users:
464+
`iterable` of :class:`User`
465+
:param trash_content:
466+
Whether to search trashed or non-trashed content.
467+
:type trash_content:
468+
`unicode` or None
469+
:param fields:
470+
Fields to include on the returned items.
471+
:type fields:
472+
`Iterable` of `unicode`
473+
:param sort:
474+
What to sort the search results by. Currently `modified_at`
475+
:type sort:
476+
`unicode` or None
477+
:param direction:
478+
The direction to display the sorted search results. Can be set to `DESC` for descending or `ASC` for ascending.
479+
:type direction:
480+
`unicode` or None
481+
:return:
482+
The collection of items that match the search query.
483+
:rtype:
484+
`Iterable` of :class:`Item`
485+
"""
486+
url = self.get_url()
487+
additional_params = {'query': query, 'include_recent_shared_links': True}
488+
if ancestor_folders is not None:
489+
additional_params['ancestor_folder_ids'] = ','.join([folder.object_id for folder in ancestor_folders])
490+
if file_extensions is not None:
491+
additional_params['file_extensions'] = ','.join(file_extensions)
492+
if metadata_filters is not None:
493+
additional_params['mdfilters'] = json.dumps(metadata_filters.as_list())
494+
if content_types is not None:
495+
additional_params['content_types'] = ','.join(content_types)
496+
if result_type is not None:
497+
additional_params['type'] = result_type
498+
if scope is not None:
499+
additional_params['scope'] = scope
500+
if created_at_range is not None:
501+
additional_params['created_at_range'] = '{},{}'.format(created_at_range[0] or '', created_at_range[1] or '')
502+
if updated_at_range is not None:
503+
additional_params['updated_at_range'] = '{},{}'.format(updated_at_range[0] or '', updated_at_range[1] or '')
504+
if size_range is not None:
505+
additional_params['size_range'] = '{},{}'.format(size_range[0] or '', size_range[1] or '')
506+
if owner_users is not None:
507+
additional_params['owner_user_ids'] = ','.join([user.object_id for user in owner_users])
508+
if trash_content is not None:
509+
additional_params['trash_content'] = trash_content
510+
if sort is not None:
511+
additional_params['sort'] = sort
512+
if direction is not None:
513+
additional_params['direction'] = direction
514+
515+
additional_params.update(kwargs)
516+
517+
return LimitOffsetBasedObjectCollection(
518+
self._session,
519+
url,
520+
limit=limit,
521+
offset=offset,
522+
fields=fields,
523+
additional_params=additional_params,
524+
return_full_pages=False,
525+
)

docs/usage/search.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ enterprise.
1010
Search for Content
1111
------------------
1212

13-
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
13+
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
1414
to iterate over the [`Item`][item_class] objects in the collection.
1515

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

53+
### Search with Shared Link Items
54+
55+
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
56+
to iterate over the search result objects in the collection.
57+
58+
<!-- sample get_search_with_shared_links -->
59+
```python
60+
search_results = client.search().query_with_shared_links(query='TEST QUERY', limit=100, file_extensions=['pdf', 'doc'])
61+
for search_result in search_results:
62+
print('The item ID is {0} and the item name is {1}'.format(search_result.item.id, search_result.item.name))
63+
```
64+
65+
[query_with_shared_links]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.search.Search.query_with_shared_links
66+
5367
Metadata Query
5468
--------------
5569
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)`

test/unit/object/test_search.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,33 @@ def metadata_query_response():
121121
}
122122

123123

124+
@pytest.fixture
125+
def search_with_shared_links_entries():
126+
return [
127+
{
128+
'accessible_via_shared_link': 'https://www.box.com/s/vspke7y05sb214wjokpk',
129+
'item': {'id': '1234', 'type': 'file'},
130+
'type': 'search_result'
131+
},
132+
{
133+
'accessible_via_shared_link': None,
134+
'item': {'id': '1234', 'type': 'file'},
135+
'type': 'search_result'
136+
}
137+
]
138+
139+
140+
@pytest.fixture
141+
def search_with_shared_links_response():
142+
entries = search_with_shared_links_entries()
143+
return {
144+
'entries': entries,
145+
'total_count': len(entries),
146+
'limit': 20,
147+
'offset': 0
148+
}
149+
150+
124151
class Matcher(object):
125152
def __init__(self, compare, some_obj):
126153
self.compare = compare
@@ -374,3 +401,44 @@ def test_make_single_metadata_filter(test_search):
374401
filter_as_dict = metadata_filter.as_dict()
375402
assert filter_as_dict['templateKey'] == template_key
376403
assert filter_as_dict['scope'] == scope
404+
405+
406+
def test_query_with_shared_links(
407+
mock_box_session,
408+
make_mock_box_request,
409+
search_content_types,
410+
search_limit,
411+
search_offset,
412+
search_query,
413+
search_result_type,
414+
search_value_based_filters,
415+
search_with_shared_links_entries,
416+
search_with_shared_links_response,
417+
test_search,
418+
):
419+
# pylint:disable=redefined-outer-name
420+
mock_box_session.get.return_value, _ = make_mock_box_request(response=search_with_shared_links_response)
421+
response = test_search.query_with_shared_links(
422+
search_query,
423+
limit=search_limit,
424+
offset=search_offset,
425+
metadata_filters=search_value_based_filters,
426+
result_type=search_result_type,
427+
content_types=search_content_types,
428+
)
429+
430+
for actual, expected in zip(response, [File(mock_box_session, entry['item']['id'], entry['item']) for entry in search_with_shared_links_entries]):
431+
assert actual.item == expected
432+
433+
mock_box_session.get.assert_called_once_with(
434+
test_search.get_url(),
435+
params=Matcher(compare_params, {
436+
'query': search_query,
437+
'include_recent_shared_links': True,
438+
'limit': search_limit,
439+
'mdfilters': json.dumps(search_value_based_filters.as_list()),
440+
'offset': search_offset,
441+
'type': search_result_type,
442+
'content_types': ','.join(search_content_types) if search_content_types else search_content_types,
443+
})
444+
)

0 commit comments

Comments
 (0)