425 lines
16 KiB
Python
425 lines
16 KiB
Python
import json
|
|
from typing import Optional, List, Any, Iterable, TYPE_CHECKING, Tuple, Union
|
|
from .base_endpoint import BaseEndpoint
|
|
from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection
|
|
from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection
|
|
from ..util.api_call_decorator import api_call
|
|
from ..util.deprecation_decorator import deprecated_param
|
|
from ..util.text_enum import TextEnum
|
|
|
|
if TYPE_CHECKING:
|
|
from boxsdk.object.folder import Folder
|
|
from boxsdk.object.user import User
|
|
from boxsdk.object.item import Item
|
|
from boxsdk.pagination.box_object_collection import BoxObjectCollection
|
|
|
|
|
|
class SearchScope(TextEnum):
|
|
"""Enum of possible serach scopes."""
|
|
USER = 'user_content'
|
|
ENTERPRISE = 'enterprise_content'
|
|
|
|
|
|
class TrashContent(TextEnum):
|
|
"""Enum of trash content values."""
|
|
NONE = 'non_trashed_only'
|
|
ONLY = 'trashed_only'
|
|
|
|
|
|
class MetadataSearchFilter:
|
|
"""
|
|
Helper class to encapsulate a single search filter. A search filter can only search against one template,
|
|
but can filter on many fields.
|
|
See :class:`MetadataSearchFilters`.
|
|
"""
|
|
def __init__(self, template_key: str, scope: str):
|
|
"""
|
|
:param template_key:
|
|
The key of the template to search on
|
|
:param scope:
|
|
The scope of the template to search on
|
|
"""
|
|
self._template_key = template_key
|
|
self._scope = scope
|
|
self._field_filters = {}
|
|
|
|
def as_dict(self) -> dict:
|
|
"""
|
|
Returns a `dict` representation of this object
|
|
|
|
:return:
|
|
The `dict` representation
|
|
"""
|
|
return {
|
|
'templateKey': self._template_key,
|
|
'scope': self._scope,
|
|
'filters': self._field_filters
|
|
}
|
|
|
|
def add_value_based_filter(self, field_key: str, value: str) -> None:
|
|
"""
|
|
Add a value-based filter (used for token-based search on string fields, and exact match search on all other fields)
|
|
|
|
:param field_key:
|
|
The field key to filter on
|
|
:param value:
|
|
The value to use to filter
|
|
"""
|
|
self._field_filters.update({field_key: value})
|
|
|
|
def add_range_filter(
|
|
self,
|
|
field_key: str,
|
|
gt_value: Union[str, int, float] = None,
|
|
lt_value: Union[str, int, float] = None
|
|
) -> None:
|
|
"""
|
|
Add a range filter (used for ranged searches on numbers and dates)
|
|
|
|
:param field_key:
|
|
The field key to filter on
|
|
:param gt_value:
|
|
The lower bound of the range filter (inclusive)
|
|
:param lt_value:
|
|
The upper bound of the range filter (inclusive)
|
|
"""
|
|
range_part = {}
|
|
if gt_value:
|
|
range_part['gt'] = gt_value
|
|
if lt_value:
|
|
range_part['lt'] = lt_value
|
|
if not range_part:
|
|
raise ValueError('Should specify gt and/or lt')
|
|
self._field_filters.update({field_key: range_part})
|
|
|
|
|
|
class MetadataSearchFilters:
|
|
"""
|
|
Helper class to encapsulate a list of metadata search filter params (mdfilters API param)
|
|
See https://developers.box.com/metadata-api/#search for more details
|
|
"""
|
|
def __init__(self):
|
|
self._filters = []
|
|
|
|
def as_list(self) -> List[dict]:
|
|
"""
|
|
Get a list of filters from this object to use as a parameter in the Search API
|
|
|
|
:return:
|
|
The list of filters
|
|
"""
|
|
return [metadata_filter.as_dict() for metadata_filter in self._filters]
|
|
|
|
def add_filter(self, metadata_filter: MetadataSearchFilter) -> None:
|
|
"""
|
|
Add a filter to this object. Note that the API only supports one filter.
|
|
|
|
:param metadata_filter:
|
|
The filter to add
|
|
"""
|
|
self._filters.append(metadata_filter)
|
|
|
|
|
|
class Search(BaseEndpoint):
|
|
"""Search Box for files and folders."""
|
|
|
|
def get_url(self, *args: Any) -> str:
|
|
"""
|
|
Gets the search endpoint URL.
|
|
|
|
:return:
|
|
The search endpoint URL.
|
|
"""
|
|
return super().get_url('search', *args)
|
|
|
|
@staticmethod
|
|
def start_metadata_filters() -> MetadataSearchFilters:
|
|
"""
|
|
Get a :class:`MetadataSearchFilters` that represents a set of metadata filters.
|
|
|
|
:return:
|
|
The new :class:`MetadataSearchFilters`
|
|
"""
|
|
return MetadataSearchFilters()
|
|
|
|
@staticmethod
|
|
def make_single_metadata_filter(template_key: str, scope: str) -> MetadataSearchFilter:
|
|
"""
|
|
Make a single :class:`MetadataSearchFilter` that represents a filter on a template. It must be
|
|
added to a :class:`MetadataSearchFilters`.
|
|
:param template_key:
|
|
The key of the template to filter on
|
|
:param scope:
|
|
The scope of the template to filter on
|
|
:return:
|
|
The new :class:`MetadataSearchFilter`
|
|
"""
|
|
return MetadataSearchFilter(template_key, scope)
|
|
|
|
@api_call
|
|
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches
|
|
def query(
|
|
self,
|
|
query: str,
|
|
limit: int = None,
|
|
offset: int = 0,
|
|
ancestor_folders: Iterable['Folder'] = None,
|
|
file_extensions: Iterable[str] = None,
|
|
metadata_filters: MetadataSearchFilters = None,
|
|
result_type: str = None,
|
|
content_types: Iterable[str] = None,
|
|
scope: Optional[str] = None,
|
|
created_at_range: Tuple[Optional[str], Optional[str]] = None,
|
|
updated_at_range: Tuple[Optional[str], Optional[str]] = None,
|
|
size_range: Tuple[Optional[int], Optional[int]] = None,
|
|
owner_users: Iterable['User'] = None,
|
|
trash_content: Optional[str] = None,
|
|
fields: Iterable[str] = None,
|
|
sort: Optional[str] = None,
|
|
direction: Optional[str] = None,
|
|
**kwargs: Any
|
|
) -> Iterable['Item']:
|
|
"""
|
|
Search Box for items matching the given query.
|
|
|
|
:param query:
|
|
The string to search for.
|
|
:param limit:
|
|
The maximum number of items to return.
|
|
:param offset:
|
|
The search result at which to start the response.
|
|
:param ancestor_folders:
|
|
Folder ids to limit the search to.
|
|
:param file_extensions:
|
|
File extensions to limit the search to.
|
|
:param metadata_filters:
|
|
Filters used for metadata search
|
|
:param result_type:
|
|
Which type of result you want. Can be file or folder.
|
|
:param content_types:
|
|
Which content types to search. Valid types include name, description, file_content, comments, and tags.
|
|
:param scope:
|
|
The scope of content to search over
|
|
:param created_at_range:
|
|
A tuple of the form (lower_bound, upper_bound) for the creation datetime of items to search.
|
|
:param updated_at_range:
|
|
A tuple of the form (lower_bound, upper_bound) for the update datetime of items to search.
|
|
:param size_range:
|
|
A tuple of the form (lower_bound, upper_bound) for the size in bytes of items to search.
|
|
:param owner_users:
|
|
Owner users to filter content by; only content belonging to these users will be returned.
|
|
:param trash_content:
|
|
Whether to search trashed or non-trashed content.
|
|
:param fields:
|
|
Fields to include on the returned items.
|
|
:param sort:
|
|
What to sort the search results by. Currently `modified_at`
|
|
:param direction:
|
|
The direction to display the sorted search results. Can be set to `DESC` for descending or `ASC` for ascending.
|
|
:return:
|
|
The collection of items that match the search query.
|
|
"""
|
|
url = self.get_url()
|
|
additional_params = {'query': query}
|
|
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'] = f'{created_at_range[0] or ""},{created_at_range[1] or ""}'
|
|
if updated_at_range is not None:
|
|
additional_params['updated_at_range'] = f'{updated_at_range[0] or ""},{updated_at_range[1] or ""}'
|
|
if size_range is not None:
|
|
additional_params['size_range'] = f'{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,
|
|
)
|
|
|
|
@deprecated_param(name="use_index", position=5, message="Parameter will be ignored. See docs for details.")
|
|
@api_call
|
|
def metadata_query(
|
|
self,
|
|
from_template: str,
|
|
ancestor_folder_id: str,
|
|
query: Optional[str] = None,
|
|
query_params: Optional[dict] = None,
|
|
use_index: Optional[str] = None,
|
|
order_by: List[dict] = None,
|
|
marker: Optional[str] = None,
|
|
limit: int = None,
|
|
fields: Iterable[Optional[str]] = None
|
|
) -> 'BoxObjectCollection':
|
|
# pylint:disable=unused-argument
|
|
"""Query Box items by their metadata.
|
|
|
|
:param from_template:
|
|
The template used in the query. Must be in the form scope.templateKey.
|
|
:param ancestor_folder_id:
|
|
The folder_id to which to restrain the query
|
|
:param query:
|
|
The logical expression of the query
|
|
:param query_params:
|
|
Required if query present. The arguments for the query.
|
|
:param use_index is deprecated
|
|
:param order_by:
|
|
The field_key(s) to order on and the corresponding direction(s)
|
|
:param marker:
|
|
The marker to use for requesting the next page
|
|
:param limit:
|
|
Max results to return for a single request (0-100 inclusive)
|
|
:param fields:
|
|
List of fields to request
|
|
:returns:
|
|
An iterator of the item search results
|
|
"""
|
|
url = super().get_url('metadata_queries/execute_read')
|
|
data = {
|
|
'from': from_template,
|
|
'ancestor_folder_id': ancestor_folder_id
|
|
}
|
|
if query is not None:
|
|
data['query'] = query
|
|
if query_params is not None:
|
|
data['query_params'] = query_params
|
|
if order_by is not None:
|
|
data['order_by'] = order_by
|
|
|
|
return MarkerBasedObjectCollection(
|
|
session=self._session,
|
|
url=url,
|
|
limit=limit,
|
|
marker=marker,
|
|
fields=fields,
|
|
additional_params=data,
|
|
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: str,
|
|
limit: int = None,
|
|
offset: int = 0,
|
|
ancestor_folders: Iterable['Folder'] = None,
|
|
file_extensions: Iterable[str] = None,
|
|
metadata_filters: MetadataSearchFilters = None,
|
|
result_type: str = None,
|
|
content_types: Iterable[str] = None,
|
|
scope: Optional[str] = None,
|
|
created_at_range: Tuple[Optional[str], Optional[str]] = None,
|
|
updated_at_range: Tuple[Optional[str], Optional[str]] = None,
|
|
size_range: Tuple[Optional[int], Optional[int]] = None,
|
|
owner_users: Iterable['User'] = None,
|
|
trash_content: Optional[str] = None,
|
|
fields: Iterable[str] = None,
|
|
sort: Optional[str] = None,
|
|
direction: Optional[str] = None,
|
|
**kwargs: Any
|
|
) -> Iterable['Item']:
|
|
"""
|
|
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.
|
|
:param limit:
|
|
The maximum number of items to return.
|
|
:param offset:
|
|
The search result at which to start the response.
|
|
:param ancestor_folders:
|
|
Folder ids to limit the search to.
|
|
:param file_extensions:
|
|
File extensions to limit the search to.
|
|
:param metadata_filters:
|
|
Filters used for metadata search
|
|
:param result_type:
|
|
Which type of result you want. Can be file or folder.
|
|
:param content_types:
|
|
Which content types to search. Valid types include name, description, file_content, comments, and tags.
|
|
:param scope:
|
|
The scope of content to search over
|
|
:param created_at_range:
|
|
A tuple of the form (lower_bound, upper_bound) for the creation datetime of items to search.
|
|
:param updated_at_range:
|
|
A tuple of the form (lower_bound, upper_bound) for the update datetime of items to search.
|
|
:param size_range:
|
|
A tuple of the form (lower_bound, upper_bound) for the size in bytes of items to search.
|
|
:param owner_users:
|
|
Owner users to filter content by; only content belonging to these users will be returned.
|
|
:param trash_content:
|
|
Whether to search trashed or non-trashed content.
|
|
:param fields:
|
|
Fields to include on the returned items.
|
|
:param sort:
|
|
What to sort the search results by. Currently `modified_at`
|
|
:param direction:
|
|
The direction to display the sorted search results. Can be set to `DESC` for descending or `ASC` for ascending.
|
|
:return:
|
|
The collection of items that match the search query.
|
|
"""
|
|
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'] = f'{created_at_range[0] or ""},{created_at_range[1] or ""}'
|
|
if updated_at_range is not None:
|
|
additional_params['updated_at_range'] = f'{updated_at_range[0] or ""},{updated_at_range[1] or ""}'
|
|
if size_range is not None:
|
|
additional_params['size_range'] = f'{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,
|
|
)
|