From d08c3e9987c3d655b2741e412cfafa48d1959b6e Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 9 May 2024 15:51:48 +0200 Subject: [PATCH 1/2] feat: Support union of primitives and objects (box/box-codegen#481) (#140) --- .codegen.json | 2 +- box_sdk_gen/internal/base_object.py | 5 ++++ box_sdk_gen/internal/utils.py | 5 ++++ docs/search.md | 2 +- test/search.py | 45 ++++++++++------------------- 5 files changed, 28 insertions(+), 31 deletions(-) diff --git a/.codegen.json b/.codegen.json index a15264d..77730fb 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "afd7974", "specHash": "a8c36df", "version": "0.6.4" } +{ "engineHash": "36bf5da", "specHash": "a8c36df", "version": "0.6.4" } diff --git a/box_sdk_gen/internal/base_object.py b/box_sdk_gen/internal/base_object.py index 3e266b4..48760b9 100644 --- a/box_sdk_gen/internal/base_object.py +++ b/box_sdk_gen/internal/base_object.py @@ -32,6 +32,11 @@ def to_dict(self) -> dict: item.to_dict() if isinstance(item, BaseObject) else item for item in v ] + elif type(v) is dict: + value = { + key: value.to_dict() if isinstance(value, BaseObject) else value + for key, value in v.items() + } elif isinstance(v, BaseObject): value = v.to_dict() elif isinstance(v, Enum): diff --git a/box_sdk_gen/internal/utils.py b/box_sdk_gen/internal/utils.py index 47d2315..4e87ace 100644 --- a/box_sdk_gen/internal/utils.py +++ b/box_sdk_gen/internal/utils.py @@ -109,9 +109,14 @@ def prepare_params(map: Dict[str, Optional[str]]) -> Dict[str, str]: def to_string(value: Any) -> Optional[str]: if value is None: return None + if isinstance(value, datetime.datetime): + return date_time_to_string(value) + if isinstance(value, datetime.date): + return date_to_string(value) if ( isinstance(value, BaseObject) or isinstance(value, list) + and len(value) >= 1 and isinstance(value[0], BaseObject) ): return ''.join(sd_to_json(serialize(value)).split()) diff --git a/docs/search.md b/docs/search.md index 2c336c9..ce6df20 100644 --- a/docs/search.md +++ b/docs/search.md @@ -63,7 +63,7 @@ See the endpoint docs at ```python -client.search.search_for_content(ancestor_folder_ids=['0'], mdfilters=[MetadataFilter(filters={'multiSelectField': ['multiSelectValue1', 'multiSelectValue2']}, scope=MetadataFilterScopeField.ENTERPRISE.value, template_key=template_key)]) +client.search.search_for_content(ancestor_folder_ids=['0'], mdfilters=[MetadataFilter(filters={'stringField': 'stringValue', 'dateField': {}(lt=date_time_from_string('2035-01-01T00:00:00Z'), gt=date_time_from_string('2035-01-03T00:00:00Z')), 'floatField': {}(lt=9.5, gt=10.5), 'enumField': 'enumValue2', 'multiSelectField': ['multiSelectValue1', 'multiSelectValue2']}, scope=MetadataFilterScopeField.ENTERPRISE.value, template_key=template_key)]) ``` ### Arguments diff --git a/test/search.py b/test/search.py index 5aa7925..c385e2c 100644 --- a/test/search.py +++ b/test/search.py @@ -46,8 +46,14 @@ from box_sdk_gen.internal.utils import generate_byte_stream +from box_sdk_gen.internal.utils import date_time_from_string + from test.commons import get_default_client +from box_sdk_gen.schemas import MetadataFieldFilterDateRange + +from box_sdk_gen.schemas import MetadataFieldFilterFloatRange + client: BoxClient = get_default_client() @@ -176,39 +182,20 @@ def testMetadataFilters(): 'multiSelectField': ['multiSelectValue1', 'multiSelectValue2'], }, ) - string_query: Union[SearchResults, SearchResultsWithSharedLinks] = ( - client.search.search_for_content( - ancestor_folder_ids=['0'], - mdfilters=[ - MetadataFilter( - filters={'stringField': 'stringValue'}, - scope=MetadataFilterScopeField.ENTERPRISE.value, - template_key=template_key, - ) - ], - ) - ) - assert len(string_query.entries) >= 0 - enum_query: Union[SearchResults, SearchResultsWithSharedLinks] = ( - client.search.search_for_content( - ancestor_folder_ids=['0'], - mdfilters=[ - MetadataFilter( - filters={'enumField': 'enumValue2'}, - scope=MetadataFilterScopeField.ENTERPRISE.value, - template_key=template_key, - ) - ], - ) - ) - assert len(enum_query.entries) >= 0 - multi_select_query: Union[SearchResults, SearchResultsWithSharedLinks] = ( + query: Union[SearchResults, SearchResultsWithSharedLinks] = ( client.search.search_for_content( ancestor_folder_ids=['0'], mdfilters=[ MetadataFilter( filters={ - 'multiSelectField': ['multiSelectValue1', 'multiSelectValue2'] + 'stringField': 'stringValue', + 'dateField': {}( + lt=date_time_from_string('2035-01-01T00:00:00Z'), + gt=date_time_from_string('2035-01-03T00:00:00Z'), + ), + 'floatField': {}(lt=9.5, gt=10.5), + 'enumField': 'enumValue2', + 'multiSelectField': ['multiSelectValue1', 'multiSelectValue2'], }, scope=MetadataFilterScopeField.ENTERPRISE.value, template_key=template_key, @@ -216,7 +203,7 @@ def testMetadataFilters(): ], ) ) - assert len(multi_select_query.entries) >= 0 + assert len(query.entries) >= 0 client.metadata_templates.delete_metadata_template( DeleteMetadataTemplateScope.ENTERPRISE.value, template.template_key ) From 145e97a7e799b1d175910d292b6e84c5c2788d8d Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 9 May 2024 16:56:00 +0200 Subject: [PATCH 2/2] test: Box AI integration tests (box/box-codegen#482) (#142) Co-authored-by: Minh Nguyen Cong --- .codegen.json | 2 +- box_sdk_gen/managers/events.py | 2 + box_sdk_gen/managers/search.py | 11 +--- box_sdk_gen/schemas.py | 28 ++++++--- docs/ai.md | 12 +++- docs/search.md | 4 +- test/ai.py | 103 +++++++++++++++++++++++++++++++++ test/search.py | 4 +- 8 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 test/ai.py diff --git a/.codegen.json b/.codegen.json index 77730fb..0aa85b2 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "36bf5da", "specHash": "a8c36df", "version": "0.6.4" } +{ "engineHash": "c2589f6", "specHash": "f7e92ba", "version": "0.6.4" } diff --git a/box_sdk_gen/managers/events.py b/box_sdk_gen/managers/events.py index 7dedf77..7b9a3e5 100644 --- a/box_sdk_gen/managers/events.py +++ b/box_sdk_gen/managers/events.py @@ -100,6 +100,7 @@ class GetEventsEventType(str, Enum): GROUP_EDITED = 'GROUP_EDITED' GROUP_REMOVE_ITEM = 'GROUP_REMOVE_ITEM' GROUP_REMOVE_USER = 'GROUP_REMOVE_USER' + ITEM_EMAIL_SEND = 'ITEM_EMAIL_SEND' ITEM_MODIFY = 'ITEM_MODIFY' ITEM_OPEN = 'ITEM_OPEN' ITEM_SHARED_UPDATE = 'ITEM_SHARED_UPDATE' @@ -127,6 +128,7 @@ class GetEventsEventType(str, Enum): RENAME = 'RENAME' RETENTION_POLICY_ASSIGNMENT_ADD = 'RETENTION_POLICY_ASSIGNMENT_ADD' SHARE = 'SHARE' + SHARED_LINK_SEND = 'SHARED_LINK_SEND' SHARE_EXPIRATION = 'SHARE_EXPIRATION' SHIELD_ALERT = 'SHIELD_ALERT' SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED = 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED' diff --git a/box_sdk_gen/managers/search.py b/box_sdk_gen/managers/search.py index d337c34..ac7d94d 100644 --- a/box_sdk_gen/managers/search.py +++ b/box_sdk_gen/managers/search.py @@ -428,14 +428,9 @@ def search_for_content( the trash * `all_items` - Searches for both trashed and non-trashed items., defaults to None :type trash_content: Optional[SearchForContentTrashContent], optional - :param mdfilters: Limits the search results to any items for which the metadata matches - the provided filter. - - This parameter contains a list of 1 metadata template to filter - the search results by. This list can currently only - contain one entry, though this might be expanded in the future. - - This parameter is required unless the `query` parameter is provided., defaults to None + :param mdfilters: Limits the search results to any items for which the metadata matches the provided filter. + This parameter is a list that specifies exactly **one** metadata template used to filter the search results. + It is required unless the `query` parameter is provided., defaults to None :type mdfilters: Optional[List[MetadataFilter]], optional :param sort: Defines the order in which search results are returned. This API defaults to returning items by relevance unless this parameter is diff --git a/box_sdk_gen/schemas.py b/box_sdk_gen/schemas.py index 3d14a58..e5b1780 100644 --- a/box_sdk_gen/schemas.py +++ b/box_sdk_gen/schemas.py @@ -9173,6 +9173,7 @@ class EventEventTypeField(str, Enum): ITEM_COPY = 'ITEM_COPY' ITEM_CREATE = 'ITEM_CREATE' ITEM_DOWNLOAD = 'ITEM_DOWNLOAD' + ITEM_EMAIL_SEND = 'ITEM_EMAIL_SEND' ITEM_MAKE_CURRENT_VERSION = 'ITEM_MAKE_CURRENT_VERSION' ITEM_MODIFY = 'ITEM_MODIFY' ITEM_MOVE = 'ITEM_MOVE' @@ -9213,6 +9214,7 @@ class EventEventTypeField(str, Enum): RENAME = 'RENAME' RETENTION_POLICY_ASSIGNMENT_ADD = 'RETENTION_POLICY_ASSIGNMENT_ADD' SHARE = 'SHARE' + SHARED_LINK_SEND = 'SHARED_LINK_SEND' SHARE_EXPIRATION = 'SHARE_EXPIRATION' SHIELD_ALERT = 'SHIELD_ALERT' SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED = 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED' @@ -12913,7 +12915,7 @@ def __init__( self.entries = entries -class MetadataFieldFilterFloatRangeValue(BaseObject): +class MetadataFieldFilterFloatRange(BaseObject): def __init__( self, *, lt: Optional[float] = None, gt: Optional[float] = None, **kwargs ): @@ -12934,7 +12936,7 @@ def __init__( self.gt = gt -class MetadataFieldFilterDateRangeValue(BaseObject): +class MetadataFieldFilterDateRange(BaseObject): def __init__( self, *, lt: Optional[DateTime] = None, gt: Optional[DateTime] = None, **kwargs ): @@ -12977,12 +12979,15 @@ def __init__( scope: Optional[MetadataFilterScopeField] = None, template_key: Optional[str] = None, filters: Optional[ - Union[ - Dict[str, str], - Dict[str, float], - Dict[str, List[str]], - Dict[str, MetadataFieldFilterFloatRangeValue], - Dict[str, MetadataFieldFilterDateRangeValue], + Dict[ + str, + Union[ + str, + float, + List[str], + MetadataFieldFilterFloatRange, + MetadataFieldFilterDateRange, + ], ] ] = None, **kwargs @@ -12994,7 +12999,7 @@ def __init__( for use in this enterprise, and `global` for general templates that are available to all enterprises using Box., defaults to None :type scope: Optional[MetadataFilterScopeField], optional - :param template_key: The key of the template to filter search results by. + :param template_key: The key of the template used to filter search results. In many cases the template key is automatically derived of its display name, for example `Contract Template` would @@ -13009,6 +13014,11 @@ def __init__( [file]: e://get-files-id-metadata [folder]: e://get-folders-id-metadata, defaults to None :type template_key: Optional[str], optional + :param filters: Specifies which fields on the template to filter the search + results by. When more than one field is specified, the query + performs a logical `AND` to ensure that the instance of the + template matches each of the fields specified., defaults to None + :type filters: Optional[Dict[str, Union[str, float, List[str], MetadataFieldFilterFloatRange, MetadataFieldFilterDateRange]]], optional """ super().__init__(**kwargs) self.scope = scope diff --git a/docs/ai.md b/docs/ai.md index 6c319a1..68e5cab 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -12,7 +12,11 @@ This operation is performed by calling function `create_ai_ask`. See the endpoint docs at [API Reference](https://developer.box.com/reference/post-ai-ask/). -_Currently we don't have an example for calling `create_ai_ask` in integration tests_ + + +```python +client.ai.create_ai_ask(CreateAiAskMode.MULTIPLE_ITEM_QA.value, 'Which direction sun rises?', [CreateAiAskItems(id=file_to_ask_1.id, type=CreateAiAskItemsTypeField.FILE.value, content='Earth goes around the sun'), CreateAiAskItems(id=file_to_ask_2.id, type=CreateAiAskItemsTypeField.FILE.value, content='Sun rises in the East in the morning')]) +``` ### Arguments @@ -40,7 +44,11 @@ This operation is performed by calling function `create_ai_text_gen`. See the endpoint docs at [API Reference](https://developer.box.com/reference/post-ai-text-gen/). -_Currently we don't have an example for calling `create_ai_text_gen` in integration tests_ + + +```python +client.ai.create_ai_text_gen('Parapharse the document.s', [CreateAiTextGenItems(id=file_to_ask.id, type=CreateAiTextGenItemsTypeField.FILE.value, content='The Earth goes around the sun. Sun rises in the East in the morning.')], dialogue_history=[CreateAiTextGenDialogueHistory(prompt='What does the earth go around?', answer='The sun', created_at=date_time_from_string('2021-01-01T00:00:00Z')), CreateAiTextGenDialogueHistory(prompt='On Earth, where does the sun rise?', answer='East', created_at=date_time_from_string('2021-01-01T00:00:00Z'))]) +``` ### Arguments diff --git a/docs/search.md b/docs/search.md index ce6df20..9566c64 100644 --- a/docs/search.md +++ b/docs/search.md @@ -63,7 +63,7 @@ See the endpoint docs at ```python -client.search.search_for_content(ancestor_folder_ids=['0'], mdfilters=[MetadataFilter(filters={'stringField': 'stringValue', 'dateField': {}(lt=date_time_from_string('2035-01-01T00:00:00Z'), gt=date_time_from_string('2035-01-03T00:00:00Z')), 'floatField': {}(lt=9.5, gt=10.5), 'enumField': 'enumValue2', 'multiSelectField': ['multiSelectValue1', 'multiSelectValue2']}, scope=MetadataFilterScopeField.ENTERPRISE.value, template_key=template_key)]) +client.search.search_for_content(ancestor_folder_ids=['0'], mdfilters=[MetadataFilter(filters={'stringField': 'stringValue', 'dateField': MetadataFieldFilterDateRange(lt=date_time_from_string('2035-01-01T00:00:00Z'), gt=date_time_from_string('2035-01-03T00:00:00Z')), 'floatField': MetadataFieldFilterFloatRange(lt=9.5, gt=10.5), 'enumField': 'enumValue2', 'multiSelectField': ['multiSelectValue1', 'multiSelectValue2']}, scope=MetadataFilterScopeField.ENTERPRISE.value, template_key=template_key)]) ``` ### Arguments @@ -93,7 +93,7 @@ client.search.search_for_content(ancestor_folder_ids=['0'], mdfilters=[MetadataF - trash_content `Optional[SearchForContentTrashContent]` - Determines if the search should look in the trash for items. By default, this API only returns search results for items not currently in the trash (`non_trashed_only`). _ `trashed_only` - Only searches for items currently in the trash _ `non_trashed_only` - Only searches for items currently not in the trash \* `all_items` - Searches for both trashed and non-trashed items. - mdfilters `Optional[List[MetadataFilter]]` - - Limits the search results to any items for which the metadata matches the provided filter. This parameter contains a list of 1 metadata template to filter the search results by. This list can currently only contain one entry, though this might be expanded in the future. This parameter is required unless the `query` parameter is provided. + - Limits the search results to any items for which the metadata matches the provided filter. This parameter is a list that specifies exactly **one** metadata template used to filter the search results. It is required unless the `query` parameter is provided. - sort `Optional[SearchForContentSort]` - Defines the order in which search results are returned. This API defaults to returning items by relevance unless this parameter is explicitly specified. _ `relevance` (default) returns the results sorted by relevance to the query search term. The relevance is based on the occurrence of the search term in the items name, description, content, and additional properties. _ `modified_at` returns the results ordered in descending order by date at which the item was last modified. - direction `Optional[SearchForContentDirection]` diff --git a/test/ai.py b/test/ai.py new file mode 100644 index 0000000..e6d2543 --- /dev/null +++ b/test/ai.py @@ -0,0 +1,103 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas import FileFull + +from box_sdk_gen.schemas import AiResponse + +from box_sdk_gen.managers.ai import CreateAiAskMode + +from box_sdk_gen.managers.ai import CreateAiAskItems + +from box_sdk_gen.managers.ai import CreateAiAskItemsTypeField + +from box_sdk_gen.managers.ai import CreateAiTextGenItems + +from box_sdk_gen.managers.ai import CreateAiTextGenItemsTypeField + +from box_sdk_gen.managers.ai import CreateAiTextGenDialogueHistory + +from test.commons import get_default_client + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import generate_byte_stream + +from box_sdk_gen.internal.utils import date_time_from_string + +from box_sdk_gen.internal.utils import date_time_to_string + +from test.commons import upload_new_file + +client: BoxClient = get_default_client() + + +def testAskAISingleItem(): + file_to_ask: FileFull = upload_new_file() + response: AiResponse = client.ai.create_ai_ask( + CreateAiAskMode.SINGLE_ITEM_QA.value, + 'which direction sun rises', + [ + CreateAiAskItems( + id=file_to_ask.id, + type=CreateAiAskItemsTypeField.FILE.value, + content='Sun rises in the East', + ) + ], + ) + assert 'East' in response.answer + assert response.completion_reason == 'done' + client.files.delete_file_by_id(file_to_ask.id) + + +def testAskAIMultipleItems(): + file_to_ask_1: FileFull = upload_new_file() + file_to_ask_2: FileFull = upload_new_file() + response: AiResponse = client.ai.create_ai_ask( + CreateAiAskMode.MULTIPLE_ITEM_QA.value, + 'Which direction sun rises?', + [ + CreateAiAskItems( + id=file_to_ask_1.id, + type=CreateAiAskItemsTypeField.FILE.value, + content='Earth goes around the sun', + ), + CreateAiAskItems( + id=file_to_ask_2.id, + type=CreateAiAskItemsTypeField.FILE.value, + content='Sun rises in the East in the morning', + ), + ], + ) + assert 'East' in response.answer + assert response.completion_reason == 'done' + client.files.delete_file_by_id(file_to_ask_1.id) + client.files.delete_file_by_id(file_to_ask_2.id) + + +def testAITextGenWithDialogueHistory(): + file_to_ask: FileFull = upload_new_file() + response: AiResponse = client.ai.create_ai_text_gen( + 'Parapharse the document.s', + [ + CreateAiTextGenItems( + id=file_to_ask.id, + type=CreateAiTextGenItemsTypeField.FILE.value, + content='The Earth goes around the sun. Sun rises in the East in the morning.', + ) + ], + dialogue_history=[ + CreateAiTextGenDialogueHistory( + prompt='What does the earth go around?', + answer='The sun', + created_at=date_time_from_string('2021-01-01T00:00:00Z'), + ), + CreateAiTextGenDialogueHistory( + prompt='On Earth, where does the sun rise?', + answer='East', + created_at=date_time_from_string('2021-01-01T00:00:00Z'), + ), + ], + ) + assert 'sun' in response.answer + assert response.completion_reason == 'done' + client.files.delete_file_by_id(file_to_ask.id) diff --git a/test/search.py b/test/search.py index c385e2c..8512c20 100644 --- a/test/search.py +++ b/test/search.py @@ -189,11 +189,11 @@ def testMetadataFilters(): MetadataFilter( filters={ 'stringField': 'stringValue', - 'dateField': {}( + 'dateField': MetadataFieldFilterDateRange( lt=date_time_from_string('2035-01-01T00:00:00Z'), gt=date_time_from_string('2035-01-03T00:00:00Z'), ), - 'floatField': {}(lt=9.5, gt=10.5), + 'floatField': MetadataFieldFilterFloatRange(lt=9.5, gt=10.5), 'enumField': 'enumValue2', 'multiSelectField': ['multiSelectValue1', 'multiSelectValue2'], },