From 3c0e2f2e9dc80bb4d286bf53bf9685707eda31f9 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 28 Jul 2025 12:17:30 +0200 Subject: [PATCH 001/139] test: Add Archives integration tests (box/box-codegen#744) (#901) --- .codegen.json | 1 + .gitattributes | 11 - .github/ISSUE_TEMPLATE/bug.md | 15 +- .github/ISSUE_TEMPLATE/config.yml | 4 +- .github/ISSUE_TEMPLATE/enhancement.md | 7 +- .github/ISSUE_TEMPLATE/question.md | 15 +- .github/stale.yml | 2 +- .github/workflows/autoupdate-pr.yml | 39 + .github/workflows/build.yml | 77 +- .github/workflows/create_release.yml | 30 - .github/workflows/integration-tests.yml | 36 - .../{releases.yml => notify-changelog.yml} | 3 +- .github/workflows/spell-check-lint.yml | 8 +- .gitignore | 127 +- .pylintrc | 2 +- .pypirc | 2 +- .versionrc | 43 +- AUTHORS.md | 16 - CONTRIBUTING.md | 90 +- MANIFEST.in | 3 - README.md | 514 +--- VERSIONS.md | 13 +- box_sdk_gen/__init__.py | 15 + box_sdk_gen/box/__init__.py | 11 + box_sdk_gen/box/ccg_auth.py | 236 ++ box_sdk_gen/box/developer_token_auth.py | 139 ++ box_sdk_gen/box/errors.py | 136 ++ box_sdk_gen/box/jwt_auth.py | 478 ++++ box_sdk_gen/box/oauth.py | 250 ++ box_sdk_gen/box/token_storage.py | 74 + box_sdk_gen/client.py | 530 +++++ box_sdk_gen/internal/__init__.py | 9 + box_sdk_gen/internal/base_object.py | 165 ++ box_sdk_gen/internal/errors.py | 6 + box_sdk_gen/internal/logging.py | 31 + box_sdk_gen/internal/null_value.py | 5 + box_sdk_gen/internal/utils.py | 501 ++++ box_sdk_gen/managers/__init__.py | 157 ++ box_sdk_gen/managers/ai.py | 477 ++++ box_sdk_gen/managers/ai_studio.py | 328 +++ box_sdk_gen/managers/app_item_associations.py | 197 ++ box_sdk_gen/managers/archives.py | 171 ++ box_sdk_gen/managers/authorization.py | 434 ++++ box_sdk_gen/managers/avatars.py | 174 ++ box_sdk_gen/managers/chunked_uploads.py | 872 +++++++ box_sdk_gen/managers/classifications.py | 707 ++++++ .../collaboration_allowlist_entries.py | 226 ++ .../collaboration_allowlist_exempt_targets.py | 227 ++ box_sdk_gen/managers/collections.py | 217 ++ box_sdk_gen/managers/comments.py | 357 +++ box_sdk_gen/managers/device_pinners.py | 188 ++ box_sdk_gen/managers/docgen.py | 297 +++ box_sdk_gen/managers/docgen_template.py | 349 +++ box_sdk_gen/managers/downloads.py | 256 ++ box_sdk_gen/managers/email_aliases.py | 175 ++ box_sdk_gen/managers/events.py | 399 ++++ box_sdk_gen/managers/file_classifications.py | 313 +++ box_sdk_gen/managers/file_metadata.py | 420 ++++ box_sdk_gen/managers/file_requests.py | 398 ++++ .../managers/file_version_legal_holds.py | 179 ++ .../managers/file_version_retentions.py | 187 ++ box_sdk_gen/managers/file_versions.py | 423 ++++ box_sdk_gen/managers/file_watermarks.py | 199 ++ box_sdk_gen/managers/files.py | 786 +++++++ .../managers/folder_classifications.py | 331 +++ box_sdk_gen/managers/folder_locks.py | 216 ++ box_sdk_gen/managers/folder_metadata.py | 450 ++++ box_sdk_gen/managers/folder_watermarks.py | 214 ++ box_sdk_gen/managers/folders.py | 966 ++++++++ box_sdk_gen/managers/groups.py | 450 ++++ box_sdk_gen/managers/hub_collaborations.py | 360 +++ box_sdk_gen/managers/hub_items.py | 177 ++ box_sdk_gen/managers/hubs.py | 475 ++++ box_sdk_gen/managers/integration_mappings.py | 504 ++++ box_sdk_gen/managers/invites.py | 185 ++ box_sdk_gen/managers/legal_hold_policies.py | 328 +++ .../managers/legal_hold_policy_assignments.py | 393 ++++ box_sdk_gen/managers/list_collaborations.py | 329 +++ box_sdk_gen/managers/memberships.py | 444 ++++ .../managers/metadata_cascade_policies.py | 333 +++ box_sdk_gen/managers/metadata_templates.py | 636 +++++ box_sdk_gen/managers/recent_items.py | 109 + box_sdk_gen/managers/retention_policies.py | 443 ++++ .../managers/retention_policy_assignments.py | 371 +++ box_sdk_gen/managers/search.py | 560 +++++ box_sdk_gen/managers/session_termination.py | 139 ++ .../managers/shared_links_app_items.py | 84 + box_sdk_gen/managers/shared_links_files.py | 540 +++++ box_sdk_gen/managers/shared_links_folders.py | 546 +++++ .../managers/shared_links_web_links.py | 508 ++++ .../shield_information_barrier_reports.py | 183 ++ ...eld_information_barrier_segment_members.py | 282 +++ ...nformation_barrier_segment_restrictions.py | 316 +++ .../shield_information_barrier_segments.py | 279 +++ .../managers/shield_information_barriers.py | 219 ++ box_sdk_gen/managers/shield_lists.py | 300 +++ box_sdk_gen/managers/sign_requests.py | 348 +++ box_sdk_gen/managers/sign_templates.py | 123 + box_sdk_gen/managers/skills.py | 442 ++++ box_sdk_gen/managers/storage_policies.py | 140 ++ .../managers/storage_policy_assignments.py | 352 +++ box_sdk_gen/managers/task_assignments.py | 303 +++ box_sdk_gen/managers/tasks.py | 338 +++ .../terms_of_service_user_statuses.py | 235 ++ box_sdk_gen/managers/terms_of_services.py | 235 ++ box_sdk_gen/managers/transfer.py | 193 ++ box_sdk_gen/managers/trashed_files.py | 252 ++ box_sdk_gen/managers/trashed_folders.py | 279 +++ box_sdk_gen/managers/trashed_items.py | 171 ++ box_sdk_gen/managers/trashed_web_links.py | 213 ++ box_sdk_gen/managers/uploads.py | 546 +++++ box_sdk_gen/managers/user_collaborations.py | 444 ++++ box_sdk_gen/managers/users.py | 701 ++++++ box_sdk_gen/managers/web_links.py | 326 +++ box_sdk_gen/managers/webhooks.py | 491 ++++ box_sdk_gen/managers/workflows.py | 261 +++ box_sdk_gen/managers/zip_downloads.py | 318 +++ box_sdk_gen/networking/__init__.py | 19 + box_sdk_gen/networking/auth.py | 47 + box_sdk_gen/networking/base_urls.py | 29 + box_sdk_gen/networking/box_network_client.py | 363 +++ box_sdk_gen/networking/fetch_options.py | 113 + box_sdk_gen/networking/fetch_response.py | 36 + box_sdk_gen/networking/network.py | 154 ++ box_sdk_gen/networking/network_client.py | 14 + box_sdk_gen/networking/proxy_config.py | 14 + box_sdk_gen/networking/retries.py | 94 + box_sdk_gen/networking/version.py | 1 + box_sdk_gen/parameters/__init__.py | 1 + box_sdk_gen/parameters/v2025_r0/__init__.py | 1 + .../v2025_r0/box_version_header_v2025_r0.py | 7 + box_sdk_gen/schemas/__init__.py | 523 +++++ box_sdk_gen/schemas/access_token.py | 60 + .../schemas/ai_agent_allowed_entity.py | 9 + box_sdk_gen/schemas/ai_agent_ask.py | 48 + .../schemas/ai_agent_basic_gen_tool.py | 69 + .../schemas/ai_agent_basic_text_tool.py | 48 + .../schemas/ai_agent_basic_text_tool_base.py | 36 + .../ai_agent_basic_text_tool_text_gen.py | 50 + box_sdk_gen/schemas/ai_agent_extract.py | 38 + .../schemas/ai_agent_extract_structured.py | 38 + box_sdk_gen/schemas/ai_agent_info.py | 49 + .../schemas/ai_agent_long_text_tool.py | 90 + .../ai_agent_long_text_tool_text_gen.py | 94 + box_sdk_gen/schemas/ai_agent_reference.py | 32 + .../schemas/ai_agent_spreadsheet_tool.py | 36 + box_sdk_gen/schemas/ai_agent_text_gen.py | 32 + box_sdk_gen/schemas/ai_ask.py | 64 + box_sdk_gen/schemas/ai_citation.py | 40 + box_sdk_gen/schemas/ai_dialogue_history.py | 30 + box_sdk_gen/schemas/ai_extract.py | 36 + box_sdk_gen/schemas/ai_extract_response.py | 8 + box_sdk_gen/schemas/ai_extract_structured.py | 134 ++ .../schemas/ai_extract_structured_response.py | 34 + box_sdk_gen/schemas/ai_item_ask.py | 37 + box_sdk_gen/schemas/ai_item_base.py | 36 + box_sdk_gen/schemas/ai_llm_endpoint_params.py | 18 + .../schemas/ai_llm_endpoint_params_aws.py | 41 + .../schemas/ai_llm_endpoint_params_google.py | 43 + .../schemas/ai_llm_endpoint_params_ibm.py | 47 + .../schemas/ai_llm_endpoint_params_open_ai.py | 54 + .../schemas/ai_multiple_agent_response.py | 38 + box_sdk_gen/schemas/ai_response.py | 34 + box_sdk_gen/schemas/ai_response_full.py | 44 + .../schemas/ai_single_agent_response.py | 78 + .../schemas/ai_single_agent_response_full.py | 90 + box_sdk_gen/schemas/ai_studio_agent_ask.py | 70 + .../schemas/ai_studio_agent_ask_response.py | 72 + .../schemas/ai_studio_agent_basic_gen_tool.py | 75 + ...ai_studio_agent_basic_gen_tool_response.py | 83 + .../ai_studio_agent_basic_text_tool.py | 54 + ...i_studio_agent_basic_text_tool_response.py | 64 + .../schemas/ai_studio_agent_extract.py | 52 + .../ai_studio_agent_extract_response.py | 54 + .../schemas/ai_studio_agent_long_text_tool.py | 62 + ...ai_studio_agent_long_text_tool_response.py | 70 + .../ai_studio_agent_spreadsheet_tool.py | 38 + ..._studio_agent_spreadsheet_tool_response.py | 48 + .../schemas/ai_studio_agent_text_gen.py | 50 + .../ai_studio_agent_text_gen_response.py | 52 + box_sdk_gen/schemas/ai_text_gen.py | 75 + box_sdk_gen/schemas/app_item.py | 34 + box_sdk_gen/schemas/app_item_association.py | 44 + box_sdk_gen/schemas/app_item_associations.py | 36 + box_sdk_gen/schemas/app_item_event_source.py | 44 + box_sdk_gen/schemas/classification.py | 88 + .../schemas/classification_template.py | 246 ++ box_sdk_gen/schemas/client_error.py | 77 + box_sdk_gen/schemas/collaboration.py | 220 ++ .../collaboration_allowlist_entries.py | 40 + .../schemas/collaboration_allowlist_entry.py | 83 + .../collaboration_allowlist_exempt_target.py | 77 + .../collaboration_allowlist_exempt_targets.py | 42 + box_sdk_gen/schemas/collaborations.py | 38 + .../collaborations_offset_paginated.py | 47 + box_sdk_gen/schemas/collaborator_variable.py | 68 + box_sdk_gen/schemas/collection.py | 50 + box_sdk_gen/schemas/collections.py | 80 + box_sdk_gen/schemas/comment.py | 66 + box_sdk_gen/schemas/comment_base.py | 32 + box_sdk_gen/schemas/comment_full.py | 64 + box_sdk_gen/schemas/comments.py | 80 + .../schemas/completion_rule_variable.py | 48 + box_sdk_gen/schemas/conflict_error.py | 71 + box_sdk_gen/schemas/create_ai_agent.py | 65 + box_sdk_gen/schemas/device_pinner.py | 40 + box_sdk_gen/schemas/device_pinners.py | 68 + box_sdk_gen/schemas/email_alias.py | 40 + box_sdk_gen/schemas/email_aliases.py | 28 + box_sdk_gen/schemas/enterprise_base.py | 32 + box_sdk_gen/schemas/event.py | 241 ++ box_sdk_gen/schemas/event_source.py | 63 + box_sdk_gen/schemas/events.py | 35 + box_sdk_gen/schemas/file.py | 278 +++ box_sdk_gen/schemas/file_base.py | 44 + box_sdk_gen/schemas/file_conflict.py | 57 + box_sdk_gen/schemas/file_full.py | 577 +++++ box_sdk_gen/schemas/file_mini.py | 61 + box_sdk_gen/schemas/file_or_folder_scope.py | 47 + box_sdk_gen/schemas/file_request.py | 129 + .../schemas/file_request_copy_request.py | 113 + .../schemas/file_request_update_request.py | 80 + box_sdk_gen/schemas/file_version.py | 69 + box_sdk_gen/schemas/file_version_base.py | 30 + box_sdk_gen/schemas/file_version_full.py | 80 + .../schemas/file_version_legal_hold.py | 55 + .../schemas/file_version_legal_holds.py | 38 + box_sdk_gen/schemas/file_version_mini.py | 39 + box_sdk_gen/schemas/file_version_retention.py | 56 + .../schemas/file_version_retentions.py | 38 + box_sdk_gen/schemas/file_versions.py | 80 + box_sdk_gen/schemas/files.py | 28 + box_sdk_gen/schemas/files_on_hold.py | 38 + box_sdk_gen/schemas/files_under_retention.py | 38 + box_sdk_gen/schemas/folder.py | 313 +++ box_sdk_gen/schemas/folder_base.py | 44 + box_sdk_gen/schemas/folder_full.py | 314 +++ box_sdk_gen/schemas/folder_lock.py | 61 + box_sdk_gen/schemas/folder_locks.py | 34 + box_sdk_gen/schemas/folder_mini.py | 41 + box_sdk_gen/schemas/folder_reference.py | 30 + box_sdk_gen/schemas/generic_source.py | 1 + box_sdk_gen/schemas/group.py | 44 + box_sdk_gen/schemas/group_base.py | 26 + box_sdk_gen/schemas/group_full.py | 128 + box_sdk_gen/schemas/group_membership.py | 59 + box_sdk_gen/schemas/group_memberships.py | 80 + box_sdk_gen/schemas/group_mini.py | 39 + box_sdk_gen/schemas/groups.py | 80 + box_sdk_gen/schemas/integration_mapping.py | 90 + .../schemas/integration_mapping_base.py | 32 + .../integration_mapping_box_item_slack.py | 30 + .../integration_mapping_partner_item_slack.py | 40 + .../integration_mapping_partner_item_teams.py | 34 + ...pping_partner_item_teams_create_request.py | 38 + ...ntegration_mapping_slack_create_request.py | 32 + .../integration_mapping_slack_options.py | 21 + .../schemas/integration_mapping_teams.py | 69 + ...ntegration_mapping_teams_create_request.py | 21 + box_sdk_gen/schemas/integration_mappings.py | 34 + .../schemas/integration_mappings_teams.py | 21 + box_sdk_gen/schemas/invite.py | 85 + box_sdk_gen/schemas/items.py | 94 + box_sdk_gen/schemas/items_offset_paginated.py | 86 + box_sdk_gen/schemas/keyword_skill_card.py | 139 ++ box_sdk_gen/schemas/legal_hold_policies.py | 38 + box_sdk_gen/schemas/legal_hold_policy.py | 122 + .../schemas/legal_hold_policy_assignment.py | 61 + .../legal_hold_policy_assignment_base.py | 32 + .../schemas/legal_hold_policy_assignments.py | 39 + box_sdk_gen/schemas/legal_hold_policy_mini.py | 30 + box_sdk_gen/schemas/metadata.py | 36 + box_sdk_gen/schemas/metadata_base.py | 55 + .../schemas/metadata_cascade_policies.py | 38 + .../schemas/metadata_cascade_policy.py | 126 + box_sdk_gen/schemas/metadata_error.py | 29 + .../metadata_field_filter_date_range.py | 28 + .../metadata_field_filter_float_range.py | 26 + box_sdk_gen/schemas/metadata_filter.py | 90 + box_sdk_gen/schemas/metadata_full.py | 76 + .../schemas/metadata_instance_value.py | 7 + box_sdk_gen/schemas/metadata_query.py | 129 + box_sdk_gen/schemas/metadata_query_index.py | 66 + box_sdk_gen/schemas/metadata_query_results.py | 43 + box_sdk_gen/schemas/metadata_template.py | 175 ++ box_sdk_gen/schemas/metadata_templates.py | 38 + box_sdk_gen/schemas/metadatas.py | 28 + box_sdk_gen/schemas/o_auth_2_error.py | 24 + box_sdk_gen/schemas/outcome.py | 36 + box_sdk_gen/schemas/post_o_auth_2_revoke.py | 30 + box_sdk_gen/schemas/post_o_auth_2_token.py | 146 ++ ...ost_o_auth_2_token_refresh_access_token.py | 36 + box_sdk_gen/schemas/realtime_server.py | 43 + box_sdk_gen/schemas/realtime_servers.py | 28 + box_sdk_gen/schemas/recent_item.py | 56 + box_sdk_gen/schemas/recent_items.py | 38 + box_sdk_gen/schemas/retention_policies.py | 34 + box_sdk_gen/schemas/retention_policy.py | 173 ++ .../schemas/retention_policy_assignment.py | 113 + .../retention_policy_assignment_base.py | 30 + .../schemas/retention_policy_assignments.py | 34 + box_sdk_gen/schemas/retention_policy_base.py | 30 + box_sdk_gen/schemas/retention_policy_mini.py | 54 + box_sdk_gen/schemas/role_variable.py | 47 + .../schemas/search_result_with_shared_link.py | 37 + box_sdk_gen/schemas/search_results.py | 59 + .../search_results_with_shared_links.py | 57 + .../schemas/session_termination_message.py | 15 + .../schemas/shield_information_barrier.py | 77 + .../shield_information_barrier_base.py | 32 + .../shield_information_barrier_reference.py | 20 + .../shield_information_barrier_report.py | 69 + .../shield_information_barrier_report_base.py | 32 + ...ield_information_barrier_report_details.py | 26 + .../shield_information_barrier_reports.py | 37 + .../shield_information_barrier_segment.py | 64 + ...ield_information_barrier_segment_member.py | 101 + ...information_barrier_segment_member_base.py | 35 + ...information_barrier_segment_member_mini.py | 35 + ...eld_information_barrier_segment_members.py | 37 + ...information_barrier_segment_restriction.py | 83 + ...mation_barrier_segment_restriction_base.py | 35 + ...mation_barrier_segment_restriction_mini.py | 107 + ...nformation_barrier_segment_restrictions.py | 37 + .../shield_information_barrier_segments.py | 37 + .../schemas/shield_information_barriers.py | 34 + box_sdk_gen/schemas/sign_request.py | 187 ++ box_sdk_gen/schemas/sign_request_base.py | 72 + .../schemas/sign_request_create_request.py | 109 + .../schemas/sign_request_create_signer.py | 98 + .../schemas/sign_request_prefill_tag.py | 34 + box_sdk_gen/schemas/sign_request_signer.py | 159 ++ .../schemas/sign_request_signer_input.py | 81 + box_sdk_gen/schemas/sign_requests.py | 34 + box_sdk_gen/schemas/sign_template.py | 251 ++ box_sdk_gen/schemas/sign_templates.py | 38 + box_sdk_gen/schemas/skill_cards_metadata.py | 102 + box_sdk_gen/schemas/skill_invocation.py | 257 ++ box_sdk_gen/schemas/status_skill_card.py | 158 ++ box_sdk_gen/schemas/storage_policies.py | 38 + box_sdk_gen/schemas/storage_policy.py | 28 + .../schemas/storage_policy_assignment.py | 53 + .../schemas/storage_policy_assignments.py | 38 + box_sdk_gen/schemas/storage_policy_mini.py | 30 + box_sdk_gen/schemas/task.py | 87 + box_sdk_gen/schemas/task_assignment.py | 76 + box_sdk_gen/schemas/task_assignments.py | 28 + box_sdk_gen/schemas/tasks.py | 30 + box_sdk_gen/schemas/template_signer.py | 80 + box_sdk_gen/schemas/template_signer_input.py | 147 ++ box_sdk_gen/schemas/terms_of_service.py | 92 + box_sdk_gen/schemas/terms_of_service_base.py | 30 + .../schemas/terms_of_service_user_status.py | 54 + .../schemas/terms_of_service_user_statuses.py | 28 + box_sdk_gen/schemas/terms_of_services.py | 28 + box_sdk_gen/schemas/timeline_skill_card.py | 182 ++ box_sdk_gen/schemas/tracking_code.py | 37 + box_sdk_gen/schemas/transcript_skill_card.py | 169 ++ box_sdk_gen/schemas/trash_file.py | 198 ++ box_sdk_gen/schemas/trash_file_restored.py | 157 ++ box_sdk_gen/schemas/trash_folder.py | 188 ++ box_sdk_gen/schemas/trash_folder_restored.py | 147 ++ box_sdk_gen/schemas/trash_web_link.py | 160 ++ .../schemas/trash_web_link_restored.py | 120 + box_sdk_gen/schemas/upload_part.py | 42 + box_sdk_gen/schemas/upload_part_mini.py | 30 + box_sdk_gen/schemas/upload_parts.py | 81 + box_sdk_gen/schemas/upload_session.py | 95 + box_sdk_gen/schemas/upload_url.py | 25 + box_sdk_gen/schemas/uploaded_part.py | 13 + box_sdk_gen/schemas/user.py | 120 + box_sdk_gen/schemas/user_avatar.py | 38 + box_sdk_gen/schemas/user_base.py | 26 + box_sdk_gen/schemas/user_collaborations.py | 36 + box_sdk_gen/schemas/user_full.py | 199 ++ .../schemas/user_integration_mappings.py | 32 + box_sdk_gen/schemas/user_mini.py | 32 + box_sdk_gen/schemas/users.py | 88 + box_sdk_gen/schemas/v2025_r0/__init__.py | 119 + .../schemas/v2025_r0/archive_v2025_r0.py | 43 + .../schemas/v2025_r0/archives_v2025_r0.py | 34 + .../schemas/v2025_r0/client_error_v2025_r0.py | 77 + .../v2025_r0/doc_gen_batch_base_v2025_r0.py | 30 + .../doc_gen_batch_create_request_v2025_r0.py | 71 + ...c_gen_document_generation_data_v2025_r0.py | 16 + .../v2025_r0/doc_gen_job_base_v2025_r0.py | 30 + .../v2025_r0/doc_gen_job_full_v2025_r0.py | 82 + .../schemas/v2025_r0/doc_gen_job_v2025_r0.py | 64 + .../v2025_r0/doc_gen_jobs_full_v2025_r0.py | 38 + .../schemas/v2025_r0/doc_gen_jobs_v2025_r0.py | 38 + .../schemas/v2025_r0/doc_gen_tag_v2025_r0.py | 38 + ...oc_gen_tags_processing_message_v2025_r0.py | 13 + .../schemas/v2025_r0/doc_gen_tags_v2025_r0.py | 38 + .../doc_gen_template_base_v2025_r0.py | 13 + ...oc_gen_template_create_request_v2025_r0.py | 11 + .../v2025_r0/doc_gen_template_v2025_r0.py | 25 + .../v2025_r0/doc_gen_templates_v2025_r0.py | 38 + .../v2025_r0/enterprise_reference_v2025_r0.py | 32 + .../v2025_r0/file_reference_v2025_r0.py | 30 + .../v2025_r0/file_version_base_v2025_r0.py | 30 + .../v2025_r0/folder_reference_v2025_r0.py | 30 + .../schemas/v2025_r0/group_base_v2025_r0.py | 30 + .../schemas/v2025_r0/group_mini_v2025_r0.py | 39 + .../v2025_r0/hub_access_grantee_v2025_r0.py | 11 + .../schemas/v2025_r0/hub_action_v2025_r0.py | 8 + .../schemas/v2025_r0/hub_base_v2025_r0.py | 36 + ...b_collaboration_create_request_v2025_r0.py | 84 + ...b_collaboration_update_request_v2025_r0.py | 16 + .../hub_collaboration_user_v2025_r0.py | 32 + .../v2025_r0/hub_collaboration_v2025_r0.py | 173 ++ .../v2025_r0/hub_collaborations_v2025_r0.py | 36 + .../v2025_r0/hub_copy_request_v2025_r0.py | 24 + .../v2025_r0/hub_create_request_v2025_r0.py | 18 + .../hub_item_operation_result_v2025_r0.py | 44 + .../v2025_r0/hub_item_operation_v2025_r0.py | 42 + .../v2025_r0/hub_item_reference_v2025_r0.py | 17 + .../schemas/v2025_r0/hub_item_v2025_r0.py | 29 + .../hub_items_manage_request_v2025_r0.py | 23 + .../hub_items_manage_response_v2025_r0.py | 19 + .../schemas/v2025_r0/hub_items_v2025_r0.py | 34 + .../v2025_r0/hub_update_request_v2025_r0.py | 42 + box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py | 78 + box_sdk_gen/schemas/v2025_r0/hubs_v2025_r0.py | 34 + .../shield_list_content_country_v2025_r0.py | 32 + .../shield_list_content_domain_v2025_r0.py | 32 + .../shield_list_content_email_v2025_r0.py | 32 + ...hield_list_content_integration_v2025_r0.py | 44 + .../shield_list_content_ip_v2025_r0.py | 32 + .../shield_list_content_request_v2025_r0.py | 26 + .../v2025_r0/shield_list_content_v2025_r0.py | 31 + .../v2025_r0/shield_list_mini_v2025_r0.py | 50 + .../schemas/v2025_r0/shield_list_v2025_r0.py | 76 + .../v2025_r0/shield_lists_create_v2025_r0.py | 48 + .../v2025_r0/shield_lists_update_v2025_r0.py | 48 + .../schemas/v2025_r0/shield_lists_v2025_r0.py | 21 + .../terms_of_service_base_v2025_r0.py | 30 + .../schemas/v2025_r0/user_base_v2025_r0.py | 30 + .../schemas/v2025_r0/user_mini_v2025_r0.py | 32 + .../v2025_r0/weblink_reference_v2025_r0.py | 30 + box_sdk_gen/schemas/watermark.py | 34 + box_sdk_gen/schemas/web_link.py | 248 ++ box_sdk_gen/schemas/web_link_base.py | 37 + box_sdk_gen/schemas/web_link_mini.py | 38 + box_sdk_gen/schemas/webhook.py | 96 + box_sdk_gen/schemas/webhook_invocation.py | 100 + box_sdk_gen/schemas/webhook_mini.py | 62 + box_sdk_gen/schemas/webhooks.py | 38 + box_sdk_gen/schemas/workflow.py | 295 +++ box_sdk_gen/schemas/workflow_full.py | 66 + box_sdk_gen/schemas/workflow_mini.py | 44 + box_sdk_gen/schemas/workflows.py | 38 + box_sdk_gen/schemas/zip_download.py | 103 + box_sdk_gen/schemas/zip_download_request.py | 51 + box_sdk_gen/schemas/zip_download_status.py | 50 + box_sdk_gen/serialization/__init__.py | 1 + box_sdk_gen/serialization/json.py | 66 + boxsdk/__init__.py | 6 - boxsdk/auth/__init__.py | 17 - boxsdk/auth/ccg_auth.py | 19 - boxsdk/auth/cooperatively_managed_oauth2.py | 32 - boxsdk/auth/developer_token_auth.py | 36 - boxsdk/auth/jwt_auth.py | 271 --- boxsdk/auth/oauth2.py | 455 ---- boxsdk/auth/redis_managed_jwt_auth.py | 15 - boxsdk/auth/redis_managed_oauth2.py | 57 - boxsdk/auth/remote_managed_oauth2.py | 34 - boxsdk/auth/server_auth.py | 212 -- boxsdk/client/__init__.py | 4 - boxsdk/client/client.py | 1904 --------------- boxsdk/client/developer_token_client.py | 16 - boxsdk/client/development_client.py | 9 - boxsdk/client/logging_client.py | 13 - boxsdk/config.py | 26 - boxsdk/exception.py | 115 - boxsdk/network/__init__.py | 0 boxsdk/network/default_network.py | 255 -- boxsdk/network/network_interface.py | 113 - boxsdk/object/__init__.py | 43 - boxsdk/object/api_json_object.py | 18 - boxsdk/object/base_api_json_object.py | 157 -- boxsdk/object/base_endpoint.py | 52 - boxsdk/object/base_item.py | 186 -- boxsdk/object/base_object.py | 159 -- boxsdk/object/cloneable.py | 48 - boxsdk/object/collaboration.py | 75 - boxsdk/object/collaboration_allowlist.py | 127 - .../object/collaboration_allowlist_entry.py | 18 - .../collaboration_allowlist_exempt_target.py | 7 - boxsdk/object/collection.py | 42 - boxsdk/object/comment.py | 48 - boxsdk/object/device_pinner.py | 7 - boxsdk/object/email_alias.py | 7 - boxsdk/object/enterprise.py | 39 - boxsdk/object/event.py | 7 - boxsdk/object/events.py | 304 --- boxsdk/object/file.py | 893 ------- boxsdk/object/file_request.py | 78 - boxsdk/object/file_version.py | 6 - boxsdk/object/file_version_retention.py | 6 - boxsdk/object/folder.py | 783 ------- boxsdk/object/folder_lock.py | 7 - boxsdk/object/group.py | 116 - boxsdk/object/group_membership.py | 7 - boxsdk/object/invite.py | 7 - boxsdk/object/item.py | 450 ---- boxsdk/object/legal_hold.py | 12 - boxsdk/object/legal_hold_policy.py | 120 - boxsdk/object/legal_hold_policy_assignment.py | 7 - boxsdk/object/metadata.py | 194 -- boxsdk/object/metadata_cascade_policy.py | 43 - boxsdk/object/metadata_template.py | 294 --- boxsdk/object/recent_item.py | 7 - boxsdk/object/retention_policy.py | 95 - boxsdk/object/retention_policy_assignment.py | 50 - boxsdk/object/search.py | 425 ---- boxsdk/object/sign_request.py | 48 - boxsdk/object/sign_template.py | 16 - boxsdk/object/storage_policy.py | 70 - boxsdk/object/storage_policy_assignment.py | 7 - boxsdk/object/task.py | 87 - boxsdk/object/task_assignment.py | 16 - boxsdk/object/terms_of_service.py | 116 - boxsdk/object/terms_of_service_user_status.py | 30 - boxsdk/object/trash.py | 155 -- boxsdk/object/upload_session.py | 205 -- boxsdk/object/user.py | 231 -- boxsdk/object/watermark.py | 7 - boxsdk/object/web_link.py | 106 - boxsdk/object/webhook.py | 74 - boxsdk/pagination/__init__.py | 0 boxsdk/pagination/box_object_collection.py | 166 -- boxsdk/pagination/dict_page.py | 8 - .../limit_offset_based_dict_collection.py | 7 - .../limit_offset_based_object_collection.py | 98 - .../marker_based_dict_collection.py | 7 - .../marker_based_object_collection.py | 102 - boxsdk/pagination/page.py | 59 - boxsdk/py.typed | 0 boxsdk/session/__init__.py | 0 boxsdk/session/box_request.py | 26 - boxsdk/session/box_response.py | 56 - boxsdk/session/session.py | 587 ----- boxsdk/util/__init__.py | 0 boxsdk/util/api_call_decorator.py | 68 - boxsdk/util/chunked_uploader.py | 205 -- boxsdk/util/datetime_formatter.py | 24 - boxsdk/util/default_arg_value.py | 1 - boxsdk/util/deprecation_decorator.py | 29 - boxsdk/util/enum.py | 134 -- boxsdk/util/json.py | 18 - boxsdk/util/log.py | 115 - boxsdk/util/lru_cache.py | 45 - boxsdk/util/multipart_stream.py | 18 - boxsdk/util/shared_link.py | 14 - boxsdk/util/text_enum.py | 9 - boxsdk/util/translator.py | 172 -- boxsdk/version.py | 1 - demo/Advanced Auth Techniques.ipynb | 193 -- demo/Development Client.ipynb | 152 -- demo/Shared Link.ipynb | 135 -- demo/__init__.py | 0 demo/auth.py | 64 - demo/cooperative_auth.py | 71 - demo/example.py | 296 --- demo/file.txt | 1 - demo/file_v2.txt | 1 - demo/music_player.py | 37 - docs/Makefile | 177 -- docs/README.md | 86 + docs/ai.md | 267 +++ docs/ai_studio.md | 192 ++ docs/app_item_associations.md | 84 + docs/archives.md | 97 + docs/authentication.md | 501 ++++ docs/authorization.md | 170 ++ docs/avatars.md | 105 + docs/chunked_uploads.md | 478 ++++ docs/classifications.md | 179 ++ docs/client.md | 166 ++ docs/collaboration_allowlist_entries.md | 133 ++ .../collaboration_allowlist_exempt_targets.md | 131 ++ docs/collections.md | 103 + docs/comments.md | 174 ++ docs/configuration.md | 66 + docs/device_pinners.md | 98 + docs/docgen.md | 148 ++ docs/docgen_template.md | 207 ++ docs/downloads.md | 128 + docs/email_aliases.md | 94 + docs/events.md | 115 + docs/file_classifications.md | 149 ++ docs/file_metadata.md | 231 ++ docs/file_requests.md | 156 ++ docs/file_version_legal_holds.md | 87 + docs/file_version_retentions.md | 86 + docs/file_versions.md | 205 ++ docs/file_watermarks.md | 99 + docs/files.md | 283 +++ docs/folder_classifications.md | 150 ++ docs/folder_locks.md | 106 + docs/folder_metadata.md | 236 ++ docs/folder_watermarks.md | 100 + docs/folders.md | 295 +++ docs/groups.md | 192 ++ docs/hub_collaborations.md | 179 ++ docs/hub_items.md | 76 + docs/hubs.md | 269 +++ docs/integration_mappings.md | 301 +++ docs/invites.md | 77 + docs/legal_hold_policies.md | 180 ++ docs/legal_hold_policy_assignments.md | 201 ++ docs/list_collaborations.md | 162 ++ docs/make.bat | 263 --- docs/memberships.md | 218 ++ docs/metadata_cascade_policies.md | 179 ++ docs/metadata_templates.md | 336 +++ docs/recent_items.md | 37 + docs/retention_policies.md | 204 ++ docs/retention_policy_assignments.md | 185 ++ docs/search.md | 141 ++ docs/session_termination.md | 70 + docs/shared_links_app_items.md | 36 + docs/shared_links_files.md | 200 ++ docs/shared_links_folders.md | 197 ++ docs/shared_links_web_links.md | 198 ++ docs/shield_information_barrier_reports.md | 101 + ...eld_information_barrier_segment_members.md | 144 ++ ...nformation_barrier_segment_restrictions.md | 150 ++ docs/shield_information_barrier_segments.md | 175 ++ docs/shield_information_barriers.md | 131 ++ docs/shield_lists.md | 177 ++ docs/sign_requests.md | 214 ++ docs/sign_templates.md | 62 + docs/skills.md | 173 ++ docs/source/boxsdk.auth.rst | 85 - docs/source/boxsdk.client.rst | 45 - docs/source/boxsdk.network.rst | 29 - docs/source/boxsdk.object.rst | 421 ---- docs/source/boxsdk.pagination.rst | 69 - docs/source/boxsdk.rst | 51 - docs/source/boxsdk.session.rst | 37 - docs/source/boxsdk.util.rst | 117 - docs/source/conf.py | 353 --- docs/source/index.rst | 106 - docs/source/modules.rst | 7 - docs/storage_policies.md | 64 + docs/storage_policy_assignments.md | 178 ++ docs/task_assignments.md | 167 ++ docs/tasks.md | 175 ++ docs/terms_of_service_user_statuses.md | 107 + docs/terms_of_services.md | 131 ++ docs/transfer.md | 73 + docs/trashed_files.md | 113 + docs/trashed_folders.md | 120 + docs/trashed_items.md | 51 + docs/trashed_web_links.md | 103 + docs/uploads.md | 173 ++ docs/usage/README.md | 11 - docs/usage/ai.md | 103 - docs/usage/authentication.md | 417 ---- docs/usage/classifications.md | 217 -- docs/usage/collaboration.md | 180 -- docs/usage/collaboration_allowlist.md | 148 -- docs/usage/collections.md | 81 - docs/usage/comments.md | 123 - docs/usage/configuration.md | 83 - docs/usage/device_pin.md | 63 - docs/usage/events.md | 151 -- docs/usage/file_request.md | 79 - docs/usage/files.md | 1060 --------- docs/usage/folders.md | 498 ---- docs/usage/group.md | 243 -- docs/usage/legal_hold.md | 228 -- docs/usage/metadata.md | 162 -- docs/usage/metadata_cascade_policies.md | 115 - docs/usage/retention_policy.md | 233 -- docs/usage/search.md | 104 - docs/usage/sign_requests.md | 120 - docs/usage/sign_templates.md | 39 - docs/usage/storage_policy.md | 139 -- docs/usage/task.md | 210 -- docs/usage/terms_of_service.md | 154 -- docs/usage/trash.md | 134 -- docs/usage/user.md | 284 --- docs/usage/watermarking.md | 86 - docs/usage/web_link.md | 198 -- docs/usage/webhook.md | 138 -- docs/usage/zip.md | 30 - docs/user_collaborations.md | 169 ++ docs/users.md | 312 +++ docs/web_links.md | 147 ++ docs/webhooks.md | 201 ++ docs/workflows.md | 91 + docs/working-with-nulls.md | 38 + docs/zip_downloads.md | 170 ++ migration-guide.md | 738 ++++++ pytest.ini | 6 +- requirements-dev.txt | 2 +- requirements-test.txt | 2 +- requirements.txt | 2 +- setup.cfg | 11 - setup.py | 140 +- test/ai.py | 441 ++++ test/ai_studio.py | 92 + test/app_item_associations.py | 55 + test/archives.py | 31 + test/auth.py | 365 +++ test/avatars.py | 47 + test/box_network_client.py | 1226 ++++++++++ test/chunked_uploads.py | 267 +++ test/classifications.py | 79 + test/client.py | 220 ++ test/collaboration_allowlist_entries.py | 54 + .../collaboration_allowlist_exempt_targets.py | 59 + test/collections.py | 45 + test/comments.py | 67 + test/commons.py | 201 ++ test/conftest.py | 287 --- test/device_pinners.py | 32 + test/docgen.py | 108 + test/docgen_template.py | 59 + test/downloads.py | 90 + test/email_aliases.py | 36 + test/events.py | 105 + test/file_classifications.py | 114 + test/file_metadata.py | 275 +++ test/file_requests.py | 44 + test/file_version_legal_holds.py | 30 + test/file_version_retentions.py | 112 + test/file_versions.py | 115 + test/file_watermarks.py | 49 + test/files.py | 158 ++ test/folder_classifications.py | 114 + test/folder_locks.py | 40 + test/folder_metadata.py | 286 +++ test/folder_watermarks.py | 39 + test/folders.py | 88 + test/functional/__init__.py | 0 test/functional/conftest.py | 122 - test/functional/mock_box/__init__.py | 0 test/functional/mock_box/behavior/__init__.py | 0 .../mock_box/behavior/event_behavior.py | 98 - .../mock_box/behavior/file_behavior.py | 135 -- .../mock_box/behavior/folder_behavior.py | 126 - .../mock_box/behavior/item_behavior.py | 17 - .../mock_box/behavior/oauth2_behavior.py | 151 -- .../mock_box/behavior/user_behavior.py | 29 - test/functional/mock_box/box.py | 336 --- test/functional/mock_box/db_model/__init__.py | 4 - .../mock_box/db_model/application_model.py | 22 - .../mock_box/db_model/collaboration_model.py | 16 - .../mock_box/db_model/event_model.py | 36 - .../mock_box/db_model/file_model.py | 35 - .../mock_box/db_model/folder_model.py | 35 - .../mock_box/db_model/group_model.py | 22 - .../mock_box/db_model/lock_model.py | 16 - .../mock_box/db_model/share_model.py | 22 - .../mock_box/db_model/token_model.py | 15 - .../mock_box/db_model/user_model.py | 29 - test/functional/mock_box/util/__init__.py | 0 test/functional/mock_box/util/chaos_utils.py | 82 - test/functional/mock_box/util/db_utils.py | 34 - test/functional/mock_box/util/http_utils.py | 113 - test/functional/mock_box/util/json_utils.py | 59 - test/functional/mock_box/views/__init__.py | 0 .../mock_box/views/html_response.tpl | 11 - test/functional/mock_box/views/oauth2.tpl | 7 - .../mock_box/views/xml_response.tpl | 4 - test/functional/test_delete.py | 35 - test/functional/test_events.py | 189 -- .../test_file_upload_update_download.py | 94 - test/functional/test_item_info.py | 110 - test/functional/test_object_clone.py | 41 - test/functional/test_rate_limits.py | 9 - test/functional/test_recovery.py | 95 - test/functional/test_token_refresh.py | 19 - test/groups.py | 40 + test/hub_collaborations.py | 88 + test/hub_items.py | 93 + test/hubs.py | 74 + test/integration/__init__.py | 0 test/integration/conftest.py | 53 - test/integration/mock_network.py | 30 - test/integration/test_as_user.py | 129 - test/integration/test_retry_and_refresh.py | 40 - test/integration/test_with_shared_link.py | 148 -- test/integration_mappings.py | 145 ++ test/integration_new/README.md | 40 - test/integration_new/__init__.py | 69 - test/integration_new/conftest.py | 62 - .../context_managers/box_metadata_template.py | 18 - .../context_managers/box_retention_policy.py | 37 - .../box_retention_policy_assigment.py | 23 - .../context_managers/box_sign_request.py | 16 - .../context_managers/box_test_file.py | 23 - .../context_managers/box_test_folder.py | 21 - .../context_managers/box_test_group.py | 20 - .../context_managers/box_test_user.py | 20 - .../context_managers/box_test_web_link.py | 22 - .../context_managers/local_large_file.py | 22 - test/integration_new/integration_tests.cfg | 3 - test/integration_new/object/ai_itest.py | 65 - .../object/collaboration_itest.py | 35 - test/integration_new/object/file_itest.py | 268 --- test/integration_new/object/folder_itest.py | 238 -- .../object/legal_hold_policy_itest.py | 36 - .../object/metadata_template_itest.py | 47 - .../retention_policy_assignement_itest.py | 52 - .../object/retention_policy_itest.py | 40 - .../object/sign_request_itest.py | 102 - .../object/sign_template_itest.py | 8 - test/integration_new/object/trash_itest.py | 63 - test/integration_new/object/user_itest.py | 3 - test/integration_new/resources/image.png | Bin 1359 -> 0 bytes test/integration_new/resources/small.pdf | Bin 5558 -> 0 bytes test/integration_new/resources/small_v2.pdf | Bin 6235 -> 0 bytes test/integration_new/util.py | 32 - test/invites.py | 33 + test/legal_hold_policies.py | 71 + test/legal_hold_policy_assignments.py | 94 + test/list_collaborations.py | 89 + test/memberships.py | 62 + test/metadata_cascade_policies.py | 108 + test/metadata_templates.py | 191 ++ test/recent_items.py | 17 + test/retention_policies.py | 54 + test/retention_policy_assignments.py | 122 + test/search.py | 271 +++ test/session_termination.py | 46 + test/shared_links_app_items.py | 20 + test/shared_links_files.py | 100 + test/shared_links_folders.py | 94 + test/shared_links_web_links.py | 108 + test/shield_information_barrier_reports.py | 71 + ...eld_information_barrier_segment_members.py | 96 + ...nformation_barrier_segment_restrictions.py | 129 + test/shield_information_barrier_segments.py | 84 + test/shield_information_barriers.py | 46 + test/shield_lists.py | 125 + test/sign_requests.py | 137 ++ test/sign_templates.py | 31 + test/skills.py | 105 + test/storage_policicy_assignments.py | 99 + test/storage_policies.py | 24 + test/task_assignments.py | 86 + test/tasks.py | 66 + test/terms_of_service_user_statuses.py | 66 + test/terms_of_services.py | 35 + test/transfer.py | 27 + test/trashed_files.py | 57 + test/trashed_folders.py | 39 + test/trashed_items.py | 18 + test/trashed_web_links.py | 49 + test/unit/__init__.py | 0 test/unit/auth/__init__.py | 0 test/unit/auth/test_ccg_auth.py | 281 --- .../auth/test_cooperatively_managed_oauth2.py | 15 - test/unit/auth/test_developer_token_auth.py | 24 - test/unit/auth/test_jwt_auth.py | 638 ----- test/unit/auth/test_oauth2.py | 482 ---- test/unit/auth/test_redis_managed_oauth2.py | 61 - test/unit/auth/test_remote_managed_oauth2.py | 16 - test/unit/client/__init__.py | 0 test/unit/client/test_client.py | 2076 ----------------- test/unit/conftest.py | 139 -- test/unit/network/__init__.py | 0 test/unit/network/conftest.py | 44 - test/unit/network/test_network.py | 307 --- test/unit/object/__init__.py | 0 test/unit/object/conftest.py | 428 ---- test/unit/object/test_api_json_object.py | 18 - test/unit/object/test_base_api_json_object.py | 70 - test/unit/object/test_base_item.py | 139 -- test/unit/object/test_base_object.py | 121 - test/unit/object/test_chunked_upload.py | 427 ---- test/unit/object/test_collaboration.py | 124 - .../object/test_collaboration_allowlist.py | 97 - .../test_collaboration_allowlist_entry.py | 25 - ...t_collaboration_allowlist_exempt_target.py | 25 - test/unit/object/test_collection.py | 48 - test/unit/object/test_comment.py | 64 - test/unit/object/test_device_pin.py | 43 - test/unit/object/test_enterprise.py | 40 - test/unit/object/test_event.py | 16 - test/unit/object/test_events.py | 351 --- test/unit/object/test_file.py | 1081 --------- test/unit/object/test_file_request.py | 90 - .../object/test_file_version_retention.py | 13 - test/unit/object/test_folder.py | 767 ------ test/unit/object/test_group.py | 259 -- test/unit/object/test_item.py | 460 ---- test/unit/object/test_legal_hold.py | 21 - .../unit/object/test_legal_hold_assignment.py | 48 - test/unit/object/test_legal_hold_policy.py | 160 -- test/unit/object/test_metadata.py | 142 -- .../object/test_metadata_cascade_policy.py | 70 - test/unit/object/test_metadata_template.py | 165 -- test/unit/object/test_retention_policy.py | 105 - .../test_retention_policy_assignment.py | 85 - test/unit/object/test_search.py | 438 ---- test/unit/object/test_sign_request.py | 146 -- test/unit/object/test_sign_templates.py | 118 - test/unit/object/test_storage_policy.py | 172 -- .../object/test_storage_policy_assignment.py | 57 - test/unit/object/test_task.py | 152 -- test/unit/object/test_task_assignment.py | 72 - test/unit/object/test_terms_of_service.py | 176 -- .../test_terms_of_service_user_status.py | 52 - test/unit/object/test_trash.py | 171 -- test/unit/object/test_upload_session.py | 348 --- test/unit/object/test_user.py | 254 -- test/unit/object/test_web_link.py | 140 -- test/unit/object/test_webhook.py | 95 - test/unit/pagination/__init__.py | 0 .../box_object_collection_test_base.py | 112 - ...st_limit_offset_based_object_collection.py | 132 -- .../test_marker_based_object_collection.py | 109 - test/unit/pagination/test_page.py | 102 - test/unit/session/__init__.py | 0 test/unit/session/test_session.py | 498 ---- test/unit/test_exception.py | 66 - test/unit/util/__init__.py | 0 test/unit/util/test_api_call_decorator.py | 113 - test/unit/util/test_datetime_formatter.py | 81 - test/unit/util/test_enum.py | 170 -- test/unit/util/test_log.py | 124 - test/unit/util/test_lru_cache.py | 42 - test/unit/util/test_multipart_stream.py | 26 - test/unit/util/test_shared_link.py | 21 - test/unit/util/test_text_enum.py | 13 - test/unit/util/test_translator.py | 224 -- test/uploads.py | 99 + test/user_collaborations.py | 143 ++ test/users.py | 42 + test/util/__init__.py | 0 test/util/streamable_mock_open.py | 26 - test/webhooks.py | 310 +++ test/weblinks.py | 50 + test/workflows.py | 83 + test/zip_downloads.py | 91 + tox.ini | 36 +- 941 files changed, 76120 insertions(+), 39139 deletions(-) create mode 100644 .codegen.json delete mode 100644 .gitattributes create mode 100644 .github/workflows/autoupdate-pr.yml delete mode 100644 .github/workflows/create_release.yml delete mode 100644 .github/workflows/integration-tests.yml rename .github/workflows/{releases.yml => notify-changelog.yml} (91%) delete mode 100644 AUTHORS.md delete mode 100644 MANIFEST.in create mode 100644 box_sdk_gen/__init__.py create mode 100644 box_sdk_gen/box/__init__.py create mode 100644 box_sdk_gen/box/ccg_auth.py create mode 100644 box_sdk_gen/box/developer_token_auth.py create mode 100644 box_sdk_gen/box/errors.py create mode 100644 box_sdk_gen/box/jwt_auth.py create mode 100644 box_sdk_gen/box/oauth.py create mode 100644 box_sdk_gen/box/token_storage.py create mode 100644 box_sdk_gen/client.py create mode 100644 box_sdk_gen/internal/__init__.py create mode 100644 box_sdk_gen/internal/base_object.py create mode 100644 box_sdk_gen/internal/errors.py create mode 100644 box_sdk_gen/internal/logging.py create mode 100644 box_sdk_gen/internal/null_value.py create mode 100644 box_sdk_gen/internal/utils.py create mode 100644 box_sdk_gen/managers/__init__.py create mode 100644 box_sdk_gen/managers/ai.py create mode 100644 box_sdk_gen/managers/ai_studio.py create mode 100644 box_sdk_gen/managers/app_item_associations.py create mode 100644 box_sdk_gen/managers/archives.py create mode 100644 box_sdk_gen/managers/authorization.py create mode 100644 box_sdk_gen/managers/avatars.py create mode 100644 box_sdk_gen/managers/chunked_uploads.py create mode 100644 box_sdk_gen/managers/classifications.py create mode 100644 box_sdk_gen/managers/collaboration_allowlist_entries.py create mode 100644 box_sdk_gen/managers/collaboration_allowlist_exempt_targets.py create mode 100644 box_sdk_gen/managers/collections.py create mode 100644 box_sdk_gen/managers/comments.py create mode 100644 box_sdk_gen/managers/device_pinners.py create mode 100644 box_sdk_gen/managers/docgen.py create mode 100644 box_sdk_gen/managers/docgen_template.py create mode 100644 box_sdk_gen/managers/downloads.py create mode 100644 box_sdk_gen/managers/email_aliases.py create mode 100644 box_sdk_gen/managers/events.py create mode 100644 box_sdk_gen/managers/file_classifications.py create mode 100644 box_sdk_gen/managers/file_metadata.py create mode 100644 box_sdk_gen/managers/file_requests.py create mode 100644 box_sdk_gen/managers/file_version_legal_holds.py create mode 100644 box_sdk_gen/managers/file_version_retentions.py create mode 100644 box_sdk_gen/managers/file_versions.py create mode 100644 box_sdk_gen/managers/file_watermarks.py create mode 100644 box_sdk_gen/managers/files.py create mode 100644 box_sdk_gen/managers/folder_classifications.py create mode 100644 box_sdk_gen/managers/folder_locks.py create mode 100644 box_sdk_gen/managers/folder_metadata.py create mode 100644 box_sdk_gen/managers/folder_watermarks.py create mode 100644 box_sdk_gen/managers/folders.py create mode 100644 box_sdk_gen/managers/groups.py create mode 100644 box_sdk_gen/managers/hub_collaborations.py create mode 100644 box_sdk_gen/managers/hub_items.py create mode 100644 box_sdk_gen/managers/hubs.py create mode 100644 box_sdk_gen/managers/integration_mappings.py create mode 100644 box_sdk_gen/managers/invites.py create mode 100644 box_sdk_gen/managers/legal_hold_policies.py create mode 100644 box_sdk_gen/managers/legal_hold_policy_assignments.py create mode 100644 box_sdk_gen/managers/list_collaborations.py create mode 100644 box_sdk_gen/managers/memberships.py create mode 100644 box_sdk_gen/managers/metadata_cascade_policies.py create mode 100644 box_sdk_gen/managers/metadata_templates.py create mode 100644 box_sdk_gen/managers/recent_items.py create mode 100644 box_sdk_gen/managers/retention_policies.py create mode 100644 box_sdk_gen/managers/retention_policy_assignments.py create mode 100644 box_sdk_gen/managers/search.py create mode 100644 box_sdk_gen/managers/session_termination.py create mode 100644 box_sdk_gen/managers/shared_links_app_items.py create mode 100644 box_sdk_gen/managers/shared_links_files.py create mode 100644 box_sdk_gen/managers/shared_links_folders.py create mode 100644 box_sdk_gen/managers/shared_links_web_links.py create mode 100644 box_sdk_gen/managers/shield_information_barrier_reports.py create mode 100644 box_sdk_gen/managers/shield_information_barrier_segment_members.py create mode 100644 box_sdk_gen/managers/shield_information_barrier_segment_restrictions.py create mode 100644 box_sdk_gen/managers/shield_information_barrier_segments.py create mode 100644 box_sdk_gen/managers/shield_information_barriers.py create mode 100644 box_sdk_gen/managers/shield_lists.py create mode 100644 box_sdk_gen/managers/sign_requests.py create mode 100644 box_sdk_gen/managers/sign_templates.py create mode 100644 box_sdk_gen/managers/skills.py create mode 100644 box_sdk_gen/managers/storage_policies.py create mode 100644 box_sdk_gen/managers/storage_policy_assignments.py create mode 100644 box_sdk_gen/managers/task_assignments.py create mode 100644 box_sdk_gen/managers/tasks.py create mode 100644 box_sdk_gen/managers/terms_of_service_user_statuses.py create mode 100644 box_sdk_gen/managers/terms_of_services.py create mode 100644 box_sdk_gen/managers/transfer.py create mode 100644 box_sdk_gen/managers/trashed_files.py create mode 100644 box_sdk_gen/managers/trashed_folders.py create mode 100644 box_sdk_gen/managers/trashed_items.py create mode 100644 box_sdk_gen/managers/trashed_web_links.py create mode 100644 box_sdk_gen/managers/uploads.py create mode 100644 box_sdk_gen/managers/user_collaborations.py create mode 100644 box_sdk_gen/managers/users.py create mode 100644 box_sdk_gen/managers/web_links.py create mode 100644 box_sdk_gen/managers/webhooks.py create mode 100644 box_sdk_gen/managers/workflows.py create mode 100644 box_sdk_gen/managers/zip_downloads.py create mode 100644 box_sdk_gen/networking/__init__.py create mode 100644 box_sdk_gen/networking/auth.py create mode 100644 box_sdk_gen/networking/base_urls.py create mode 100644 box_sdk_gen/networking/box_network_client.py create mode 100644 box_sdk_gen/networking/fetch_options.py create mode 100644 box_sdk_gen/networking/fetch_response.py create mode 100644 box_sdk_gen/networking/network.py create mode 100644 box_sdk_gen/networking/network_client.py create mode 100644 box_sdk_gen/networking/proxy_config.py create mode 100644 box_sdk_gen/networking/retries.py create mode 100644 box_sdk_gen/networking/version.py create mode 100644 box_sdk_gen/parameters/__init__.py create mode 100644 box_sdk_gen/parameters/v2025_r0/__init__.py create mode 100644 box_sdk_gen/parameters/v2025_r0/box_version_header_v2025_r0.py create mode 100644 box_sdk_gen/schemas/__init__.py create mode 100644 box_sdk_gen/schemas/access_token.py create mode 100644 box_sdk_gen/schemas/ai_agent_allowed_entity.py create mode 100644 box_sdk_gen/schemas/ai_agent_ask.py create mode 100644 box_sdk_gen/schemas/ai_agent_basic_gen_tool.py create mode 100644 box_sdk_gen/schemas/ai_agent_basic_text_tool.py create mode 100644 box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py create mode 100644 box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py create mode 100644 box_sdk_gen/schemas/ai_agent_extract.py create mode 100644 box_sdk_gen/schemas/ai_agent_extract_structured.py create mode 100644 box_sdk_gen/schemas/ai_agent_info.py create mode 100644 box_sdk_gen/schemas/ai_agent_long_text_tool.py create mode 100644 box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py create mode 100644 box_sdk_gen/schemas/ai_agent_reference.py create mode 100644 box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py create mode 100644 box_sdk_gen/schemas/ai_agent_text_gen.py create mode 100644 box_sdk_gen/schemas/ai_ask.py create mode 100644 box_sdk_gen/schemas/ai_citation.py create mode 100644 box_sdk_gen/schemas/ai_dialogue_history.py create mode 100644 box_sdk_gen/schemas/ai_extract.py create mode 100644 box_sdk_gen/schemas/ai_extract_response.py create mode 100644 box_sdk_gen/schemas/ai_extract_structured.py create mode 100644 box_sdk_gen/schemas/ai_extract_structured_response.py create mode 100644 box_sdk_gen/schemas/ai_item_ask.py create mode 100644 box_sdk_gen/schemas/ai_item_base.py create mode 100644 box_sdk_gen/schemas/ai_llm_endpoint_params.py create mode 100644 box_sdk_gen/schemas/ai_llm_endpoint_params_aws.py create mode 100644 box_sdk_gen/schemas/ai_llm_endpoint_params_google.py create mode 100644 box_sdk_gen/schemas/ai_llm_endpoint_params_ibm.py create mode 100644 box_sdk_gen/schemas/ai_llm_endpoint_params_open_ai.py create mode 100644 box_sdk_gen/schemas/ai_multiple_agent_response.py create mode 100644 box_sdk_gen/schemas/ai_response.py create mode 100644 box_sdk_gen/schemas/ai_response_full.py create mode 100644 box_sdk_gen/schemas/ai_single_agent_response.py create mode 100644 box_sdk_gen/schemas/ai_single_agent_response_full.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_ask.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_ask_response.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_extract.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_extract_response.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_text_gen.py create mode 100644 box_sdk_gen/schemas/ai_studio_agent_text_gen_response.py create mode 100644 box_sdk_gen/schemas/ai_text_gen.py create mode 100644 box_sdk_gen/schemas/app_item.py create mode 100644 box_sdk_gen/schemas/app_item_association.py create mode 100644 box_sdk_gen/schemas/app_item_associations.py create mode 100644 box_sdk_gen/schemas/app_item_event_source.py create mode 100644 box_sdk_gen/schemas/classification.py create mode 100644 box_sdk_gen/schemas/classification_template.py create mode 100644 box_sdk_gen/schemas/client_error.py create mode 100644 box_sdk_gen/schemas/collaboration.py create mode 100644 box_sdk_gen/schemas/collaboration_allowlist_entries.py create mode 100644 box_sdk_gen/schemas/collaboration_allowlist_entry.py create mode 100644 box_sdk_gen/schemas/collaboration_allowlist_exempt_target.py create mode 100644 box_sdk_gen/schemas/collaboration_allowlist_exempt_targets.py create mode 100644 box_sdk_gen/schemas/collaborations.py create mode 100644 box_sdk_gen/schemas/collaborations_offset_paginated.py create mode 100644 box_sdk_gen/schemas/collaborator_variable.py create mode 100644 box_sdk_gen/schemas/collection.py create mode 100644 box_sdk_gen/schemas/collections.py create mode 100644 box_sdk_gen/schemas/comment.py create mode 100644 box_sdk_gen/schemas/comment_base.py create mode 100644 box_sdk_gen/schemas/comment_full.py create mode 100644 box_sdk_gen/schemas/comments.py create mode 100644 box_sdk_gen/schemas/completion_rule_variable.py create mode 100644 box_sdk_gen/schemas/conflict_error.py create mode 100644 box_sdk_gen/schemas/create_ai_agent.py create mode 100644 box_sdk_gen/schemas/device_pinner.py create mode 100644 box_sdk_gen/schemas/device_pinners.py create mode 100644 box_sdk_gen/schemas/email_alias.py create mode 100644 box_sdk_gen/schemas/email_aliases.py create mode 100644 box_sdk_gen/schemas/enterprise_base.py create mode 100644 box_sdk_gen/schemas/event.py create mode 100644 box_sdk_gen/schemas/event_source.py create mode 100644 box_sdk_gen/schemas/events.py create mode 100644 box_sdk_gen/schemas/file.py create mode 100644 box_sdk_gen/schemas/file_base.py create mode 100644 box_sdk_gen/schemas/file_conflict.py create mode 100644 box_sdk_gen/schemas/file_full.py create mode 100644 box_sdk_gen/schemas/file_mini.py create mode 100644 box_sdk_gen/schemas/file_or_folder_scope.py create mode 100644 box_sdk_gen/schemas/file_request.py create mode 100644 box_sdk_gen/schemas/file_request_copy_request.py create mode 100644 box_sdk_gen/schemas/file_request_update_request.py create mode 100644 box_sdk_gen/schemas/file_version.py create mode 100644 box_sdk_gen/schemas/file_version_base.py create mode 100644 box_sdk_gen/schemas/file_version_full.py create mode 100644 box_sdk_gen/schemas/file_version_legal_hold.py create mode 100644 box_sdk_gen/schemas/file_version_legal_holds.py create mode 100644 box_sdk_gen/schemas/file_version_mini.py create mode 100644 box_sdk_gen/schemas/file_version_retention.py create mode 100644 box_sdk_gen/schemas/file_version_retentions.py create mode 100644 box_sdk_gen/schemas/file_versions.py create mode 100644 box_sdk_gen/schemas/files.py create mode 100644 box_sdk_gen/schemas/files_on_hold.py create mode 100644 box_sdk_gen/schemas/files_under_retention.py create mode 100644 box_sdk_gen/schemas/folder.py create mode 100644 box_sdk_gen/schemas/folder_base.py create mode 100644 box_sdk_gen/schemas/folder_full.py create mode 100644 box_sdk_gen/schemas/folder_lock.py create mode 100644 box_sdk_gen/schemas/folder_locks.py create mode 100644 box_sdk_gen/schemas/folder_mini.py create mode 100644 box_sdk_gen/schemas/folder_reference.py create mode 100644 box_sdk_gen/schemas/generic_source.py create mode 100644 box_sdk_gen/schemas/group.py create mode 100644 box_sdk_gen/schemas/group_base.py create mode 100644 box_sdk_gen/schemas/group_full.py create mode 100644 box_sdk_gen/schemas/group_membership.py create mode 100644 box_sdk_gen/schemas/group_memberships.py create mode 100644 box_sdk_gen/schemas/group_mini.py create mode 100644 box_sdk_gen/schemas/groups.py create mode 100644 box_sdk_gen/schemas/integration_mapping.py create mode 100644 box_sdk_gen/schemas/integration_mapping_base.py create mode 100644 box_sdk_gen/schemas/integration_mapping_box_item_slack.py create mode 100644 box_sdk_gen/schemas/integration_mapping_partner_item_slack.py create mode 100644 box_sdk_gen/schemas/integration_mapping_partner_item_teams.py create mode 100644 box_sdk_gen/schemas/integration_mapping_partner_item_teams_create_request.py create mode 100644 box_sdk_gen/schemas/integration_mapping_slack_create_request.py create mode 100644 box_sdk_gen/schemas/integration_mapping_slack_options.py create mode 100644 box_sdk_gen/schemas/integration_mapping_teams.py create mode 100644 box_sdk_gen/schemas/integration_mapping_teams_create_request.py create mode 100644 box_sdk_gen/schemas/integration_mappings.py create mode 100644 box_sdk_gen/schemas/integration_mappings_teams.py create mode 100644 box_sdk_gen/schemas/invite.py create mode 100644 box_sdk_gen/schemas/items.py create mode 100644 box_sdk_gen/schemas/items_offset_paginated.py create mode 100644 box_sdk_gen/schemas/keyword_skill_card.py create mode 100644 box_sdk_gen/schemas/legal_hold_policies.py create mode 100644 box_sdk_gen/schemas/legal_hold_policy.py create mode 100644 box_sdk_gen/schemas/legal_hold_policy_assignment.py create mode 100644 box_sdk_gen/schemas/legal_hold_policy_assignment_base.py create mode 100644 box_sdk_gen/schemas/legal_hold_policy_assignments.py create mode 100644 box_sdk_gen/schemas/legal_hold_policy_mini.py create mode 100644 box_sdk_gen/schemas/metadata.py create mode 100644 box_sdk_gen/schemas/metadata_base.py create mode 100644 box_sdk_gen/schemas/metadata_cascade_policies.py create mode 100644 box_sdk_gen/schemas/metadata_cascade_policy.py create mode 100644 box_sdk_gen/schemas/metadata_error.py create mode 100644 box_sdk_gen/schemas/metadata_field_filter_date_range.py create mode 100644 box_sdk_gen/schemas/metadata_field_filter_float_range.py create mode 100644 box_sdk_gen/schemas/metadata_filter.py create mode 100644 box_sdk_gen/schemas/metadata_full.py create mode 100644 box_sdk_gen/schemas/metadata_instance_value.py create mode 100644 box_sdk_gen/schemas/metadata_query.py create mode 100644 box_sdk_gen/schemas/metadata_query_index.py create mode 100644 box_sdk_gen/schemas/metadata_query_results.py create mode 100644 box_sdk_gen/schemas/metadata_template.py create mode 100644 box_sdk_gen/schemas/metadata_templates.py create mode 100644 box_sdk_gen/schemas/metadatas.py create mode 100644 box_sdk_gen/schemas/o_auth_2_error.py create mode 100644 box_sdk_gen/schemas/outcome.py create mode 100644 box_sdk_gen/schemas/post_o_auth_2_revoke.py create mode 100644 box_sdk_gen/schemas/post_o_auth_2_token.py create mode 100644 box_sdk_gen/schemas/post_o_auth_2_token_refresh_access_token.py create mode 100644 box_sdk_gen/schemas/realtime_server.py create mode 100644 box_sdk_gen/schemas/realtime_servers.py create mode 100644 box_sdk_gen/schemas/recent_item.py create mode 100644 box_sdk_gen/schemas/recent_items.py create mode 100644 box_sdk_gen/schemas/retention_policies.py create mode 100644 box_sdk_gen/schemas/retention_policy.py create mode 100644 box_sdk_gen/schemas/retention_policy_assignment.py create mode 100644 box_sdk_gen/schemas/retention_policy_assignment_base.py create mode 100644 box_sdk_gen/schemas/retention_policy_assignments.py create mode 100644 box_sdk_gen/schemas/retention_policy_base.py create mode 100644 box_sdk_gen/schemas/retention_policy_mini.py create mode 100644 box_sdk_gen/schemas/role_variable.py create mode 100644 box_sdk_gen/schemas/search_result_with_shared_link.py create mode 100644 box_sdk_gen/schemas/search_results.py create mode 100644 box_sdk_gen/schemas/search_results_with_shared_links.py create mode 100644 box_sdk_gen/schemas/session_termination_message.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_base.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_reference.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_report.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_report_base.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_report_details.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_reports.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_segment.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_segment_member.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_segment_member_base.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_segment_member_mini.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_segment_members.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_segment_restriction.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_segment_restriction_base.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_segment_restriction_mini.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_segment_restrictions.py create mode 100644 box_sdk_gen/schemas/shield_information_barrier_segments.py create mode 100644 box_sdk_gen/schemas/shield_information_barriers.py create mode 100644 box_sdk_gen/schemas/sign_request.py create mode 100644 box_sdk_gen/schemas/sign_request_base.py create mode 100644 box_sdk_gen/schemas/sign_request_create_request.py create mode 100644 box_sdk_gen/schemas/sign_request_create_signer.py create mode 100644 box_sdk_gen/schemas/sign_request_prefill_tag.py create mode 100644 box_sdk_gen/schemas/sign_request_signer.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input.py create mode 100644 box_sdk_gen/schemas/sign_requests.py create mode 100644 box_sdk_gen/schemas/sign_template.py create mode 100644 box_sdk_gen/schemas/sign_templates.py create mode 100644 box_sdk_gen/schemas/skill_cards_metadata.py create mode 100644 box_sdk_gen/schemas/skill_invocation.py create mode 100644 box_sdk_gen/schemas/status_skill_card.py create mode 100644 box_sdk_gen/schemas/storage_policies.py create mode 100644 box_sdk_gen/schemas/storage_policy.py create mode 100644 box_sdk_gen/schemas/storage_policy_assignment.py create mode 100644 box_sdk_gen/schemas/storage_policy_assignments.py create mode 100644 box_sdk_gen/schemas/storage_policy_mini.py create mode 100644 box_sdk_gen/schemas/task.py create mode 100644 box_sdk_gen/schemas/task_assignment.py create mode 100644 box_sdk_gen/schemas/task_assignments.py create mode 100644 box_sdk_gen/schemas/tasks.py create mode 100644 box_sdk_gen/schemas/template_signer.py create mode 100644 box_sdk_gen/schemas/template_signer_input.py create mode 100644 box_sdk_gen/schemas/terms_of_service.py create mode 100644 box_sdk_gen/schemas/terms_of_service_base.py create mode 100644 box_sdk_gen/schemas/terms_of_service_user_status.py create mode 100644 box_sdk_gen/schemas/terms_of_service_user_statuses.py create mode 100644 box_sdk_gen/schemas/terms_of_services.py create mode 100644 box_sdk_gen/schemas/timeline_skill_card.py create mode 100644 box_sdk_gen/schemas/tracking_code.py create mode 100644 box_sdk_gen/schemas/transcript_skill_card.py create mode 100644 box_sdk_gen/schemas/trash_file.py create mode 100644 box_sdk_gen/schemas/trash_file_restored.py create mode 100644 box_sdk_gen/schemas/trash_folder.py create mode 100644 box_sdk_gen/schemas/trash_folder_restored.py create mode 100644 box_sdk_gen/schemas/trash_web_link.py create mode 100644 box_sdk_gen/schemas/trash_web_link_restored.py create mode 100644 box_sdk_gen/schemas/upload_part.py create mode 100644 box_sdk_gen/schemas/upload_part_mini.py create mode 100644 box_sdk_gen/schemas/upload_parts.py create mode 100644 box_sdk_gen/schemas/upload_session.py create mode 100644 box_sdk_gen/schemas/upload_url.py create mode 100644 box_sdk_gen/schemas/uploaded_part.py create mode 100644 box_sdk_gen/schemas/user.py create mode 100644 box_sdk_gen/schemas/user_avatar.py create mode 100644 box_sdk_gen/schemas/user_base.py create mode 100644 box_sdk_gen/schemas/user_collaborations.py create mode 100644 box_sdk_gen/schemas/user_full.py create mode 100644 box_sdk_gen/schemas/user_integration_mappings.py create mode 100644 box_sdk_gen/schemas/user_mini.py create mode 100644 box_sdk_gen/schemas/users.py create mode 100644 box_sdk_gen/schemas/v2025_r0/__init__.py create mode 100644 box_sdk_gen/schemas/v2025_r0/archive_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/archives_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/client_error_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_batch_base_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_batch_create_request_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_document_generation_data_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_job_base_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_jobs_full_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_jobs_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_tag_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_tags_processing_message_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_tags_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_template_base_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_template_create_request_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_template_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/doc_gen_templates_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_reference_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/file_reference_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/file_version_base_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/folder_reference_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/group_base_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/group_mini_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_access_grantee_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_action_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_base_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_collaboration_create_request_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_collaboration_update_request_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_collaboration_user_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_collaboration_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_collaborations_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_create_request_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_item_operation_result_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_item_reference_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_item_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_items_manage_request_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_items_manage_response_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_items_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hubs_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_list_content_country_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_list_content_domain_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_list_content_email_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_list_content_integration_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_list_content_ip_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_list_content_request_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_list_content_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_list_mini_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_list_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_lists_create_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_lists_update_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_lists_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/terms_of_service_base_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/user_base_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/user_mini_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py create mode 100644 box_sdk_gen/schemas/watermark.py create mode 100644 box_sdk_gen/schemas/web_link.py create mode 100644 box_sdk_gen/schemas/web_link_base.py create mode 100644 box_sdk_gen/schemas/web_link_mini.py create mode 100644 box_sdk_gen/schemas/webhook.py create mode 100644 box_sdk_gen/schemas/webhook_invocation.py create mode 100644 box_sdk_gen/schemas/webhook_mini.py create mode 100644 box_sdk_gen/schemas/webhooks.py create mode 100644 box_sdk_gen/schemas/workflow.py create mode 100644 box_sdk_gen/schemas/workflow_full.py create mode 100644 box_sdk_gen/schemas/workflow_mini.py create mode 100644 box_sdk_gen/schemas/workflows.py create mode 100644 box_sdk_gen/schemas/zip_download.py create mode 100644 box_sdk_gen/schemas/zip_download_request.py create mode 100644 box_sdk_gen/schemas/zip_download_status.py create mode 100644 box_sdk_gen/serialization/__init__.py create mode 100644 box_sdk_gen/serialization/json.py delete mode 100644 boxsdk/__init__.py delete mode 100644 boxsdk/auth/__init__.py delete mode 100644 boxsdk/auth/ccg_auth.py delete mode 100644 boxsdk/auth/cooperatively_managed_oauth2.py delete mode 100644 boxsdk/auth/developer_token_auth.py delete mode 100644 boxsdk/auth/jwt_auth.py delete mode 100644 boxsdk/auth/oauth2.py delete mode 100644 boxsdk/auth/redis_managed_jwt_auth.py delete mode 100644 boxsdk/auth/redis_managed_oauth2.py delete mode 100644 boxsdk/auth/remote_managed_oauth2.py delete mode 100644 boxsdk/auth/server_auth.py delete mode 100644 boxsdk/client/__init__.py delete mode 100644 boxsdk/client/client.py delete mode 100644 boxsdk/client/developer_token_client.py delete mode 100644 boxsdk/client/development_client.py delete mode 100644 boxsdk/client/logging_client.py delete mode 100644 boxsdk/config.py delete mode 100644 boxsdk/exception.py delete mode 100644 boxsdk/network/__init__.py delete mode 100644 boxsdk/network/default_network.py delete mode 100644 boxsdk/network/network_interface.py delete mode 100644 boxsdk/object/__init__.py delete mode 100644 boxsdk/object/api_json_object.py delete mode 100644 boxsdk/object/base_api_json_object.py delete mode 100644 boxsdk/object/base_endpoint.py delete mode 100644 boxsdk/object/base_item.py delete mode 100644 boxsdk/object/base_object.py delete mode 100644 boxsdk/object/cloneable.py delete mode 100644 boxsdk/object/collaboration.py delete mode 100644 boxsdk/object/collaboration_allowlist.py delete mode 100644 boxsdk/object/collaboration_allowlist_entry.py delete mode 100644 boxsdk/object/collaboration_allowlist_exempt_target.py delete mode 100644 boxsdk/object/collection.py delete mode 100644 boxsdk/object/comment.py delete mode 100644 boxsdk/object/device_pinner.py delete mode 100644 boxsdk/object/email_alias.py delete mode 100644 boxsdk/object/enterprise.py delete mode 100644 boxsdk/object/event.py delete mode 100644 boxsdk/object/events.py delete mode 100644 boxsdk/object/file.py delete mode 100644 boxsdk/object/file_request.py delete mode 100644 boxsdk/object/file_version.py delete mode 100644 boxsdk/object/file_version_retention.py delete mode 100644 boxsdk/object/folder.py delete mode 100644 boxsdk/object/folder_lock.py delete mode 100644 boxsdk/object/group.py delete mode 100644 boxsdk/object/group_membership.py delete mode 100644 boxsdk/object/invite.py delete mode 100644 boxsdk/object/item.py delete mode 100644 boxsdk/object/legal_hold.py delete mode 100644 boxsdk/object/legal_hold_policy.py delete mode 100644 boxsdk/object/legal_hold_policy_assignment.py delete mode 100644 boxsdk/object/metadata.py delete mode 100644 boxsdk/object/metadata_cascade_policy.py delete mode 100644 boxsdk/object/metadata_template.py delete mode 100644 boxsdk/object/recent_item.py delete mode 100644 boxsdk/object/retention_policy.py delete mode 100644 boxsdk/object/retention_policy_assignment.py delete mode 100644 boxsdk/object/search.py delete mode 100644 boxsdk/object/sign_request.py delete mode 100644 boxsdk/object/sign_template.py delete mode 100644 boxsdk/object/storage_policy.py delete mode 100644 boxsdk/object/storage_policy_assignment.py delete mode 100644 boxsdk/object/task.py delete mode 100644 boxsdk/object/task_assignment.py delete mode 100644 boxsdk/object/terms_of_service.py delete mode 100644 boxsdk/object/terms_of_service_user_status.py delete mode 100644 boxsdk/object/trash.py delete mode 100644 boxsdk/object/upload_session.py delete mode 100644 boxsdk/object/user.py delete mode 100644 boxsdk/object/watermark.py delete mode 100644 boxsdk/object/web_link.py delete mode 100644 boxsdk/object/webhook.py delete mode 100644 boxsdk/pagination/__init__.py delete mode 100644 boxsdk/pagination/box_object_collection.py delete mode 100644 boxsdk/pagination/dict_page.py delete mode 100644 boxsdk/pagination/limit_offset_based_dict_collection.py delete mode 100644 boxsdk/pagination/limit_offset_based_object_collection.py delete mode 100644 boxsdk/pagination/marker_based_dict_collection.py delete mode 100644 boxsdk/pagination/marker_based_object_collection.py delete mode 100644 boxsdk/pagination/page.py delete mode 100644 boxsdk/py.typed delete mode 100644 boxsdk/session/__init__.py delete mode 100644 boxsdk/session/box_request.py delete mode 100644 boxsdk/session/box_response.py delete mode 100644 boxsdk/session/session.py delete mode 100644 boxsdk/util/__init__.py delete mode 100644 boxsdk/util/api_call_decorator.py delete mode 100644 boxsdk/util/chunked_uploader.py delete mode 100644 boxsdk/util/datetime_formatter.py delete mode 100644 boxsdk/util/default_arg_value.py delete mode 100644 boxsdk/util/deprecation_decorator.py delete mode 100644 boxsdk/util/enum.py delete mode 100644 boxsdk/util/json.py delete mode 100644 boxsdk/util/log.py delete mode 100644 boxsdk/util/lru_cache.py delete mode 100644 boxsdk/util/multipart_stream.py delete mode 100644 boxsdk/util/shared_link.py delete mode 100644 boxsdk/util/text_enum.py delete mode 100644 boxsdk/util/translator.py delete mode 100644 boxsdk/version.py delete mode 100644 demo/Advanced Auth Techniques.ipynb delete mode 100644 demo/Development Client.ipynb delete mode 100644 demo/Shared Link.ipynb delete mode 100644 demo/__init__.py delete mode 100644 demo/auth.py delete mode 100644 demo/cooperative_auth.py delete mode 100644 demo/example.py delete mode 100644 demo/file.txt delete mode 100644 demo/file_v2.txt delete mode 100644 demo/music_player.py delete mode 100644 docs/Makefile create mode 100644 docs/README.md create mode 100644 docs/ai.md create mode 100644 docs/ai_studio.md create mode 100644 docs/app_item_associations.md create mode 100644 docs/archives.md create mode 100644 docs/authentication.md create mode 100644 docs/authorization.md create mode 100644 docs/avatars.md create mode 100644 docs/chunked_uploads.md create mode 100644 docs/classifications.md create mode 100644 docs/client.md create mode 100644 docs/collaboration_allowlist_entries.md create mode 100644 docs/collaboration_allowlist_exempt_targets.md create mode 100644 docs/collections.md create mode 100644 docs/comments.md create mode 100644 docs/configuration.md create mode 100644 docs/device_pinners.md create mode 100644 docs/docgen.md create mode 100644 docs/docgen_template.md create mode 100644 docs/downloads.md create mode 100644 docs/email_aliases.md create mode 100644 docs/events.md create mode 100644 docs/file_classifications.md create mode 100644 docs/file_metadata.md create mode 100644 docs/file_requests.md create mode 100644 docs/file_version_legal_holds.md create mode 100644 docs/file_version_retentions.md create mode 100644 docs/file_versions.md create mode 100644 docs/file_watermarks.md create mode 100644 docs/files.md create mode 100644 docs/folder_classifications.md create mode 100644 docs/folder_locks.md create mode 100644 docs/folder_metadata.md create mode 100644 docs/folder_watermarks.md create mode 100644 docs/folders.md create mode 100644 docs/groups.md create mode 100644 docs/hub_collaborations.md create mode 100644 docs/hub_items.md create mode 100644 docs/hubs.md create mode 100644 docs/integration_mappings.md create mode 100644 docs/invites.md create mode 100644 docs/legal_hold_policies.md create mode 100644 docs/legal_hold_policy_assignments.md create mode 100644 docs/list_collaborations.md delete mode 100644 docs/make.bat create mode 100644 docs/memberships.md create mode 100644 docs/metadata_cascade_policies.md create mode 100644 docs/metadata_templates.md create mode 100644 docs/recent_items.md create mode 100644 docs/retention_policies.md create mode 100644 docs/retention_policy_assignments.md create mode 100644 docs/search.md create mode 100644 docs/session_termination.md create mode 100644 docs/shared_links_app_items.md create mode 100644 docs/shared_links_files.md create mode 100644 docs/shared_links_folders.md create mode 100644 docs/shared_links_web_links.md create mode 100644 docs/shield_information_barrier_reports.md create mode 100644 docs/shield_information_barrier_segment_members.md create mode 100644 docs/shield_information_barrier_segment_restrictions.md create mode 100644 docs/shield_information_barrier_segments.md create mode 100644 docs/shield_information_barriers.md create mode 100644 docs/shield_lists.md create mode 100644 docs/sign_requests.md create mode 100644 docs/sign_templates.md create mode 100644 docs/skills.md delete mode 100644 docs/source/boxsdk.auth.rst delete mode 100644 docs/source/boxsdk.client.rst delete mode 100644 docs/source/boxsdk.network.rst delete mode 100644 docs/source/boxsdk.object.rst delete mode 100644 docs/source/boxsdk.pagination.rst delete mode 100644 docs/source/boxsdk.rst delete mode 100644 docs/source/boxsdk.session.rst delete mode 100644 docs/source/boxsdk.util.rst delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/index.rst delete mode 100644 docs/source/modules.rst create mode 100644 docs/storage_policies.md create mode 100644 docs/storage_policy_assignments.md create mode 100644 docs/task_assignments.md create mode 100644 docs/tasks.md create mode 100644 docs/terms_of_service_user_statuses.md create mode 100644 docs/terms_of_services.md create mode 100644 docs/transfer.md create mode 100644 docs/trashed_files.md create mode 100644 docs/trashed_folders.md create mode 100644 docs/trashed_items.md create mode 100644 docs/trashed_web_links.md create mode 100644 docs/uploads.md delete mode 100644 docs/usage/README.md delete mode 100644 docs/usage/ai.md delete mode 100644 docs/usage/authentication.md delete mode 100644 docs/usage/classifications.md delete mode 100644 docs/usage/collaboration.md delete mode 100644 docs/usage/collaboration_allowlist.md delete mode 100644 docs/usage/collections.md delete mode 100644 docs/usage/comments.md delete mode 100644 docs/usage/configuration.md delete mode 100644 docs/usage/device_pin.md delete mode 100644 docs/usage/events.md delete mode 100644 docs/usage/file_request.md delete mode 100644 docs/usage/files.md delete mode 100644 docs/usage/folders.md delete mode 100644 docs/usage/group.md delete mode 100644 docs/usage/legal_hold.md delete mode 100644 docs/usage/metadata.md delete mode 100644 docs/usage/metadata_cascade_policies.md delete mode 100644 docs/usage/retention_policy.md delete mode 100644 docs/usage/search.md delete mode 100644 docs/usage/sign_requests.md delete mode 100644 docs/usage/sign_templates.md delete mode 100644 docs/usage/storage_policy.md delete mode 100644 docs/usage/task.md delete mode 100644 docs/usage/terms_of_service.md delete mode 100644 docs/usage/trash.md delete mode 100644 docs/usage/user.md delete mode 100644 docs/usage/watermarking.md delete mode 100644 docs/usage/web_link.md delete mode 100644 docs/usage/webhook.md delete mode 100644 docs/usage/zip.md create mode 100644 docs/user_collaborations.md create mode 100644 docs/users.md create mode 100644 docs/web_links.md create mode 100644 docs/webhooks.md create mode 100644 docs/workflows.md create mode 100644 docs/working-with-nulls.md create mode 100644 docs/zip_downloads.md create mode 100644 migration-guide.md delete mode 100644 setup.cfg create mode 100644 test/ai.py create mode 100644 test/ai_studio.py create mode 100644 test/app_item_associations.py create mode 100644 test/archives.py create mode 100644 test/auth.py create mode 100644 test/avatars.py create mode 100644 test/box_network_client.py create mode 100644 test/chunked_uploads.py create mode 100644 test/classifications.py create mode 100644 test/client.py create mode 100644 test/collaboration_allowlist_entries.py create mode 100644 test/collaboration_allowlist_exempt_targets.py create mode 100644 test/collections.py create mode 100644 test/comments.py create mode 100644 test/commons.py delete mode 100644 test/conftest.py create mode 100644 test/device_pinners.py create mode 100644 test/docgen.py create mode 100644 test/docgen_template.py create mode 100644 test/downloads.py create mode 100644 test/email_aliases.py create mode 100644 test/events.py create mode 100644 test/file_classifications.py create mode 100644 test/file_metadata.py create mode 100644 test/file_requests.py create mode 100644 test/file_version_legal_holds.py create mode 100644 test/file_version_retentions.py create mode 100644 test/file_versions.py create mode 100644 test/file_watermarks.py create mode 100644 test/files.py create mode 100644 test/folder_classifications.py create mode 100644 test/folder_locks.py create mode 100644 test/folder_metadata.py create mode 100644 test/folder_watermarks.py create mode 100644 test/folders.py delete mode 100644 test/functional/__init__.py delete mode 100644 test/functional/conftest.py delete mode 100644 test/functional/mock_box/__init__.py delete mode 100644 test/functional/mock_box/behavior/__init__.py delete mode 100644 test/functional/mock_box/behavior/event_behavior.py delete mode 100644 test/functional/mock_box/behavior/file_behavior.py delete mode 100644 test/functional/mock_box/behavior/folder_behavior.py delete mode 100644 test/functional/mock_box/behavior/item_behavior.py delete mode 100644 test/functional/mock_box/behavior/oauth2_behavior.py delete mode 100644 test/functional/mock_box/behavior/user_behavior.py delete mode 100644 test/functional/mock_box/box.py delete mode 100644 test/functional/mock_box/db_model/__init__.py delete mode 100644 test/functional/mock_box/db_model/application_model.py delete mode 100644 test/functional/mock_box/db_model/collaboration_model.py delete mode 100644 test/functional/mock_box/db_model/event_model.py delete mode 100644 test/functional/mock_box/db_model/file_model.py delete mode 100644 test/functional/mock_box/db_model/folder_model.py delete mode 100644 test/functional/mock_box/db_model/group_model.py delete mode 100644 test/functional/mock_box/db_model/lock_model.py delete mode 100644 test/functional/mock_box/db_model/share_model.py delete mode 100644 test/functional/mock_box/db_model/token_model.py delete mode 100644 test/functional/mock_box/db_model/user_model.py delete mode 100644 test/functional/mock_box/util/__init__.py delete mode 100644 test/functional/mock_box/util/chaos_utils.py delete mode 100644 test/functional/mock_box/util/db_utils.py delete mode 100644 test/functional/mock_box/util/http_utils.py delete mode 100644 test/functional/mock_box/util/json_utils.py delete mode 100644 test/functional/mock_box/views/__init__.py delete mode 100644 test/functional/mock_box/views/html_response.tpl delete mode 100644 test/functional/mock_box/views/oauth2.tpl delete mode 100644 test/functional/mock_box/views/xml_response.tpl delete mode 100644 test/functional/test_delete.py delete mode 100644 test/functional/test_events.py delete mode 100644 test/functional/test_file_upload_update_download.py delete mode 100644 test/functional/test_item_info.py delete mode 100644 test/functional/test_object_clone.py delete mode 100644 test/functional/test_rate_limits.py delete mode 100644 test/functional/test_recovery.py delete mode 100644 test/functional/test_token_refresh.py create mode 100644 test/groups.py create mode 100644 test/hub_collaborations.py create mode 100644 test/hub_items.py create mode 100644 test/hubs.py delete mode 100644 test/integration/__init__.py delete mode 100644 test/integration/conftest.py delete mode 100644 test/integration/mock_network.py delete mode 100644 test/integration/test_as_user.py delete mode 100644 test/integration/test_retry_and_refresh.py delete mode 100644 test/integration/test_with_shared_link.py create mode 100644 test/integration_mappings.py delete mode 100644 test/integration_new/README.md delete mode 100644 test/integration_new/__init__.py delete mode 100644 test/integration_new/conftest.py delete mode 100644 test/integration_new/context_managers/box_metadata_template.py delete mode 100644 test/integration_new/context_managers/box_retention_policy.py delete mode 100644 test/integration_new/context_managers/box_retention_policy_assigment.py delete mode 100644 test/integration_new/context_managers/box_sign_request.py delete mode 100644 test/integration_new/context_managers/box_test_file.py delete mode 100644 test/integration_new/context_managers/box_test_folder.py delete mode 100644 test/integration_new/context_managers/box_test_group.py delete mode 100644 test/integration_new/context_managers/box_test_user.py delete mode 100644 test/integration_new/context_managers/box_test_web_link.py delete mode 100644 test/integration_new/context_managers/local_large_file.py delete mode 100644 test/integration_new/integration_tests.cfg delete mode 100644 test/integration_new/object/ai_itest.py delete mode 100644 test/integration_new/object/collaboration_itest.py delete mode 100644 test/integration_new/object/file_itest.py delete mode 100644 test/integration_new/object/folder_itest.py delete mode 100644 test/integration_new/object/legal_hold_policy_itest.py delete mode 100644 test/integration_new/object/metadata_template_itest.py delete mode 100644 test/integration_new/object/retention_policy_assignement_itest.py delete mode 100644 test/integration_new/object/retention_policy_itest.py delete mode 100644 test/integration_new/object/sign_request_itest.py delete mode 100644 test/integration_new/object/sign_template_itest.py delete mode 100644 test/integration_new/object/trash_itest.py delete mode 100644 test/integration_new/object/user_itest.py delete mode 100644 test/integration_new/resources/image.png delete mode 100644 test/integration_new/resources/small.pdf delete mode 100644 test/integration_new/resources/small_v2.pdf delete mode 100644 test/integration_new/util.py create mode 100644 test/invites.py create mode 100644 test/legal_hold_policies.py create mode 100644 test/legal_hold_policy_assignments.py create mode 100644 test/list_collaborations.py create mode 100644 test/memberships.py create mode 100644 test/metadata_cascade_policies.py create mode 100644 test/metadata_templates.py create mode 100644 test/recent_items.py create mode 100644 test/retention_policies.py create mode 100644 test/retention_policy_assignments.py create mode 100644 test/search.py create mode 100644 test/session_termination.py create mode 100644 test/shared_links_app_items.py create mode 100644 test/shared_links_files.py create mode 100644 test/shared_links_folders.py create mode 100644 test/shared_links_web_links.py create mode 100644 test/shield_information_barrier_reports.py create mode 100644 test/shield_information_barrier_segment_members.py create mode 100644 test/shield_information_barrier_segment_restrictions.py create mode 100644 test/shield_information_barrier_segments.py create mode 100644 test/shield_information_barriers.py create mode 100644 test/shield_lists.py create mode 100644 test/sign_requests.py create mode 100644 test/sign_templates.py create mode 100644 test/skills.py create mode 100644 test/storage_policicy_assignments.py create mode 100644 test/storage_policies.py create mode 100644 test/task_assignments.py create mode 100644 test/tasks.py create mode 100644 test/terms_of_service_user_statuses.py create mode 100644 test/terms_of_services.py create mode 100644 test/transfer.py create mode 100644 test/trashed_files.py create mode 100644 test/trashed_folders.py create mode 100644 test/trashed_items.py create mode 100644 test/trashed_web_links.py delete mode 100644 test/unit/__init__.py delete mode 100644 test/unit/auth/__init__.py delete mode 100644 test/unit/auth/test_ccg_auth.py delete mode 100644 test/unit/auth/test_cooperatively_managed_oauth2.py delete mode 100644 test/unit/auth/test_developer_token_auth.py delete mode 100644 test/unit/auth/test_jwt_auth.py delete mode 100644 test/unit/auth/test_oauth2.py delete mode 100644 test/unit/auth/test_redis_managed_oauth2.py delete mode 100644 test/unit/auth/test_remote_managed_oauth2.py delete mode 100644 test/unit/client/__init__.py delete mode 100644 test/unit/client/test_client.py delete mode 100644 test/unit/conftest.py delete mode 100644 test/unit/network/__init__.py delete mode 100644 test/unit/network/conftest.py delete mode 100644 test/unit/network/test_network.py delete mode 100644 test/unit/object/__init__.py delete mode 100644 test/unit/object/conftest.py delete mode 100644 test/unit/object/test_api_json_object.py delete mode 100644 test/unit/object/test_base_api_json_object.py delete mode 100644 test/unit/object/test_base_item.py delete mode 100644 test/unit/object/test_base_object.py delete mode 100644 test/unit/object/test_chunked_upload.py delete mode 100644 test/unit/object/test_collaboration.py delete mode 100644 test/unit/object/test_collaboration_allowlist.py delete mode 100644 test/unit/object/test_collaboration_allowlist_entry.py delete mode 100644 test/unit/object/test_collaboration_allowlist_exempt_target.py delete mode 100644 test/unit/object/test_collection.py delete mode 100644 test/unit/object/test_comment.py delete mode 100644 test/unit/object/test_device_pin.py delete mode 100644 test/unit/object/test_enterprise.py delete mode 100644 test/unit/object/test_event.py delete mode 100644 test/unit/object/test_events.py delete mode 100644 test/unit/object/test_file.py delete mode 100644 test/unit/object/test_file_request.py delete mode 100644 test/unit/object/test_file_version_retention.py delete mode 100644 test/unit/object/test_folder.py delete mode 100644 test/unit/object/test_group.py delete mode 100644 test/unit/object/test_item.py delete mode 100644 test/unit/object/test_legal_hold.py delete mode 100644 test/unit/object/test_legal_hold_assignment.py delete mode 100644 test/unit/object/test_legal_hold_policy.py delete mode 100644 test/unit/object/test_metadata.py delete mode 100644 test/unit/object/test_metadata_cascade_policy.py delete mode 100644 test/unit/object/test_metadata_template.py delete mode 100644 test/unit/object/test_retention_policy.py delete mode 100644 test/unit/object/test_retention_policy_assignment.py delete mode 100644 test/unit/object/test_search.py delete mode 100644 test/unit/object/test_sign_request.py delete mode 100644 test/unit/object/test_sign_templates.py delete mode 100644 test/unit/object/test_storage_policy.py delete mode 100644 test/unit/object/test_storage_policy_assignment.py delete mode 100644 test/unit/object/test_task.py delete mode 100644 test/unit/object/test_task_assignment.py delete mode 100644 test/unit/object/test_terms_of_service.py delete mode 100644 test/unit/object/test_terms_of_service_user_status.py delete mode 100644 test/unit/object/test_trash.py delete mode 100644 test/unit/object/test_upload_session.py delete mode 100644 test/unit/object/test_user.py delete mode 100644 test/unit/object/test_web_link.py delete mode 100644 test/unit/object/test_webhook.py delete mode 100644 test/unit/pagination/__init__.py delete mode 100644 test/unit/pagination/box_object_collection_test_base.py delete mode 100644 test/unit/pagination/test_limit_offset_based_object_collection.py delete mode 100644 test/unit/pagination/test_marker_based_object_collection.py delete mode 100644 test/unit/pagination/test_page.py delete mode 100644 test/unit/session/__init__.py delete mode 100644 test/unit/session/test_session.py delete mode 100644 test/unit/test_exception.py delete mode 100644 test/unit/util/__init__.py delete mode 100644 test/unit/util/test_api_call_decorator.py delete mode 100644 test/unit/util/test_datetime_formatter.py delete mode 100644 test/unit/util/test_enum.py delete mode 100644 test/unit/util/test_log.py delete mode 100644 test/unit/util/test_lru_cache.py delete mode 100644 test/unit/util/test_multipart_stream.py delete mode 100644 test/unit/util/test_shared_link.py delete mode 100644 test/unit/util/test_text_enum.py delete mode 100644 test/unit/util/test_translator.py create mode 100644 test/uploads.py create mode 100644 test/user_collaborations.py create mode 100644 test/users.py delete mode 100644 test/util/__init__.py delete mode 100644 test/util/streamable_mock_open.py create mode 100644 test/webhooks.py create mode 100644 test/weblinks.py create mode 100644 test/workflows.py create mode 100644 test/zip_downloads.py diff --git a/.codegen.json b/.codegen.json new file mode 100644 index 000000000..c01c9ff2d --- /dev/null +++ b/.codegen.json @@ -0,0 +1 @@ +{ "engineHash": "c34d8bf", "specHash": "c27c421", "version": "0.1.0" } diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 3825a5bc9..000000000 --- a/.gitattributes +++ /dev/null @@ -1,11 +0,0 @@ -# See GITATTRIBUTES(5) (``man gitattributes``). -# - ``diff=`` declares that diff hunk headers and git-diff word diffs -# (for ``git diff --word-diff`` and ``git diff --color-words``) should be -# specially computed for the language grammar. This makes git-diff more -# powerful and useful. -*.py text eol=lf diff=python -*.html text eol=lf diff=html -*.txt text eol=lf -*.rst text eol=lf -*.in text eol=lf -*.ini text eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 7f70585ff..eaf3366d7 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -3,8 +3,7 @@ name: Bug about: Report a bug found in the SDK title: '' labels: bug -assignees: mwwoda, mhagmajer, antusus, arjankowski, lukaszsocha2, bartlomiejleszczynski - +assignees: mwwoda, mhagmajer, arjankowski, lukaszsocha2, bartlomiejleszczynski, congminh1254 --- - [ ] I have checked that the [SDK documentation][sdk-docs] doesn't solve my issue. @@ -13,36 +12,44 @@ assignees: mwwoda, mhagmajer, antusus, arjankowski, lukaszsocha2, bartlomiejlesz - [ ] I have searched [Issues in this repo][github-repo] and my issue isn't already reported. ### Description of the Issue + ### Steps to Reproduce + + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error ### Expected Behavior + ### Error Message, Including Stack Trace + ### Screenshots + ### Versions Used + Python SDK: Python: -[sdk-docs]: ./doc + +[sdk-docs]: https://github.com/box/box-python-sdk-gen/tree/main/docs [api-docs]: https://developer.box.com/docs [dev-forums]: https://community.box.com/t5/Platform-and-Development-Forum/bd-p/DeveloperForum -[github-repo]: https://github.com/box/box-python-sdk/search?type=Issues +[github-repo]: https://github.com/box/box-python-sdk-gen/search?type=Issues diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index f78153f94..5c9f2e19d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: false contact_links: - name: SDK documentation - url: https://github.com/box/box-python-sdk/tree/main/docs + url: https://github.com/box/box-python-sdk-gen/tree/main/docs about: Before creating an issue, I have checked that the SDK documentation doesn't solve my issue. - name: API documentation url: https://developer.box.com/docs @@ -10,5 +10,5 @@ contact_links: url: https://community.box.com/t5/Platform-and-Development-Forum/bd-p/DeveloperForum about: Before creating an issue, I have searched the Box Developer Forums and my issue isn't already reported there. - name: Issues in this repo - url: https://github.com/box/box-python-sdk/search?type=Issues + url: https://github.com/box/box-python-sdk-gen/search?type=Issues about: Before creating an issue, I have searched Issues in this repo and my issue isn't already reported. diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md index c6a01bbfa..ea91c6e44 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -3,18 +3,21 @@ name: Enhancement about: Suggest a new feature or change title: '' labels: enhancement -assignees: mwwoda, mhagmajer, antusus, arjankowski, lukaszsocha2, bartlomiejleszczynski - +assignees: mwwoda, mhagmajer, arjankowski, lukaszsocha2, bartlomiejleszczynski, congminh1254 --- ### Is your feature request related to a problem? Please describe. + ### Describe the solution you'd like + ### Describe alternatives you've considered + ### Additional context + diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 22ba07ea9..0ff57e911 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -3,8 +3,7 @@ name: Question about: Request for Assistance title: '' labels: question -assignees: mwwoda, mhagmajer, antusus, arjankowski, lukaszsocha2, bartlomiejleszczynski - +assignees: mwwoda, mhagmajer, arjankowski, lukaszsocha2, bartlomiejleszczynski, congminh1254 --- - [ ] I have checked that the [SDK documentation][sdk-docs] doesn't solve my issue. @@ -13,36 +12,44 @@ assignees: mwwoda, mhagmajer, antusus, arjankowski, lukaszsocha2, bartlomiejlesz - [ ] I have searched [Issues in this repo][github-repo] and my issue isn't already reported. ### Description of the Issue + ### Steps to Reproduce + + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error ### Expected Behavior + ### Error Message, Including Stack Trace + ### Screenshots + ### Versions Used + Python SDK: Python: -[sdk-docs]: ./doc + +[sdk-docs]: https://github.com/box/box-python-sdk-gen/tree/main/docs [api-docs]: https://developer.box.com/docs [dev-forums]: https://community.box.com/t5/Platform-and-Development-Forum/bd-p/DeveloperForum -[github-repo]: https://github.com/box/box-python-sdk/search?type=Issues +[github-repo]: https://github.com/box/box-python-sdk-gen/search?type=Issues diff --git a/.github/stale.yml b/.github/stale.yml index 1bc211c83..70cf69f6b 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -19,4 +19,4 @@ markComment: > # Comment to post when closing a stale issue. Set to `false` to disable closeComment: > - This issue has been automatically closed due to maximum period of being stale. Thank you for your contribution to Box Python SDK and feel free to open another PR/issue at any time. + This issue has been automatically closed due to maximum period of being stale. Thank you for your contribution to Box SDK and feel free to open another PR/issue at any time. diff --git a/.github/workflows/autoupdate-pr.yml b/.github/workflows/autoupdate-pr.yml new file mode 100644 index 000000000..185ab949d --- /dev/null +++ b/.github/workflows/autoupdate-pr.yml @@ -0,0 +1,39 @@ +name: Autoupdate PR +on: + push: + branches: + - main + +jobs: + update_pull_requests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.DISPATCH_ACCESS_TOKEN }} + + - name: Set up Git + run: | + git config --global user.name 'box-sdk-build' + git config --global user.email 'box-sdk-build@box.com' + + - name: Fetch all branches and tags + run: git fetch --prune --unshallow + + - name: Auto update pull requests + run: | + PR_LIST=$(curl -s -H "Authorization: Bearer ${{ secrets.DISPATCH_ACCESS_TOKEN }}" "https://api.github.com/repos/$GITHUB_REPOSITORY/pulls?state=open" | jq -r '.[] | .head.ref') + for pr_branch in $PR_LIST; do + git checkout "$pr_branch" + if git merge origin/main; then + git push + else + # Conflict occurred, resolve by keeping our changes + git checkout --ours . + git add . + git commit -m "Auto resolve conflict by keeping our changes" + git push + fi + done diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0792ceff8..a419259de 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,47 +9,80 @@ on: jobs: build: - name: Build and unit tests - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest strategy: + max-parallel: 1 matrix: python-version: - - '3.6' - - '3.7' - '3.8' - - '3.9' - - '3.10' - '3.11' - - '3.12' - '3.13' - container: - image: python:${{ matrix.python-version }} + name: Build with Python ${{ matrix.python-version }} steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Upgrade pip and install dependencies + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install -e .[gh] + python -m pip install -e .[dev] + python -m pip install tox-gh-actions + - name: Test with tox + env: + JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} + ADMIN_USER_ID: ${{ secrets.ADMIN_USER_ID }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + USER_ID: ${{ secrets.USER_ID }} + ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }} + BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }} + BOX_EXTERNAL_USER_EMAIL: ${{ secrets.BOX_EXTERNAL_USER_EMAIL }} + APP_ITEM_ASSOCIATION_FILE_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FILE_ID }} + APP_ITEM_ASSOCIATION_FOLDER_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FOLDER_ID }} + WORKFLOW_FOLDER_ID: ${{ secrets.WORKFLOW_FOLDER_ID }} + APP_ITEM_SHARED_LINK: ${{ secrets.APP_ITEM_SHARED_LINK }} + SLACK_AUTOMATION_USER_ID: ${{ secrets.SLACK_AUTOMATION_USER_ID }} + SLACK_ORG_ID: ${{ secrets.SLACK_ORG_ID }} + SLACK_PARTNER_ITEM_ID: ${{ secrets.SLACK_PARTNER_ITEM_ID }} + run: | + tox - - name: Run tests with tox - run: tox -e py$(echo "${{ matrix.python-version }}" | tr -d '.') coverage: name: Coverage report runs-on: ubuntu-latest - container: - image: python:3.13 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: '3.11' - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install -e .[coveralls] + python -m pip install -e .[dev] + python -m pip install coveralls - name: Send coverage report to Coveralls - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | tox -e coverage coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} + ADMIN_USER_ID: ${{ secrets.ADMIN_USER_ID }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + USER_ID: ${{ secrets.USER_ID }} + ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }} + BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }} + BOX_EXTERNAL_USER_EMAIL: ${{ secrets.BOX_EXTERNAL_USER_EMAIL }} + WORKFLOW_FOLDER_ID: ${{ secrets.WORKFLOW_FOLDER_ID }} + APP_ITEM_ASSOCIATION_FILE_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FILE_ID }} + APP_ITEM_ASSOCIATION_FOLDER_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FOLDER_ID }} + APP_ITEM_SHARED_LINK: ${{ secrets.APP_ITEM_SHARED_LINK }} + SLACK_AUTOMATION_USER_ID: ${{ secrets.SLACK_AUTOMATION_USER_ID }} + SLACK_ORG_ID: ${{ secrets.SLACK_ORG_ID }} + SLACK_PARTNER_ITEM_ID: ${{ secrets.SLACK_PARTNER_ITEM_ID }} diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml deleted file mode 100644 index 6b8bbf08c..000000000 --- a/.github/workflows/create_release.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: Manually triggered release - -# Controls when the workflow will run -on: - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - # Runs a single command using the runners shell - - name: Run a one-line script - run: echo Hello, world! - - # Runs a set of commands using the runners shell - - name: Run a multi-line script - run: | - echo Add other actions to build, - echo test, and deploy your project. diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml deleted file mode 100644 index 41c7afb09..000000000 --- a/.github/workflows/integration-tests.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Integration tests -on: - pull_request: - branches: - - main - push: - branches: - - main - -jobs: - tests: - name: Integration tests - runs-on: ubuntu-latest - strategy: - max-parallel: 1 - matrix: - python-version: - - '3.6' - - '3.12' - - '3.13' - container: - image: python:${{ matrix.python-version }} - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e ".[dev]" - - name: Run integration tests - run: | - tox -e integration-tests - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} - ADMIN_USER_ID: ${{ secrets.ADMIN_USER_ID }} diff --git a/.github/workflows/releases.yml b/.github/workflows/notify-changelog.yml similarity index 91% rename from .github/workflows/releases.yml rename to .github/workflows/notify-changelog.yml index ef3e1b616..c7c05c926 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/notify-changelog.yml @@ -18,11 +18,10 @@ jobs: timeout-minutes: 3 steps: - # There's really only 1 step, and i - name: Notify changelog of new release uses: peter-evans/repository-dispatch@v1 with: token: ${{ secrets.DISPATCH_ACCESS_TOKEN }} repository: box/box-developer-changelog event-type: new-release-note - client-payload: '{"ref": "${{ github.ref }}", "repository": "${{github.repository}}", "labels": "sdks,python", "repo_display_name": "Box Python SDK"}' + client-payload: '{"ref": "${{ github.ref }}", "repository": "${{github.repository}}", "labels": "sdks,python", "repo_display_name": "Box Python SDK Generated"}' diff --git a/.github/workflows/spell-check-lint.yml b/.github/workflows/spell-check-lint.yml index d2cad31c3..659bbd3fa 100644 --- a/.github/workflows/spell-check-lint.yml +++ b/.github/workflows/spell-check-lint.yml @@ -5,13 +5,13 @@ on: branches: - main jobs: - spellcheck-request-title: + spellcheck-request: runs-on: ubuntu-latest steps: - name: Checkout current repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Checkout spellchecker - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: ref: refs/heads/main repository: box/box-sdk-spellchecker @@ -19,3 +19,5 @@ jobs: path: spellchecker - name: Execute spellchecker uses: ./spellchecker + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index de592b02e..6769e21d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,39 +1,30 @@ -# Backup files -*.~ - # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] -.pytest_cache/ +*$py.class # C extensions *.so # Distribution / packaging -bin/ .Python -env*/ build/ develop-eggs/ dist/ -pypi-dist/ downloads/ eggs/ +.eggs/ lib/ lib64/ parts/ sdist/ var/ +wheels/ +share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST -.eggs/ -.env*/ -.pyenv/ -venv*/ -.venv*/ -python.properties # PyInstaller # Usually these files are written by a python script from a template @@ -48,12 +39,17 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage +.coverage.* .cache nosetests.xml coverage.xml -.coverage.* -*,cover +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ # Translations *.mo @@ -61,25 +57,104 @@ coverage.xml # Django stuff: *.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy # Sphinx documentation docs/_build/ # PyBuilder +.pybuilder/ target/ -# IntelliJ -.idea/ -*.iml +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json -# VSCode -.vscode/ +# Pyre type checker +.pyre/ -# Mac -.DS_Store +# pytype static type analyzer +.pytype/ -# Misc -tokens.pk +# Cython debug symbols +cython_debug/ -#pyenv -.python-version +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/.pylintrc b/.pylintrc index 20209a128..e4301cde0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -7,7 +7,7 @@ # pygtk.require(). #init-hook= -# Add files or directories to the blacklist. They should be base names, not +# Add files or directories to the denylist. They should be base names, not # paths. #ignore=CVS diff --git a/.pypirc b/.pypirc index 72484f78e..987841a49 100644 --- a/.pypirc +++ b/.pypirc @@ -6,4 +6,4 @@ index-servers= [pypi] [testpypi] -repository: https://test.pypi.org/legacy/ +repository: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/.versionrc b/.versionrc index 44c1e6dba..ca2ea3076 100644 --- a/.versionrc +++ b/.versionrc @@ -1,13 +1,34 @@ { - "types": [ - {"type": "feat", "section": "New Features and Enhancements:", "hidden": false}, - {"type": "fix", "section": "Bug Fixes:", "hidden": false}, - {"type": "build", "section": "Dependency Upgrades:", "hidden": false}, - {"type": "chore", "hidden": true}, - {"type": "docs", "hidden": true}, - {"type": "style", "hidden": true}, - {"type": "refactor", "hidden": true}, - {"type": "test", "hidden": true}, - {"type": "ci", "hidden":true} - ] + "types": [ + { + "type": "feat", + "section": "New Features and Enhancements", + "hidden": false + }, + { + "type": "fix", + "section": "Bug Fixes", + "hidden": false + }, + { + "type": "chore", + "hidden": true + }, + { + "type": "docs", + "hidden": true + }, + { + "type": "style", + "hidden": true + }, + { + "type": "refactor", + "hidden": true + }, + { + "type": "test", + "hidden": true + } + ] } diff --git a/AUTHORS.md b/AUTHORS.md deleted file mode 100644 index 0529e2da5..000000000 --- a/AUTHORS.md +++ /dev/null @@ -1,16 +0,0 @@ -The Box Python SDK is an open source project supported by -[Box](https://box.com) used to interact with the Box API. This is a list -of contributors. - -- [\@Jeff-Meadows](https://github.com/Jeff-Meadows) -- [\@jmoldow](https://github.com/jmoldow) -- [\@aptxkid](https://github.com/aptxkid) -- [\@hnguyen08](https://github.com/hnguyen08) -- [\@potrebic](https://github.com/potrebic) -- [\@nsundareswaran](https://github.com/nsundareswaran) -- [\@kelseymorris95](https://github.com/kelseymorris95) -- [\@sp4x](https://github.com/sp4x) -- [\@capk1rk](https://github.com/capk1rk) -- [\@aculler](https://github.com/aculler) -- [\@ben-reilly](https://github.com/ben-reilly) -- [\@ab](https://github.com/ab) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 929e04cc6..e12872653 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,91 +16,5 @@ This project adheres to the [Box Open Code of Conduct](http://opensource.box.com ## How to contribute -* **File an issue** - if you found a bug, want to request an enhancement, or want to implement something (bug fix or feature). -* **Send a pull request** - if you want to contribute code. Please be sure to file an issue first. - -## Pull request best practices - -We want to accept your pull requests. Please follow these steps: - -### Step 1: File an issue - -Before writing any code, please file an issue stating the problem you want to solve or the feature you want to implement. This allows us to give you feedback before you spend any time writing code. There may be a known limitation that can't be addressed, or a bug that has already been fixed in a different way. The issue allows us to communicate and figure out if it's worth your time to write a bunch of code for the project. - -### Step 2: Fork this repository in GitHub - -This will create your own copy of our repository. - -### Step 3: Add the upstream source - -The upstream source is the project under the Box organization on GitHub. To add an upstream source for this project, type: - -``` -git remote add upstream git@github.com:box/box-python-sdk.git -``` - -This will come in useful later. - -### Step 4: Create a feature branch - -Create a branch with a descriptive name, such as `add-search`. - -### Step 5: Push your feature branch to your fork - -We use [semantic-versioning](https://semver.org/) and the [conventional commit message format](https://www.conventionalcommits.org/en/v1.0.0/). Keep a separate feature branch for each issue you want to address. As you develop code, continue to push code to your remote feature branch. Example: - -``` -tag: short description -longer description here if necessary. -``` - -The message summary should be a one-sentence description of the change, and it must be 72 characters in length or shorter. For a list of tags, please [click here](https://github.com/commitizen/conventional-commit-types/blob/master/index.json). Note that you must include the `!` for breaking changes (e.g. `feat!: removed old apis`). - -Shown below are examples of the release type that will be done based on a commit message. - -#### Commit Types - -"Semantic versioning" means that changes to the version number of the package (e.g. `3.42.11` to `3.43.0`) are done according to rules that indicate how the change will affect consumers. Read more on the [semver page](https://semver.org/). - -The version number is broken into 3 positions — `Major.Minor.Patch`. In semantic release terms, changes to the numbers follow `Breaking.Feature.Fix`. The `release` script parses commit messages and decides what type of release to make based on the types of commits detected since the last release. - -The rules for commit types are: - -- Anything that changes or removes an API, option, or output format is a breaking change denoted by `!`. -- Anything that adds new functionality in a backwards-compatible way is a feature (`feat`). Consumers have to upgrade to the new version to use the feature, but nothing will break if they do so. -- Bugfixes (`fix`) for existing behavior are a patch. Consumers don't have to do anything but upgrade. -- Other prefixes, such as `docs` or `chore`, don't trigger releases and don't appear in the changelog. These tags signal that there are **no external changes to _any_ APIs** (including non-breaking ones). - -In most cases, commits will be a `feat` or `fix`. Make sure to include the `!` in the title if there are non-backwards-compatible changes in the commit. - -| Commit message | Release type | New version | -| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----------- | -| `feat!: remove old files endpoints` | Major ("breaking") | `X+1.0.0` | -| `feat: add new file upload endpoint` | Minor ("feature") | `X.Y+1.0` | -| `fix: file streaming during download` | Patch ("fix") | `X.Y.Z+1` | -| `docs: document files api` | No release | `X.Y.Z` | -| `chore: remove commented code from file upload` | No release | `X.Y.Z` | -| `refactor: rename a variable (invisible change)` | No release | `X.Y.Z` | - -### Step 6: Rebase - -Before sending a pull request, rebase against upstream, such as: - -``` -git fetch upstream -git rebase upstream/main -``` - -This will add your changes on top of what's already in upstream, minimizing merge issues. - -### Step 7: Run the tests - -Make sure that all tests are passing before submitting a pull request. - -### Step 8: Send the pull request - -Send the pull request from your feature branch to us. Be sure to include a description that lets us know what work you did. - -Keep in mind that we like to see one issue addressed per pull request, as this helps keep our git history clean and we can more easily track down issues. - -In addition, feel free to add yourself to AUTHORS.md if you aren't already listed. +- **File an issue** - if you found a bug, want to request an enhancement, or want to implement something (bug fix or feature). +- **Send a pull request** - this project is generated using [Box Codegen](https://github.com/box/box-codegen) based on[Box OpenAPI 3.0 Specification](https://github.com/box/box-openapi), so if you want found something you want to change in the generated code, you'll need to modify the codegen project and submit a pull request there. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 25c571323..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include README.md LICENSE - -recursive-include test * diff --git a/README.md b/README.md index 3bb9b346e..16aab67a2 100644 --- a/README.md +++ b/README.md @@ -2,53 +2,43 @@ “box-dev-logo”

-# Deprecation notice +# Box Python SDK Gen -This version of the Box Python SDK is under maintenance mode, and will be deprecated soon, only critical security updates and bug fixes will be provided. We recommend using the new version Box Python SDK, which can be found at [box/box-python-sdk-gen](https://github.com/box/box-python-sdk-gen) +[![Project Status](http://opensource.box.com/badges/active.svg)](http://opensource.box.com/badges) +![build](https://github.com/box/box-python-sdk-gen/actions/workflows/build.yml/badge.svg) +[![PyPI version](https://badge.fury.io/py/box-sdk-gen.svg)](https://badge.fury.io/py/box-sdk-gen) +[![image](https://img.shields.io/pypi/dm/box-sdk-gen.svg)](https://pypi.python.org/pypi/box-sdk-gen) +![Platform](https://img.shields.io/badge/python-3.8+-blue) +[![Coverage](https://coveralls.io/repos/github/box/box-python-sdk-gen/badge.svg?branch=main)](https://coveralls.io/github/box/box-python-sdk-gen?branch=main) -You can find the migration guide [here](https://github.com/box/box-python-sdk-gen/blob/main/migration-guide.md) for transitioning from Box Python SDK v3.x to the new `box-sdk-gen` package. If you have any questions, please create an issue in the new repository or reach out to [Box Developer Support](https://developer.box.com/support/). +We are excited to introduce the stable release of the latest generation of Box Python SDK, designed to elevate the developer experience and streamline your integration with the Box Content Cloud. -# Box Python SDK +With this SDK, you’ll have access to: -[![image](http://opensource.box.com/badges/stable.svg)](http://opensource.box.com/badges) -[![Documentation Status](https://readthedocs.org/projects/box-python-sdk/badge/?version=latest)](http://box-python-sdk.readthedocs.org/en/latest) -[![image](https://github.com/box/box-python-sdk/actions/workflows/build.yml/badge.svg)](https://github.com/box/box-python-sdk/actions) -[![image](https://img.shields.io/pypi/v/boxsdk.svg)](https://pypi.python.org/pypi/boxsdk) -[![image](https://img.shields.io/pypi/dm/boxsdk.svg)](https://pypi.python.org/pypi/boxsdk) -[![image](https://coveralls.io/repos/github/box/box-python-sdk/badge.svg?branch=main)](https://coveralls.io/github/box/box-python-sdk?branch=main) +1. Full API Support: The new generation of Box SDKs empowers developers with complete coverage of the Box API ecosystem. You can now access all the latest features and functionalities offered by Box, allowing you to build even more sophisticated and feature-rich applications. +2. Rapid API Updates: Say goodbye to waiting for new Box APIs to be incorporated into the SDK. With our new auto-generation development approach, we can now add new Box APIs to the SDK at a much faster pace (in a matter of days). This means you can leverage the most up-to-date features in your applications without delay. +3. Embedded Documentation: We understand that easy access to information is crucial for developers. With our new approach, we have included comprehensive documentation for all objects and parameters directly in the source code of the SDK. This means you no longer need to look up this information on the developer portal, saving you time and streamlining your development process. +4. Enhanced Convenience Methods: Our commitment to enhancing your development experience continues with the introduction of convenience methods. These methods cover various aspects such as chunk uploads, classification, and much more. +5. Seamless Start: The new SDKs integrate essential functionalities like authentication, automatic retries with exponential backoff, exception handling, request cancellation, and type checking, enabling you to focus solely on your application's business logic. -Getting Started Docs: +Embrace the new generation of Box SDKs and unlock the full potential of the Box Content Cloud. + +# Table of contents -- [Deprecation notice](#deprecation-notice) -- [Box Python SDK](#box-python-sdk) +- [Box Python SDK Gen](#box-python-sdk-gen) +- [Table of contents](#table-of-contents) - [Installing](#installing) - [Getting Started](#getting-started) -- [Authorization](#authorization) - - [Server-to-Server Auth with JWT](#server-to-server-auth-with-jwt) - - [Traditional 3-legged OAuth2](#traditional-3-legged-oauth2) - - [Get the Authorization URL](#get-the-authorization-url) - - [Authenticate (Get Access/Refresh Tokens)](#authenticate-get-accessrefresh-tokens) - - [Create an Authenticated Client](#create-an-authenticated-client) - - [Instantiate a Client Given an Access and a Refresh Token](#instantiate-a-client-given-an-access-and-a-refresh-token) - - [Other Auth Options](#other-auth-options) -- [Usage Documentation](#usage-documentation) - - [Making API Calls Manually](#making-api-calls-manually) -- [Other Client Options](#other-client-options) - - [Logging Client](#logging-client) - - [Developer Token Client](#developer-token-client) - - [Development Client](#development-client) -- [Customization](#customization) - - [Custom Subclasses](#custom-subclasses) -- [FIPS 140-2 Compliance](#fips-140-2-compliance) -- [Versions](#versions) - - [Supported Version](#supported-version) - - [Version schedule](#version-schedule) -- [Contributing](#contributing) - - [Developer Setup](#developer-setup) - - [Testing](#testing) +- [Documentation](#documentation) +- [Upgrades](#upgrades) +- [Integration Tests](#integration-tests) + - [Running integration tests locally](#running-integration-tests-locally) + - [Create Platform Application](#create-platform-application) + - [Export configuration](#export-configuration) + - [Running tests](#running-tests) - [Questions, Bugs, and Feature Requests?](#questions-bugs-and-feature-requests) - [Copyright and License](#copyright-and-license) @@ -56,431 +46,107 @@ Getting Started Docs: # Installing -``` console -pip install boxsdk -``` - -The current version of the SDK is v3.x --- With this release support for -Python 3.5 and earlier (including 2.x) has been dropped. if you're -looking for the code or documentation for v1.5.x, please see the [1.5 -branch](https://github.com/box/box-python-sdk/tree/1.5). - -# Getting Started - -To get started with the SDK, get a Developer Token from the -Configuration page of your app in the [Box Developer -Console](https://app.box.com/developers/console). You can use this token -to make test calls for your own Box account. - -The SDK provides an interactive `DevelopmentClient` that makes it easy -to test out the SDK in a REPL. This client will automatically prompt for -a new Developer Token when it requires one, and will log HTTP requests -and responses to aid in debugging and understanding how the SDK makes -API calls. - -``` pycon ->>> from boxsdk import DevelopmentClient ->>> client = DevelopmentClient() -Enter developer token: ->>> user = client.user().get() -GET https://api.box.com/2.0/users/me {'headers': {'Authorization': '---wXyZ', - 'User-Agent': 'box-python-sdk-2.0.0', - 'X-Box-UA': 'agent=box-python-sdk/2.0.0; env=python/3.6.5'}, -'params': None} -"GET https://api.box.com/2.0/users/me" 200 454 -{'Date': 'Thu, 01 Nov 2018 23:32:11 GMT', 'Content-Type': 'application/json', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Strict-Transport-Security': 'max-age=31536000', 'Cache-Control': 'no-cache, no-store', 'Content-Encoding': 'gzip', 'Vary': 'Accept-Encoding', 'BOX-REQUEST-ID': '0b50luc09ahp56m2jmkla8mgmh2', 'Age': '0'} -{'address': '', -'avatar_url': 'https://cloud.app.box.com/api/avatar/large/123456789', -'created_at': '2012-06-07T11:14:50-07:00', -'id': '123456789', -'job_title': '', -'language': 'en', -'login': 'user@example.com', -'max_upload_size': 16106127360, -'modified_at': '2018-10-30T17:01:27-07:00', -'name': 'Example User', -'phone': '', -'space_amount': 1000000000000000.0, -'space_used': 14330018065, -'status': 'active', -'timezone': 'America/Los_Angeles', -'type': 'user'} - ->>> print(f'The current user ID is {user.id}') -The current user ID is 123456789 -``` - -Outside of a REPL, you can initialize a new `Client` with just the -Developer Token to get started. - -``` python -from boxsdk import OAuth2, Client - -auth = OAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - access_token='YOUR_DEVELOPER_TOKEN', -) -client = Client(auth) - -user = client.user().get() -print(f'The current user ID is {user.id}') -``` - -# Authorization - -The Box API uses OAuth2 for auth. The SDK makes it relatively painless -to work with OAuth2 tokens. - -## Server-to-Server Auth with JWT - -The Python SDK supports your [JWT -Authentication](https://developer.box.com/en/guides/authentication/jwt/) -applications. - -Authenticating with a JWT requires some extra dependencies. To get them, -simply - -``` console -pip install "boxsdk[jwt]" -``` - -Instead of instantiating your `Client` with an instance of `OAuth2`, -instead use an instance of `JWTAuth`. - -``` python -from boxsdk import JWTAuth -from boxsdk import Client - -auth = JWTAuth( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - enterprise_id='YOUR_ENTERPRISE_ID', - jwt_key_id='YOUR_JWT_KEY_ID', - rsa_private_key_file_sys_path='CERT.PEM', - rsa_private_key_passphrase='PASSPHRASE', -) - -access_token = auth.authenticate_instance() -client = Client(auth) -``` - -This client is able to create application users: - -``` python -ned_stark_user = client.create_user('Ned Stark') -``` - -These users can then be authenticated: - -``` python -ned_auth = JWTAuth( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - user=ned_stark_user, - jwt_key_id='YOUR_JWT_KEY_ID', - rsa_private_key_file_sys_path='CERT.PEM', - rsa_private_key_passphrase='PASSPHRASE' -) -ned_auth.authenticate_user() -ned_client = Client(ned_auth) -``` - -Requests made with `ned_client` (or objects returned from -`ned_client`'s methods) will be performed on behalf of the newly -created app user. - -## Traditional 3-legged OAuth2 - -### Get the Authorization URL - -``` python -from boxsdk import OAuth2 - -oauth = OAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - store_tokens=your_store_tokens_callback_method, -) - -auth_url, csrf_token = oauth.get_authorization_url('http://YOUR_REDIRECT_URL') -``` - -store_tokens is a callback used to store the access token and refresh -token. You might want to define something like this: - -``` python -def store_tokens(access_token, refresh_token): - # store the tokens at secure storage (e.g. Keychain) -``` - -The SDK will keep the tokens in memory for the duration of the Python -script run, so you don't always need to pass store_tokens. - -### Authenticate (Get Access/Refresh Tokens) - -If you navigate the user to the auth_url, the user will eventually get -redirected to . After -getting the code, you will be able to use the code to exchange for an -access token and refresh token. - -The SDK handles all the work for you; all you need to do is run: - -``` python -# Make sure that the csrf token you get from the `state` parameter -# in the final redirect URI is the same token you get from the -# get_authorization_url method. -assert 'THE_CSRF_TOKEN_YOU_GOT' == csrf_token -access_token, refresh_token = oauth.authenticate('YOUR_AUTH_CODE') -``` - -### Create an Authenticated Client - -``` python -from boxsdk import Client - -client = Client(oauth) -``` - -And that's it! You can start using the client to do all kinds of cool -stuff and the SDK will handle the token refresh for you automatically. - -### Instantiate a Client Given an Access and a Refresh Token - -Alternatively, you can instantiate an OAuth2 object with the access -token and refresh token. Once you have an oauth object you can pass that -into the Client object to instantiate a client and begin making calls. - -``` python -from boxsdk import Client, OAuth2 - -oauth = OAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - access_token='ACCESS_TOKEN', - refresh_token='REFRESH_TOKEN', -) - -client = Client(oauth) -user = client.user().get() -``` - -This will retrieve the current user! From here you can use the client -you created to start making calls. - -## Other Auth Options - -For advanced uses of the SDK, three additional auth classes are -provided: - -- `CooperativelyManagedOAuth2`: Allows multiple auth instances to - share tokens. -- `RemoteOAuth2`: Allows use of the SDK on clients without access to - your application's client secret. Instead, you provide a - `retrieve_access_token` callback. That callback should perform the - token refresh, perhaps on your server that does have access to the - client secret. -- `RedisManagedOAuth2`: Stores access and refresh tokens in Redis. - This allows multiple processes (possibly spanning multiple machines) - to share access tokens while synchronizing token refresh. This could - be useful for a multiprocess web server, for example. - -# Usage Documentation - -Full documentation of the available functionality with example code is -available in the [SDK documentation -pages](https://github.com/box/box-python-sdk/blob/main/docs/usage), and -there is also method-level documentation available on -[ReadTheDocs](https://box-python-sdk.readthedocs.io/en/stable/index.html). - -## Making API Calls Manually - -The Box API is continually evolving. As such, there are API endpoints -available that are not specifically supported by the SDK. You can still -use these endpoints by using the `make_request` method of the `Client`. - -``` python -# https://developer.box.com/en/reference/get-metadata-templates-id/ -# Returns a Python dictionary containing the result of the API request -json_response = client.make_request( - 'GET', - client.get_url('metadata_templates', 'enterprise', 'customer', 'schema'), -).json() -``` - -`make_request()` takes two parameters: - -- `method` - an HTTP verb like `GET` or `POST` -- `url` - the URL of the requested API endpoint - -The `Client` class and Box objects have a `get_url` method. Pass it an -endpoint to get the correct URL for use with that object and endpoint. - -For API calls which require body or query params, you can use `**kwargs` -to pass extra params: - -- `data` - takes a jsonified dictionary of body parameters -- `params` - takes a dictionary of query parameters - -``` python -# https://developer.box.com/reference/post-folders/ -# Creates a new folder - -# JSONify the body -body = json.dumps({ - 'name': 'test-subfolder', - 'parent': { - 'id': '0', - } -}) - -client.make_request( - 'POST', - client.get_url('folders'), - params={'fields': 'name,id'}, - data=body -) +```console +pip install box-sdk-gen ``` -# Other Client Options - -## Logging Client +This is autogenerated Box SDK version. +Supported Python versions are Python 3.8 and above. -For more insight into the network calls the SDK is making, you can use -the `LoggingClient` class. This class logs information about network -requests and responses made to the Box API. +To install also extra dependencies required for JWT authentication, use command: -``` pycon ->>> from boxsdk import LoggingClient ->>> client = LoggingClient() ->>> client.user().get() -GET https://api.box.com/2.0/users/me {'headers': {u'Authorization': u'Bearer ---------------------------kBjp', - u'User-Agent': u'box-python-sdk-1.5.0'}, - 'params': None} -{"type":"user","id":"..","name":"Jeffrey Meadows","login":"..",..} - +```console +pip install "box-sdk-gen[jwt]" ``` -## Developer Token Client - -The Box Developer Console allows for the creation of short-lived -developer tokens. The SDK makes it easy to use these tokens. Use the -`get_new_token_callback` parameter to control how the client will get -new developer tokens as needed. The default is to prompt standard input -for a token. - -## Development Client - -For exploring the Box API, or to quickly get going using the SDK, the -`DevelopmentClient` class combines the `LoggingClient` with the -`DeveloperTokenClient`. - -# Customization +# Getting Started -## Custom Subclasses +To get started with the SDK, get a Developer Token from the Configuration page of your app in the [Box Developer +Console](https://app.box.com/developers/console). You can use this token to make test calls for your own Box account. -Custom object subclasses can be defined: +The SDK provides an `BoxDeveloperTokenAuth` class, which allows you to authenticate using your Developer Token. +Use instance of `BoxDeveloperTokenAuth` to initialize `BoxClient` object. +Using `BoxClient` object you can access managers, which allow you to perform some operations on your Box account. -``` pycon -from boxsdk import Client -from boxsdk import Folder +The example below demonstrates how to authenticate with Developer Token and print names of all items inside a root folder. -class MyFolderSubclass(Folder): - pass +```python +from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth -client = Client(oauth) -client.translator.register('folder', MyFolderSubclass) -folder = client.folder('0') +def main(token: str): + auth: BoxDeveloperTokenAuth = BoxDeveloperTokenAuth(token=token) + client: BoxClient = BoxClient(auth=auth) + for item in client.folders.get_folder_items('0').entries: + print(item.name) ->>> print folder ->>> +if __name__ == '__main__': + main('INSERT YOUR DEVELOPER TOKEN HERE') ``` -If an object subclass is registered in this way, instances of this -subclass will be returned from all SDK methods that previously returned -an instance of the parent. See `BaseAPIJSONObjectMeta` and `Translator` -to see how the SDK performs dynamic lookups to determine return types. - -# FIPS 140-2 Compliance - -The Python SDK allows the use of FIPS 140-2 validated SSL libraries, such as OpenSSL 3.0. -However, some actions are required to enable this functionality. - -Currently, the latest distributions of Python default to OpenSSL v1.1.1, which is not FIPS compliant. -Therefore, if you want to use OpenSSL 3.0 in your network communication, -you need to ensure that Python uses a custom SSL library. -One way to achieve this is by creating a custom Python distribution with the ssl module replaced. +# Documentation -If you are using JWT for authentication, it is also necessary to ensure that the cryptography library, -which is one of the extra dependencies for JWT, uses OpenSSL 3.0. -To enable FIPS mode for the `cryptography` library, you need to install a FIPS-compliant version of OpenSSL -during the installation process of cryptography using the `pip` command. +Browse the [docs](docs/README.md) or see [API Reference](https://developer.box.com/reference/) for more information. -# Versions -We use a modified version of [Semantic Versioning](https://semver.org/) for all changes. See [version strategy](VERSIONS.md) for details which is effective from 30 July 2022. +# Upgrades -## Supported Version +Upgrading from our legacy SDKs to the new generation SDKs is a straightforward process. See our [migration guide](migration-guide.md) and [changelog](CHANGELOG.md) for more information. -Only the current MAJOR version of SDK is supported. New features, functionality, bug fixes, and security updates will only be added to the current MAJOR version. +# Integration Tests -A current release is on the leading edge of our SDK development, and is intended for customers who are in active development and want the latest and greatest features. Instead of stating a release date for a new feature, we set a fixed minor or patch release cadence of maximum 2-3 months (while we may release more often). At the same time, there is no schedule for major or breaking release. Instead, we will communicate one quarter in advance the upcoming breaking change to allow customers to plan for the upgrade. We always recommend that all users run the latest available minor release for whatever major version is in use. We highly recommend upgrading to the latest SDK major release at the earliest convenient time and before the EOL date. +## Running integration tests locally -## Version schedule +### Create Platform Application -| Version | Supported Environments | State | First Release | EOL/Terminated | -|---------|---------------------------------------------------------|-----------|---------------|----------------| -| 3 | Python 3.6+ | Supported | 17 Jan 2022 | TBD | -| 2 | | EOL | 01 Nov 2018 | 17 Jan 2022 | -| 1 | | EOL | 10 Feb 2015 | 01 Nov 2018 | +To run integration tests locally you will need a `Custom App` created in the [Box Developer +Console](https://app.box.com/developers/console) +with `Server Authentication (with JWT)` selected as authentication method. +Once created you can edit properties of the application: -# Contributing +- In section `App Access Level` select `App + Enterprise Access`. You can enable all `Application Scopes`. +- In section `Advanced Features` enable `Make API calls using the as-user header` and `Generate user access tokens`. -See -[CONTRIBUTING.md](https://github.com/box/box-python-sdk/blob/main/CONTRIBUTING.md). +Now select `Authorization` and submit application to be reviewed by account admin. -## Developer Setup +### Export configuration -Create a virtual environment and install packages - +1. Select `Configuration` tab and in the bottom in the section `App Settings` + download your app configuration settings as JSON. +2. Encode configuration file to Base64, e.g. using command: `base64 -i path_to_json_file` +3. Set environment variable: `JWT_CONFIG_BASE_64` with base64 encoded jwt configuration file +4. Set environment variable: `BOX_FILE_REQUEST_ID` with ID of file request already created in the user account, `BOX_EXTERNAL_USER_EMAIL` with email of free external user which not belongs to any enterprise. +5. Set environment variable: `WORKFLOW_FOLDER_ID` with the ID of the Relay workflow that deletes the file that triggered the workflow. The workflow should have a manual start to be able to start it from the API. +6. Set environment variable: `APP_ITEM_ASSOCIATION_FILE_ID` to the ID of the file with associated app item and `APP_ITEM_ASSOCIATION_FOLDER_ID` to the ID of the folder with associated app item. +7. Set environment variable: `APP_ITEM_SHARED_LINK` to the shared link associated with app item. +8. Set environment variable: `SLACK_AUTOMATION_USER_ID` to the ID of the user responsible for the Slack automation, `SLACK_ORG_ID` to the ID of the Slack organization and `SLACK_PARTNER_ITEM_ID` to the ID of the Slack partner item. -``` console -mkvirtualenv boxsdk -pip install -r requirements-dev.txt -``` - -## Testing - -Run all tests using - - -``` console -tox -``` +### Running tests -The tox tests include code style checks via pep8 and pylint. +To run integration tests locally: -The tox tests are configured to run on Python 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13. +1. `pip install -r requirements-test.txt` +2. `pytest` # Questions, Bugs, and Feature Requests? Need to contact us directly? [Browse the issues -tickets](https://github.com/box/box-python-sdk/issues)! Or, if that +tickets](https://github.com/box/box-python-sdk-gen/issues)! Or, if that doesn't work, [file a new -one](https://github.com/box/box-python-sdk/issues/new) and we will get +one](https://github.com/box/box-python-sdk-gen/issues/new) and we will get back to you. If you have general questions about the Box API, you can -post to the [Box Developer -Forum](https://community.box.com/t5/Developer-Forum/bd-p/DeveloperForum). +post to the [Box Developer Forum](https://forum.box.com/). # Copyright and License - Copyright 2019 Box, Inc. All rights reserved. +Copyright 2023 Box, Inc. All rights reserved. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/VERSIONS.md b/VERSIONS.md index 41fde357a..45c972031 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -1,5 +1,6 @@ # Version Lifecycle -We use a modified version of [Semantic Versioning](https://semver.org/) for all changes. It is strongly encouraged that you pin at least the major version and potentially the minor version to avoid pulling in breaking changes. Increasing the major version of an SDK indicates that this SDK underwent significant and substantial changes to support new idioms and patterns in the language. Major versions are introduced when public interfaces (e.g. classes, methods, types, etc.), behaviours, or semantics have changed. + +We use a modified version of [Semantic Versioning](https://semver.org/) for all changes. It is strongly encouraged that you pin at least the major version and potentially the minor version to avoid pulling in breaking changes. Increasing the major version of an SDK indicates that this SDK underwent significant and substantial changes to support new idioms and patterns in the language. Major versions are introduced when public interfaces (e.g. classes, methods, types, etc.), behaviours, or semantics have changed. Semantic Versions take the form of `MAJOR.MINOR.PATCH`. @@ -11,13 +12,13 @@ The MAJOR version is used to indicate the family of technology represented by th New MAJOR versions will be communicated in advance via: -* An email announcement is sent to affected accounts, announcing our plans to end support for the specific SDK version. The email will outline the path to end-of-support, specify the campaign timelines, and provide upgrade guidance. -* Box SDK documentation, such as API reference documentation, user guides, SDK product marketing pages, and GitHub readme(s) are updated to indicate the campaign timeline and provide guidance on upgrading affected applications. -* Deprecation warnings are added to the SDKs, outlining the path to end-of-support and linking to the SDK documentation. +- An email announcement is sent to affected accounts, announcing our plans to end support for the specific SDK version. The email will outline the path to end-of-support, specify the campaign timelines, and provide upgrade guidance. +- Box SDK documentation, such as API reference documentation, user guides, SDK product marketing pages, and GitHub readme(s) are updated to indicate the campaign timeline and provide guidance on upgrading affected applications. +- Deprecation warnings are added to the SDKs, outlining the path to end-of-support and linking to the SDK documentation. Deprecations are introduced in minor releases. We will not introduce new deprecations in patch releases. These deprecations will preserve the existing behaviour while emitting a warning that provide guidance on: -* How to achieve similar behaviour if an alternative is available -* The version in which the deprecation will be enforced. +- How to achieve similar behaviour if an alternative is available +- The version in which the deprecation will be enforced. Deprecations will only be enforced in major releases. For example, if a behaviour is deprecated in version 1.2.0, it will continue to work, with a warning, for all releases in the 1.x series. The behaviour will change and the deprecation will be removed in the next major release (2.x.x). diff --git a/box_sdk_gen/__init__.py b/box_sdk_gen/__init__.py new file mode 100644 index 000000000..6b93cb29f --- /dev/null +++ b/box_sdk_gen/__init__.py @@ -0,0 +1,15 @@ +from box_sdk_gen.box import * + +from box_sdk_gen.serialization import * + +from box_sdk_gen.internal import * + +from box_sdk_gen.networking import * + +from box_sdk_gen.schemas import * + +from box_sdk_gen.parameters import * + +from box_sdk_gen.managers import * + +from box_sdk_gen.client import * diff --git a/box_sdk_gen/box/__init__.py b/box_sdk_gen/box/__init__.py new file mode 100644 index 000000000..78c141131 --- /dev/null +++ b/box_sdk_gen/box/__init__.py @@ -0,0 +1,11 @@ +from box_sdk_gen.box.errors import * + +from box_sdk_gen.box.token_storage import * + +from box_sdk_gen.box.developer_token_auth import * + +from box_sdk_gen.box.oauth import * + +from box_sdk_gen.box.jwt_auth import * + +from box_sdk_gen.box.ccg_auth import * diff --git a/box_sdk_gen/box/ccg_auth.py b/box_sdk_gen/box/ccg_auth.py new file mode 100644 index 000000000..f4dbd05a8 --- /dev/null +++ b/box_sdk_gen/box/ccg_auth.py @@ -0,0 +1,236 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenGrantTypeField + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenSubjectTokenTypeField + +from box_sdk_gen.schemas.access_token import AccessToken + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenBoxSubjectTypeField + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.box.token_storage import TokenStorage + +from box_sdk_gen.box.token_storage import InMemoryTokenStorage + +from box_sdk_gen.managers.authorization import AuthorizationManager + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2Token + +from box_sdk_gen.schemas.post_o_auth_2_revoke import PostOAuth2Revoke + + +class CCGConfig: + def __init__( + self, + client_id: str, + client_secret: str, + *, + enterprise_id: Optional[str] = None, + user_id: Optional[str] = None, + token_storage: TokenStorage = None + ): + """ + :param client_id: Box API key used for identifying the application the user is authenticating with + :type client_id: str + :param client_secret: Box API secret used for making auth requests. + :type client_secret: str + :param enterprise_id: The ID of the Box Developer Edition enterprise., defaults to None + :type enterprise_id: Optional[str], optional + :param user_id: The user id to authenticate. This value is not required. But if it is provided, then the user will be auto-authenticated at the time of the first API call., defaults to None + :type user_id: Optional[str], optional + :param token_storage: Object responsible for storing token. If no custom implementation provided,the token will be stored in memory., defaults to None + :type token_storage: TokenStorage, optional + """ + if token_storage is None: + token_storage = InMemoryTokenStorage() + self.client_id = client_id + self.client_secret = client_secret + self.enterprise_id = enterprise_id + self.user_id = user_id + self.token_storage = token_storage + + +class BoxCCGAuth(Authentication): + def __init__(self, config: CCGConfig, **kwargs): + """ + :param config: Configuration object of Client Credentials Grant auth. + :type config: CCGConfig + """ + super().__init__(**kwargs) + self.config = config + self.token_storage = self.config.token_storage + self.subject_id = ( + self.config.user_id + if not self.config.user_id == None + else self.config.enterprise_id + ) + self.subject_type = ( + PostOAuth2TokenBoxSubjectTypeField.USER + if not self.config.user_id == None + else PostOAuth2TokenBoxSubjectTypeField.ENTERPRISE + ) + + def refresh_token( + self, *, network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Get a new access token using CCG auth + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + token: AccessToken = auth_manager.request_access_token( + PostOAuth2TokenGrantTypeField.CLIENT_CREDENTIALS, + client_id=self.config.client_id, + client_secret=self.config.client_secret, + box_subject_type=self.subject_type, + box_subject_id=self.subject_id, + ) + self.token_storage.store(token) + return token + + def retrieve_token( + self, *, network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Return a current token or get a new one when not available. + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + old_token: Optional[AccessToken] = self.token_storage.get() + if old_token == None: + new_token: AccessToken = self.refresh_token(network_session=network_session) + return new_token + return old_token + + def retrieve_authorization_header( + self, *, network_session: Optional[NetworkSession] = None + ) -> str: + token: AccessToken = self.retrieve_token(network_session=network_session) + return ''.join(['Bearer ', token.access_token]) + + def with_user_subject( + self, user_id: str, *, token_storage: TokenStorage = None + ) -> 'BoxCCGAuth': + """ + Create a new BoxCCGAuth instance that uses the provided user ID as the subject ID. + + May be one of this application's created App User. Depending on the configured User Access Level, may also be any other App User or Managed User in the enterprise. + + + + + + + + :param user_id: The id of the user to authenticate + :type user_id: str + :param token_storage: Object responsible for storing token in newly created BoxCCGAuth. If no custom implementation provided, the token will be stored in memory., defaults to None + :type token_storage: TokenStorage, optional + """ + if token_storage is None: + token_storage = InMemoryTokenStorage() + new_config: CCGConfig = CCGConfig( + client_id=self.config.client_id, + client_secret=self.config.client_secret, + enterprise_id=self.config.enterprise_id, + user_id=user_id, + token_storage=token_storage, + ) + return BoxCCGAuth(config=new_config) + + def with_enterprise_subject( + self, enterprise_id: str, *, token_storage: TokenStorage = None + ) -> 'BoxCCGAuth': + """ + Create a new BoxCCGAuth instance that uses the provided enterprise ID as the subject ID. + :param enterprise_id: The id of the enterprise to authenticate + :type enterprise_id: str + :param token_storage: Object responsible for storing token in newly created BoxCCGAuth. If no custom implementation provided, the token will be stored in memory., defaults to None + :type token_storage: TokenStorage, optional + """ + if token_storage is None: + token_storage = InMemoryTokenStorage() + new_config: CCGConfig = CCGConfig( + client_id=self.config.client_id, + client_secret=self.config.client_secret, + enterprise_id=enterprise_id, + user_id=None, + token_storage=token_storage, + ) + return BoxCCGAuth(config=new_config) + + def downscope_token( + self, + scopes: List[str], + *, + resource: Optional[str] = None, + shared_link: Optional[str] = None, + network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Downscope access token to the provided scopes. Returning a new access token with the provided scopes, with the original access token unchanged. + :param scopes: The scope(s) to apply to the resulting token. + :type scopes: List[str] + :param resource: The file or folder to get a downscoped token for. If None and shared_link None, the resulting token will not be scoped down to just a single item. The resource should be a full URL to an item, e.g. https://api.box.com/2.0/files/123456., defaults to None + :type resource: Optional[str], optional + :param shared_link: The shared link to get a downscoped token for. If None and item None, the resulting token will not be scoped down to just a single item., defaults to None + :type shared_link: Optional[str], optional + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + token: Optional[AccessToken] = self.retrieve_token( + network_session=network_session + ) + if token == None: + raise BoxSDKError( + message='No access token is available. Make an API call to retrieve a token before calling this method.' + ) + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + downscoped_token: AccessToken = auth_manager.request_access_token( + PostOAuth2TokenGrantTypeField.URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE, + subject_token=token.access_token, + subject_token_type=PostOAuth2TokenSubjectTokenTypeField.URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ACCESS_TOKEN, + resource=resource, + scope=' '.join(scopes), + box_shared_link=shared_link, + ) + return downscoped_token + + def revoke_token(self, *, network_session: Optional[NetworkSession] = None) -> None: + """ + Revoke the current access token and remove it from token storage. + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + old_token: Optional[AccessToken] = self.token_storage.get() + if old_token == None: + return None + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + auth_manager.revoke_access_token( + client_id=self.config.client_id, + client_secret=self.config.client_secret, + token=old_token.access_token, + ) + self.token_storage.clear() + return None diff --git a/box_sdk_gen/box/developer_token_auth.py b/box_sdk_gen/box/developer_token_auth.py new file mode 100644 index 000000000..8c803c8a4 --- /dev/null +++ b/box_sdk_gen/box/developer_token_auth.py @@ -0,0 +1,139 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenGrantTypeField + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenSubjectTokenTypeField + +from box_sdk_gen.schemas.access_token import AccessToken + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.box.token_storage import TokenStorage + +from box_sdk_gen.box.token_storage import InMemoryTokenStorage + +from box_sdk_gen.managers.authorization import AuthorizationManager + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2Token + +from box_sdk_gen.schemas.post_o_auth_2_revoke import PostOAuth2Revoke + + +class DeveloperTokenConfig: + def __init__( + self, *, client_id: Optional[str] = None, client_secret: Optional[str] = None + ): + self.client_id = client_id + self.client_secret = client_secret + + +class BoxDeveloperTokenAuth(Authentication): + def __init__(self, token: str, *, config: DeveloperTokenConfig = None, **kwargs): + """ + :param config: Configuration object of DeveloperTokenAuth., defaults to None + :type config: DeveloperTokenConfig, optional + """ + super().__init__(**kwargs) + self.token = token + self.config = config + self.token_storage = InMemoryTokenStorage( + token=AccessToken(access_token=self.token) + ) + + def retrieve_token( + self, *, network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Retrieves stored developer token + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + token: Optional[AccessToken] = self.token_storage.get() + if token == None: + raise BoxSDKError(message='No access token is available.') + return token + + def refresh_token( + self, *, network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Developer token cannot be refreshed + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + raise BoxSDKError( + message='Developer token has expired. Please provide a new one.' + ) + + def retrieve_authorization_header( + self, *, network_session: Optional[NetworkSession] = None + ) -> str: + token: AccessToken = self.retrieve_token(network_session=network_session) + return ''.join(['Bearer ', token.access_token]) + + def revoke_token(self, *, network_session: Optional[NetworkSession] = None) -> None: + """ + Revoke an active Access Token, effectively logging a user out that has been previously authenticated. + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + token: Optional[AccessToken] = self.token_storage.get() + if token == None: + return None + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + auth_manager.revoke_access_token( + client_id=self.config.client_id, + client_secret=self.config.client_secret, + token=token.access_token, + ) + self.token_storage.clear() + return None + + def downscope_token( + self, + scopes: List[str], + *, + resource: Optional[str] = None, + shared_link: Optional[str] = None, + network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Downscope access token to the provided scopes. Returning a new access token with the provided scopes, with the original access token unchanged. + :param scopes: The scope(s) to apply to the resulting token. + :type scopes: List[str] + :param resource: The file or folder to get a downscoped token for. If None and shared_link None, the resulting token will not be scoped down to just a single item. The resource should be a full URL to an item, e.g. https://api.box.com/2.0/files/123456., defaults to None + :type resource: Optional[str], optional + :param shared_link: The shared link to get a downscoped token for. If None and item None, the resulting token will not be scoped down to just a single item., defaults to None + :type shared_link: Optional[str], optional + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + token: Optional[AccessToken] = self.retrieve_token( + network_session=network_session + ) + if token == None or token.access_token == None: + raise BoxSDKError(message='No access token is available.') + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + downscoped_token: AccessToken = auth_manager.request_access_token( + PostOAuth2TokenGrantTypeField.URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE, + subject_token=token.access_token, + subject_token_type=PostOAuth2TokenSubjectTokenTypeField.URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ACCESS_TOKEN, + resource=resource, + scope=' '.join(scopes), + box_shared_link=shared_link, + ) + return downscoped_token diff --git a/box_sdk_gen/box/errors.py b/box_sdk_gen/box/errors.py new file mode 100644 index 000000000..b1baf3d72 --- /dev/null +++ b/box_sdk_gen/box/errors.py @@ -0,0 +1,136 @@ +import pprint +from datetime import datetime +from typing import Any, Dict, Optional + +from ..internal.logging import DataSanitizer +from ..internal.errors import GeneratedCodeError + + +class BoxSDKError(GeneratedCodeError): + def __init__( + self, + message: str, + timestamp: Optional[datetime] = None, + error: Optional[Exception] = None, + **kwargs, + ): + super().__init__(message, **kwargs) + self.name = 'BoxSDKError' + self.message = message + self.timestamp = timestamp if timestamp is not None else datetime.now() + self.error = error + + def __str__(self): + return ''.join( + ( + f'\nTimestamp: {self.timestamp}', + f'\nUnderlying error: {self.error}', + f'\nMessage: {self.message}', + ) + ) + + +class RequestInfo: + def __init__( + self, + method: str, + url: str, + query_params: Dict[str, str], + headers: Dict[str, str], + body: Optional[str] = None, + ): + self.method = method + self.url = url + self.query_params = query_params + self.headers = headers + self.body = body + + def print(self, data_sanitizer: DataSanitizer): + return ''.join( + ( + f'\n\tMethod: {self.method}', + f'\n\tURL: {self.url}', + f'\n\tQuery params: \n{pprint.pformat(self.query_params, indent=8)}', + f'\n\tHeaders: \n{pprint.pformat(data_sanitizer.sanitize_headers(self.headers), indent=8)}', + ''.join( + [ + '\n\tBody: ', + '\n' if self.body else '', + pprint.pformat(self.body, indent=8), + ] + ), + ) + ) + + +class ResponseInfo: + def __init__( + self, + status_code: int, + headers: Dict[str, str], + body: Dict = None, + raw_body: Optional[str] = None, + code: Optional[str] = None, + context_info: Optional[Dict[str, Any]] = None, + request_id: Optional[str] = None, + help_url: Optional[str] = None, + ): + self.status_code = status_code + self.headers = headers + self.body = body + self.raw_body = raw_body + self.code = code + self.context_info = context_info + self.request_id = request_id + self.help_url = help_url + + def print(self, data_sanitizer: DataSanitizer): + return ''.join( + ( + f'\n\tStatus code: {self.status_code}', + f'\n\tHeaders: \n{pprint.pformat(data_sanitizer.sanitize_headers(self.headers), indent=8)}', + f'\n\tCode: {self.code}', + f'\n\tContext Info: \n{pprint.pformat(self.context_info, indent=8)}', + f'\n\tRequest Id: {self.request_id}', + f'\n\tHelp Url: {self.help_url}', + ''.join( + [ + '\n\tBody: ', + '\n' if self.body else '', + pprint.pformat( + data_sanitizer.sanitize_body(self.body), indent=8 + ), + ] + ), + ) + ) + + +class BoxAPIError(BoxSDKError): + def __init__( + self, + request_info: RequestInfo, + response_info: ResponseInfo, + message: str, + timestamp: Optional[datetime] = None, + error: Optional[str] = None, + *, + data_sanitizer: DataSanitizer = None, + **kwargs, + ): + super().__init__(message=message, timestamp=timestamp, error=error, **kwargs) + if data_sanitizer is None: + data_sanitizer = DataSanitizer() + self.name = 'BoxAPIError' + self.request_info = request_info + self.response_info = response_info + self.data_sanitizer = data_sanitizer + + def __str__(self): + return ''.join( + [ + f'\t{super(BoxAPIError, self).__str__()}', + f'\nRequest: {self.request_info.print(self.data_sanitizer)}', + f'\nResponse: {self.response_info.print(self.data_sanitizer)}', + ] + ) diff --git a/box_sdk_gen/box/jwt_auth.py b/box_sdk_gen/box/jwt_auth.py new file mode 100644 index 000000000..0040f7da9 --- /dev/null +++ b/box_sdk_gen/box/jwt_auth.py @@ -0,0 +1,478 @@ +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from box_sdk_gen.serialization.json import deserialize + +from typing import List + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenGrantTypeField + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenSubjectTokenTypeField + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.schemas.access_token import AccessToken + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2Token + +from box_sdk_gen.schemas.post_o_auth_2_revoke import PostOAuth2Revoke + +from box_sdk_gen.box.token_storage import TokenStorage + +from box_sdk_gen.box.token_storage import InMemoryTokenStorage + +from box_sdk_gen.serialization.json import json_to_serialized_data + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import read_text_from_file + +from box_sdk_gen.internal.utils import is_browser + +from box_sdk_gen.internal.utils import get_epoch_time_in_seconds + +from box_sdk_gen.internal.utils import create_jwt_assertion + +from box_sdk_gen.internal.utils import JwtSignOptions + +from box_sdk_gen.internal.utils import JwtKey + +from box_sdk_gen.internal.utils import JwtAlgorithm + +from box_sdk_gen.internal.utils import PrivateKeyDecryptor + +from box_sdk_gen.internal.utils import DefaultPrivateKeyDecryptor + +from box_sdk_gen.managers.authorization import AuthorizationManager + +from box_sdk_gen.box.errors import BoxSDKError + + +class JwtConfigAppSettingsAppAuth(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'public_key_id': 'publicKeyID', + 'private_key': 'privateKey', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'publicKeyID': 'public_key_id', + 'privateKey': 'private_key', + **BaseObject._json_to_fields_mapping, + } + + def __init__(self, public_key_id: str, private_key: str, passphrase: str, **kwargs): + """ + :param public_key_id: Public key ID + :type public_key_id: str + :param private_key: Private key + :type private_key: str + :param passphrase: Passphrase + :type passphrase: str + """ + super().__init__(**kwargs) + self.public_key_id = public_key_id + self.private_key = private_key + self.passphrase = passphrase + + +class JwtConfigAppSettings(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'client_id': 'clientID', + 'client_secret': 'clientSecret', + 'app_auth': 'appAuth', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'clientID': 'client_id', + 'clientSecret': 'client_secret', + 'appAuth': 'app_auth', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + client_id: str, + client_secret: str, + app_auth: JwtConfigAppSettingsAppAuth, + **kwargs + ): + """ + :param client_id: App client ID + :type client_id: str + :param client_secret: App client secret + :type client_secret: str + :param app_auth: App auth settings + :type app_auth: JwtConfigAppSettingsAppAuth + """ + super().__init__(**kwargs) + self.client_id = client_id + self.client_secret = client_secret + self.app_auth = app_auth + + +class JwtConfigFile(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'enterprise_id': 'enterpriseID', + 'user_id': 'userID', + 'box_app_settings': 'boxAppSettings', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'enterpriseID': 'enterprise_id', + 'userID': 'user_id', + 'boxAppSettings': 'box_app_settings', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + box_app_settings: JwtConfigAppSettings, + *, + enterprise_id: Optional[str] = None, + user_id: Optional[str] = None, + **kwargs + ): + """ + :param box_app_settings: App settings + :type box_app_settings: JwtConfigAppSettings + :param enterprise_id: Enterprise ID, defaults to None + :type enterprise_id: Optional[str], optional + :param user_id: User ID, defaults to None + :type user_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.box_app_settings = box_app_settings + self.enterprise_id = enterprise_id + self.user_id = user_id + + +class JWTConfig: + def __init__( + self, + client_id: str, + client_secret: str, + jwt_key_id: str, + private_key: str, + private_key_passphrase: str, + *, + enterprise_id: Optional[str] = None, + user_id: Optional[str] = None, + algorithm: Optional[JwtAlgorithm] = JwtAlgorithm.RS256, + token_storage: TokenStorage = None, + private_key_decryptor: PrivateKeyDecryptor = None + ): + """ + :param client_id: App client ID + :type client_id: str + :param client_secret: App client secret + :type client_secret: str + :param jwt_key_id: Public key ID + :type jwt_key_id: str + :param private_key: Private key + :type private_key: str + :param private_key_passphrase: Passphrase + :type private_key_passphrase: str + :param enterprise_id: Enterprise ID, defaults to None + :type enterprise_id: Optional[str], optional + :param user_id: User ID, defaults to None + :type user_id: Optional[str], optional + """ + if token_storage is None: + token_storage = InMemoryTokenStorage() + if private_key_decryptor is None: + private_key_decryptor = DefaultPrivateKeyDecryptor() + self.client_id = client_id + self.client_secret = client_secret + self.jwt_key_id = jwt_key_id + self.private_key = private_key + self.private_key_passphrase = private_key_passphrase + self.enterprise_id = enterprise_id + self.user_id = user_id + self.algorithm = algorithm + self.token_storage = token_storage + self.private_key_decryptor = private_key_decryptor + + @staticmethod + def from_config_json_string( + config_json_string: str, + *, + token_storage: Optional[TokenStorage] = None, + private_key_decryptor: Optional[PrivateKeyDecryptor] = None + ) -> 'JWTConfig': + """ + Create an auth instance as defined by a string content of JSON file downloaded from the Box Developer Console. + + See https://developer.box.com/en/guides/authentication/jwt/ for more information. + + :param config_json_string: String content of JSON file containing the configuration. + :type config_json_string: str + :param token_storage: Object responsible for storing token. If no custom implementation provided, the token will be stored in memory, defaults to None + :type token_storage: Optional[TokenStorage], optional + :param private_key_decryptor: Object responsible for decrypting private key for jwt auth. If no custom implementation provided, the DefaultPrivateKeyDecryptor will be used., defaults to None + :type private_key_decryptor: Optional[PrivateKeyDecryptor], optional + """ + config_json: JwtConfigFile = deserialize( + json_to_serialized_data(config_json_string), JwtConfigFile + ) + token_storage_to_use: Optional[TokenStorage] = ( + InMemoryTokenStorage() if token_storage == None else token_storage + ) + private_key_decryptor_to_use: Optional[PrivateKeyDecryptor] = ( + DefaultPrivateKeyDecryptor() + if private_key_decryptor == None + else private_key_decryptor + ) + new_config: 'JWTConfig' = JWTConfig( + client_id=config_json.box_app_settings.client_id, + client_secret=config_json.box_app_settings.client_secret, + enterprise_id=config_json.enterprise_id, + user_id=config_json.user_id, + jwt_key_id=config_json.box_app_settings.app_auth.public_key_id, + private_key=config_json.box_app_settings.app_auth.private_key, + private_key_passphrase=config_json.box_app_settings.app_auth.passphrase, + token_storage=token_storage_to_use, + private_key_decryptor=private_key_decryptor_to_use, + ) + return new_config + + @staticmethod + def from_config_file( + config_file_path: str, + *, + token_storage: Optional[TokenStorage] = None, + private_key_decryptor: Optional[PrivateKeyDecryptor] = None + ) -> 'JWTConfig': + """ + Create an auth instance as defined by a JSON file downloaded from the Box Developer Console. + + See https://developer.box.com/en/guides/authentication/jwt/ for more information. + + :param config_file_path: Path to the JSON file containing the configuration. + :type config_file_path: str + :param token_storage: Object responsible for storing token. If no custom implementation provided, the token will be stored in memory., defaults to None + :type token_storage: Optional[TokenStorage], optional + :param private_key_decryptor: Object responsible for decrypting private key for jwt auth. If no custom implementation provided, the DefaultPrivateKeyDecryptor will be used., defaults to None + :type private_key_decryptor: Optional[PrivateKeyDecryptor], optional + """ + config_json_string: str = read_text_from_file(config_file_path) + return JWTConfig.from_config_json_string( + config_json_string, + token_storage=token_storage, + private_key_decryptor=private_key_decryptor, + ) + + +class BoxJWTAuth(Authentication): + def __init__(self, config: JWTConfig, **kwargs): + """ + :param config: An object containing all JWT configuration to use for authentication + :type config: JWTConfig + """ + super().__init__(**kwargs) + self.config = config + self.token_storage = self.config.token_storage + self.subject_id = ( + self.config.enterprise_id + if not self.config.enterprise_id == None + else self.config.user_id + ) + self.subject_type = ( + 'enterprise' if not self.config.enterprise_id == None else 'user' + ) + + def refresh_token( + self, *, network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Get new access token using JWT auth. + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + if is_browser(): + raise BoxSDKError( + message='JWT auth is not supported in browser environment.' + ) + alg: JwtAlgorithm = ( + self.config.algorithm + if not self.config.algorithm == None + else JwtAlgorithm.RS256 + ) + claims: Dict = { + 'exp': get_epoch_time_in_seconds() + 30, + 'box_sub_type': self.subject_type, + } + jwt_options: JwtSignOptions = JwtSignOptions( + algorithm=alg, + audience='https://api.box.com/oauth2/token', + subject=self.subject_id, + issuer=self.config.client_id, + jwtid=get_uuid(), + keyid=self.config.jwt_key_id, + private_key_decryptor=self.config.private_key_decryptor, + ) + jwt_key: JwtKey = JwtKey( + key=self.config.private_key, passphrase=self.config.private_key_passphrase + ) + assertion: str = create_jwt_assertion(claims, jwt_key, jwt_options) + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + token: AccessToken = auth_manager.request_access_token( + PostOAuth2TokenGrantTypeField.URN_IETF_PARAMS_OAUTH_GRANT_TYPE_JWT_BEARER, + assertion=assertion, + client_id=self.config.client_id, + client_secret=self.config.client_secret, + ) + self.token_storage.store(token) + return token + + def retrieve_token( + self, *, network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Get the current access token. If the current access token is expired or not found, this method will attempt to refresh the token. + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + old_token: Optional[AccessToken] = self.token_storage.get() + if old_token == None: + new_token: AccessToken = self.refresh_token(network_session=network_session) + return new_token + return old_token + + def retrieve_authorization_header( + self, *, network_session: Optional[NetworkSession] = None + ) -> str: + token: AccessToken = self.retrieve_token(network_session=network_session) + return ''.join(['Bearer ', token.access_token]) + + def with_user_subject( + self, user_id: str, *, token_storage: TokenStorage = None + ) -> 'BoxJWTAuth': + """ + Create a new BoxJWTAuth instance that uses the provided user ID as the subject of the JWT assertion. + + May be one of this application's created App User. Depending on the configured User Access Level, may also be any other App User or Managed User in the enterprise. + + + + + + + + :param user_id: The id of the user to authenticate + :type user_id: str + :param token_storage: Object responsible for storing token in newly created BoxJWTAuth. If no custom implementation provided, the token will be stored in memory., defaults to None + :type token_storage: TokenStorage, optional + """ + if token_storage is None: + token_storage = InMemoryTokenStorage() + new_config: JWTConfig = JWTConfig( + client_id=self.config.client_id, + client_secret=self.config.client_secret, + enterprise_id=None, + user_id=user_id, + jwt_key_id=self.config.jwt_key_id, + private_key=self.config.private_key, + private_key_passphrase=self.config.private_key_passphrase, + token_storage=token_storage, + ) + new_auth: 'BoxJWTAuth' = BoxJWTAuth(config=new_config) + return new_auth + + def with_enterprise_subject( + self, enterprise_id: str, *, token_storage: TokenStorage = None + ) -> 'BoxJWTAuth': + """ + Create a new BoxJWTAuth instance that uses the provided enterprise ID as the subject of the JWT assertion. + :param enterprise_id: The id of the enterprise to authenticate + :type enterprise_id: str + :param token_storage: Object responsible for storing token in newly created BoxJWTAuth. If no custom implementation provided, the token will be stored in memory., defaults to None + :type token_storage: TokenStorage, optional + """ + if token_storage is None: + token_storage = InMemoryTokenStorage() + new_config: JWTConfig = JWTConfig( + client_id=self.config.client_id, + client_secret=self.config.client_secret, + enterprise_id=enterprise_id, + user_id=None, + jwt_key_id=self.config.jwt_key_id, + private_key=self.config.private_key, + private_key_passphrase=self.config.private_key_passphrase, + token_storage=token_storage, + ) + new_auth: 'BoxJWTAuth' = BoxJWTAuth(config=new_config) + return new_auth + + def downscope_token( + self, + scopes: List[str], + *, + resource: Optional[str] = None, + shared_link: Optional[str] = None, + network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Downscope access token to the provided scopes. Returning a new access token with the provided scopes, with the original access token unchanged. + :param scopes: The scope(s) to apply to the resulting token. + :type scopes: List[str] + :param resource: The file or folder to get a downscoped token for. If None and shared_link None, the resulting token will not be scoped down to just a single item. The resource should be a full URL to an item, e.g. https://api.box.com/2.0/files/123456., defaults to None + :type resource: Optional[str], optional + :param shared_link: The shared link to get a downscoped token for. If None and item None, the resulting token will not be scoped down to just a single item., defaults to None + :type shared_link: Optional[str], optional + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + token: Optional[AccessToken] = self.retrieve_token( + network_session=network_session + ) + if token == None: + raise BoxSDKError( + message='No access token is available. Make an API call to retrieve a token before calling this method.' + ) + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + downscoped_token: AccessToken = auth_manager.request_access_token( + PostOAuth2TokenGrantTypeField.URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE, + subject_token=token.access_token, + subject_token_type=PostOAuth2TokenSubjectTokenTypeField.URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ACCESS_TOKEN, + resource=resource, + scope=' '.join(scopes), + box_shared_link=shared_link, + ) + return downscoped_token + + def revoke_token(self, *, network_session: Optional[NetworkSession] = None) -> None: + """ + Revoke the current access token and remove it from token storage. + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + old_token: Optional[AccessToken] = self.token_storage.get() + if old_token == None: + return None + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + auth_manager.revoke_access_token( + client_id=self.config.client_id, + client_secret=self.config.client_secret, + token=old_token.access_token, + ) + self.token_storage.clear() + return None diff --git a/box_sdk_gen/box/oauth.py b/box_sdk_gen/box/oauth.py new file mode 100644 index 000000000..1c62ead04 --- /dev/null +++ b/box_sdk_gen/box/oauth.py @@ -0,0 +1,250 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.serialization.json import serialize + +from typing import List + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenGrantTypeField + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenSubjectTokenTypeField + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.schemas.access_token import AccessToken + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2Token + +from box_sdk_gen.schemas.post_o_auth_2_revoke import PostOAuth2Revoke + +from box_sdk_gen.managers.authorization import AuthorizationManager + +from box_sdk_gen.box.token_storage import TokenStorage + +from box_sdk_gen.box.token_storage import InMemoryTokenStorage + +from box_sdk_gen.serialization.json import sd_to_url_params + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.serialization.json import SerializedData + + +class OAuthConfig: + def __init__( + self, client_id: str, client_secret: str, *, token_storage: TokenStorage = None + ): + if token_storage is None: + token_storage = InMemoryTokenStorage() + self.client_id = client_id + self.client_secret = client_secret + self.token_storage = token_storage + + +class GetAuthorizeUrlOptions: + def __init__( + self, + *, + client_id: Optional[str] = None, + redirect_uri: Optional[str] = None, + response_type: Optional[str] = None, + state: Optional[str] = None, + scope: Optional[str] = None + ): + """ + :param client_id: Box API key used for identifying the application the user is authenticating with, defaults to None + :type client_id: Optional[str], optional + :param redirect_uri: The URI to which Box redirects the browser after the user has granted or denied the application permission. This URI match one of the redirect URIs in the configuration of your application., defaults to None + :type redirect_uri: Optional[str], optional + :param response_type: The type of response we would like to receive., defaults to None + :type response_type: Optional[str], optional + :param state: A custom string of your choice. Box will pass the same string to the redirect URL when authentication is complete. This parameter can be used to identify a user on redirect, as well as protect against hijacked sessions and other exploits., defaults to None + :type state: Optional[str], optional + :param scope: A space-separated list of application scopes you'd like to authenticate the user for. This defaults to all the scopes configured for the application in its configuration page., defaults to None + :type scope: Optional[str], optional + """ + self.client_id = client_id + self.redirect_uri = redirect_uri + self.response_type = response_type + self.state = state + self.scope = scope + + +class BoxOAuth(Authentication): + def __init__(self, config: OAuthConfig, **kwargs): + """ + :param config: Configuration object of OAuth. + :type config: OAuthConfig + """ + super().__init__(**kwargs) + self.config = config + self.token_storage = self.config.token_storage + + def get_authorize_url(self, *, options: GetAuthorizeUrlOptions = None) -> str: + """ + Get the authorization URL for the app user. + """ + if options is None: + options = GetAuthorizeUrlOptions() + params_map: Dict[str, str] = prepare_params( + { + 'client_id': ( + options.client_id + if not options.client_id == None + else self.config.client_id + ), + 'response_type': ( + options.response_type + if not options.response_type == None + else 'code' + ), + 'redirect_uri': options.redirect_uri, + 'state': options.state, + 'scope': options.scope, + } + ) + return ''.join( + [ + 'https://account.box.com/api/oauth2/authorize?', + sd_to_url_params(serialize(params_map)), + ] + ) + + def get_tokens_authorization_code_grant( + self, + authorization_code: str, + *, + network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Acquires token info using an authorization code. + :param authorization_code: The authorization code to use to get tokens. + :type authorization_code: str + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + token: AccessToken = auth_manager.request_access_token( + PostOAuth2TokenGrantTypeField.AUTHORIZATION_CODE, + code=authorization_code, + client_id=self.config.client_id, + client_secret=self.config.client_secret, + ) + self.token_storage.store(token) + return token + + def retrieve_token( + self, *, network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Get the current access token. If the current access token is expired or not found, this method will attempt to refresh the token. + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + token: Optional[AccessToken] = self.token_storage.get() + if token == None: + raise BoxSDKError( + message='Access and refresh tokens not available. Authenticate before making any API call first.' + ) + return token + + def refresh_token( + self, *, network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Get a new access token for the platform app user. + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + old_token: Optional[AccessToken] = self.token_storage.get() + token_used_for_refresh: Optional[str] = ( + old_token.refresh_token if not old_token == None else None + ) + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + token: AccessToken = auth_manager.request_access_token( + PostOAuth2TokenGrantTypeField.REFRESH_TOKEN, + client_id=self.config.client_id, + client_secret=self.config.client_secret, + refresh_token=token_used_for_refresh, + ) + self.token_storage.store(token) + return token + + def retrieve_authorization_header( + self, *, network_session: Optional[NetworkSession] = None + ) -> str: + token: AccessToken = self.retrieve_token(network_session=network_session) + return ''.join(['Bearer ', token.access_token]) + + def revoke_token(self, *, network_session: Optional[NetworkSession] = None) -> None: + """ + Revoke an active Access Token, effectively logging a user out that has been previously authenticated. + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + token: Optional[AccessToken] = self.token_storage.get() + if token == None: + return None + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + auth_manager.revoke_access_token( + client_id=self.config.client_id, + client_secret=self.config.client_secret, + token=token.access_token, + ) + return None + + def downscope_token( + self, + scopes: List[str], + *, + resource: Optional[str] = None, + shared_link: Optional[str] = None, + network_session: Optional[NetworkSession] = None + ) -> AccessToken: + """ + Downscope access token to the provided scopes. Returning a new access token with the provided scopes, with the original access token unchanged. + :param scopes: The scope(s) to apply to the resulting token. + :type scopes: List[str] + :param resource: The file or folder to get a downscoped token for. If None and shared_link None, the resulting token will not be scoped down to just a single item. The resource should be a full URL to an item, e.g. https://api.box.com/2.0/files/123456., defaults to None + :type resource: Optional[str], optional + :param shared_link: The shared link to get a downscoped token for. If None and item None, the resulting token will not be scoped down to just a single item., defaults to None + :type shared_link: Optional[str], optional + :param network_session: An object to keep network session state, defaults to None + :type network_session: Optional[NetworkSession], optional + """ + token: Optional[AccessToken] = self.retrieve_token( + network_session=network_session + ) + if token == None or token.access_token == None: + raise BoxSDKError(message='No access token is available.') + auth_manager: AuthorizationManager = AuthorizationManager( + network_session=( + network_session if not network_session == None else NetworkSession() + ) + ) + downscoped_token: AccessToken = auth_manager.request_access_token( + PostOAuth2TokenGrantTypeField.URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE, + subject_token=token.access_token, + subject_token_type=PostOAuth2TokenSubjectTokenTypeField.URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ACCESS_TOKEN, + resource=resource, + scope=' '.join(scopes), + box_shared_link=shared_link, + ) + return downscoped_token diff --git a/box_sdk_gen/box/token_storage.py b/box_sdk_gen/box/token_storage.py new file mode 100644 index 000000000..5b4d2e4b7 --- /dev/null +++ b/box_sdk_gen/box/token_storage.py @@ -0,0 +1,74 @@ +import shelve +from abc import abstractmethod +from typing import Optional + +from ..schemas.access_token import AccessToken + + +class TokenStorage: + @abstractmethod + def store(self, token: AccessToken) -> None: + pass + + @abstractmethod + def get(self) -> Optional[AccessToken]: + pass + + @abstractmethod + def clear(self) -> None: + pass + + +class InMemoryTokenStorage(TokenStorage): + def __init__(self, token: Optional[AccessToken] = None): + self._token = token + + def store(self, token: AccessToken) -> None: + self._token = token + + def get(self) -> Optional[AccessToken]: + return self._token + + def clear(self) -> None: + self._token = None + + +class FileTokenStorage(TokenStorage): + def __init__(self, filename: str = 'token_storage'): + self.filename = filename + + def store(self, token: AccessToken) -> None: + with shelve.open(self.filename) as file: + file['token'] = token + + def get(self) -> Optional[AccessToken]: + with shelve.open(self.filename) as file: + return file.get('token', None) + + def clear(self) -> None: + with shelve.open(self.filename) as file: + if 'token' in file: + del file['token'] + + +class FileWithInMemoryCacheTokenStorage(TokenStorage): + def __init__(self, filename: str = 'token_storage'): + self.filename = filename + self.cached_token: Optional[AccessToken] = None + + def store(self, token: AccessToken) -> None: + with shelve.open(self.filename) as file: + file['token'] = token + self.cached_token = token + + def get(self) -> Optional[AccessToken]: + if self.cached_token is None: + with shelve.open(self.filename) as file: + self.cached_token = file.get('token', None) + return self.cached_token + + def clear(self) -> None: + with shelve.open(self.filename) as file: + if 'token' in file: + del file['token'] + self.cached_token = None diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py new file mode 100644 index 000000000..b49eebb45 --- /dev/null +++ b/box_sdk_gen/client.py @@ -0,0 +1,530 @@ +from typing import Dict + +from box_sdk_gen.managers.authorization import AuthorizationManager + +from box_sdk_gen.managers.files import FilesManager + +from box_sdk_gen.managers.trashed_files import TrashedFilesManager + +from box_sdk_gen.managers.app_item_associations import AppItemAssociationsManager + +from box_sdk_gen.managers.downloads import DownloadsManager + +from box_sdk_gen.managers.uploads import UploadsManager + +from box_sdk_gen.managers.chunked_uploads import ChunkedUploadsManager + +from box_sdk_gen.managers.list_collaborations import ListCollaborationsManager + +from box_sdk_gen.managers.comments import CommentsManager + +from box_sdk_gen.managers.tasks import TasksManager + +from box_sdk_gen.managers.file_versions import FileVersionsManager + +from box_sdk_gen.managers.file_metadata import FileMetadataManager + +from box_sdk_gen.managers.file_classifications import FileClassificationsManager + +from box_sdk_gen.managers.skills import SkillsManager + +from box_sdk_gen.managers.file_watermarks import FileWatermarksManager + +from box_sdk_gen.managers.file_requests import FileRequestsManager + +from box_sdk_gen.managers.folders import FoldersManager + +from box_sdk_gen.managers.trashed_folders import TrashedFoldersManager + +from box_sdk_gen.managers.folder_metadata import FolderMetadataManager + +from box_sdk_gen.managers.folder_classifications import FolderClassificationsManager + +from box_sdk_gen.managers.trashed_items import TrashedItemsManager + +from box_sdk_gen.managers.folder_watermarks import FolderWatermarksManager + +from box_sdk_gen.managers.folder_locks import FolderLocksManager + +from box_sdk_gen.managers.metadata_templates import MetadataTemplatesManager + +from box_sdk_gen.managers.classifications import ClassificationsManager + +from box_sdk_gen.managers.metadata_cascade_policies import ( + MetadataCascadePoliciesManager, +) + +from box_sdk_gen.managers.search import SearchManager + +from box_sdk_gen.managers.user_collaborations import UserCollaborationsManager + +from box_sdk_gen.managers.task_assignments import TaskAssignmentsManager + +from box_sdk_gen.managers.shared_links_files import SharedLinksFilesManager + +from box_sdk_gen.managers.shared_links_folders import SharedLinksFoldersManager + +from box_sdk_gen.managers.web_links import WebLinksManager + +from box_sdk_gen.managers.trashed_web_links import TrashedWebLinksManager + +from box_sdk_gen.managers.shared_links_web_links import SharedLinksWebLinksManager + +from box_sdk_gen.managers.shared_links_app_items import SharedLinksAppItemsManager + +from box_sdk_gen.managers.users import UsersManager + +from box_sdk_gen.managers.session_termination import SessionTerminationManager + +from box_sdk_gen.managers.avatars import AvatarsManager + +from box_sdk_gen.managers.transfer import TransferManager + +from box_sdk_gen.managers.email_aliases import EmailAliasesManager + +from box_sdk_gen.managers.memberships import MembershipsManager + +from box_sdk_gen.managers.invites import InvitesManager + +from box_sdk_gen.managers.groups import GroupsManager + +from box_sdk_gen.managers.webhooks import WebhooksManager + +from box_sdk_gen.managers.events import EventsManager + +from box_sdk_gen.managers.collections import CollectionsManager + +from box_sdk_gen.managers.recent_items import RecentItemsManager + +from box_sdk_gen.managers.retention_policies import RetentionPoliciesManager + +from box_sdk_gen.managers.retention_policy_assignments import ( + RetentionPolicyAssignmentsManager, +) + +from box_sdk_gen.managers.legal_hold_policies import LegalHoldPoliciesManager + +from box_sdk_gen.managers.legal_hold_policy_assignments import ( + LegalHoldPolicyAssignmentsManager, +) + +from box_sdk_gen.managers.file_version_retentions import FileVersionRetentionsManager + +from box_sdk_gen.managers.file_version_legal_holds import FileVersionLegalHoldsManager + +from box_sdk_gen.managers.shield_information_barriers import ( + ShieldInformationBarriersManager, +) + +from box_sdk_gen.managers.shield_information_barrier_reports import ( + ShieldInformationBarrierReportsManager, +) + +from box_sdk_gen.managers.shield_information_barrier_segments import ( + ShieldInformationBarrierSegmentsManager, +) + +from box_sdk_gen.managers.shield_information_barrier_segment_members import ( + ShieldInformationBarrierSegmentMembersManager, +) + +from box_sdk_gen.managers.shield_information_barrier_segment_restrictions import ( + ShieldInformationBarrierSegmentRestrictionsManager, +) + +from box_sdk_gen.managers.device_pinners import DevicePinnersManager + +from box_sdk_gen.managers.terms_of_services import TermsOfServicesManager + +from box_sdk_gen.managers.terms_of_service_user_statuses import ( + TermsOfServiceUserStatusesManager, +) + +from box_sdk_gen.managers.collaboration_allowlist_entries import ( + CollaborationAllowlistEntriesManager, +) + +from box_sdk_gen.managers.collaboration_allowlist_exempt_targets import ( + CollaborationAllowlistExemptTargetsManager, +) + +from box_sdk_gen.managers.storage_policies import StoragePoliciesManager + +from box_sdk_gen.managers.storage_policy_assignments import ( + StoragePolicyAssignmentsManager, +) + +from box_sdk_gen.managers.zip_downloads import ZipDownloadsManager + +from box_sdk_gen.managers.sign_requests import SignRequestsManager + +from box_sdk_gen.managers.workflows import WorkflowsManager + +from box_sdk_gen.managers.sign_templates import SignTemplatesManager + +from box_sdk_gen.managers.integration_mappings import IntegrationMappingsManager + +from box_sdk_gen.managers.ai import AiManager + +from box_sdk_gen.managers.ai_studio import AiStudioManager + +from box_sdk_gen.managers.docgen_template import DocgenTemplateManager + +from box_sdk_gen.managers.docgen import DocgenManager + +from box_sdk_gen.managers.hubs import HubsManager + +from box_sdk_gen.managers.hub_collaborations import HubCollaborationsManager + +from box_sdk_gen.managers.hub_items import HubItemsManager + +from box_sdk_gen.managers.shield_lists import ShieldListsManager + +from box_sdk_gen.managers.archives import ArchivesManager + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.networking.base_urls import BaseUrls + +from box_sdk_gen.networking.proxy_config import ProxyConfig + + +class BoxClient: + def __init__(self, auth: Authentication, *, network_session: NetworkSession = None): + if network_session is None: + network_session = NetworkSession(base_urls=BaseUrls()) + self.auth = auth + self.network_session = network_session + self.authorization = AuthorizationManager( + auth=self.auth, network_session=self.network_session + ) + self.files = FilesManager(auth=self.auth, network_session=self.network_session) + self.trashed_files = TrashedFilesManager( + auth=self.auth, network_session=self.network_session + ) + self.app_item_associations = AppItemAssociationsManager( + auth=self.auth, network_session=self.network_session + ) + self.downloads = DownloadsManager( + auth=self.auth, network_session=self.network_session + ) + self.uploads = UploadsManager( + auth=self.auth, network_session=self.network_session + ) + self.chunked_uploads = ChunkedUploadsManager( + auth=self.auth, network_session=self.network_session + ) + self.list_collaborations = ListCollaborationsManager( + auth=self.auth, network_session=self.network_session + ) + self.comments = CommentsManager( + auth=self.auth, network_session=self.network_session + ) + self.tasks = TasksManager(auth=self.auth, network_session=self.network_session) + self.file_versions = FileVersionsManager( + auth=self.auth, network_session=self.network_session + ) + self.file_metadata = FileMetadataManager( + auth=self.auth, network_session=self.network_session + ) + self.file_classifications = FileClassificationsManager( + auth=self.auth, network_session=self.network_session + ) + self.skills = SkillsManager( + auth=self.auth, network_session=self.network_session + ) + self.file_watermarks = FileWatermarksManager( + auth=self.auth, network_session=self.network_session + ) + self.file_requests = FileRequestsManager( + auth=self.auth, network_session=self.network_session + ) + self.folders = FoldersManager( + auth=self.auth, network_session=self.network_session + ) + self.trashed_folders = TrashedFoldersManager( + auth=self.auth, network_session=self.network_session + ) + self.folder_metadata = FolderMetadataManager( + auth=self.auth, network_session=self.network_session + ) + self.folder_classifications = FolderClassificationsManager( + auth=self.auth, network_session=self.network_session + ) + self.trashed_items = TrashedItemsManager( + auth=self.auth, network_session=self.network_session + ) + self.folder_watermarks = FolderWatermarksManager( + auth=self.auth, network_session=self.network_session + ) + self.folder_locks = FolderLocksManager( + auth=self.auth, network_session=self.network_session + ) + self.metadata_templates = MetadataTemplatesManager( + auth=self.auth, network_session=self.network_session + ) + self.classifications = ClassificationsManager( + auth=self.auth, network_session=self.network_session + ) + self.metadata_cascade_policies = MetadataCascadePoliciesManager( + auth=self.auth, network_session=self.network_session + ) + self.search = SearchManager( + auth=self.auth, network_session=self.network_session + ) + self.user_collaborations = UserCollaborationsManager( + auth=self.auth, network_session=self.network_session + ) + self.task_assignments = TaskAssignmentsManager( + auth=self.auth, network_session=self.network_session + ) + self.shared_links_files = SharedLinksFilesManager( + auth=self.auth, network_session=self.network_session + ) + self.shared_links_folders = SharedLinksFoldersManager( + auth=self.auth, network_session=self.network_session + ) + self.web_links = WebLinksManager( + auth=self.auth, network_session=self.network_session + ) + self.trashed_web_links = TrashedWebLinksManager( + auth=self.auth, network_session=self.network_session + ) + self.shared_links_web_links = SharedLinksWebLinksManager( + auth=self.auth, network_session=self.network_session + ) + self.shared_links_app_items = SharedLinksAppItemsManager( + auth=self.auth, network_session=self.network_session + ) + self.users = UsersManager(auth=self.auth, network_session=self.network_session) + self.session_termination = SessionTerminationManager( + auth=self.auth, network_session=self.network_session + ) + self.avatars = AvatarsManager( + auth=self.auth, network_session=self.network_session + ) + self.transfer = TransferManager( + auth=self.auth, network_session=self.network_session + ) + self.email_aliases = EmailAliasesManager( + auth=self.auth, network_session=self.network_session + ) + self.memberships = MembershipsManager( + auth=self.auth, network_session=self.network_session + ) + self.invites = InvitesManager( + auth=self.auth, network_session=self.network_session + ) + self.groups = GroupsManager( + auth=self.auth, network_session=self.network_session + ) + self.webhooks = WebhooksManager( + auth=self.auth, network_session=self.network_session + ) + self.events = EventsManager( + auth=self.auth, network_session=self.network_session + ) + self.collections = CollectionsManager( + auth=self.auth, network_session=self.network_session + ) + self.recent_items = RecentItemsManager( + auth=self.auth, network_session=self.network_session + ) + self.retention_policies = RetentionPoliciesManager( + auth=self.auth, network_session=self.network_session + ) + self.retention_policy_assignments = RetentionPolicyAssignmentsManager( + auth=self.auth, network_session=self.network_session + ) + self.legal_hold_policies = LegalHoldPoliciesManager( + auth=self.auth, network_session=self.network_session + ) + self.legal_hold_policy_assignments = LegalHoldPolicyAssignmentsManager( + auth=self.auth, network_session=self.network_session + ) + self.file_version_retentions = FileVersionRetentionsManager( + auth=self.auth, network_session=self.network_session + ) + self.file_version_legal_holds = FileVersionLegalHoldsManager( + auth=self.auth, network_session=self.network_session + ) + self.shield_information_barriers = ShieldInformationBarriersManager( + auth=self.auth, network_session=self.network_session + ) + self.shield_information_barrier_reports = ( + ShieldInformationBarrierReportsManager( + auth=self.auth, network_session=self.network_session + ) + ) + self.shield_information_barrier_segments = ( + ShieldInformationBarrierSegmentsManager( + auth=self.auth, network_session=self.network_session + ) + ) + self.shield_information_barrier_segment_members = ( + ShieldInformationBarrierSegmentMembersManager( + auth=self.auth, network_session=self.network_session + ) + ) + self.shield_information_barrier_segment_restrictions = ( + ShieldInformationBarrierSegmentRestrictionsManager( + auth=self.auth, network_session=self.network_session + ) + ) + self.device_pinners = DevicePinnersManager( + auth=self.auth, network_session=self.network_session + ) + self.terms_of_services = TermsOfServicesManager( + auth=self.auth, network_session=self.network_session + ) + self.terms_of_service_user_statuses = TermsOfServiceUserStatusesManager( + auth=self.auth, network_session=self.network_session + ) + self.collaboration_allowlist_entries = CollaborationAllowlistEntriesManager( + auth=self.auth, network_session=self.network_session + ) + self.collaboration_allowlist_exempt_targets = ( + CollaborationAllowlistExemptTargetsManager( + auth=self.auth, network_session=self.network_session + ) + ) + self.storage_policies = StoragePoliciesManager( + auth=self.auth, network_session=self.network_session + ) + self.storage_policy_assignments = StoragePolicyAssignmentsManager( + auth=self.auth, network_session=self.network_session + ) + self.zip_downloads = ZipDownloadsManager( + auth=self.auth, network_session=self.network_session + ) + self.sign_requests = SignRequestsManager( + auth=self.auth, network_session=self.network_session + ) + self.workflows = WorkflowsManager( + auth=self.auth, network_session=self.network_session + ) + self.sign_templates = SignTemplatesManager( + auth=self.auth, network_session=self.network_session + ) + self.integration_mappings = IntegrationMappingsManager( + auth=self.auth, network_session=self.network_session + ) + self.ai = AiManager(auth=self.auth, network_session=self.network_session) + self.ai_studio = AiStudioManager( + auth=self.auth, network_session=self.network_session + ) + self.docgen_template = DocgenTemplateManager( + auth=self.auth, network_session=self.network_session + ) + self.docgen = DocgenManager( + auth=self.auth, network_session=self.network_session + ) + self.hubs = HubsManager(auth=self.auth, network_session=self.network_session) + self.hub_collaborations = HubCollaborationsManager( + auth=self.auth, network_session=self.network_session + ) + self.hub_items = HubItemsManager( + auth=self.auth, network_session=self.network_session + ) + self.shield_lists = ShieldListsManager( + auth=self.auth, network_session=self.network_session + ) + self.archives = ArchivesManager( + auth=self.auth, network_session=self.network_session + ) + + def make_request(self, fetch_options: FetchOptions) -> FetchResponse: + """ + Make a custom http request using the client authentication and network session. + :param fetch_options: Options to be passed to the fetch call + :type fetch_options: FetchOptions + """ + auth: Authentication = ( + self.auth if fetch_options.auth == None else fetch_options.auth + ) + network_session: NetworkSession = ( + self.network_session + if fetch_options.network_session == None + else fetch_options.network_session + ) + enriched_fetch_options: FetchOptions = FetchOptions( + auth=auth, + network_session=network_session, + url=fetch_options.url, + method=fetch_options.method, + params=fetch_options.params, + headers=fetch_options.headers, + data=fetch_options.data, + file_stream=fetch_options.file_stream, + multipart_data=fetch_options.multipart_data, + content_type=fetch_options.content_type, + response_format=fetch_options.response_format, + follow_redirects=fetch_options.follow_redirects, + ) + return network_session.network_client.fetch(enriched_fetch_options) + + def with_as_user_header(self, user_id: str) -> 'BoxClient': + """ + Create a new client to impersonate user with the provided ID. All calls made with the new client will be made in context of the impersonated user, leaving the original client unmodified. + :param user_id: ID of an user to impersonate + :type user_id: str + """ + return BoxClient( + auth=self.auth, + network_session=self.network_session.with_additional_headers( + {'As-User': user_id} + ), + ) + + def with_suppressed_notifications(self) -> 'BoxClient': + """ + Create a new client with suppressed notifications. Calls made with the new client will not trigger email or webhook notifications + """ + return BoxClient( + auth=self.auth, + network_session=self.network_session.with_additional_headers( + {'Box-Notifications': 'off'} + ), + ) + + def with_extra_headers( + self, *, extra_headers: Dict[str, str] = None + ) -> 'BoxClient': + """ + Create a new client with a custom set of headers that will be included in every API call + :param extra_headers: Custom set of headers that will be included in every API call, defaults to None + :type extra_headers: Dict[str, str], optional + """ + if extra_headers is None: + extra_headers = {} + return BoxClient( + auth=self.auth, + network_session=self.network_session.with_additional_headers(extra_headers), + ) + + def with_custom_base_urls(self, base_urls: BaseUrls) -> 'BoxClient': + """ + Create a new client with a custom set of base urls that will be used for every API call + :param base_urls: Custom set of base urls that will be used for every API call + :type base_urls: BaseUrls + """ + return BoxClient( + auth=self.auth, + network_session=self.network_session.with_custom_base_urls(base_urls), + ) + + def with_proxy(self, config: ProxyConfig) -> 'BoxClient': + """ + Create a new client with a custom proxy that will be used for every API call + """ + return BoxClient( + auth=self.auth, network_session=self.network_session.with_proxy(config) + ) diff --git a/box_sdk_gen/internal/__init__.py b/box_sdk_gen/internal/__init__.py new file mode 100644 index 000000000..af3032fc6 --- /dev/null +++ b/box_sdk_gen/internal/__init__.py @@ -0,0 +1,9 @@ +from box_sdk_gen.internal.utils import * + +from box_sdk_gen.internal.logging import * + +from box_sdk_gen.internal.errors import * + +from box_sdk_gen.internal.base_object import * + +from box_sdk_gen.internal.null_value import * diff --git a/box_sdk_gen/internal/base_object.py b/box_sdk_gen/internal/base_object.py new file mode 100644 index 000000000..47c407323 --- /dev/null +++ b/box_sdk_gen/internal/base_object.py @@ -0,0 +1,165 @@ +from datetime import datetime, date +from enum import EnumMeta, Enum +from typing import get_args, get_origin, Union, Optional +from .null_value import NullValue + + +class BaseObject: + _discriminator = (None, {}) + _json_to_fields_mapping = {} + _fields_to_json_mapping = {} + + def __init__(self, **kwargs): + self._raw_data: dict = {} + self.__dict__.update(kwargs) + + @classmethod + def from_dict(cls, data: dict): + unpacked_attributes = {} + for key, value in data.items(): + mapping_field_name = cls._json_to_fields_mapping.get(key, key) + annotation = cls.__init__.__annotations__.get(mapping_field_name, None) + unpacked_attributes[mapping_field_name] = cls._deserialize( + key, value, annotation + ) + return cls(**unpacked_attributes) + + @property + def raw_data(self): + """ + Returns the raw json representation returned by the API + :return: dict with the raw json data + """ + return self._raw_data + + def to_dict(self) -> dict: + result_dict = {} + for k, v in vars(self).items(): + # Skip private and protected attributes + if k.startswith("_"): + continue + if v is None: + continue + if isinstance(v, NullValue): + value = None + elif type(v) is list: + value = [ + 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): + value = v.value + elif isinstance(v, date): + value = v.isoformat() + elif isinstance(v, datetime): + value = v.isoformat().replace('+00:00', 'Z') + else: + value = v + result_dict[self._fields_to_json_mapping.get(k, k)] = value + + return result_dict + + @classmethod + def _deserialize(cls, key, value, annotation=None): + if annotation is None or value is None: + return value + if get_origin(annotation) == Optional: + return cls._deserialize(key, value, get_args(annotation)) + if get_origin(annotation) == Union: + union_without_none_type = [ + arg for arg in get_args(annotation) if arg is not type(None) + ] + if len(union_without_none_type) == 1: + return cls._deserialize(key, value, union_without_none_type[0]) + + if get_origin(annotation) == list: + return cls._deserialize_list(key, value, annotation) + elif get_origin(annotation) == Union: + return cls._deserialize_union(key, value, annotation) + elif isinstance(annotation, EnumMeta): + return cls._deserialize_enum(key, value, annotation) + elif annotation == datetime: + return cls._deserialize_datetime(key, value, annotation) + elif annotation == date: + return cls._deserialize_date(key, value, annotation) + elif isinstance(annotation, type) and issubclass(annotation, BaseObject): + return cls._deserialize_nested_type(key, value, annotation) + else: + return value + + @classmethod + def _deserialize_list(cls, key, value, annotation: list): + try: + list_type = get_args(annotation)[0] + return [ + cls._deserialize(key, list_entry, list_type) for list_entry in value + ] + except Exception: + return value + + @classmethod + def _deserialize_union(cls, key, value, annotation): + try: + possible_types = get_args(annotation) + if value is None: + if type(None) not in possible_types: + print( + 'Value: ', value, 'should not be allowed in Union:', annotation + ) + return value + + for possible_type in possible_types: + if ( + isinstance(possible_type, type) + and issubclass(possible_type, BaseObject) + and value.get(possible_type._discriminator[0], None) + in possible_type._discriminator[1] + ): + return cls._deserialize(key, value, possible_type) + + for possible_type in possible_types: + try: + return cls._deserialize(key, value, possible_type) + except Exception: + continue + return value + except Exception: + return value + + @classmethod + def _deserialize_enum(cls, key, value, annotation): + try: + return getattr(annotation, value.upper().replace(' ', '_')) + except Exception: + return value + + @classmethod + def _deserialize_datetime(cls, key, value, annotation): + try: + return datetime.fromisoformat(value.replace('Z', '+00:00')) + except Exception: + return value + + @classmethod + def _deserialize_date(cls, key, value, annotation): + try: + return date.fromisoformat(value) + except Exception: + return value + + @classmethod + def _deserialize_nested_type(cls, key, value, annotation): + try: + return annotation.from_dict(value) + except Exception: + return value + + def __repr__(self) -> str: + return f'{self.__class__} {self.to_dict()}' diff --git a/box_sdk_gen/internal/errors.py b/box_sdk_gen/internal/errors.py new file mode 100644 index 000000000..856fd6a38 --- /dev/null +++ b/box_sdk_gen/internal/errors.py @@ -0,0 +1,6 @@ +class GeneratedCodeError(Exception): + def __init__(self, message: str, **kwargs): + super().__init__(message) + self.name = 'GeneratedCodeError' + self.message = message + self.extra = kwargs diff --git a/box_sdk_gen/internal/logging.py b/box_sdk_gen/internal/logging.py new file mode 100644 index 000000000..41371aa47 --- /dev/null +++ b/box_sdk_gen/internal/logging.py @@ -0,0 +1,31 @@ +from typing import Dict + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import sanitize_map + +from box_sdk_gen.serialization.json import sanitize_serialized_data + + +class DataSanitizer: + def __init__(self): + self._keys_to_sanitize = { + 'authorization': '', + 'access_token': '', + 'refresh_token': '', + 'subject_token': '', + 'token': '', + 'client_id': '', + 'client_secret': '', + 'shared_link': '', + 'download_url': '', + 'jwt_private_key': '', + 'jwt_private_key_passphrase': '', + 'password': '', + } + + def sanitize_headers(self, headers: Dict[str, str]) -> Dict[str, str]: + return sanitize_map(headers, self._keys_to_sanitize) + + def sanitize_body(self, body: SerializedData) -> SerializedData: + return sanitize_serialized_data(body, self._keys_to_sanitize) diff --git a/box_sdk_gen/internal/null_value.py b/box_sdk_gen/internal/null_value.py new file mode 100644 index 000000000..9001c9a06 --- /dev/null +++ b/box_sdk_gen/internal/null_value.py @@ -0,0 +1,5 @@ +class NullValue: + pass + + +null = NullValue() diff --git a/box_sdk_gen/internal/utils.py b/box_sdk_gen/internal/utils.py new file mode 100644 index 000000000..9289a03ca --- /dev/null +++ b/box_sdk_gen/internal/utils.py @@ -0,0 +1,501 @@ +import base64 +import datetime +import hashlib +import os +import re +import shutil +import uuid +import time +import hmac +from random import uniform +from enum import Enum +from io import SEEK_CUR, SEEK_END, SEEK_SET, BufferedIOBase, BytesIO +from typing import Any, Callable, Dict, Iterable, Optional, TypeVar, BinaryIO + +from abc import abstractmethod +from typing import Any + +try: + import jwt + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization +except ImportError: + jwt, default_backend, serialization = None, None, None + +from .base_object import BaseObject +from ..serialization.json import sd_to_json, sanitized_value +from ..serialization.json import serialize +from .null_value import null + +ByteStream = BufferedIOBase +OutputStream = BinaryIO +Buffer = bytes + + +class ResponseByteStream(ByteStream): + def __init__(self, request_iterator): + self._iterator = request_iterator + self._buffer = b'' + self._position = 0 + self._eos = False + + def _read_from_iterator(self, size): + """ + Read up to `size` bytes from the iterator into the buffer + :param size: Number of bytes to read. If None, read the entire stream. + """ + if self._eos: + return + + while len(self._buffer) < size: + try: + chunk = next(self._iterator) + self._buffer += chunk + except StopIteration: + self._eos = True + break + + def tell(self): + """ + Returns the current position in the stream + :return: + """ + return self._position + + def read(self, size=None): + """ + Reads up to `size` bytes from the stream + :param size: Read up to `size` bytes from the stream. If None read the entire stream. + :return: Bytes read from the stream + """ + if size is None: + # Read everything remaining in the stream. + result = self._buffer + b''.join(self._iterator) + self._buffer = b'' + self._position += len(result) + self._eos = True + return result + + self._read_from_iterator(size) + result = self._buffer[:size] + self._buffer = self._buffer[size:] + self._position += len(result) + return result + + def seek(self, position, whence=SEEK_SET): + """ + Move the stream to given position + :param position: Position to move to + :param whence: One of SEEK_SET = 0, SEEK_CUR = 1 or SEEK_END = 2 + :return: The new position in the stream + """ + if whence == SEEK_SET: + if position < self._position: + raise ValueError('Cannot seek backwards in a stream') + self.read(position - self._position) + elif whence == SEEK_CUR: + self.read(position) + elif whence == SEEK_END: + raise NotImplementedError('SEEK_END is not supported for streams') + else: + raise ValueError('Invalid value for `whence`') + + return self._position + + +def get_env_var(name: str) -> str: + return os.getenv(name) + + +def get_uuid() -> str: + return str(uuid.uuid1()) + + +def decode_base_64(value: str) -> str: + return base64.b64decode(value).decode() + + +def generate_byte_buffer(size: int) -> Buffer: + return Buffer(os.urandom(size)) + + +def generate_byte_stream_from_buffer(buffer: Buffer) -> ByteStream: + return BytesIO(buffer) + + +def generate_byte_stream(size: int) -> ByteStream: + return BytesIO(os.urandom(size)) + + +def buffer_equals(buffer1: Buffer, buffer2: Buffer) -> bool: + return buffer1 == buffer2 + + +def buffer_length(buffer: Buffer) -> int: + return len(buffer) + + +def decode_base_64_byte_stream(value: str) -> ByteStream: + return BytesIO(base64.b64decode(value)) + + +def string_to_byte_stream(value: str) -> ByteStream: + return BytesIO(bytes(value, 'utf-8')) + + +def read_byte_stream(byte_stream: ByteStream) -> Buffer: + return Buffer(byte_stream.read()) + + +def write_input_stream_to_output_stream( + input_stream: ByteStream, output_stream: OutputStream +): + shutil.copyfileobj(input_stream, output_stream) + + +def get_file_output_stream(file_path: str) -> OutputStream: + return open(file_path, 'wb') + + +def close_file_output_stream(file_output_stream: OutputStream): + file_output_stream.close() + + +def read_buffer_from_file(path: str) -> bytes: + with open(path, 'rb') as file: + return file.read() + + +def prepare_params(map: Dict[str, Optional[str]]) -> Dict[str, str]: + return {k: v for k, v in map.items() if v is not None} + + +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()) + if isinstance(value, list): + return ','.join(map(to_string, value)) + if isinstance(value, Enum): + return value.value + return str(value) + + +class HashName(str, Enum): + SHA1 = 'sha1' + + +class Hash: + def __init__(self, algorithm: HashName): + self.algorithm = algorithm + self.hash = hashlib.sha1() + + def update_hash(self, data: Buffer): + self.hash.update(data) + + def digest_hash(self, encoding): + return base64.b64encode(self.hash.digest()).decode("utf-8") + + +def hex_to_base_64(data: hex): + return base64.b64encode(bytes.fromhex(data)).decode() + + +T = TypeVar('T') +Iterator = Iterable[T] +Accumulator = TypeVar('Accumulator') + + +def iterate_chunks( + stream: ByteStream, chunk_size: int, file_size: int +) -> Iterable[ByteStream]: + stream_is_finished = False + while not stream_is_finished: + copied_length = 0 + chunk = b'' + while copied_length < chunk_size: + bytes_read = stream.read(chunk_size - copied_length) + if bytes_read is None: + # stream returns none when no bytes are ready currently but there are + # potentially more bytes in the stream to be read. + continue + if not bytes_read: + # stream is exhausted. + stream_is_finished = True + break + chunk += bytes_read + copied_length += len(bytes_read) + if chunk: + yield BytesIO(chunk) + + +def reduce_iterator( + iterator: Iterator, + reducer: Callable[[Accumulator, T], Accumulator], + initial_value: Accumulator, +) -> Accumulator: + result = initial_value + + for item in iterator: + result = reducer(result, item) + + return result + + +def read_text_from_file(file_path: str) -> str: + with open(file_path, 'r') as file: + return file.read() + + +def is_browser() -> bool: + return False + + +def get_epoch_time_in_seconds() -> int: + return int(time.time()) + + +def get_value_from_object_raw_data(obj: BaseObject, key: str) -> Any: + keys = key.split('.') + value: dict = obj.raw_data + for k in keys: + value = value.get(k, {}) + return value + + +class PrivateKeyDecryptor: + """Class used for private key decryption in JWT auth.""" + + @abstractmethod + def decrypt_private_key(self, encryptedPrivateKey: str, passphrase: str) -> Any: + """Decrypts private key using a passphrase.""" + pass + + +class DefaultPrivateKeyDecryptor(PrivateKeyDecryptor): + def decrypt_private_key(self, encryptedPrivateKey: str, passphrase: str) -> Any: + if default_backend is None or serialization is None: + raise ImportError( + 'Missing `cryptography` dependency. `cryptography` library is required to create JWT assertion.' + ) + encoded_private_key = encode_str_ascii_or_raise(encryptedPrivateKey) + encoded_passphrase = encode_str_ascii_or_raise(passphrase) + + return serialization.load_pem_private_key( + encoded_private_key, + password=encoded_passphrase, + backend=default_backend(), + ) + + +class JwtAlgorithm(str, Enum): + HS256 = 'HS256' + HS384 = 'HS384' + HS512 = 'HS512' + RS256 = 'RS256' + RS384 = 'RS384' + RS512 = 'RS512' + ES256 = 'ES256' + ES384 = 'ES384' + ES512 = 'ES512' + PS256 = 'PS256' + PS384 = 'PS384' + PS512 = 'PS512' + none = 'none' + + +class JwtSignOptions(BaseObject): + def __init__( + self, + algorithm: JwtAlgorithm, + headers: Dict[str, str] = None, + audience: Optional[str] = None, + issuer: Optional[str] = None, + subject: Optional[str] = None, + jwtid: Optional[str] = None, + keyid: Optional[str] = None, + private_key_decryptor: Optional[PrivateKeyDecryptor] = None, + **kwargs, + ): + super().__init__(**kwargs) + if headers is None: + headers = {} + self.algorithm = algorithm + self.headers = headers + self.audience = audience + self.issuer = issuer + self.subject = subject + self.jwtid = jwtid + self.keyid = keyid + self.private_key_decryptor = ( + private_key_decryptor + if private_key_decryptor is not None + else DefaultPrivateKeyDecryptor() + ) + + +class JwtKey(BaseObject): + def __init__(self, key: str, passphrase: str, **kwargs): + super().__init__(**kwargs) + self.key = key + self.passphrase = passphrase + + +def encode_str_ascii_or_raise(passphrase: str) -> bytes: + try: + return passphrase.encode('ascii') + except UnicodeError as unicode_error: + raise TypeError( + "private_key and private_key_passphrase must contain binary data (bytes/str), not a text/unicode string" + ) from unicode_error + + +def create_jwt_assertion(claims: dict, key: JwtKey, options: JwtSignOptions) -> str: + if jwt is None: + raise ImportError( + 'Missing `PyJWT` dependency. `PyJWT` library is required to create JWT assertion.' + ) + return jwt.encode( + { + 'iss': options.issuer, + 'sub': options.subject, + 'box_sub_type': claims['box_sub_type'], + 'aud': options.audience, + 'jti': options.jwtid, + 'exp': claims['exp'], + }, + options.private_key_decryptor.decrypt_private_key(key.key, key.passphrase), + algorithm=options.algorithm, + headers={'kid': options.keyid}, + ) + + +Date = datetime.date +DateTime = datetime.datetime + + +def date_to_string(date: Date) -> str: + return date.isoformat() + + +def date_from_string(date: str) -> Date: + return Date.fromisoformat(date) + + +def date_time_to_string(date_time: DateTime) -> str: + return date_time.isoformat().replace('+00:00', 'Z') + + +def date_time_from_string(date_time: str) -> DateTime: + return DateTime.fromisoformat(date_time.replace('Z', '+00:00')) + + +def date_time_to_epoch_seconds(date_time: DateTime) -> int: + return int(date_time.timestamp()) + + +def epoch_seconds_to_date_time(epoch_seconds: int) -> DateTime: + return DateTime.fromtimestamp(epoch_seconds, datetime.timezone.utc) + + +def delay_in_seconds(seconds: int): + time.sleep(seconds) + + +def create_null(): + return null + + +def escape_unicode(value: str) -> str: + def replace_char(match): + char = match.group(0) + code_point = ord(char) + if char == '\n': + return '\\n' + elif char == '\r': + return '\\r' + elif char == '\t': + return '\\t' + elif code_point <= 0xFFFF: # Basic Multilingual Plane (BMP) + return f"\\u{code_point:04x}" + else: # Supplementary Plane (Surrogate Pair) + code_point -= 0x10000 + high_surrogate = 0xD800 + (code_point >> 10) + low_surrogate = 0xDC00 + (code_point & 0x3FF) + return f"\\u{high_surrogate:04x}\\u{low_surrogate:04x}" + + # Replace any backslashes that are NOT part of a \/ with double backslash + temp = re.sub(r'\\(?!/)', r'\\\\', value) + + # Match special characters, non-ASCII characters + return re.sub(r'[^\x20-\x7e]|[\n\r\t]', replace_char, temp) + + +def compute_webhook_signature( + body: str, + headers: Dict[str, str], + signature_key: str, + escape_body: Optional[bool] = False, +) -> Optional[str]: + """ + Computes the Hmac for the webhook notification given one signature key. + + :param body: + The encoded webhook body. + :param headers: + The headers for the `Webhook` notification. + :param signature_key: + The `Webhook` signature key for this application. + :param escape_body: + Indicates if payload should be escaped or left as is. + :return: + An Hmac signature. + """ + if signature_key is None: + return None + if headers.get('box-signature-version') != '1': + return None + if headers.get('box-signature-algorithm') != 'HmacSHA256': + return None + + encoded_body = (escape_unicode(body) if escape_body else body).encode('utf-8') + encoded_signature_key = signature_key.encode('utf-8') + encoded_delivery_time_stamp = headers.get('box-delivery-timestamp').encode('utf-8') + new_hmac = hmac.new(encoded_signature_key, digestmod=hashlib.sha256) + new_hmac.update(encoded_body) + new_hmac.update(encoded_delivery_time_stamp) + signature = base64.b64encode(new_hmac.digest()).decode() + return signature + + +def compare_signatures( + expected_signature: Optional[str], received_signature: Optional[str] +) -> bool: + if not expected_signature or not received_signature: + return False + if len(expected_signature) != len(received_signature): + return False + return hmac.compare_digest(expected_signature, received_signature) + + +def random(min: float, max: float) -> float: + return uniform(min, max) + + +def sanitize_map( + dictionary: Dict[str, str], keys_to_sanitize: Dict[str, str] +) -> Dict[str, str]: + return { + k: sanitized_value() if k.lower() in keys_to_sanitize else v + for k, v in dictionary.items() + } diff --git a/box_sdk_gen/managers/__init__.py b/box_sdk_gen/managers/__init__.py new file mode 100644 index 000000000..d5a840f81 --- /dev/null +++ b/box_sdk_gen/managers/__init__.py @@ -0,0 +1,157 @@ +from box_sdk_gen.managers.authorization import * + +from box_sdk_gen.managers.files import * + +from box_sdk_gen.managers.trashed_files import * + +from box_sdk_gen.managers.app_item_associations import * + +from box_sdk_gen.managers.downloads import * + +from box_sdk_gen.managers.uploads import * + +from box_sdk_gen.managers.chunked_uploads import * + +from box_sdk_gen.managers.list_collaborations import * + +from box_sdk_gen.managers.comments import * + +from box_sdk_gen.managers.tasks import * + +from box_sdk_gen.managers.file_versions import * + +from box_sdk_gen.managers.file_metadata import * + +from box_sdk_gen.managers.file_classifications import * + +from box_sdk_gen.managers.skills import * + +from box_sdk_gen.managers.file_watermarks import * + +from box_sdk_gen.managers.file_requests import * + +from box_sdk_gen.managers.folders import * + +from box_sdk_gen.managers.trashed_folders import * + +from box_sdk_gen.managers.folder_metadata import * + +from box_sdk_gen.managers.folder_classifications import * + +from box_sdk_gen.managers.trashed_items import * + +from box_sdk_gen.managers.folder_watermarks import * + +from box_sdk_gen.managers.folder_locks import * + +from box_sdk_gen.managers.metadata_templates import * + +from box_sdk_gen.managers.classifications import * + +from box_sdk_gen.managers.metadata_cascade_policies import * + +from box_sdk_gen.managers.search import * + +from box_sdk_gen.managers.user_collaborations import * + +from box_sdk_gen.managers.task_assignments import * + +from box_sdk_gen.managers.shared_links_files import * + +from box_sdk_gen.managers.shared_links_folders import * + +from box_sdk_gen.managers.web_links import * + +from box_sdk_gen.managers.trashed_web_links import * + +from box_sdk_gen.managers.shared_links_web_links import * + +from box_sdk_gen.managers.shared_links_app_items import * + +from box_sdk_gen.managers.users import * + +from box_sdk_gen.managers.session_termination import * + +from box_sdk_gen.managers.avatars import * + +from box_sdk_gen.managers.transfer import * + +from box_sdk_gen.managers.email_aliases import * + +from box_sdk_gen.managers.memberships import * + +from box_sdk_gen.managers.invites import * + +from box_sdk_gen.managers.groups import * + +from box_sdk_gen.managers.webhooks import * + +from box_sdk_gen.managers.events import * + +from box_sdk_gen.managers.collections import * + +from box_sdk_gen.managers.recent_items import * + +from box_sdk_gen.managers.retention_policies import * + +from box_sdk_gen.managers.retention_policy_assignments import * + +from box_sdk_gen.managers.legal_hold_policies import * + +from box_sdk_gen.managers.legal_hold_policy_assignments import * + +from box_sdk_gen.managers.file_version_retentions import * + +from box_sdk_gen.managers.file_version_legal_holds import * + +from box_sdk_gen.managers.shield_information_barriers import * + +from box_sdk_gen.managers.shield_information_barrier_reports import * + +from box_sdk_gen.managers.shield_information_barrier_segments import * + +from box_sdk_gen.managers.shield_information_barrier_segment_members import * + +from box_sdk_gen.managers.shield_information_barrier_segment_restrictions import * + +from box_sdk_gen.managers.device_pinners import * + +from box_sdk_gen.managers.terms_of_services import * + +from box_sdk_gen.managers.terms_of_service_user_statuses import * + +from box_sdk_gen.managers.collaboration_allowlist_entries import * + +from box_sdk_gen.managers.collaboration_allowlist_exempt_targets import * + +from box_sdk_gen.managers.storage_policies import * + +from box_sdk_gen.managers.storage_policy_assignments import * + +from box_sdk_gen.managers.zip_downloads import * + +from box_sdk_gen.managers.sign_requests import * + +from box_sdk_gen.managers.workflows import * + +from box_sdk_gen.managers.sign_templates import * + +from box_sdk_gen.managers.integration_mappings import * + +from box_sdk_gen.managers.ai import * + +from box_sdk_gen.managers.ai_studio import * + +from box_sdk_gen.managers.docgen_template import * + +from box_sdk_gen.managers.docgen import * + +from box_sdk_gen.managers.hubs import * + +from box_sdk_gen.managers.hub_collaborations import * + +from box_sdk_gen.managers.hub_items import * + +from box_sdk_gen.managers.shield_lists import * + +from box_sdk_gen.managers.archives import * diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py new file mode 100644 index 000000000..4ab53e5b9 --- /dev/null +++ b/box_sdk_gen/managers/ai.py @@ -0,0 +1,477 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from typing import Union + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.schemas.ai_item_ask import AiItemAsk + +from box_sdk_gen.schemas.ai_dialogue_history import AiDialogueHistory + +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.ai_item_base import AiItemBase + +from box_sdk_gen.schemas.ai_response_full import AiResponseFull + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.ai_ask import AiAsk + +from box_sdk_gen.schemas.ai_response import AiResponse + +from box_sdk_gen.schemas.ai_text_gen import AiTextGen + +from box_sdk_gen.schemas.ai_agent_ask import AiAgentAsk + +from box_sdk_gen.schemas.ai_agent_text_gen import AiAgentTextGen + +from box_sdk_gen.schemas.ai_agent_extract import AiAgentExtract + +from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured + +from box_sdk_gen.schemas.ai_extract import AiExtract + +from box_sdk_gen.schemas.ai_extract_structured_response import ( + AiExtractStructuredResponse, +) + +from box_sdk_gen.schemas.ai_extract_structured import AiExtractStructured + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.serialization.json import sd_to_json + + +class CreateAiAskMode(str, Enum): + MULTIPLE_ITEM_QA = 'multiple_item_qa' + SINGLE_ITEM_QA = 'single_item_qa' + + +class CreateAiTextGenItemsTypeField(str, Enum): + FILE = 'file' + + +class CreateAiTextGenItems(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + id: str, + *, + type: CreateAiTextGenItemsTypeField = CreateAiTextGenItemsTypeField.FILE, + content: Optional[str] = None, + **kwargs + ): + """ + :param id: The ID of the item. + :type id: str + :param type: The type of the item., defaults to CreateAiTextGenItemsTypeField.FILE + :type type: CreateAiTextGenItemsTypeField, optional + :param content: The content to use as context for generating new text or editing existing text., defaults to None + :type content: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.content = content + + +class GetAiAgentDefaultConfigMode(str, Enum): + ASK = 'ask' + TEXT_GEN = 'text_gen' + EXTRACT = 'extract' + EXTRACT_STRUCTURED = 'extract_structured' + + +class CreateAiExtractStructuredMetadataTemplateTypeField(str, Enum): + METADATA_TEMPLATE = 'metadata_template' + + +class CreateAiExtractStructuredMetadataTemplate(BaseObject): + _discriminator = 'type', {'metadata_template'} + + def __init__( + self, + *, + template_key: Optional[str] = None, + type: Optional[CreateAiExtractStructuredMetadataTemplateTypeField] = None, + scope: Optional[str] = None, + **kwargs + ): + """ + :param template_key: The name of the metadata template., defaults to None + :type template_key: Optional[str], optional + :param type: Value is always `metadata_template`., defaults to None + :type type: Optional[CreateAiExtractStructuredMetadataTemplateTypeField], optional + :param scope: The scope of the metadata template that can either be global or + enterprise. + * The **global** scope is used for templates that are + available to any Box enterprise. + * The **enterprise** scope represents templates created within a specific enterprise, + containing the ID of that enterprise., defaults to None + :type scope: Optional[str], optional + """ + super().__init__(**kwargs) + self.template_key = template_key + self.type = type + self.scope = scope + + +class CreateAiExtractStructuredFieldsOptionsField(BaseObject): + def __init__(self, key: str, **kwargs): + """ + :param key: A unique identifier for the field. + :type key: str + """ + super().__init__(**kwargs) + self.key = key + + +class CreateAiExtractStructuredFields(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + key: str, + *, + description: Optional[str] = None, + display_name: Optional[str] = None, + prompt: Optional[str] = None, + type: Optional[str] = None, + options: Optional[List[CreateAiExtractStructuredFieldsOptionsField]] = None, + **kwargs + ): + """ + :param key: A unique identifier for the field. + :type key: str + :param description: A description of the field., defaults to None + :type description: Optional[str], optional + :param display_name: The display name of the field., defaults to None + :type display_name: Optional[str], optional + :param prompt: The context about the key that may include how to find and format it., defaults to None + :type prompt: Optional[str], optional + :param type: The type of the field. It include but is not limited to string, float, date, enum, and multiSelect., defaults to None + :type type: Optional[str], optional + :param options: A list of options for this field. This is most often used in combination with the enum and multiSelect field types., defaults to None + :type options: Optional[List[CreateAiExtractStructuredFieldsOptionsField]], optional + """ + super().__init__(**kwargs) + self.key = key + self.description = description + self.display_name = display_name + self.prompt = prompt + self.type = type + self.options = options + + +class AiManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_ai_ask( + self, + mode: CreateAiAskMode, + prompt: str, + items: List[AiItemAsk], + *, + dialogue_history: Optional[List[AiDialogueHistory]] = None, + include_citations: Optional[bool] = None, + ai_agent: Optional[Union[AiAgentAsk, AiAgentReference]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Optional[AiResponseFull]: + """ + Sends an AI request to supported LLMs and returns an answer specifically focused on the user's question given the provided context. + :param mode: Box AI handles text documents with text representations up to 1MB in size, or a maximum of 25 files, + whichever comes first. If the text file size exceeds 1MB, the first 1MB of text representation will be processed. + Box AI handles image documents with a resolution of 1024 x 1024 pixels, with a maximum of 5 images or 5 pages + for multi-page images. If the number of image or image pages exceeds 5, the first 5 images or pages will + be processed. If you set mode parameter to `single_item_qa`, the items array can have one element only. + Currently Box AI does not support multi-modal requests. If both images and text are sent Box AI will only + process the text. + :type mode: CreateAiAskMode + :param prompt: The prompt provided by the client to be answered by the LLM. + The prompt's length is limited to 10000 characters. + :type prompt: str + :param items: The items to be processed by the LLM, often files. + :type items: List[AiItemAsk] + :param dialogue_history: The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response., defaults to None + :type dialogue_history: Optional[List[AiDialogueHistory]], optional + :param include_citations: A flag to indicate whether citations should be returned., defaults to None + :type include_citations: Optional[bool], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'mode': mode, + 'prompt': prompt, + 'items': items, + 'dialogue_history': dialogue_history, + 'include_citations': include_citations, + 'ai_agent': ai_agent, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/ai/ask']), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + if to_string(response.status) == '204': + return None + return deserialize(response.data, AiResponseFull) + + def create_ai_text_gen( + self, + prompt: str, + items: List[CreateAiTextGenItems], + *, + dialogue_history: Optional[List[AiDialogueHistory]] = None, + ai_agent: Optional[Union[AiAgentReference, AiAgentTextGen]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AiResponse: + """ + Sends an AI request to supported Large Language Models (LLMs) and returns generated text based on the provided prompt. + :param prompt: The prompt provided by the client to be answered by the LLM. The prompt's length is limited to 10000 characters. + :type prompt: str + :param items: The items to be processed by the LLM, often files. + The array can include **exactly one** element. + + **Note**: Box AI handles documents with text representations up to 1MB in size. + If the file size exceeds 1MB, the first 1MB of text representation will be processed. + :type items: List[CreateAiTextGenItems] + :param dialogue_history: The history of prompts and answers previously passed to the LLM. This parameter provides the additional context to the LLM when generating the response., defaults to None + :type dialogue_history: Optional[List[AiDialogueHistory]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'prompt': prompt, + 'items': items, + 'dialogue_history': dialogue_history, + 'ai_agent': ai_agent, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/ai/text_gen'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AiResponse) + + def get_ai_agent_default_config( + self, + mode: GetAiAgentDefaultConfigMode, + *, + language: Optional[str] = None, + model: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Union[AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured]: + """ + Get the AI agent default config. + :param mode: The mode to filter the agent config to return. + :type mode: GetAiAgentDefaultConfigMode + :param language: The ISO language code to return the agent config for. + If the language is not supported the default agent config is returned., defaults to None + :type language: Optional[str], optional + :param model: The model to return the default agent config for., defaults to None + :type model: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'mode': to_string(mode), + 'language': to_string(language), + 'model': to_string(model), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/ai_agent_default'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize( + response.data, + Union[AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured], + ) + + def create_ai_extract( + self, + prompt: str, + items: List[AiItemBase], + *, + ai_agent: Optional[Union[AiAgentReference, AiAgentExtract]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AiResponse: + """ + Sends an AI request to supported Large Language Models (LLMs) and extracts metadata in form of key-value pairs. + + In this request, both the prompt and the output can be freeform. + + + Metadata template setup before sending the request is not required. + + :param prompt: The prompt provided to a Large Language Model (LLM) in the request. The prompt can be up to 10000 characters long and it can be an XML or a JSON schema. + :type prompt: str + :param items: The items that LLM will process. Currently, you can use files only. + :type items: List[AiItemBase] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'prompt': prompt, 'items': items, 'ai_agent': ai_agent} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/ai/extract'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AiResponse) + + def create_ai_extract_structured( + self, + items: List[AiItemBase], + *, + metadata_template: Optional[CreateAiExtractStructuredMetadataTemplate] = None, + fields: Optional[List[CreateAiExtractStructuredFields]] = None, + ai_agent: Optional[Union[AiAgentReference, AiAgentExtractStructured]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AiExtractStructuredResponse: + """ + Sends an AI request to supported Large Language Models (LLMs) and returns extracted metadata as a set of key-value pairs. + + For this request, you either need a metadata template or a list of fields you want to extract. + + + Input is **either** a metadata template or a list of fields to ensure the structure. + + + To learn more about creating templates, see [Creating metadata templates in the Admin Console](https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates) + + + or use the [metadata template API](g://metadata/templates/create). + + :param items: The items to be processed by the LLM. Currently you can use files only. + :type items: List[AiItemBase] + :param metadata_template: The metadata template containing the fields to extract. + For your request to work, you must provide either `metadata_template` or `fields`, but not both., defaults to None + :type metadata_template: Optional[CreateAiExtractStructuredMetadataTemplate], optional + :param fields: The fields to be extracted from the provided items. + For your request to work, you must provide either `metadata_template` or `fields`, but not both., defaults to None + :type fields: Optional[List[CreateAiExtractStructuredFields]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'items': items, + 'metadata_template': metadata_template, + 'fields': fields, + 'ai_agent': ai_agent, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/ai/extract_structured', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AiExtractStructuredResponse) diff --git a/box_sdk_gen/managers/ai_studio.py b/box_sdk_gen/managers/ai_studio.py new file mode 100644 index 000000000..d43740e2a --- /dev/null +++ b/box_sdk_gen/managers/ai_studio.py @@ -0,0 +1,328 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.schemas.group_base import GroupBase + +from box_sdk_gen.schemas.ai_agent_allowed_entity import AiAgentAllowedEntity + +from box_sdk_gen.schemas.ai_studio_agent_ask import AiStudioAgentAsk + +from box_sdk_gen.schemas.ai_studio_agent_text_gen import AiStudioAgentTextGen + +from box_sdk_gen.schemas.ai_studio_agent_extract import AiStudioAgentExtract + +from box_sdk_gen.schemas.ai_multiple_agent_response import AiMultipleAgentResponse + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.ai_single_agent_response_full import AiSingleAgentResponseFull + +from box_sdk_gen.schemas.create_ai_agent import CreateAiAgent + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateAiAgentType(str, Enum): + AI_AGENT = 'ai_agent' + + +class UpdateAiAgentByIdType(str, Enum): + AI_AGENT = 'ai_agent' + + +class AiStudioManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_ai_agents( + self, + *, + mode: Optional[List[str]] = None, + fields: Optional[List[str]] = None, + agent_state: Optional[List[str]] = None, + include_box_default: Optional[bool] = None, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AiMultipleAgentResponse: + """ + Lists AI agents based on the provided parameters. + :param mode: The mode to filter the agent config to return. Possible values are: `ask`, `text_gen`, and `extract`., defaults to None + :type mode: Optional[List[str]], optional + :param fields: The fields to return in the response., defaults to None + :type fields: Optional[List[str]], optional + :param agent_state: The state of the agents to return. Possible values are: `enabled`, `disabled` and `enabled_for_selected_users`., defaults to None + :type agent_state: Optional[List[str]], optional + :param include_box_default: Whether to include the Box default agents in the response., defaults to None + :type include_box_default: Optional[bool], optional + :param marker: Defines the position marker at which to begin returning results., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'mode': to_string(mode), + 'fields': to_string(fields), + 'agent_state': to_string(agent_state), + 'include_box_default': to_string(include_box_default), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/ai_agents'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AiMultipleAgentResponse) + + def create_ai_agent( + self, + name: str, + access_state: str, + *, + type: CreateAiAgentType = CreateAiAgentType.AI_AGENT, + icon_reference: Optional[str] = None, + allowed_entities: Optional[List[AiAgentAllowedEntity]] = None, + ask: Optional[AiStudioAgentAsk] = None, + text_gen: Optional[AiStudioAgentTextGen] = None, + extract: Optional[AiStudioAgentExtract] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AiSingleAgentResponseFull: + """ + Creates an AI agent. At least one of the following capabilities must be provided: `ask`, `text_gen`, `extract`. + :param name: The name of the AI Agent. + :type name: str + :param access_state: The state of the AI Agent. Possible values are: `enabled`, `disabled`, and `enabled_for_selected_users`. + :type access_state: str + :param type: The type of agent used to handle queries., defaults to CreateAiAgentType.AI_AGENT + :type type: CreateAiAgentType, optional + :param icon_reference: The icon reference of the AI Agent. It should have format of the URL `https://cdn01.boxcdn.net/app-assets/aistudio/avatars/` + where possible values of `file_name` are: `logo_boxAi.png`,`logo_stamp.png`,`logo_legal.png`,`logo_finance.png`,`logo_config.png`,`logo_handshake.png`,`logo_analytics.png`,`logo_classification.png`., defaults to None + :type icon_reference: Optional[str], optional + :param allowed_entities: List of allowed users or groups., defaults to None + :type allowed_entities: Optional[List[AiAgentAllowedEntity]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'type': type, + 'name': name, + 'access_state': access_state, + 'icon_reference': icon_reference, + 'allowed_entities': allowed_entities, + 'ask': ask, + 'text_gen': text_gen, + 'extract': extract, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/ai_agents'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AiSingleAgentResponseFull) + + def update_ai_agent_by_id( + self, + agent_id: str, + name: str, + access_state: str, + *, + type: UpdateAiAgentByIdType = UpdateAiAgentByIdType.AI_AGENT, + icon_reference: Optional[str] = None, + allowed_entities: Optional[List[AiAgentAllowedEntity]] = None, + ask: Optional[AiStudioAgentAsk] = None, + text_gen: Optional[AiStudioAgentTextGen] = None, + extract: Optional[AiStudioAgentExtract] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AiSingleAgentResponseFull: + """ + Updates an AI agent. + :param agent_id: The ID of the agent to update. + Example: "1234" + :type agent_id: str + :param name: The name of the AI Agent. + :type name: str + :param access_state: The state of the AI Agent. Possible values are: `enabled`, `disabled`, and `enabled_for_selected_users`. + :type access_state: str + :param type: The type of agent used to handle queries., defaults to UpdateAiAgentByIdType.AI_AGENT + :type type: UpdateAiAgentByIdType, optional + :param icon_reference: The icon reference of the AI Agent. It should have format of the URL `https://cdn01.boxcdn.net/app-assets/aistudio/avatars/` + where possible values of `file_name` are: `logo_boxAi.png`,`logo_stamp.png`,`logo_legal.png`,`logo_finance.png`,`logo_config.png`,`logo_handshake.png`,`logo_analytics.png`,`logo_classification.png`., defaults to None + :type icon_reference: Optional[str], optional + :param allowed_entities: List of allowed users or groups., defaults to None + :type allowed_entities: Optional[List[AiAgentAllowedEntity]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'type': type, + 'name': name, + 'access_state': access_state, + 'icon_reference': icon_reference, + 'allowed_entities': allowed_entities, + 'ask': ask, + 'text_gen': text_gen, + 'extract': extract, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/ai_agents/', + to_string(agent_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AiSingleAgentResponseFull) + + def get_ai_agent_by_id( + self, + agent_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AiSingleAgentResponseFull: + """ + Gets an AI Agent using the `agent_id` parameter. + :param agent_id: The agent id to get. + Example: "1234" + :type agent_id: str + :param fields: The fields to return in the response., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/ai_agents/', + to_string(agent_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AiSingleAgentResponseFull) + + def delete_ai_agent_by_id( + self, agent_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes an AI agent using the provided parameters. + :param agent_id: The ID of the agent to delete. + Example: "1234" + :type agent_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/ai_agents/', + to_string(agent_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/app_item_associations.py b/box_sdk_gen/managers/app_item_associations.py new file mode 100644 index 000000000..6f32278ed --- /dev/null +++ b/box_sdk_gen/managers/app_item_associations.py @@ -0,0 +1,197 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.app_item_associations import AppItemAssociations + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class AppItemAssociationsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_app_item_associations( + self, + file_id: str, + *, + limit: Optional[int] = None, + marker: Optional[str] = None, + application_type: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AppItemAssociations: + """ + **This is a beta feature, which means that its availability might be limited.** + + Returns all app items the file is associated with. This includes app items + + + associated with ancestors of the file. Assuming the context user has access + + + to the file, the type/ids are revealed even if the context user does not + + + have **View** permission on the app item. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param application_type: If given, only return app items for this application type., defaults to None + :type application_type: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'limit': to_string(limit), + 'marker': to_string(marker), + 'application_type': to_string(application_type), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/app_item_associations', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AppItemAssociations) + + def get_folder_app_item_associations( + self, + folder_id: str, + *, + limit: Optional[int] = None, + marker: Optional[str] = None, + application_type: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AppItemAssociations: + """ + **This is a beta feature, which means that its availability might be limited.** + + Returns all app items the folder is associated with. This includes app items + + + associated with ancestors of the folder. Assuming the context user has access + + + to the folder, the type/ids are revealed even if the context user does not + + + have **View** permission on the app item. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param application_type: If given, returns only app items for this application type., defaults to None + :type application_type: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'limit': to_string(limit), + 'marker': to_string(marker), + 'application_type': to_string(application_type), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/app_item_associations', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AppItemAssociations) diff --git a/box_sdk_gen/managers/archives.py b/box_sdk_gen/managers/archives.py new file mode 100644 index 000000000..bf0a3f58f --- /dev/null +++ b/box_sdk_gen/managers/archives.py @@ -0,0 +1,171 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2025_r0.archives_v2025_r0 import ArchivesV2025R0 + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import ClientErrorV2025R0 + +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import ( + BoxVersionHeaderV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.archive_v2025_r0 import ArchiveV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class ArchivesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_archives_v2025_r0( + self, + *, + limit: Optional[int] = None, + marker: Optional[str] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ArchivesV2025R0: + """ + Retrieves archives for an enterprise. + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination., defaults to None + :type marker: Optional[str], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'limit': to_string(limit), 'marker': to_string(marker)} + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/archives']), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ArchivesV2025R0) + + def create_archive_v2025_r0( + self, + name: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ArchiveV2025R0: + """ + Creates an archive. + :param name: The name of the archive. + :type name: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'name': name} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/archives']), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ArchiveV2025R0) + + def delete_archive_by_id_v2025_r0( + self, + archive_id: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Permanently deletes an archive. + :param archive_id: The ID of the archive. + Example: "982312" + :type archive_id: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/archives/', + to_string(archive_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/authorization.py b/box_sdk_gen/managers/authorization.py new file mode 100644 index 000000000..7bcb2af9c --- /dev/null +++ b/box_sdk_gen/managers/authorization.py @@ -0,0 +1,434 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenGrantTypeField + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenSubjectTokenTypeField + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2TokenBoxSubjectTypeField + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.access_token import AccessToken + +from box_sdk_gen.schemas.o_auth_2_error import OAuth2Error + +from box_sdk_gen.schemas.post_o_auth_2_token import PostOAuth2Token + +from box_sdk_gen.schemas.post_o_auth_2_token_refresh_access_token import ( + PostOAuth2TokenRefreshAccessToken, +) + +from box_sdk_gen.schemas.post_o_auth_2_revoke import PostOAuth2Revoke + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class AuthorizeUserResponseType(str, Enum): + CODE = 'code' + + +class RequestAccessTokenGrantType(str, Enum): + AUTHORIZATION_CODE = 'authorization_code' + REFRESH_TOKEN = 'refresh_token' + CLIENT_CREDENTIALS = 'client_credentials' + URN_IETF_PARAMS_OAUTH_GRANT_TYPE_JWT_BEARER = ( + 'urn:ietf:params:oauth:grant-type:jwt-bearer' + ) + URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE = ( + 'urn:ietf:params:oauth:grant-type:token-exchange' + ) + + +class RequestAccessTokenSubjectTokenType(str, Enum): + URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ACCESS_TOKEN = ( + 'urn:ietf:params:oauth:token-type:access_token' + ) + + +class RequestAccessTokenActorTokenType(str, Enum): + URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ID_TOKEN = ( + 'urn:ietf:params:oauth:token-type:id_token' + ) + + +class RequestAccessTokenBoxSubjectType(str, Enum): + ENTERPRISE = 'enterprise' + USER = 'user' + + +class RefreshAccessTokenGrantType(str, Enum): + REFRESH_TOKEN = 'refresh_token' + + +class AuthorizationManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def authorize_user( + self, + response_type: AuthorizeUserResponseType, + client_id: str, + *, + redirect_uri: Optional[str] = None, + state: Optional[str] = None, + scope: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Authorize a user by sending them through the [Box](https://box.com) + + website and request their permission to act on their behalf. + + + This is the first step when authenticating a user using + + + OAuth 2.0. To request a user's authorization to use the Box APIs + + + on their behalf you will need to send a user to the URL with this + + + format. + + :param response_type: The type of response we'd like to receive. + :type response_type: AuthorizeUserResponseType + :param client_id: The Client ID of the application that is requesting to authenticate + the user. To get the Client ID for your application, log in to your + Box developer console and click the **Edit Application** link for + the application you're working with. In the OAuth 2.0 Parameters section + of the configuration page, find the item labelled `client_id`. The + text of that item is your application's Client ID. + :type client_id: str + :param redirect_uri: The URI to which Box redirects the browser after the user has granted + or denied the application permission. This URI match one of the redirect + URIs in the configuration of your application. It must be a + valid HTTPS URI and it needs to be able to handle the redirection to + complete the next step in the OAuth 2.0 flow. + Although this parameter is optional, it must be a part of the + authorization URL if you configured multiple redirect URIs + for the application in the developer console. A missing parameter causes + a `redirect_uri_missing` error after the user grants application access., defaults to None + :type redirect_uri: Optional[str], optional + :param state: A custom string of your choice. Box will pass the same string to + the redirect URL when authentication is complete. This parameter + can be used to identify a user on redirect, as well as protect + against hijacked sessions and other exploits., defaults to None + :type state: Optional[str], optional + :param scope: A space-separated list of application scopes you'd like to + authenticate the user for. This defaults to all the scopes configured + for the application in its configuration page., defaults to None + :type scope: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'response_type': to_string(response_type), + 'client_id': to_string(client_id), + 'redirect_uri': to_string(redirect_uri), + 'state': to_string(state), + 'scope': to_string(scope), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.oauth_2_url, '/authorize']), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def request_access_token( + self, + grant_type: RequestAccessTokenGrantType, + *, + client_id: Optional[str] = None, + client_secret: Optional[str] = None, + code: Optional[str] = None, + refresh_token: Optional[str] = None, + assertion: Optional[str] = None, + subject_token: Optional[str] = None, + subject_token_type: Optional[RequestAccessTokenSubjectTokenType] = None, + actor_token: Optional[str] = None, + actor_token_type: Optional[RequestAccessTokenActorTokenType] = None, + scope: Optional[str] = None, + resource: Optional[str] = None, + box_subject_type: Optional[RequestAccessTokenBoxSubjectType] = None, + box_subject_id: Optional[str] = None, + box_shared_link: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AccessToken: + """ + Request an Access Token using either a client-side obtained OAuth 2.0 + + authorization code or a server-side JWT assertion. + + + An Access Token is a string that enables Box to verify that a + + + request belongs to an authorized session. In the normal order of + + + operations you will begin by requesting authentication from the + + + [authorize](#get-authorize) endpoint and Box will send you an + + + authorization code. + + + You will then send this code to this endpoint to exchange it for + + + an Access Token. The returned Access Token can then be used to to make + + + Box API calls. + + :param grant_type: The type of request being made, either using a client-side obtained + authorization code, a refresh token, a JWT assertion, client credentials + grant or another access token for the purpose of downscoping a token. + :type grant_type: RequestAccessTokenGrantType + :param client_id: The Client ID of the application requesting an access token. + + Used in combination with `authorization_code`, `client_credentials`, or + `urn:ietf:params:oauth:grant-type:jwt-bearer` as the `grant_type`., defaults to None + :type client_id: Optional[str], optional + :param client_secret: The client secret of the application requesting an access token. + + Used in combination with `authorization_code`, `client_credentials`, or + `urn:ietf:params:oauth:grant-type:jwt-bearer` as the `grant_type`., defaults to None + :type client_secret: Optional[str], optional + :param code: The client-side authorization code passed to your application by + Box in the browser redirect after the user has successfully + granted your application permission to make API calls on their + behalf. + + Used in combination with `authorization_code` as the `grant_type`., defaults to None + :type code: Optional[str], optional + :param refresh_token: A refresh token used to get a new access token with. + + Used in combination with `refresh_token` as the `grant_type`., defaults to None + :type refresh_token: Optional[str], optional + :param assertion: A JWT assertion for which to request a new access token. + + Used in combination with `urn:ietf:params:oauth:grant-type:jwt-bearer` + as the `grant_type`., defaults to None + :type assertion: Optional[str], optional + :param subject_token: The token to exchange for a downscoped token. This can be a regular + access token, a JWT assertion, or an app token. + + Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` + as the `grant_type`., defaults to None + :type subject_token: Optional[str], optional + :param subject_token_type: The type of `subject_token` passed in. + + Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` + as the `grant_type`., defaults to None + :type subject_token_type: Optional[RequestAccessTokenSubjectTokenType], optional + :param actor_token: The token used to create an annotator token. + This is a JWT assertion. + + Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` + as the `grant_type`., defaults to None + :type actor_token: Optional[str], optional + :param actor_token_type: The type of `actor_token` passed in. + + Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` + as the `grant_type`., defaults to None + :type actor_token_type: Optional[RequestAccessTokenActorTokenType], optional + :param scope: The space-delimited list of scopes that you want apply to the + new access token. + + The `subject_token` will need to have all of these scopes or + the call will error with **401 Unauthorized**.., defaults to None + :type scope: Optional[str], optional + :param resource: Full URL for the file that the token should be generated for., defaults to None + :type resource: Optional[str], optional + :param box_subject_type: Used in combination with `client_credentials` as the `grant_type`., defaults to None + :type box_subject_type: Optional[RequestAccessTokenBoxSubjectType], optional + :param box_subject_id: Used in combination with `client_credentials` as the `grant_type`. + Value is determined by `box_subject_type`. If `user` use user ID and if + `enterprise` use enterprise ID., defaults to None + :type box_subject_id: Optional[str], optional + :param box_shared_link: Full URL of the shared link on the file or folder + that the token should be generated for., defaults to None + :type box_shared_link: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'grant_type': grant_type, + 'client_id': client_id, + 'client_secret': client_secret, + 'code': code, + 'refresh_token': refresh_token, + 'assertion': assertion, + 'subject_token': subject_token, + 'subject_token_type': subject_token_type, + 'actor_token': actor_token, + 'actor_token_type': actor_token_type, + 'scope': scope, + 'resource': resource, + 'box_subject_type': box_subject_type, + 'box_subject_id': box_subject_id, + 'box_shared_link': box_shared_link, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/oauth2/token']), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/x-www-form-urlencoded', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AccessToken) + + def refresh_access_token( + self, + client_id: str, + client_secret: str, + refresh_token: str, + *, + grant_type: RefreshAccessTokenGrantType = RefreshAccessTokenGrantType.REFRESH_TOKEN, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AccessToken: + """ + Refresh an Access Token using its client ID, secret, and refresh token. + :param client_id: The client ID of the application requesting to refresh the token. + :type client_id: str + :param client_secret: The client secret of the application requesting to refresh the token. + :type client_secret: str + :param refresh_token: The refresh token to refresh. + :type refresh_token: str + :param grant_type: The type of request being made, in this case a refresh request., defaults to RefreshAccessTokenGrantType.REFRESH_TOKEN + :type grant_type: RefreshAccessTokenGrantType, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'grant_type': grant_type, + 'client_id': client_id, + 'client_secret': client_secret, + 'refresh_token': refresh_token, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/oauth2/token#refresh'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/x-www-form-urlencoded', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AccessToken) + + def revoke_access_token( + self, + *, + client_id: Optional[str] = None, + client_secret: Optional[str] = None, + token: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Revoke an active Access Token, effectively logging a user out + + that has been previously authenticated. + + :param client_id: The Client ID of the application requesting to revoke the + access token., defaults to None + :type client_id: Optional[str], optional + :param client_secret: The client secret of the application requesting to revoke + an access token., defaults to None + :type client_secret: Optional[str], optional + :param token: The access token to revoke., defaults to None + :type token: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'client_id': client_id, + 'client_secret': client_secret, + 'token': token, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/oauth2/revoke'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/x-www-form-urlencoded', + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/avatars.py b/box_sdk_gen/managers/avatars.py new file mode 100644 index 000000000..a6ff17103 --- /dev/null +++ b/box_sdk_gen/managers/avatars.py @@ -0,0 +1,174 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.user_avatar import UserAvatar + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.networking.fetch_options import MultipartItem + +from box_sdk_gen.serialization.json import SerializedData + + +class AvatarsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_user_avatar( + self, user_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ByteStream: + """ + Retrieves an image of a the user's avatar. + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + '/avatar', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.BINARY, + auth=self.auth, + network_session=self.network_session, + ) + ) + return response.content + + def create_user_avatar( + self, + user_id: str, + pic: ByteStream, + *, + pic_file_name: Optional[str] = None, + pic_content_type: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UserAvatar: + """ + Adds or updates a user avatar. + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param pic: The image file to be uploaded to Box. + Accepted file extensions are `.jpg` or `.png`. + The maximum file size is 1MB. + :type pic: ByteStream + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'pic': pic, + 'pic_file_name': pic_file_name, + 'pic_content_type': pic_content_type, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + '/avatar', + ] + ), + method='POST', + headers=headers_map, + multipart_data=[ + MultipartItem( + part_name='pic', + file_stream=pic, + file_name=pic_file_name, + content_type=pic_content_type, + ) + ], + content_type='multipart/form-data', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UserAvatar) + + def delete_user_avatar( + self, user_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes an existing user avatar. + + You cannot reverse this operation. + + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + '/avatar', + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/chunked_uploads.py b/box_sdk_gen/managers/chunked_uploads.py new file mode 100644 index 000000000..1ff148681 --- /dev/null +++ b/box_sdk_gen/managers/chunked_uploads.py @@ -0,0 +1,872 @@ +from typing import List + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.internal.utils import Buffer + +from box_sdk_gen.internal.utils import HashName + +from box_sdk_gen.internal.utils import Iterator + +from box_sdk_gen.schemas.upload_session import UploadSession + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.uploaded_part import UploadedPart + +from box_sdk_gen.schemas.upload_parts import UploadParts + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.schemas.upload_part import UploadPart + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.internal.utils import generate_byte_stream_from_buffer + +from box_sdk_gen.internal.utils import hex_to_base_64 + +from box_sdk_gen.internal.utils import iterate_chunks + +from box_sdk_gen.internal.utils import read_byte_stream + +from box_sdk_gen.internal.utils import reduce_iterator + +from box_sdk_gen.internal.utils import Hash + +from box_sdk_gen.internal.utils import buffer_length + +from box_sdk_gen.schemas.file_full import FileFull + + +class _PartAccumulator: + def __init__( + self, + last_index: int, + parts: List[UploadPart], + file_size: int, + upload_part_url: str, + file_hash: Hash, + ): + self.last_index = last_index + self.parts = parts + self.file_size = file_size + self.upload_part_url = upload_part_url + self.file_hash = file_hash + + +class ChunkedUploadsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_file_upload_session( + self, + folder_id: str, + file_size: int, + file_name: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadSession: + """ + Creates an upload session for a new file. + :param folder_id: The ID of the folder to upload the new file to. + :type folder_id: str + :param file_size: The total number of bytes of the file to be uploaded. + :type file_size: int + :param file_name: The name of new file. + :type file_name: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'folder_id': folder_id, + 'file_size': file_size, + 'file_name': file_name, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.upload_url, + '/2.0/files/upload_sessions', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadSession) + + def create_file_upload_session_for_existing_file( + self, + file_id: str, + file_size: int, + *, + file_name: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadSession: + """ + Creates an upload session for an existing file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param file_size: The total number of bytes of the file to be uploaded. + :type file_size: int + :param file_name: The optional new name of new file., defaults to None + :type file_name: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'file_size': file_size, 'file_name': file_name} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.upload_url, + '/2.0/files/', + to_string(file_id), + '/upload_sessions', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadSession) + + def get_file_upload_session_by_url( + self, url: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadSession: + """ + Using this method with urls provided in response when creating a new upload session is preferred to use over GetFileUploadSessionById method. + + This allows to always upload your content to the closest Box data center and can significantly improve upload speed. + + + Return information about an upload session. + + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) endpoint. + + :param url: URL of getFileUploadSessionById method + :type url: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=url, + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadSession) + + def get_file_upload_session_by_id( + self, + upload_session_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadSession: + """ + Return information about an upload session. + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) endpoint. + + :param upload_session_id: The ID of the upload session. + Example: "D5E3F7A" + :type upload_session_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.upload_url, + '/2.0/files/upload_sessions/', + to_string(upload_session_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadSession) + + def upload_file_part_by_url( + self, + url: str, + request_body: ByteStream, + digest: str, + content_range: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadedPart: + """ + Using this method with urls provided in response when creating a new upload session is preferred to use over UploadFilePart method. + + This allows to always upload your content to the closest Box data center and can significantly improve upload speed. + + + Uploads a chunk of a file for an upload session. + + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + + + and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + + :param url: URL of uploadFilePart method + :type url: str + :param request_body: Request body of uploadFilePart method + :type request_body: ByteStream + :param digest: The [RFC3230][1] message digest of the chunk uploaded. + + Only SHA1 is supported. The SHA1 digest must be base64 + encoded. The format of this header is as + `sha=BASE64_ENCODED_DIGEST`. + + To get the value for the `SHA` digest, use the + openSSL command to encode the file part: + `openssl sha1 -binary | base64`. + + [1]: https://tools.ietf.org/html/rfc3230 + :type digest: str + :param content_range: The byte range of the chunk. + + Must not overlap with the range of a part already + uploaded this session. Each part’s size must be + exactly equal in size to the part size specified + in the upload session that you created. + One exception is the last part of the file, as this can be smaller. + + When providing the value for `content-range`, remember that: + + * The lower bound of each part's byte range + must be a multiple of the part size. + * The higher bound must be a multiple of the part size - 1. + :type content_range: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + { + 'digest': to_string(digest), + 'content-range': to_string(content_range), + **extra_headers, + } + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=url, + method='PUT', + headers=headers_map, + file_stream=request_body, + content_type='application/octet-stream', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadedPart) + + def upload_file_part( + self, + upload_session_id: str, + request_body: ByteStream, + digest: str, + content_range: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadedPart: + """ + Uploads a chunk of a file for an upload session. + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + + + and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + + :param upload_session_id: The ID of the upload session. + Example: "D5E3F7A" + :type upload_session_id: str + :param request_body: Request body of uploadFilePart method + :type request_body: ByteStream + :param digest: The [RFC3230][1] message digest of the chunk uploaded. + + Only SHA1 is supported. The SHA1 digest must be base64 + encoded. The format of this header is as + `sha=BASE64_ENCODED_DIGEST`. + + To get the value for the `SHA` digest, use the + openSSL command to encode the file part: + `openssl sha1 -binary | base64`. + + [1]: https://tools.ietf.org/html/rfc3230 + :type digest: str + :param content_range: The byte range of the chunk. + + Must not overlap with the range of a part already + uploaded this session. Each part’s size must be + exactly equal in size to the part size specified + in the upload session that you created. + One exception is the last part of the file, as this can be smaller. + + When providing the value for `content-range`, remember that: + + * The lower bound of each part's byte range + must be a multiple of the part size. + * The higher bound must be a multiple of the part size - 1. + :type content_range: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + { + 'digest': to_string(digest), + 'content-range': to_string(content_range), + **extra_headers, + } + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.upload_url, + '/2.0/files/upload_sessions/', + to_string(upload_session_id), + ] + ), + method='PUT', + headers=headers_map, + file_stream=request_body, + content_type='application/octet-stream', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadedPart) + + def delete_file_upload_session_by_url( + self, url: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Using this method with urls provided in response when creating a new upload session is preferred to use over DeleteFileUploadSessionById method. + + This allows to always upload your content to the closest Box data center and can significantly improve upload speed. + + + Abort an upload session and discard all data uploaded. + + + This cannot be reversed. + + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + + + and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + + :param url: URL of deleteFileUploadSessionById method + :type url: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=url, + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def delete_file_upload_session_by_id( + self, + upload_session_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Abort an upload session and discard all data uploaded. + + This cannot be reversed. + + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + + + and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + + :param upload_session_id: The ID of the upload session. + Example: "D5E3F7A" + :type upload_session_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.upload_url, + '/2.0/files/upload_sessions/', + to_string(upload_session_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_file_upload_session_parts_by_url( + self, + url: str, + *, + offset: Optional[int] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadParts: + """ + Using this method with urls provided in response when creating a new upload session is preferred to use over GetFileUploadSessionParts method. + + This allows to always upload your content to the closest Box data center and can significantly improve upload speed. + + + Return a list of the chunks uploaded to the upload session so far. + + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + + + and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + + :param url: URL of getFileUploadSessionParts method + :type url: str + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'offset': to_string(offset), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=url, + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadParts) + + def get_file_upload_session_parts( + self, + upload_session_id: str, + *, + offset: Optional[int] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadParts: + """ + Return a list of the chunks uploaded to the upload session so far. + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + + + and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + + :param upload_session_id: The ID of the upload session. + Example: "D5E3F7A" + :type upload_session_id: str + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'offset': to_string(offset), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.upload_url, + '/2.0/files/upload_sessions/', + to_string(upload_session_id), + '/parts', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadParts) + + def create_file_upload_session_commit_by_url( + self, + url: str, + parts: List[UploadPart], + digest: str, + *, + if_match: Optional[str] = None, + if_none_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Optional[Files]: + """ + Using this method with urls provided in response when creating a new upload session is preferred to use over CreateFileUploadSessionCommit method. + + This allows to always upload your content to the closest Box data center and can significantly improve upload speed. + + + Close an upload session and create a file from the uploaded chunks. + + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + + + and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + + :param url: URL of createFileUploadSessionCommit method + :type url: str + :param parts: The list details for the uploaded parts. + :type parts: List[UploadPart] + :param digest: The [RFC3230][1] message digest of the whole file. + + Only SHA1 is supported. The SHA1 digest must be Base64 + encoded. The format of this header is as + `sha=BASE64_ENCODED_DIGEST`. + + [1]: https://tools.ietf.org/html/rfc3230 + :type digest: str + :param if_match: Ensures this item hasn't recently changed before + making changes. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `412 Precondition Failed` if it + has changed since., defaults to None + :type if_match: Optional[str], optional + :param if_none_match: Ensures an item is only returned if it has changed. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `304 Not Modified` if the item has not + changed since., defaults to None + :type if_none_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'parts': parts} + headers_map: Dict[str, str] = prepare_params( + { + 'digest': to_string(digest), + 'if-match': to_string(if_match), + 'if-none-match': to_string(if_none_match), + **extra_headers, + } + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=url, + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + if to_string(response.status) == '202': + return None + return deserialize(response.data, Files) + + def create_file_upload_session_commit( + self, + upload_session_id: str, + parts: List[UploadPart], + digest: str, + *, + if_match: Optional[str] = None, + if_none_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Optional[Files]: + """ + Close an upload session and create a file from the uploaded chunks. + + The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + + + and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + + :param upload_session_id: The ID of the upload session. + Example: "D5E3F7A" + :type upload_session_id: str + :param parts: The list details for the uploaded parts. + :type parts: List[UploadPart] + :param digest: The [RFC3230][1] message digest of the whole file. + + Only SHA1 is supported. The SHA1 digest must be Base64 + encoded. The format of this header is as + `sha=BASE64_ENCODED_DIGEST`. + + [1]: https://tools.ietf.org/html/rfc3230 + :type digest: str + :param if_match: Ensures this item hasn't recently changed before + making changes. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `412 Precondition Failed` if it + has changed since., defaults to None + :type if_match: Optional[str], optional + :param if_none_match: Ensures an item is only returned if it has changed. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `304 Not Modified` if the item has not + changed since., defaults to None + :type if_none_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'parts': parts} + headers_map: Dict[str, str] = prepare_params( + { + 'digest': to_string(digest), + 'if-match': to_string(if_match), + 'if-none-match': to_string(if_none_match), + **extra_headers, + } + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.upload_url, + '/2.0/files/upload_sessions/', + to_string(upload_session_id), + '/commit', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + if to_string(response.status) == '202': + return None + return deserialize(response.data, Files) + + def _reducer(self, acc: _PartAccumulator, chunk: ByteStream) -> _PartAccumulator: + last_index: int = acc.last_index + parts: List[UploadPart] = acc.parts + chunk_buffer: Buffer = read_byte_stream(chunk) + hash: Hash = Hash(algorithm=HashName.SHA1) + hash.update_hash(chunk_buffer) + sha_1: str = hash.digest_hash('base64') + digest: str = ''.join(['sha=', sha_1]) + chunk_size: int = buffer_length(chunk_buffer) + bytes_start: int = last_index + 1 + bytes_end: int = last_index + chunk_size + content_range: str = ''.join( + [ + 'bytes ', + to_string(bytes_start), + '-', + to_string(bytes_end), + '/', + to_string(acc.file_size), + ] + ) + uploaded_part: UploadedPart = self.upload_file_part_by_url( + acc.upload_part_url, + generate_byte_stream_from_buffer(chunk_buffer), + digest, + content_range, + ) + part: UploadPart = uploaded_part.part + part_sha_1: str = hex_to_base_64(part.sha_1) + assert part_sha_1 == sha_1 + assert part.size == chunk_size + assert part.offset == bytes_start + acc.file_hash.update_hash(chunk_buffer) + return _PartAccumulator( + last_index=bytes_end, + parts=parts + [part], + file_size=acc.file_size, + upload_part_url=acc.upload_part_url, + file_hash=acc.file_hash, + ) + + def upload_big_file( + self, file: ByteStream, file_name: str, file_size: int, parent_folder_id: str + ) -> FileFull: + """ + Starts the process of chunk uploading a big file. Should return a File object representing uploaded file. + :param file: The stream of the file to upload. + :type file: ByteStream + :param file_name: The name of the file, which will be used for storage in Box. + :type file_name: str + :param file_size: The total size of the file for the chunked upload in bytes. + :type file_size: int + :param parent_folder_id: The ID of the folder where the file should be uploaded. + :type parent_folder_id: str + """ + upload_session: UploadSession = self.create_file_upload_session( + parent_folder_id, file_size, file_name + ) + upload_part_url: str = upload_session.session_endpoints.upload_part + commit_url: str = upload_session.session_endpoints.commit + list_parts_url: str = upload_session.session_endpoints.list_parts + part_size: int = upload_session.part_size + total_parts: int = upload_session.total_parts + assert part_size * total_parts >= file_size + assert upload_session.num_parts_processed == 0 + file_hash: Hash = Hash(algorithm=HashName.SHA1) + chunks_iterator: Iterator = iterate_chunks(file, part_size, file_size) + results: _PartAccumulator = reduce_iterator( + chunks_iterator, + self._reducer, + _PartAccumulator( + last_index=-1, + parts=[], + file_size=file_size, + upload_part_url=upload_part_url, + file_hash=file_hash, + ), + ) + parts: List[UploadPart] = results.parts + processed_session_parts: UploadParts = ( + self.get_file_upload_session_parts_by_url(list_parts_url) + ) + assert processed_session_parts.total_count == total_parts + sha_1: str = file_hash.digest_hash('base64') + digest: str = ''.join(['sha=', sha_1]) + committed_session: Optional[Files] = ( + self.create_file_upload_session_commit_by_url(commit_url, parts, digest) + ) + return committed_session.entries[0] diff --git a/box_sdk_gen/managers/classifications.py b/box_sdk_gen/managers/classifications.py new file mode 100644 index 000000000..a76312ce7 --- /dev/null +++ b/box_sdk_gen/managers/classifications.py @@ -0,0 +1,707 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.classification_template import ClassificationTemplate + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import SerializedData + + +class AddClassificationRequestBodyOpField(str, Enum): + ADDENUMOPTION = 'addEnumOption' + + +class AddClassificationRequestBodyFieldKeyField(str, Enum): + BOX__SECURITY__CLASSIFICATION__KEY = 'Box__Security__Classification__Key' + + +class AddClassificationRequestBodyDataStaticConfigClassificationField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'classification_definition': 'classificationDefinition', + 'color_id': 'colorID', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'classificationDefinition': 'classification_definition', + 'colorID': 'color_id', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + classification_definition: Optional[str] = None, + color_id: Optional[int] = None, + **kwargs + ): + """ + :param classification_definition: A longer description of the classification., defaults to None + :type classification_definition: Optional[str], optional + :param color_id: An internal Box identifier used to assign a color to + a classification label. + + Mapping between a `colorID` and a color may change + without notice. Currently, the color mappings are as + follows. + + * `0`: Yellow. + * `1`: Orange. + * `2`: Watermelon red. + * `3`: Purple rain. + * `4`: Light blue. + * `5`: Dark blue. + * `6`: Light green. + * `7`: Gray., defaults to None + :type color_id: Optional[int], optional + """ + super().__init__(**kwargs) + self.classification_definition = classification_definition + self.color_id = color_id + + +class AddClassificationRequestBodyDataStaticConfigField(BaseObject): + def __init__( + self, + *, + classification: Optional[ + AddClassificationRequestBodyDataStaticConfigClassificationField + ] = None, + **kwargs + ): + """ + :param classification: Additional details for the classification., defaults to None + :type classification: Optional[AddClassificationRequestBodyDataStaticConfigClassificationField], optional + """ + super().__init__(**kwargs) + self.classification = classification + + +class AddClassificationRequestBodyDataField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'static_config': 'staticConfig', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'staticConfig': 'static_config', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + key: str, + *, + static_config: Optional[ + AddClassificationRequestBodyDataStaticConfigField + ] = None, + **kwargs + ): + """ + :param key: The label of the classification as shown in the web and + mobile interfaces. This is the only field required to + add a classification. + :type key: str + :param static_config: A static configuration for the classification., defaults to None + :type static_config: Optional[AddClassificationRequestBodyDataStaticConfigField], optional + """ + super().__init__(**kwargs) + self.key = key + self.static_config = static_config + + +class AddClassificationRequestBody(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'field_key': 'fieldKey', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'fieldKey': 'field_key', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + data: AddClassificationRequestBodyDataField, + *, + op: AddClassificationRequestBodyOpField = AddClassificationRequestBodyOpField.ADDENUMOPTION, + field_key: AddClassificationRequestBodyFieldKeyField = AddClassificationRequestBodyFieldKeyField.BOX__SECURITY__CLASSIFICATION__KEY, + **kwargs + ): + """ + :param data: The details of the classification to add. + :type data: AddClassificationRequestBodyDataField + :param op: The type of change to perform on the classification + object., defaults to AddClassificationRequestBodyOpField.ADDENUMOPTION + :type op: AddClassificationRequestBodyOpField, optional + :param field_key: Defines classifications + available in the enterprise., defaults to AddClassificationRequestBodyFieldKeyField.BOX__SECURITY__CLASSIFICATION__KEY + :type field_key: AddClassificationRequestBodyFieldKeyField, optional + """ + super().__init__(**kwargs) + self.data = data + self.op = op + self.field_key = field_key + + +class UpdateClassificationRequestBodyOpField(str, Enum): + EDITENUMOPTION = 'editEnumOption' + + +class UpdateClassificationRequestBodyFieldKeyField(str, Enum): + BOX__SECURITY__CLASSIFICATION__KEY = 'Box__Security__Classification__Key' + + +class UpdateClassificationRequestBodyDataStaticConfigClassificationField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'classification_definition': 'classificationDefinition', + 'color_id': 'colorID', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'classificationDefinition': 'classification_definition', + 'colorID': 'color_id', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + classification_definition: Optional[str] = None, + color_id: Optional[int] = None, + **kwargs + ): + """ + :param classification_definition: A longer description of the classification., defaults to None + :type classification_definition: Optional[str], optional + :param color_id: An internal Box identifier used to assign a color to + a classification label. + + Mapping between a `colorID` and a color may change + without notice. Currently, the color mappings are as + follows. + + * `0`: Yellow. + * `1`: Orange. + * `2`: Watermelon red. + * `3`: Purple rain. + * `4`: Light blue. + * `5`: Dark blue. + * `6`: Light green. + * `7`: Gray., defaults to None + :type color_id: Optional[int], optional + """ + super().__init__(**kwargs) + self.classification_definition = classification_definition + self.color_id = color_id + + +class UpdateClassificationRequestBodyDataStaticConfigField(BaseObject): + def __init__( + self, + *, + classification: Optional[ + UpdateClassificationRequestBodyDataStaticConfigClassificationField + ] = None, + **kwargs + ): + """ + :param classification: Additional details for the classification., defaults to None + :type classification: Optional[UpdateClassificationRequestBodyDataStaticConfigClassificationField], optional + """ + super().__init__(**kwargs) + self.classification = classification + + +class UpdateClassificationRequestBodyDataField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'static_config': 'staticConfig', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'staticConfig': 'static_config', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + key: str, + *, + static_config: Optional[ + UpdateClassificationRequestBodyDataStaticConfigField + ] = None, + **kwargs + ): + """ + :param key: A new label for the classification, as it will be + shown in the web and mobile interfaces. + :type key: str + :param static_config: A static configuration for the classification., defaults to None + :type static_config: Optional[UpdateClassificationRequestBodyDataStaticConfigField], optional + """ + super().__init__(**kwargs) + self.key = key + self.static_config = static_config + + +class UpdateClassificationRequestBody(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'field_key': 'fieldKey', + 'enum_option_key': 'enumOptionKey', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'fieldKey': 'field_key', + 'enumOptionKey': 'enum_option_key', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + enum_option_key: str, + data: UpdateClassificationRequestBodyDataField, + *, + op: UpdateClassificationRequestBodyOpField = UpdateClassificationRequestBodyOpField.EDITENUMOPTION, + field_key: UpdateClassificationRequestBodyFieldKeyField = UpdateClassificationRequestBodyFieldKeyField.BOX__SECURITY__CLASSIFICATION__KEY, + **kwargs + ): + """ + :param enum_option_key: The original label of the classification to change. + :type enum_option_key: str + :param data: The details of the updated classification. + :type data: UpdateClassificationRequestBodyDataField + :param op: The type of change to perform on the classification + object., defaults to UpdateClassificationRequestBodyOpField.EDITENUMOPTION + :type op: UpdateClassificationRequestBodyOpField, optional + :param field_key: Defines classifications + available in the enterprise., defaults to UpdateClassificationRequestBodyFieldKeyField.BOX__SECURITY__CLASSIFICATION__KEY + :type field_key: UpdateClassificationRequestBodyFieldKeyField, optional + """ + super().__init__(**kwargs) + self.enum_option_key = enum_option_key + self.data = data + self.op = op + self.field_key = field_key + + +class CreateClassificationTemplateScope(str, Enum): + ENTERPRISE = 'enterprise' + + +class CreateClassificationTemplateTemplateKey(str, Enum): + SECURITYCLASSIFICATION_6VMVOCHWUWO = 'securityClassification-6VMVochwUWo' + + +class CreateClassificationTemplateDisplayName(str, Enum): + CLASSIFICATION = 'Classification' + + +class CreateClassificationTemplateFieldsTypeField(str, Enum): + ENUM = 'enum' + + +class CreateClassificationTemplateFieldsKeyField(str, Enum): + BOX__SECURITY__CLASSIFICATION__KEY = 'Box__Security__Classification__Key' + + +class CreateClassificationTemplateFieldsDisplayNameField(str, Enum): + CLASSIFICATION = 'Classification' + + +class CreateClassificationTemplateFieldsOptionsStaticConfigClassificationField( + BaseObject +): + _fields_to_json_mapping: Dict[str, str] = { + 'classification_definition': 'classificationDefinition', + 'color_id': 'colorID', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'classificationDefinition': 'classification_definition', + 'colorID': 'color_id', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + classification_definition: Optional[str] = None, + color_id: Optional[int] = None, + **kwargs + ): + """ + :param classification_definition: A longer description of the classification., defaults to None + :type classification_definition: Optional[str], optional + :param color_id: An identifier used to assign a color to + a classification label. + + Mapping between a `colorID` and a color may + change without notice. Currently, the color + mappings are as follows. + + * `0`: Yellow. + * `1`: Orange. + * `2`: Watermelon red. + * `3`: Purple rain. + * `4`: Light blue. + * `5`: Dark blue. + * `6`: Light green. + * `7`: Gray., defaults to None + :type color_id: Optional[int], optional + """ + super().__init__(**kwargs) + self.classification_definition = classification_definition + self.color_id = color_id + + +class CreateClassificationTemplateFieldsOptionsStaticConfigField(BaseObject): + def __init__( + self, + *, + classification: Optional[ + CreateClassificationTemplateFieldsOptionsStaticConfigClassificationField + ] = None, + **kwargs + ): + """ + :param classification: Additional information about the classification., defaults to None + :type classification: Optional[CreateClassificationTemplateFieldsOptionsStaticConfigClassificationField], optional + """ + super().__init__(**kwargs) + self.classification = classification + + +class CreateClassificationTemplateFieldsOptionsField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'static_config': 'staticConfig', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'staticConfig': 'static_config', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + key: str, + *, + static_config: Optional[ + CreateClassificationTemplateFieldsOptionsStaticConfigField + ] = None, + **kwargs + ): + """ + :param key: The display name and key this classification. This + will be show in the Box UI. + :type key: str + :param static_config: Additional information about the classification., defaults to None + :type static_config: Optional[CreateClassificationTemplateFieldsOptionsStaticConfigField], optional + """ + super().__init__(**kwargs) + self.key = key + self.static_config = static_config + + +class CreateClassificationTemplateFields(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + **BaseObject._json_to_fields_mapping, + } + _discriminator = 'type', {'enum'} + + def __init__( + self, + options: List[CreateClassificationTemplateFieldsOptionsField], + *, + type: CreateClassificationTemplateFieldsTypeField = CreateClassificationTemplateFieldsTypeField.ENUM, + key: CreateClassificationTemplateFieldsKeyField = CreateClassificationTemplateFieldsKeyField.BOX__SECURITY__CLASSIFICATION__KEY, + display_name: CreateClassificationTemplateFieldsDisplayNameField = CreateClassificationTemplateFieldsDisplayNameField.CLASSIFICATION, + hidden: Optional[bool] = None, + **kwargs + ): + """ + :param options: The actual list of classifications that are present on + this template. + :type options: List[CreateClassificationTemplateFieldsOptionsField] + :param type: The type of the field + that is always enum., defaults to CreateClassificationTemplateFieldsTypeField.ENUM + :type type: CreateClassificationTemplateFieldsTypeField, optional + :param key: Defines classifications + available in the enterprise., defaults to CreateClassificationTemplateFieldsKeyField.BOX__SECURITY__CLASSIFICATION__KEY + :type key: CreateClassificationTemplateFieldsKeyField, optional + :param display_name: A display name for the classification., defaults to CreateClassificationTemplateFieldsDisplayNameField.CLASSIFICATION + :type display_name: CreateClassificationTemplateFieldsDisplayNameField, optional + :param hidden: Determines if the classification + template is + hidden or available on + web and mobile + devices., defaults to None + :type hidden: Optional[bool], optional + """ + super().__init__(**kwargs) + self.options = options + self.type = type + self.key = key + self.display_name = display_name + self.hidden = hidden + + +class ClassificationsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_classification_template( + self, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ClassificationTemplate: + """ + Retrieves the classification metadata template and lists all the + + classifications available to this enterprise. + + + This API can also be called by including the enterprise ID in the + + + URL explicitly, for example + + + `/metadata_templates/enterprise_12345/securityClassification-6VMVochwUWo/schema`. + + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/enterprise/securityClassification-6VMVochwUWo/schema', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ClassificationTemplate) + + def add_classification( + self, + request_body: List[AddClassificationRequestBody], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ClassificationTemplate: + """ + Adds one or more new classifications to the list of classifications + + available to the enterprise. + + + This API can also be called by including the enterprise ID in the + + + URL explicitly, for example + + + `/metadata_templates/enterprise_12345/securityClassification-6VMVochwUWo/schema`. + + :param request_body: Request body of addClassification method + :type request_body: List[AddClassificationRequestBody] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/enterprise/securityClassification-6VMVochwUWo/schema#add', + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ClassificationTemplate) + + def update_classification( + self, + request_body: List[UpdateClassificationRequestBody], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ClassificationTemplate: + """ + Updates the labels and descriptions of one or more classifications + + available to the enterprise. + + + This API can also be called by including the enterprise ID in the + + + URL explicitly, for example + + + `/metadata_templates/enterprise_12345/securityClassification-6VMVochwUWo/schema`. + + :param request_body: Request body of updateClassification method + :type request_body: List[UpdateClassificationRequestBody] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/enterprise/securityClassification-6VMVochwUWo/schema#update', + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json-patch+json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ClassificationTemplate) + + def create_classification_template( + self, + fields: List[CreateClassificationTemplateFields], + *, + scope: CreateClassificationTemplateScope = CreateClassificationTemplateScope.ENTERPRISE, + template_key: CreateClassificationTemplateTemplateKey = CreateClassificationTemplateTemplateKey.SECURITYCLASSIFICATION_6VMVOCHWUWO, + display_name: CreateClassificationTemplateDisplayName = CreateClassificationTemplateDisplayName.CLASSIFICATION, + hidden: Optional[bool] = None, + copy_instance_on_item_copy: Optional[bool] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ClassificationTemplate: + """ + When an enterprise does not yet have any classifications, this API call + + initializes the classification template with an initial set of + + + classifications. + + + If an enterprise already has a classification, the template will already + + + exist and instead an API call should be made to add additional + + + classifications. + + :param fields: The classification template requires exactly + one field, which holds + all the valid classification values. + :type fields: List[CreateClassificationTemplateFields] + :param scope: The scope in which to create the classifications. This should + be `enterprise` or `enterprise_{id}` where `id` is the unique + ID of the enterprise., defaults to CreateClassificationTemplateScope.ENTERPRISE + :type scope: CreateClassificationTemplateScope, optional + :param template_key: Defines the list of metadata templates., defaults to CreateClassificationTemplateTemplateKey.SECURITYCLASSIFICATION_6VMVOCHWUWO + :type template_key: CreateClassificationTemplateTemplateKey, optional + :param display_name: The name of the + template as shown in web and mobile interfaces., defaults to CreateClassificationTemplateDisplayName.CLASSIFICATION + :type display_name: CreateClassificationTemplateDisplayName, optional + :param hidden: Determines if the classification template is + hidden or available on web and mobile + devices., defaults to None + :type hidden: Optional[bool], optional + :param copy_instance_on_item_copy: Determines if classifications are + copied along when the file or folder is + copied., defaults to None + :type copy_instance_on_item_copy: Optional[bool], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'scope': scope, + 'templateKey': template_key, + 'displayName': display_name, + 'hidden': hidden, + 'copyInstanceOnItemCopy': copy_instance_on_item_copy, + 'fields': fields, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/schema#classifications', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ClassificationTemplate) diff --git a/box_sdk_gen/managers/collaboration_allowlist_entries.py b/box_sdk_gen/managers/collaboration_allowlist_entries.py new file mode 100644 index 000000000..15176d4fe --- /dev/null +++ b/box_sdk_gen/managers/collaboration_allowlist_entries.py @@ -0,0 +1,226 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.collaboration_allowlist_entries import ( + CollaborationAllowlistEntries, +) + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.collaboration_allowlist_entry import ( + CollaborationAllowlistEntry, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateCollaborationWhitelistEntryDirection(str, Enum): + INBOUND = 'inbound' + OUTBOUND = 'outbound' + BOTH = 'both' + + +class CollaborationAllowlistEntriesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_collaboration_whitelist_entries( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CollaborationAllowlistEntries: + """ + Returns the list domains that have been deemed safe to create collaborations + + for within the current enterprise. + + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaboration_whitelist_entries', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CollaborationAllowlistEntries) + + def create_collaboration_whitelist_entry( + self, + domain: str, + direction: CreateCollaborationWhitelistEntryDirection, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CollaborationAllowlistEntry: + """ + Creates a new entry in the list of allowed domains to allow + + collaboration for. + + :param domain: The domain to add to the list of allowed domains. + :type domain: str + :param direction: The direction in which to allow collaborations. + :type direction: CreateCollaborationWhitelistEntryDirection + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'domain': domain, 'direction': direction} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaboration_whitelist_entries', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CollaborationAllowlistEntry) + + def get_collaboration_whitelist_entry_by_id( + self, + collaboration_whitelist_entry_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CollaborationAllowlistEntry: + """ + Returns a domain that has been deemed safe to create collaborations + + for within the current enterprise. + + :param collaboration_whitelist_entry_id: The ID of the entry in the list. + Example: "213123" + :type collaboration_whitelist_entry_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaboration_whitelist_entries/', + to_string(collaboration_whitelist_entry_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CollaborationAllowlistEntry) + + def delete_collaboration_whitelist_entry_by_id( + self, + collaboration_whitelist_entry_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes a domain from the list of domains that have been deemed safe to create + + collaborations for within the current enterprise. + + :param collaboration_whitelist_entry_id: The ID of the entry in the list. + Example: "213123" + :type collaboration_whitelist_entry_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaboration_whitelist_entries/', + to_string(collaboration_whitelist_entry_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/collaboration_allowlist_exempt_targets.py b/box_sdk_gen/managers/collaboration_allowlist_exempt_targets.py new file mode 100644 index 000000000..aaacce06f --- /dev/null +++ b/box_sdk_gen/managers/collaboration_allowlist_exempt_targets.py @@ -0,0 +1,227 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.collaboration_allowlist_exempt_targets import ( + CollaborationAllowlistExemptTargets, +) + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.collaboration_allowlist_exempt_target import ( + CollaborationAllowlistExemptTarget, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateCollaborationWhitelistExemptTargetUser(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The ID of the user to exempt. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class CollaborationAllowlistExemptTargetsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_collaboration_whitelist_exempt_targets( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CollaborationAllowlistExemptTargets: + """ + Returns a list of users who have been exempt from the collaboration + + domain restrictions. + + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaboration_whitelist_exempt_targets', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CollaborationAllowlistExemptTargets) + + def create_collaboration_whitelist_exempt_target( + self, + user: CreateCollaborationWhitelistExemptTargetUser, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CollaborationAllowlistExemptTarget: + """ + Exempts a user from the restrictions set out by the allowed list of domains + + for collaborations. + + :param user: The user to exempt. + :type user: CreateCollaborationWhitelistExemptTargetUser + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'user': user} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaboration_whitelist_exempt_targets', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CollaborationAllowlistExemptTarget) + + def get_collaboration_whitelist_exempt_target_by_id( + self, + collaboration_whitelist_exempt_target_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CollaborationAllowlistExemptTarget: + """ + Returns a users who has been exempt from the collaboration + + domain restrictions. + + :param collaboration_whitelist_exempt_target_id: The ID of the exemption to the list. + Example: "984923" + :type collaboration_whitelist_exempt_target_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaboration_whitelist_exempt_targets/', + to_string(collaboration_whitelist_exempt_target_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CollaborationAllowlistExemptTarget) + + def delete_collaboration_whitelist_exempt_target_by_id( + self, + collaboration_whitelist_exempt_target_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes a user's exemption from the restrictions set out by the allowed list + + of domains for collaborations. + + :param collaboration_whitelist_exempt_target_id: The ID of the exemption to the list. + Example: "984923" + :type collaboration_whitelist_exempt_target_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaboration_whitelist_exempt_targets/', + to_string(collaboration_whitelist_exempt_target_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/collections.py b/box_sdk_gen/managers/collections.py new file mode 100644 index 000000000..bb060a8d3 --- /dev/null +++ b/box_sdk_gen/managers/collections.py @@ -0,0 +1,217 @@ +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.collections import Collections + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.items_offset_paginated import ItemsOffsetPaginated + +from box_sdk_gen.schemas.collection import Collection + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CollectionsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_collections( + self, + *, + fields: Optional[List[str]] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Collections: + """ + Retrieves all collections for a given user. + + Currently, only the `favorites` collection + + + is supported. + + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'offset': to_string(offset), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/collections'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Collections) + + def get_collection_items( + self, + collection_id: str, + *, + fields: Optional[List[str]] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ItemsOffsetPaginated: + """ + Retrieves the files and/or folders contained within + + this collection. + + :param collection_id: The ID of the collection. + Example: "926489" + :type collection_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'offset': to_string(offset), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collections/', + to_string(collection_id), + '/items', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ItemsOffsetPaginated) + + def get_collection_by_id( + self, + collection_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Collection: + """ + Retrieves a collection by its ID. + :param collection_id: The ID of the collection. + Example: "926489" + :type collection_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collections/', + to_string(collection_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Collection) diff --git a/box_sdk_gen/managers/comments.py b/box_sdk_gen/managers/comments.py new file mode 100644 index 000000000..f09708a1b --- /dev/null +++ b/box_sdk_gen/managers/comments.py @@ -0,0 +1,357 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.comments import Comments + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.comment_full import CommentFull + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateCommentItemTypeField(str, Enum): + FILE = 'file' + COMMENT = 'comment' + + +class CreateCommentItem(BaseObject): + _discriminator = 'type', {'file', 'comment'} + + def __init__(self, id: str, type: CreateCommentItemTypeField, **kwargs): + """ + :param id: The ID of the item. + :type id: str + :param type: The type of the item that this comment will be placed on. + :type type: CreateCommentItemTypeField + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CommentsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_comments( + self, + file_id: str, + *, + fields: Optional[List[str]] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Comments: + """ + Retrieves a list of comments for a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'limit': to_string(limit), + 'offset': to_string(offset), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/comments', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Comments) + + def get_comment_by_id( + self, + comment_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CommentFull: + """ + Retrieves the message and metadata for a specific comment, as well + + as information on the user who created the comment. + + :param comment_id: The ID of the comment. + Example: "12345" + :type comment_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/comments/', + to_string(comment_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CommentFull) + + def update_comment_by_id( + self, + comment_id: str, + *, + message: Optional[str] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CommentFull: + """ + Update the message of a comment. + :param comment_id: The ID of the comment. + Example: "12345" + :type comment_id: str + :param message: The text of the comment to update., defaults to None + :type message: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'message': message} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/comments/', + to_string(comment_id), + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CommentFull) + + def delete_comment_by_id( + self, + comment_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Permanently deletes a comment. + :param comment_id: The ID of the comment. + Example: "12345" + :type comment_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/comments/', + to_string(comment_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def create_comment( + self, + message: str, + item: CreateCommentItem, + *, + tagged_message: Optional[str] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CommentFull: + """ + Adds a comment by the user to a specific file, or + + as a reply to an other comment. + + :param message: The text of the comment. + + To mention a user, use the `tagged_message` + parameter instead. + :type message: str + :param item: The item to attach the comment to. + :type item: CreateCommentItem + :param tagged_message: The text of the comment, including `@[user_id:name]` + somewhere in the message to mention another user, which + will send them an email notification, letting them know + they have been mentioned. + + The `user_id` is the target user's ID, where the `name` + can be any custom phrase. In the Box UI this name will + link to the user's profile. + + If you are not mentioning another user, use `message` + instead., defaults to None + :type tagged_message: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'message': message, + 'tagged_message': tagged_message, + 'item': item, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/comments']), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CommentFull) diff --git a/box_sdk_gen/managers/device_pinners.py b/box_sdk_gen/managers/device_pinners.py new file mode 100644 index 000000000..612289415 --- /dev/null +++ b/box_sdk_gen/managers/device_pinners.py @@ -0,0 +1,188 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.device_pinner import DevicePinner + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.device_pinners import DevicePinners + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetEnterpriseDevicePinnersDirection(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class DevicePinnersManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_device_pinner_by_id( + self, + device_pinner_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DevicePinner: + """ + Retrieves information about an individual device pin. + :param device_pinner_id: The ID of the device pin. + Example: "2324234" + :type device_pinner_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/device_pinners/', + to_string(device_pinner_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DevicePinner) + + def delete_device_pinner_by_id( + self, + device_pinner_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes an individual device pin. + :param device_pinner_id: The ID of the device pin. + Example: "2324234" + :type device_pinner_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/device_pinners/', + to_string(device_pinner_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_enterprise_device_pinners( + self, + enterprise_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + direction: Optional[GetEnterpriseDevicePinnersDirection] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DevicePinners: + """ + Retrieves all the device pins within an enterprise. + + The user must have admin privileges, and the application + + + needs the "manage enterprise" scope to make this call. + + :param enterprise_id: The ID of the enterprise. + Example: "3442311" + :type enterprise_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param direction: The direction to sort results in. This can be either in alphabetical ascending + (`ASC`) or descending (`DESC`) order., defaults to None + :type direction: Optional[GetEnterpriseDevicePinnersDirection], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'marker': to_string(marker), + 'limit': to_string(limit), + 'direction': to_string(direction), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/enterprises/', + to_string(enterprise_id), + '/device_pinners', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DevicePinners) diff --git a/box_sdk_gen/managers/docgen.py b/box_sdk_gen/managers/docgen.py new file mode 100644 index 000000000..b5aad98a2 --- /dev/null +++ b/box_sdk_gen/managers/docgen.py @@ -0,0 +1,297 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from typing import List + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.schemas.v2025_r0.file_version_base_v2025_r0 import ( + FileVersionBaseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_document_generation_data_v2025_r0 import ( + DocGenDocumentGenerationDataV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_v2025_r0 import DocGenJobV2025R0 + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import ClientErrorV2025R0 + +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import ( + BoxVersionHeaderV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_jobs_full_v2025_r0 import ( + DocGenJobsFullV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_jobs_v2025_r0 import DocGenJobsV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_batch_base_v2025_r0 import ( + DocGenBatchBaseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_batch_create_request_v2025_r0 import ( + DocGenBatchCreateRequestV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateDocgenBatchV2025R0DestinationFolderTypeField(str, Enum): + FOLDER = 'folder' + + +class CreateDocgenBatchV2025R0DestinationFolder(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + id: str, + *, + type: CreateDocgenBatchV2025R0DestinationFolderTypeField = CreateDocgenBatchV2025R0DestinationFolderTypeField.FOLDER, + **kwargs + ): + """ + :param id: ID of the folder. + :type id: str + :param type: The value will always be `folder`., defaults to CreateDocgenBatchV2025R0DestinationFolderTypeField.FOLDER + :type type: CreateDocgenBatchV2025R0DestinationFolderTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class DocgenManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_docgen_job_by_id_v2025_r0( + self, + job_id: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DocGenJobV2025R0: + """ + Get details of the Box Doc Gen job. + :param job_id: Box Doc Gen job ID. + Example: 123 + :type job_id: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/docgen_jobs/', + to_string(job_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DocGenJobV2025R0) + + def get_docgen_jobs_v2025_r0( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DocGenJobsFullV2025R0: + """ + Lists all Box Doc Gen jobs for a user. + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/docgen_jobs'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DocGenJobsFullV2025R0) + + def get_docgen_batch_job_by_id_v2025_r0( + self, + batch_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DocGenJobsV2025R0: + """ + Lists Box Doc Gen jobs in a batch. + :param batch_id: Box Doc Gen batch ID. + Example: 123 + :type batch_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/docgen_batch_jobs/', + to_string(batch_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DocGenJobsV2025R0) + + def create_docgen_batch_v2025_r0( + self, + file: FileReferenceV2025R0, + input_source: str, + destination_folder: CreateDocgenBatchV2025R0DestinationFolder, + output_type: str, + document_generation_data: List[DocGenDocumentGenerationDataV2025R0], + *, + file_version: Optional[FileVersionBaseV2025R0] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DocGenBatchBaseV2025R0: + """ + Generates a document using a Box Doc Gen template. + :param input_source: Source of input. The value has to be `api` for all the API-based document generation requests. + :type input_source: str + :param output_type: Type of the output file. + :type output_type: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'file': file, + 'file_version': file_version, + 'input_source': input_source, + 'destination_folder': destination_folder, + 'output_type': output_type, + 'document_generation_data': document_generation_data, + } + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/docgen_batches'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DocGenBatchBaseV2025R0) diff --git a/box_sdk_gen/managers/docgen_template.py b/box_sdk_gen/managers/docgen_template.py new file mode 100644 index 000000000..203f2b097 --- /dev/null +++ b/box_sdk_gen/managers/docgen_template.py @@ -0,0 +1,349 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_base_v2025_r0 import ( + DocGenTemplateBaseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import ClientErrorV2025R0 + +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import ( + BoxVersionHeaderV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_create_request_v2025_r0 import ( + DocGenTemplateCreateRequestV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_templates_v2025_r0 import ( + DocGenTemplatesV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_v2025_r0 import DocGenTemplateV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_tags_v2025_r0 import DocGenTagsV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_tags_processing_message_v2025_r0 import ( + DocGenTagsProcessingMessageV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_jobs_v2025_r0 import DocGenJobsV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class DocgenTemplateManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_docgen_template_v2025_r0( + self, + file: FileReferenceV2025R0, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DocGenTemplateBaseV2025R0: + """ + Marks a file as a Box Doc Gen template. + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'file': file} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/docgen_templates'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DocGenTemplateBaseV2025R0) + + def get_docgen_templates_v2025_r0( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DocGenTemplatesV2025R0: + """ + Lists Box Doc Gen templates on which the user is a collaborator. + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/docgen_templates'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DocGenTemplatesV2025R0) + + def delete_docgen_template_by_id_v2025_r0( + self, + template_id: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Unmarks file as Box Doc Gen template. + :param template_id: ID of the file which will no longer be marked as a Box Doc Gen template. + Example: "123" + :type template_id: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/docgen_templates/', + to_string(template_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_docgen_template_by_id_v2025_r0( + self, + template_id: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DocGenTemplateV2025R0: + """ + Lists details of a specific Box Doc Gen template. + :param template_id: The ID of a Box Doc Gen template. + Example: 123 + :type template_id: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/docgen_templates/', + to_string(template_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DocGenTemplateV2025R0) + + def get_docgen_template_tags_v2025_r0( + self, + template_id: str, + *, + template_version_id: Optional[str] = None, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DocGenTagsV2025R0: + """ + Lists all tags in a Box Doc Gen template. + :param template_id: ID of template. + Example: 123 + :type template_id: str + :param template_version_id: Id of template version., defaults to None + :type template_version_id: Optional[str], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'template_version_id': to_string(template_version_id), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/docgen_templates/', + to_string(template_id), + '/tags', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DocGenTagsV2025R0) + + def get_docgen_template_job_by_id_v2025_r0( + self, + template_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> DocGenJobsV2025R0: + """ + Lists the users jobs which use this template. + :param template_id: Id of template to fetch jobs for. + Example: 123 + :type template_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/docgen_template_jobs/', + to_string(template_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, DocGenJobsV2025R0) diff --git a/box_sdk_gen/managers/downloads.py b/box_sdk_gen/managers/downloads.py new file mode 100644 index 000000000..c5bde893c --- /dev/null +++ b/box_sdk_gen/managers/downloads.py @@ -0,0 +1,256 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.internal.utils import write_input_stream_to_output_stream + +from box_sdk_gen.internal.utils import OutputStream + + +class DownloadsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_download_file_url( + self, + file_id: str, + *, + version: Optional[str] = None, + access_token: Optional[str] = None, + range: Optional[str] = None, + boxapi: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> str: + """ + Returns the contents of a file in binary format. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param version: The file version to download., defaults to None + :type version: Optional[str], optional + :param access_token: An optional access token that can be used to pre-authenticate this request, which means that a download link can be shared with a browser or a third party service without them needing to know how to handle the authentication. + When using this parameter, please make sure that the access token is sufficiently scoped down to only allow read access to that file and no other files or folders., defaults to None + :type access_token: Optional[str], optional + :param range: The byte range of the content to download. + + The format `bytes={start_byte}-{end_byte}` can be used to specify + what section of the file to download., defaults to None + :type range: Optional[str], optional + :param boxapi: The URL, and optional password, for the shared link of this item. + + This header can be used to access items that have not been + explicitly shared with a user. + + Use the format `shared_link=[link]` or if a password is required then + use `shared_link=[link]&shared_link_password=[password]`. + + This header can be used on the file or folder shared, as well as on any files + or folders nested within the item., defaults to None + :type boxapi: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'version': to_string(version), 'access_token': to_string(access_token)} + ) + headers_map: Dict[str, str] = prepare_params( + {'range': to_string(range), 'boxapi': to_string(boxapi), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/content', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + follow_redirects=False, + ) + ) + if 'location' in response.headers: + return response.headers.get('location') + if 'Location' in response.headers: + return response.headers.get('Location') + raise BoxSDKError(message='No location header in response') + + def download_file( + self, + file_id: str, + *, + version: Optional[str] = None, + access_token: Optional[str] = None, + range: Optional[str] = None, + boxapi: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Optional[ByteStream]: + """ + Returns the contents of a file in binary format. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param version: The file version to download., defaults to None + :type version: Optional[str], optional + :param access_token: An optional access token that can be used to pre-authenticate this request, which means that a download link can be shared with a browser or a third party service without them needing to know how to handle the authentication. + When using this parameter, please make sure that the access token is sufficiently scoped down to only allow read access to that file and no other files or folders., defaults to None + :type access_token: Optional[str], optional + :param range: The byte range of the content to download. + + The format `bytes={start_byte}-{end_byte}` can be used to specify + what section of the file to download., defaults to None + :type range: Optional[str], optional + :param boxapi: The URL, and optional password, for the shared link of this item. + + This header can be used to access items that have not been + explicitly shared with a user. + + Use the format `shared_link=[link]` or if a password is required then + use `shared_link=[link]&shared_link_password=[password]`. + + This header can be used on the file or folder shared, as well as on any files + or folders nested within the item., defaults to None + :type boxapi: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'version': to_string(version), 'access_token': to_string(access_token)} + ) + headers_map: Dict[str, str] = prepare_params( + {'range': to_string(range), 'boxapi': to_string(boxapi), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/content', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.BINARY, + auth=self.auth, + network_session=self.network_session, + ) + ) + if to_string(response.status) == '202': + return None + return response.content + + def download_file_to_output_stream( + self, + file_id: str, + output_stream: OutputStream, + *, + version: Optional[str] = None, + access_token: Optional[str] = None, + range: Optional[str] = None, + boxapi: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param output_stream: Download file to a given output stream + :type output_stream: OutputStream + :param version: The file version to download., defaults to None + :type version: Optional[str], optional + :param access_token: An optional access token that can be used to pre-authenticate this request, which means that a download link can be shared with a browser or a third party service without them needing to know how to handle the authentication. + When using this parameter, please make sure that the access token is sufficiently scoped down to only allow read access to that file and no other files or folders., defaults to None + :type access_token: Optional[str], optional + :param range: The byte range of the content to download. + + The format `bytes={start_byte}-{end_byte}` can be used to specify + what section of the file to download., defaults to None + :type range: Optional[str], optional + :param boxapi: The URL, and optional password, for the shared link of this item. + + This header can be used to access items that have not been + explicitly shared with a user. + + Use the format `shared_link=[link]` or if a password is required then + use `shared_link=[link]&shared_link_password=[password]`. + + This header can be used on the file or folder shared, as well as on any files + or folders nested within the item., defaults to None + :type boxapi: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + download_stream: ByteStream = self.download_file( + file_id, + version=version, + access_token=access_token, + range=range, + boxapi=boxapi, + extra_headers=extra_headers, + ) + write_input_stream_to_output_stream(download_stream, output_stream) diff --git a/box_sdk_gen/managers/email_aliases.py b/box_sdk_gen/managers/email_aliases.py new file mode 100644 index 000000000..6d837b116 --- /dev/null +++ b/box_sdk_gen/managers/email_aliases.py @@ -0,0 +1,175 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.email_aliases import EmailAliases + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.email_alias import EmailAlias + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class EmailAliasesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_user_email_aliases( + self, user_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> EmailAliases: + """ + Retrieves all email aliases for a user. The collection + + does not include the primary login for the user. + + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + '/email_aliases', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, EmailAliases) + + def create_user_email_alias( + self, + user_id: str, + email: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> EmailAlias: + """ + Adds a new email alias to a user account.. + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param email: The email address to add to the account as an alias. + + Note: The domain of the email alias needs to be registered + to your enterprise. + See the [domain verification guide]( + https://support.box.com/hc/en-us/articles/4408619650579-Domain-Verification + ) for steps to add a new domain. + :type email: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'email': email} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + '/email_aliases', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, EmailAlias) + + def delete_user_email_alias_by_id( + self, + user_id: str, + email_alias_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes an email alias from a user. + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param email_alias_id: The ID of the email alias. + Example: "23432" + :type email_alias_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + '/email_aliases/', + to_string(email_alias_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/events.py b/box_sdk_gen/managers/events.py new file mode 100644 index 000000000..16460b95c --- /dev/null +++ b/box_sdk_gen/managers/events.py @@ -0,0 +1,399 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.serialization.json import deserialize + +from typing import List + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.realtime_servers import RealtimeServers + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.events import Events + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.serialization.json import sd_to_json + + +class GetEventsStreamType(str, Enum): + ALL = 'all' + CHANGES = 'changes' + SYNC = 'sync' + ADMIN_LOGS = 'admin_logs' + ADMIN_LOGS_STREAMING = 'admin_logs_streaming' + + +class GetEventsEventType(str, Enum): + ACCESS_GRANTED = 'ACCESS_GRANTED' + ACCESS_REVOKED = 'ACCESS_REVOKED' + ADD_DEVICE_ASSOCIATION = 'ADD_DEVICE_ASSOCIATION' + ADD_LOGIN_ACTIVITY_DEVICE = 'ADD_LOGIN_ACTIVITY_DEVICE' + ADMIN_LOGIN = 'ADMIN_LOGIN' + APPLICATION_CREATED = 'APPLICATION_CREATED' + APPLICATION_PUBLIC_KEY_ADDED = 'APPLICATION_PUBLIC_KEY_ADDED' + APPLICATION_PUBLIC_KEY_DELETED = 'APPLICATION_PUBLIC_KEY_DELETED' + CHANGE_ADMIN_ROLE = 'CHANGE_ADMIN_ROLE' + CHANGE_FOLDER_PERMISSION = 'CHANGE_FOLDER_PERMISSION' + COLLABORATION_ACCEPT = 'COLLABORATION_ACCEPT' + COLLABORATION_EXPIRATION = 'COLLABORATION_EXPIRATION' + COLLABORATION_INVITE = 'COLLABORATION_INVITE' + COLLABORATION_REMOVE = 'COLLABORATION_REMOVE' + COLLABORATION_ROLE_CHANGE = 'COLLABORATION_ROLE_CHANGE' + COMMENT_CREATE = 'COMMENT_CREATE' + COMMENT_DELETE = 'COMMENT_DELETE' + CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY = ( + 'CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY' + ) + CONTENT_WORKFLOW_AUTOMATION_ADD = 'CONTENT_WORKFLOW_AUTOMATION_ADD' + CONTENT_WORKFLOW_AUTOMATION_DELETE = 'CONTENT_WORKFLOW_AUTOMATION_DELETE' + CONTENT_WORKFLOW_POLICY_ADD = 'CONTENT_WORKFLOW_POLICY_ADD' + CONTENT_WORKFLOW_SHARING_POLICY_VIOLATION = ( + 'CONTENT_WORKFLOW_SHARING_POLICY_VIOLATION' + ) + CONTENT_WORKFLOW_UPLOAD_POLICY_VIOLATION = ( + 'CONTENT_WORKFLOW_UPLOAD_POLICY_VIOLATION' + ) + COPY = 'COPY' + DATA_RETENTION_CREATE_RETENTION = 'DATA_RETENTION_CREATE_RETENTION' + DATA_RETENTION_REMOVE_RETENTION = 'DATA_RETENTION_REMOVE_RETENTION' + DELETE = 'DELETE' + DELETE_USER = 'DELETE_USER' + DEVICE_TRUST_CHECK_FAILED = 'DEVICE_TRUST_CHECK_FAILED' + DOWNLOAD = 'DOWNLOAD' + EDIT = 'EDIT' + EDIT_USER = 'EDIT_USER' + EMAIL_ALIAS_CONFIRM = 'EMAIL_ALIAS_CONFIRM' + EMAIL_ALIAS_REMOVE = 'EMAIL_ALIAS_REMOVE' + ENTERPRISE_APP_AUTHORIZATION_UPDATE = 'ENTERPRISE_APP_AUTHORIZATION_UPDATE' + EXTERNAL_COLLAB_SECURITY_SETTINGS = 'EXTERNAL_COLLAB_SECURITY_SETTINGS' + FAILED_LOGIN = 'FAILED_LOGIN' + FILE_MARKED_MALICIOUS = 'FILE_MARKED_MALICIOUS' + FILE_WATERMARKED_DOWNLOAD = 'FILE_WATERMARKED_DOWNLOAD' + GROUP_ADD_ITEM = 'GROUP_ADD_ITEM' + GROUP_ADD_USER = 'GROUP_ADD_USER' + GROUP_CREATION = 'GROUP_CREATION' + GROUP_DELETION = 'GROUP_DELETION' + 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' + ITEM_SYNC = 'ITEM_SYNC' + ITEM_UNSYNC = 'ITEM_UNSYNC' + LEGAL_HOLD_ASSIGNMENT_CREATE = 'LEGAL_HOLD_ASSIGNMENT_CREATE' + LEGAL_HOLD_ASSIGNMENT_DELETE = 'LEGAL_HOLD_ASSIGNMENT_DELETE' + LEGAL_HOLD_POLICY_CREATE = 'LEGAL_HOLD_POLICY_CREATE' + LEGAL_HOLD_POLICY_DELETE = 'LEGAL_HOLD_POLICY_DELETE' + LEGAL_HOLD_POLICY_UPDATE = 'LEGAL_HOLD_POLICY_UPDATE' + LOCK = 'LOCK' + LOGIN = 'LOGIN' + METADATA_INSTANCE_CREATE = 'METADATA_INSTANCE_CREATE' + METADATA_INSTANCE_DELETE = 'METADATA_INSTANCE_DELETE' + METADATA_INSTANCE_UPDATE = 'METADATA_INSTANCE_UPDATE' + METADATA_TEMPLATE_CREATE = 'METADATA_TEMPLATE_CREATE' + METADATA_TEMPLATE_DELETE = 'METADATA_TEMPLATE_DELETE' + METADATA_TEMPLATE_UPDATE = 'METADATA_TEMPLATE_UPDATE' + MOVE = 'MOVE' + NEW_USER = 'NEW_USER' + OAUTH2_ACCESS_TOKEN_REVOKE = 'OAUTH2_ACCESS_TOKEN_REVOKE' + PREVIEW = 'PREVIEW' + REMOVE_DEVICE_ASSOCIATION = 'REMOVE_DEVICE_ASSOCIATION' + REMOVE_LOGIN_ACTIVITY_DEVICE = 'REMOVE_LOGIN_ACTIVITY_DEVICE' + 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' + SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION = ( + 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION' + ) + SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED = 'SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED' + SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED_MISSING_JUSTIFICATION = ( + 'SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED_MISSING_JUSTIFICATION' + ) + SHIELD_JUSTIFICATION_APPROVAL = 'SHIELD_JUSTIFICATION_APPROVAL' + SHIELD_SHARED_LINK_ACCESS_BLOCKED = 'SHIELD_SHARED_LINK_ACCESS_BLOCKED' + SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE = ( + 'SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE' + ) + SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_UPDATE = ( + 'SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_UPDATE' + ) + SIGN_DOCUMENT_ASSIGNED = 'SIGN_DOCUMENT_ASSIGNED' + SIGN_DOCUMENT_CANCELLED = 'SIGN_DOCUMENT_CANCELLED' + SIGN_DOCUMENT_COMPLETED = 'SIGN_DOCUMENT_COMPLETED' + SIGN_DOCUMENT_CONVERTED = 'SIGN_DOCUMENT_CONVERTED' + SIGN_DOCUMENT_CREATED = 'SIGN_DOCUMENT_CREATED' + SIGN_DOCUMENT_DECLINED = 'SIGN_DOCUMENT_DECLINED' + SIGN_DOCUMENT_EXPIRED = 'SIGN_DOCUMENT_EXPIRED' + SIGN_DOCUMENT_SIGNED = 'SIGN_DOCUMENT_SIGNED' + SIGN_DOCUMENT_VIEWED_BY_SIGNED = 'SIGN_DOCUMENT_VIEWED_BY_SIGNED' + SIGNER_DOWNLOADED = 'SIGNER_DOWNLOADED' + SIGNER_FORWARDED = 'SIGNER_FORWARDED' + STORAGE_EXPIRATION = 'STORAGE_EXPIRATION' + TASK_ASSIGNMENT_CREATE = 'TASK_ASSIGNMENT_CREATE' + TASK_ASSIGNMENT_DELETE = 'TASK_ASSIGNMENT_DELETE' + TASK_ASSIGNMENT_UPDATE = 'TASK_ASSIGNMENT_UPDATE' + TASK_CREATE = 'TASK_CREATE' + TASK_UPDATE = 'TASK_UPDATE' + TERMS_OF_SERVICE_ACCEPT = 'TERMS_OF_SERVICE_ACCEPT' + TERMS_OF_SERVICE_REJECT = 'TERMS_OF_SERVICE_REJECT' + UNDELETE = 'UNDELETE' + UNLOCK = 'UNLOCK' + UNSHARE = 'UNSHARE' + UPDATE_COLLABORATION_EXPIRATION = 'UPDATE_COLLABORATION_EXPIRATION' + UPDATE_SHARE_EXPIRATION = 'UPDATE_SHARE_EXPIRATION' + UPLOAD = 'UPLOAD' + USER_AUTHENTICATE_OAUTH2_ACCESS_TOKEN_CREATE = ( + 'USER_AUTHENTICATE_OAUTH2_ACCESS_TOKEN_CREATE' + ) + WATERMARK_LABEL_CREATE = 'WATERMARK_LABEL_CREATE' + WATERMARK_LABEL_DELETE = 'WATERMARK_LABEL_DELETE' + + +class EventsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_events_with_long_polling( + self, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> RealtimeServers: + """ + Returns a list of real-time servers that can be used for long-polling updates + + to the [event stream](#get-events). + + + Long polling is the concept where a HTTP request is kept open until the + + + server sends a response, then repeating the process over and over to receive + + + updated responses. + + + Long polling the event stream can only be used for user events, not for + + + enterprise events. + + + To use long polling, first use this endpoint to retrieve a list of long poll + + + URLs. Next, make a long poll request to any of the provided URLs. + + + When an event occurs in monitored account a response with the value + + + `new_change` will be sent. The response contains no other details as + + + it only serves as a prompt to take further action such as sending a + + + request to the [events endpoint](#get-events) with the last known + + + `stream_position`. + + + After the server sends this response it closes the connection. You must now + + + repeat the long poll process to begin listening for events again. + + + If no events occur for a while and the connection times out you will + + + receive a response with the value `reconnect`. When you receive this response + + + you’ll make another call to this endpoint to restart the process. + + + If you receive no events in `retry_timeout` seconds then you will need to + + + make another request to the real-time server (one of the URLs in the response + + + for this endpoint). This might be necessary due to network errors. + + + Finally, if you receive a `max_retries` error when making a request to the + + + real-time server, you should start over by making a call to this endpoint + + + first. + + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/events']), + method='OPTIONS', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, RealtimeServers) + + def get_events( + self, + *, + stream_type: Optional[GetEventsStreamType] = None, + stream_position: Optional[str] = None, + limit: Optional[int] = None, + event_type: Optional[List[GetEventsEventType]] = None, + created_after: Optional[DateTime] = None, + created_before: Optional[DateTime] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Events: + """ + Returns up to a year of past events for a given user + + or for the entire enterprise. + + + By default this returns events for the authenticated user. To retrieve events + + + for the entire enterprise, set the `stream_type` to `admin_logs_streaming` + + + for live monitoring of new events, or `admin_logs` for querying across + + + historical events. The user making the API call will + + + need to have admin privileges, and the application will need to have the + + + scope `manage enterprise properties` checked. + + :param stream_type: Defines the type of events that are returned + + * `all` returns everything for a user and is the default + * `changes` returns events that may cause file tree changes + such as file updates or collaborations. + * `sync` is similar to `changes` but only applies to synced folders + * `admin_logs` returns all events for an entire enterprise and + requires the user making the API call to have admin permissions. This + stream type is for programmatically pulling from a 1 year history of + events across all users within the enterprise and within a + `created_after` and `created_before` time frame. The complete history + of events will be returned in chronological order based on the event + time, but latency will be much higher than `admin_logs_streaming`. + * `admin_logs_streaming` returns all events for an entire enterprise and + requires the user making the API call to have admin permissions. This + stream type is for polling for recent events across all users within + the enterprise. Latency will be much lower than `admin_logs`, but + events will not be returned in chronological order and may + contain duplicates., defaults to None + :type stream_type: Optional[GetEventsStreamType], optional + :param stream_position: The location in the event stream to start receiving events from. + + * `now` will return an empty list events and + the latest stream position for initialization. + * `0` or `null` will return all events., defaults to None + :type stream_position: Optional[str], optional + :param limit: Limits the number of events returned. + + Note: Sometimes, the events less than the limit requested can be returned + even when there may be more events remaining. This is primarily done in + the case where a number of events have already been retrieved and these + retrieved events are returned rather than delaying for an unknown amount + of time to see if there are any more results., defaults to None + :type limit: Optional[int], optional + :param event_type: A comma-separated list of events to filter by. This can only be used when + requesting the events with a `stream_type` of `admin_logs` or + `adming_logs_streaming`. For any other `stream_type` this value will be + ignored., defaults to None + :type event_type: Optional[List[GetEventsEventType]], optional + :param created_after: The lower bound date and time to return events for. This can only be used + when requesting the events with a `stream_type` of `admin_logs`. For any + other `stream_type` this value will be ignored., defaults to None + :type created_after: Optional[DateTime], optional + :param created_before: The upper bound date and time to return events for. This can only be used + when requesting the events with a `stream_type` of `admin_logs`. For any + other `stream_type` this value will be ignored., defaults to None + :type created_before: Optional[DateTime], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'stream_type': to_string(stream_type), + 'stream_position': to_string(stream_position), + 'limit': to_string(limit), + 'event_type': to_string(event_type), + 'created_after': to_string(created_after), + 'created_before': to_string(created_before), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/events']), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Events) diff --git a/box_sdk_gen/managers/file_classifications.py b/box_sdk_gen/managers/file_classifications.py new file mode 100644 index 000000000..c38a2bbff --- /dev/null +++ b/box_sdk_gen/managers/file_classifications.py @@ -0,0 +1,313 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from typing import List + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.classification import Classification + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class UpdateClassificationOnFileRequestBodyOpField(str, Enum): + REPLACE = 'replace' + + +class UpdateClassificationOnFileRequestBodyPathField(str, Enum): + _BOX__SECURITY__CLASSIFICATION__KEY = '/Box__Security__Classification__Key' + + +class UpdateClassificationOnFileRequestBody(BaseObject): + def __init__( + self, + value: str, + *, + op: UpdateClassificationOnFileRequestBodyOpField = UpdateClassificationOnFileRequestBodyOpField.REPLACE, + path: UpdateClassificationOnFileRequestBodyPathField = UpdateClassificationOnFileRequestBodyPathField._BOX__SECURITY__CLASSIFICATION__KEY, + **kwargs + ): + """ + :param value: The name of the classification to apply to this file. + + To list the available classifications in an enterprise, + use the classification API to retrieve the + [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) + which lists all available classification keys. + :type value: str + :param op: The value will always be `replace`., defaults to UpdateClassificationOnFileRequestBodyOpField.REPLACE + :type op: UpdateClassificationOnFileRequestBodyOpField, optional + :param path: Defines classifications + available in the enterprise., defaults to UpdateClassificationOnFileRequestBodyPathField._BOX__SECURITY__CLASSIFICATION__KEY + :type path: UpdateClassificationOnFileRequestBodyPathField, optional + """ + super().__init__(**kwargs) + self.value = value + self.op = op + self.path = path + + +class FileClassificationsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_classification_on_file( + self, file_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Classification: + """ + Retrieves the classification metadata instance that + + has been applied to a file. + + + This API can also be called by including the enterprise ID in the + + + URL explicitly, for example + + + `/files/:id//enterprise_12345/securityClassification-6VMVochwUWo`. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/enterprise/securityClassification-6VMVochwUWo', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Classification) + + def add_classification_to_file( + self, + file_id: str, + *, + box_security_classification_key: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Classification: + """ + Adds a classification to a file by specifying the label of the + + classification to add. + + + This API can also be called by including the enterprise ID in the + + + URL explicitly, for example + + + `/files/:id//enterprise_12345/securityClassification-6VMVochwUWo`. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param box_security_classification_key: The name of the classification to apply to this file. + + To list the available classifications in an enterprise, + use the classification API to retrieve the + [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) + which lists all available classification keys., defaults to None + :type box_security_classification_key: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'Box__Security__Classification__Key': box_security_classification_key + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/enterprise/securityClassification-6VMVochwUWo', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Classification) + + def update_classification_on_file( + self, + file_id: str, + request_body: List[UpdateClassificationOnFileRequestBody], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Classification: + """ + Updates a classification on a file. + + The classification can only be updated if a classification has already been + + + applied to the file before. When editing classifications, only values are + + + defined for the enterprise will be accepted. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param request_body: Request body of updateClassificationOnFile method + :type request_body: List[UpdateClassificationOnFileRequestBody] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/enterprise/securityClassification-6VMVochwUWo', + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json-patch+json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Classification) + + def delete_classification_from_file( + self, file_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes any classifications from a file. + + This API can also be called by including the enterprise ID in the + + + URL explicitly, for example + + + `/files/:id//enterprise_12345/securityClassification-6VMVochwUWo`. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/enterprise/securityClassification-6VMVochwUWo', + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/file_metadata.py b/box_sdk_gen/managers/file_metadata.py new file mode 100644 index 000000000..8dc0577f2 --- /dev/null +++ b/box_sdk_gen/managers/file_metadata.py @@ -0,0 +1,420 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from typing import List + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.metadatas import Metadatas + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.metadata_full import MetadataFull + +from box_sdk_gen.schemas.metadata_error import MetadataError + +from box_sdk_gen.schemas.metadata_instance_value import MetadataInstanceValue + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetFileMetadataByIdScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class CreateFileMetadataByIdScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class UpdateFileMetadataByIdScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class UpdateFileMetadataByIdRequestBodyOpField(str, Enum): + ADD = 'add' + REPLACE = 'replace' + REMOVE = 'remove' + TEST = 'test' + MOVE = 'move' + COPY = 'copy' + + +class UpdateFileMetadataByIdRequestBody(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'from_': 'from', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'from': 'from_', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + op: Optional[UpdateFileMetadataByIdRequestBodyOpField] = None, + path: Optional[str] = None, + value: Optional[MetadataInstanceValue] = None, + from_: Optional[str] = None, + **kwargs + ): + """ + :param op: The type of change to perform on the template. Some + of these are hazardous as they will change existing templates., defaults to None + :type op: Optional[UpdateFileMetadataByIdRequestBodyOpField], optional + :param path: The location in the metadata JSON object + to apply the changes to, in the format of a + [JSON-Pointer](https://tools.ietf.org/html/rfc6901). + + The path must always be prefixed with a `/` to represent the root + of the template. The characters `~` and `/` are reserved + characters and must be escaped in the key., defaults to None + :type path: Optional[str], optional + :param from_: The location in the metadata JSON object to move or copy a value + from. Required for `move` or `copy` operations and must be in the + format of a [JSON-Pointer](https://tools.ietf.org/html/rfc6901)., defaults to None + :type from_: Optional[str], optional + """ + super().__init__(**kwargs) + self.op = op + self.path = path + self.value = value + self.from_ = from_ + + +class DeleteFileMetadataByIdScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class FileMetadataManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_metadata( + self, file_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Metadatas: + """ + Retrieves all metadata for a given file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Metadatas) + + def get_file_metadata_by_id( + self, + file_id: str, + scope: GetFileMetadataByIdScope, + template_key: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataFull: + """ + Retrieves the instance of a metadata template that has been applied to a + + file. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param scope: The scope of the metadata template. + Example: "global" + :type scope: GetFileMetadataByIdScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/', + to_string(scope), + '/', + to_string(template_key), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataFull) + + def create_file_metadata_by_id( + self, + file_id: str, + scope: CreateFileMetadataByIdScope, + template_key: str, + request_body: Dict, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataFull: + """ + Applies an instance of a metadata template to a file. + + In most cases only values that are present in the metadata template + + + will be accepted, except for the `global.properties` template which accepts + + + any key-value pair. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param scope: The scope of the metadata template. + Example: "global" + :type scope: CreateFileMetadataByIdScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param request_body: Request body of createFileMetadataById method + :type request_body: Dict + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/', + to_string(scope), + '/', + to_string(template_key), + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataFull) + + def update_file_metadata_by_id( + self, + file_id: str, + scope: UpdateFileMetadataByIdScope, + template_key: str, + request_body: List[UpdateFileMetadataByIdRequestBody], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataFull: + """ + Updates a piece of metadata on a file. + + The metadata instance can only be updated if the template has already been + + + applied to the file before. When editing metadata, only values that match + + + the metadata template schema will be accepted. + + + The update is applied atomically. If any errors occur during the + + + application of the operations, the metadata instance will not be changed. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param scope: The scope of the metadata template. + Example: "global" + :type scope: UpdateFileMetadataByIdScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param request_body: Request body of updateFileMetadataById method + :type request_body: List[UpdateFileMetadataByIdRequestBody] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/', + to_string(scope), + '/', + to_string(template_key), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json-patch+json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataFull) + + def delete_file_metadata_by_id( + self, + file_id: str, + scope: DeleteFileMetadataByIdScope, + template_key: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a piece of file metadata. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param scope: The scope of the metadata template. + Example: "global" + :type scope: DeleteFileMetadataByIdScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/', + to_string(scope), + '/', + to_string(template_key), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/file_requests.py b/box_sdk_gen/managers/file_requests.py new file mode 100644 index 000000000..594365858 --- /dev/null +++ b/box_sdk_gen/managers/file_requests.py @@ -0,0 +1,398 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.file_request import FileRequest + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.file_request_update_request import FileRequestUpdateRequest + +from box_sdk_gen.schemas.file_request_copy_request import FileRequestCopyRequest + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class UpdateFileRequestByIdStatus(str, Enum): + ACTIVE = 'active' + INACTIVE = 'inactive' + + +class CreateFileRequestCopyFolderTypeField(str, Enum): + FOLDER = 'folder' + + +class CreateFileRequestCopyFolder(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + id: str, + *, + type: Optional[CreateFileRequestCopyFolderTypeField] = None, + **kwargs + ): + """ + :param id: The ID of the folder to associate the new + file request to. + :type id: str + :param type: The value will always be `folder`., defaults to None + :type type: Optional[CreateFileRequestCopyFolderTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CreateFileRequestCopyStatus(str, Enum): + ACTIVE = 'active' + INACTIVE = 'inactive' + + +class FileRequestsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_request_by_id( + self, + file_request_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileRequest: + """ + Retrieves the information about a file request. + :param file_request_id: The unique identifier that represent a file request. + + The ID for any file request can be determined + by visiting a file request builder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/filerequest/123` + the `file_request_id` is `123`. + Example: "123" + :type file_request_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/file_requests/', + to_string(file_request_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileRequest) + + def update_file_request_by_id( + self, + file_request_id: str, + *, + title: Optional[str] = None, + description: Optional[str] = None, + status: Optional[UpdateFileRequestByIdStatus] = None, + is_email_required: Optional[bool] = None, + is_description_required: Optional[bool] = None, + expires_at: Optional[DateTime] = None, + if_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileRequest: + """ + Updates a file request. This can be used to activate or + + deactivate a file request. + + :param file_request_id: The unique identifier that represent a file request. + + The ID for any file request can be determined + by visiting a file request builder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/filerequest/123` + the `file_request_id` is `123`. + Example: "123" + :type file_request_id: str + :param title: An optional new title for the file request. This can be + used to change the title of the file request. + + This will default to the value on the existing file request., defaults to None + :type title: Optional[str], optional + :param description: An optional new description for the file request. This can be + used to change the description of the file request. + + This will default to the value on the existing file request., defaults to None + :type description: Optional[str], optional + :param status: An optional new status of the file request. + + When the status is set to `inactive`, the file request + will no longer accept new submissions, and any visitor + to the file request URL will receive a `HTTP 404` status + code. + + This will default to the value on the existing file request., defaults to None + :type status: Optional[UpdateFileRequestByIdStatus], optional + :param is_email_required: Whether a file request submitter is required to provide + their email address. + + When this setting is set to true, the Box UI will show + an email field on the file request form. + + This will default to the value on the existing file request., defaults to None + :type is_email_required: Optional[bool], optional + :param is_description_required: Whether a file request submitter is required to provide + a description of the files they are submitting. + + When this setting is set to true, the Box UI will show + a description field on the file request form. + + This will default to the value on the existing file request., defaults to None + :type is_description_required: Optional[bool], optional + :param expires_at: The date after which a file request will no longer accept new + submissions. + + After this date, the `status` will automatically be set to + `inactive`. + + This will default to the value on the existing file request., defaults to None + :type expires_at: Optional[DateTime], optional + :param if_match: Ensures this item hasn't recently changed before + making changes. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `412 Precondition Failed` if it + has changed since., defaults to None + :type if_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'title': title, + 'description': description, + 'status': status, + 'is_email_required': is_email_required, + 'is_description_required': is_description_required, + 'expires_at': expires_at, + } + headers_map: Dict[str, str] = prepare_params( + {'if-match': to_string(if_match), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/file_requests/', + to_string(file_request_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileRequest) + + def delete_file_request_by_id( + self, + file_request_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a file request permanently. + :param file_request_id: The unique identifier that represent a file request. + + The ID for any file request can be determined + by visiting a file request builder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/filerequest/123` + the `file_request_id` is `123`. + Example: "123" + :type file_request_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/file_requests/', + to_string(file_request_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def create_file_request_copy( + self, + file_request_id: str, + folder: CreateFileRequestCopyFolder, + *, + title: Optional[str] = None, + description: Optional[str] = None, + status: Optional[CreateFileRequestCopyStatus] = None, + is_email_required: Optional[bool] = None, + is_description_required: Optional[bool] = None, + expires_at: Optional[DateTime] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileRequest: + """ + Copies an existing file request that is already present on one folder, + + and applies it to another folder. + + :param file_request_id: The unique identifier that represent a file request. + + The ID for any file request can be determined + by visiting a file request builder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/filerequest/123` + the `file_request_id` is `123`. + Example: "123" + :type file_request_id: str + :param folder: The folder to associate the new file request to. + :type folder: CreateFileRequestCopyFolder + :param title: An optional new title for the file request. This can be + used to change the title of the file request. + + This will default to the value on the existing file request., defaults to None + :type title: Optional[str], optional + :param description: An optional new description for the file request. This can be + used to change the description of the file request. + + This will default to the value on the existing file request., defaults to None + :type description: Optional[str], optional + :param status: An optional new status of the file request. + + When the status is set to `inactive`, the file request + will no longer accept new submissions, and any visitor + to the file request URL will receive a `HTTP 404` status + code. + + This will default to the value on the existing file request., defaults to None + :type status: Optional[CreateFileRequestCopyStatus], optional + :param is_email_required: Whether a file request submitter is required to provide + their email address. + + When this setting is set to true, the Box UI will show + an email field on the file request form. + + This will default to the value on the existing file request., defaults to None + :type is_email_required: Optional[bool], optional + :param is_description_required: Whether a file request submitter is required to provide + a description of the files they are submitting. + + When this setting is set to true, the Box UI will show + a description field on the file request form. + + This will default to the value on the existing file request., defaults to None + :type is_description_required: Optional[bool], optional + :param expires_at: The date after which a file request will no longer accept new + submissions. + + After this date, the `status` will automatically be set to + `inactive`. + + This will default to the value on the existing file request., defaults to None + :type expires_at: Optional[DateTime], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'folder': folder, + 'title': title, + 'description': description, + 'status': status, + 'is_email_required': is_email_required, + 'is_description_required': is_description_required, + 'expires_at': expires_at, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/file_requests/', + to_string(file_request_id), + '/copy', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileRequest) diff --git a/box_sdk_gen/managers/file_version_legal_holds.py b/box_sdk_gen/managers/file_version_legal_holds.py new file mode 100644 index 000000000..2dc2451de --- /dev/null +++ b/box_sdk_gen/managers/file_version_legal_holds.py @@ -0,0 +1,179 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.file_version_legal_hold import FileVersionLegalHold + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.file_version_legal_holds import FileVersionLegalHolds + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class FileVersionLegalHoldsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_version_legal_hold_by_id( + self, + file_version_legal_hold_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileVersionLegalHold: + """ + Retrieves information about the legal hold policies + + assigned to a file version. + + :param file_version_legal_hold_id: The ID of the file version legal hold. + Example: "2348213" + :type file_version_legal_hold_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/file_version_legal_holds/', + to_string(file_version_legal_hold_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileVersionLegalHold) + + def get_file_version_legal_holds( + self, + policy_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileVersionLegalHolds: + """ + Get a list of file versions on legal hold for a legal hold + + assignment. + + + Due to ongoing re-architecture efforts this API might not return all file + + + versions for this policy ID. + + + Instead, this API will only return file versions held in the legacy + + + architecture. Two new endpoints will available to request any file versions + + + held in the new architecture. + + + For file versions held in the new architecture, the `GET + + + /legal_hold_policy_assignments/:id/file_versions_on_hold` API can be used to + + + return all past file versions available for this policy assignment, and the + + + `GET /legal_hold_policy_assignments/:id/files_on_hold` API can be used to + + + return any current (latest) versions of a file under legal hold. + + + The `GET /legal_hold_policy_assignments?policy_id={id}` API can be used to + + + find a list of policy assignments for a given policy ID. + + + Once the re-architecture is completed this API will be deprecated. + + :param policy_id: The ID of the legal hold policy to get the file version legal + holds for. + :type policy_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'policy_id': to_string(policy_id), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/file_version_legal_holds', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileVersionLegalHolds) diff --git a/box_sdk_gen/managers/file_version_retentions.py b/box_sdk_gen/managers/file_version_retentions.py new file mode 100644 index 000000000..8c2ddf75c --- /dev/null +++ b/box_sdk_gen/managers/file_version_retentions.py @@ -0,0 +1,187 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.file_version_retentions import FileVersionRetentions + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.file_version_retention import FileVersionRetention + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetFileVersionRetentionsDispositionAction(str, Enum): + PERMANENTLY_DELETE = 'permanently_delete' + REMOVE_RETENTION = 'remove_retention' + + +class FileVersionRetentionsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_version_retentions( + self, + *, + file_id: Optional[str] = None, + file_version_id: Optional[str] = None, + policy_id: Optional[str] = None, + disposition_action: Optional[GetFileVersionRetentionsDispositionAction] = None, + disposition_before: Optional[str] = None, + disposition_after: Optional[str] = None, + limit: Optional[int] = None, + marker: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileVersionRetentions: + """ + Retrieves all file version retentions for the given enterprise. + + **Note**: + + + File retention API is now **deprecated**. + + + To get information about files and file versions under retention, + + + see [files under retention](e://get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](e://get-retention-policy-assignments-id-file-versions-under-retention) endpoints. + + :param file_id: Filters results by files with this ID., defaults to None + :type file_id: Optional[str], optional + :param file_version_id: Filters results by file versions with this ID., defaults to None + :type file_version_id: Optional[str], optional + :param policy_id: Filters results by the retention policy with this ID., defaults to None + :type policy_id: Optional[str], optional + :param disposition_action: Filters results by the retention policy with this disposition + action., defaults to None + :type disposition_action: Optional[GetFileVersionRetentionsDispositionAction], optional + :param disposition_before: Filters results by files that will have their disposition + come into effect before this date., defaults to None + :type disposition_before: Optional[str], optional + :param disposition_after: Filters results by files that will have their disposition + come into effect after this date., defaults to None + :type disposition_after: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'file_id': to_string(file_id), + 'file_version_id': to_string(file_version_id), + 'policy_id': to_string(policy_id), + 'disposition_action': to_string(disposition_action), + 'disposition_before': to_string(disposition_before), + 'disposition_after': to_string(disposition_after), + 'limit': to_string(limit), + 'marker': to_string(marker), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/file_version_retentions', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileVersionRetentions) + + def get_file_version_retention_by_id( + self, + file_version_retention_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileVersionRetention: + """ + Returns information about a file version retention. + + **Note**: + + + File retention API is now **deprecated**. + + + To get information about files and file versions under retention, + + + see [files under retention](e://get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](e://get-retention-policy-assignments-id-file-versions-under-retention) endpoints. + + :param file_version_retention_id: The ID of the file version retention. + Example: "3424234" + :type file_version_retention_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/file_version_retentions/', + to_string(file_version_retention_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileVersionRetention) diff --git a/box_sdk_gen/managers/file_versions.py b/box_sdk_gen/managers/file_versions.py new file mode 100644 index 000000000..5f79b0122 --- /dev/null +++ b/box_sdk_gen/managers/file_versions.py @@ -0,0 +1,423 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.file_versions import FileVersions + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.file_version_full import FileVersionFull + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class PromoteFileVersionType(str, Enum): + FILE_VERSION = 'file_version' + + +class FileVersionsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_versions( + self, + file_id: str, + *, + fields: Optional[List[str]] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileVersions: + """ + Retrieve a list of the past versions for a file. + + Versions are only tracked by Box users with premium accounts. To fetch the ID + + + of the current version of a file, use the `GET /file/:id` API. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'limit': to_string(limit), + 'offset': to_string(offset), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/versions', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileVersions) + + def get_file_version_by_id( + self, + file_id: str, + file_version_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileVersionFull: + """ + Retrieve a specific version of a file. + + Versions are only tracked for Box users with premium accounts. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param file_version_id: The ID of the file version. + Example: "1234" + :type file_version_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/versions/', + to_string(file_version_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileVersionFull) + + def delete_file_version_by_id( + self, + file_id: str, + file_version_id: str, + *, + if_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Move a file version to the trash. + + Versions are only tracked for Box users with premium accounts. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param file_version_id: The ID of the file version. + Example: "1234" + :type file_version_id: str + :param if_match: Ensures this item hasn't recently changed before + making changes. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `412 Precondition Failed` if it + has changed since., defaults to None + :type if_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'if-match': to_string(if_match), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/versions/', + to_string(file_version_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def update_file_version_by_id( + self, + file_id: str, + file_version_id: str, + *, + trashed_at: Union[Optional[str], NullValue] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileVersionFull: + """ + Restores a specific version of a file after it was deleted. + + Don't use this endpoint to restore Box Notes, + + + as it works with file formats such as PDF, DOC, + + + PPTX or similar. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param file_version_id: The ID of the file version. + Example: "1234" + :type file_version_id: str + :param trashed_at: Set this to `null` to clear + the date and restore the file., defaults to None + :type trashed_at: Union[Optional[str], NullValue], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'trashed_at': trashed_at} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/versions/', + to_string(file_version_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileVersionFull) + + def promote_file_version( + self, + file_id: str, + *, + id: Optional[str] = None, + type: Optional[PromoteFileVersionType] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileVersionFull: + """ + Promote a specific version of a file. + + If previous versions exist, this method can be used to + + + promote one of the older versions to the top of the version history. + + + This creates a new copy of the old version and puts it at the + + + top of the versions history. The file will have the exact same contents + + + as the older version, with the the same hash digest, `etag`, and + + + name as the original. + + + Other properties such as comments do not get updated to their + + + former values. + + + Don't use this endpoint to restore Box Notes, + + + as it works with file formats such as PDF, DOC, + + + PPTX or similar. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param id: The file version ID., defaults to None + :type id: Optional[str], optional + :param type: The type to promote., defaults to None + :type type: Optional[PromoteFileVersionType], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'id': id, 'type': type} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/versions/current', + ] + ), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileVersionFull) diff --git a/box_sdk_gen/managers/file_watermarks.py b/box_sdk_gen/managers/file_watermarks.py new file mode 100644 index 000000000..c82eeb42d --- /dev/null +++ b/box_sdk_gen/managers/file_watermarks.py @@ -0,0 +1,199 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.watermark import Watermark + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class UpdateFileWatermarkWatermarkImprintField(str, Enum): + DEFAULT = 'default' + + +class UpdateFileWatermarkWatermark(BaseObject): + def __init__( + self, + *, + imprint: UpdateFileWatermarkWatermarkImprintField = UpdateFileWatermarkWatermarkImprintField.DEFAULT, + **kwargs + ): + """ + :param imprint: The type of watermark to apply. + + Currently only supports one option., defaults to UpdateFileWatermarkWatermarkImprintField.DEFAULT + :type imprint: UpdateFileWatermarkWatermarkImprintField, optional + """ + super().__init__(**kwargs) + self.imprint = imprint + + +class FileWatermarksManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_watermark( + self, file_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Watermark: + """ + Retrieve the watermark for a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/watermark', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Watermark) + + def update_file_watermark( + self, + file_id: str, + watermark: UpdateFileWatermarkWatermark, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Watermark: + """ + Applies or update a watermark on a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param watermark: The watermark to imprint on the file. + :type watermark: UpdateFileWatermarkWatermark + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'watermark': watermark} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/watermark', + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Watermark) + + def delete_file_watermark( + self, file_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes the watermark from a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/watermark', + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/files.py b/box_sdk_gen/managers/files.py new file mode 100644 index 000000000..6b975d358 --- /dev/null +++ b/box_sdk_gen/managers/files.py @@ -0,0 +1,786 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from enum import Enum + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import DateTime + + +class UpdateFileByIdParent(BaseObject): + def __init__( + self, *, id: Optional[str] = None, user_id: Optional[str] = None, **kwargs + ): + """ + :param id: The ID of parent item., defaults to None + :type id: Optional[str], optional + :param user_id: The input for `user_id` is optional. Moving to non-root folder is not allowed when `user_id` is present. Parent folder id should be zero when `user_id` is provided., defaults to None + :type user_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.user_id = user_id + + +class UpdateFileByIdSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class UpdateFileByIdSharedLinkPermissionsField(BaseObject): + def __init__(self, *, can_download: Optional[bool] = None, **kwargs): + """ + :param can_download: If the shared link allows for downloading of files. + This can only be set when `access` is set to + `open` or `company`., defaults to None + :type can_download: Optional[bool], optional + """ + super().__init__(**kwargs) + self.can_download = can_download + + +class UpdateFileByIdSharedLink(BaseObject): + def __init__( + self, + *, + access: Optional[UpdateFileByIdSharedLinkAccessField] = None, + password: Optional[str] = None, + vanity_name: Optional[str] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[UpdateFileByIdSharedLinkPermissionsField] = None, + **kwargs + ): + """ + :param access: The level of access for the shared link. This can be + restricted to anyone with the link (`open`), only people + within the company (`company`) and only those who + have been invited to the folder (`collaborators`). + + If not set, this field defaults to the access level specified + by the enterprise admin. To create a shared link with this + default setting pass the `shared_link` object with + no `access` field, for example `{ "shared_link": {} }`. + + The `company` access level is only available to paid + accounts., defaults to None + :type access: Optional[UpdateFileByIdSharedLinkAccessField], optional + :param password: The password required to access the shared link. Set the + password to `null` to remove it. + Passwords must now be at least eight characters + long and include a number, upper case letter, or + a non-numeric or non-alphabetic character. + A password can only be set when `access` is set to `open`., defaults to None + :type password: Optional[str], optional + :param vanity_name: Defines a custom vanity name to use in the shared link URL, + for example `https://app.box.com/v/my-shared-link`. + + Custom URLs should not be used when sharing sensitive content + as vanity URLs are a lot easier to guess than regular shared links., defaults to None + :type vanity_name: Optional[str], optional + :param unshared_at: The timestamp at which this shared link will + expire. This field can only be set by + users with paid accounts., defaults to None + :type unshared_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.access = access + self.password = password + self.vanity_name = vanity_name + self.unshared_at = unshared_at + self.permissions = permissions + + +class UpdateFileByIdLockAccessField(str, Enum): + LOCK = 'lock' + + +class UpdateFileByIdLock(BaseObject): + def __init__( + self, + *, + access: Optional[UpdateFileByIdLockAccessField] = None, + expires_at: Optional[DateTime] = None, + is_download_prevented: Optional[bool] = None, + **kwargs + ): + """ + :param access: The type of this object., defaults to None + :type access: Optional[UpdateFileByIdLockAccessField], optional + :param expires_at: Defines the time at which the lock expires., defaults to None + :type expires_at: Optional[DateTime], optional + :param is_download_prevented: Defines if the file can be downloaded while it is locked., defaults to None + :type is_download_prevented: Optional[bool], optional + """ + super().__init__(**kwargs) + self.access = access + self.expires_at = expires_at + self.is_download_prevented = is_download_prevented + + +class UpdateFileByIdPermissionsCanDownloadField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + + +class UpdateFileByIdPermissions(BaseObject): + def __init__( + self, + *, + can_download: Optional[UpdateFileByIdPermissionsCanDownloadField] = None, + **kwargs + ): + """ + :param can_download: Defines who is allowed to download this file. The possible + values are either `open` for everyone or `company` for + the other members of the user's enterprise. + + This setting overrides the download permissions that are + normally part of the `role` of a collaboration. When set to + `company`, this essentially removes the download option for + external users with `viewer` or `editor` a roles., defaults to None + :type can_download: Optional[UpdateFileByIdPermissionsCanDownloadField], optional + """ + super().__init__(**kwargs) + self.can_download = can_download + + +class UpdateFileByIdCollections(BaseObject): + def __init__( + self, *, id: Optional[str] = None, type: Optional[str] = None, **kwargs + ): + """ + :param id: The unique identifier for this object., defaults to None + :type id: Optional[str], optional + :param type: The type for this object., defaults to None + :type type: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CopyFileParent(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The ID of folder to copy the file to. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class GetFileThumbnailUrlExtension(str, Enum): + PNG = 'png' + JPG = 'jpg' + + +class GetFileThumbnailByIdExtension(str, Enum): + PNG = 'png' + JPG = 'jpg' + + +class FilesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_by_id( + self, + file_id: str, + *, + fields: Optional[List[str]] = None, + if_none_match: Optional[str] = None, + boxapi: Optional[str] = None, + x_rep_hints: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileFull: + """ + Retrieves the details about a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested. + + Additionally this field can be used to query any metadata + applied to the file by specifying the `metadata` field as well + as the scope and key of the template to retrieve, for example + `?fields=metadata.enterprise_12345.contractTemplate`., defaults to None + :type fields: Optional[List[str]], optional + :param if_none_match: Ensures an item is only returned if it has changed. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `304 Not Modified` if the item has not + changed since., defaults to None + :type if_none_match: Optional[str], optional + :param boxapi: The URL, and optional password, for the shared link of this item. + + This header can be used to access items that have not been + explicitly shared with a user. + + Use the format `shared_link=[link]` or if a password is required then + use `shared_link=[link]&shared_link_password=[password]`. + + This header can be used on the file or folder shared, as well as on any files + or folders nested within the item., defaults to None + :type boxapi: Optional[str], optional + :param x_rep_hints: A header required to request specific `representations` + of a file. Use this in combination with the `fields` query + parameter to request a specific file representation. + + The general format for these representations is + `X-Rep-Hints: [...]` where `[...]` is one or many + hints in the format `[fileType?query]`. + + For example, to request a `png` representation in `32x32` + as well as `64x64` pixel dimensions provide the following + hints. + + `x-rep-hints: [jpg?dimensions=32x32][jpg?dimensions=64x64]` + + Additionally, a `text` representation is available for all + document file types in Box using the `[extracted_text]` + representation. + + `x-rep-hints: [extracted_text]`., defaults to None + :type x_rep_hints: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params( + { + 'if-none-match': to_string(if_none_match), + 'boxapi': to_string(boxapi), + 'x-rep-hints': to_string(x_rep_hints), + **extra_headers, + } + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileFull) + + def update_file_by_id( + self, + file_id: str, + *, + name: Optional[str] = None, + description: Optional[str] = None, + parent: Optional[UpdateFileByIdParent] = None, + shared_link: Union[Optional[UpdateFileByIdSharedLink], NullValue] = None, + lock: Union[Optional[UpdateFileByIdLock], NullValue] = None, + disposition_at: Optional[DateTime] = None, + permissions: Optional[UpdateFileByIdPermissions] = None, + collections: Union[Optional[List[UpdateFileByIdCollections]], NullValue] = None, + tags: Optional[List[str]] = None, + fields: Optional[List[str]] = None, + if_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileFull: + """ + Updates a file. This can be used to rename or move a file, + + create a shared link, or lock a file. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param name: An optional different name for the file. This can be used to + rename the file. + + File names must be unique within their parent folder. The name check is case-insensitive, so a file + named `New File` cannot be created in a parent folder that already contains a folder named `new file`., defaults to None + :type name: Optional[str], optional + :param description: The description for a file. This can be seen in the right-hand sidebar panel + when viewing a file in the Box web app. Additionally, this index is used in + the search index of the file, allowing users to find the file by the content + in the description., defaults to None + :type description: Optional[str], optional + :param lock: Defines a lock on an item. This prevents the item from being + moved, renamed, or otherwise changed by anyone other than the user + who created the lock. + + Set this to `null` to remove the lock., defaults to None + :type lock: Union[Optional[UpdateFileByIdLock], NullValue], optional + :param disposition_at: The retention expiration timestamp for the given file. This + date cannot be shortened once set on a file., defaults to None + :type disposition_at: Optional[DateTime], optional + :param permissions: Defines who can download a file., defaults to None + :type permissions: Optional[UpdateFileByIdPermissions], optional + :param collections: An array of collections to make this file + a member of. Currently + we only support the `favorites` collection. + + To get the ID for a collection, use the + [List all collections][1] endpoint. + + Passing an empty array `[]` or `null` will remove + the file from all collections. + + [1]: e://get-collections, defaults to None + :type collections: Union[Optional[List[UpdateFileByIdCollections]], NullValue], optional + :param tags: The tags for this item. These tags are shown in + the Box web app and mobile apps next to an item. + + To add or remove a tag, retrieve the item's current tags, + modify them, and then update this field. + + There is a limit of 100 tags per item, and 10,000 + unique tags per enterprise., defaults to None + :type tags: Optional[List[str]], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param if_match: Ensures this item hasn't recently changed before + making changes. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `412 Precondition Failed` if it + has changed since., defaults to None + :type if_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'name': name, + 'description': description, + 'parent': parent, + 'shared_link': shared_link, + 'lock': lock, + 'disposition_at': disposition_at, + 'permissions': permissions, + 'collections': collections, + 'tags': tags, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params( + {'if-match': to_string(if_match), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileFull) + + def delete_file_by_id( + self, + file_id: str, + *, + if_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a file, either permanently or by moving it to + + the trash. + + + The the enterprise settings determine whether the item will + + + be permanently deleted from Box or moved to the trash. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param if_match: Ensures this item hasn't recently changed before + making changes. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `412 Precondition Failed` if it + has changed since., defaults to None + :type if_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'if-match': to_string(if_match), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def copy_file( + self, + file_id: str, + parent: CopyFileParent, + *, + name: Optional[str] = None, + version: Optional[str] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileFull: + r""" + Creates a copy of a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param parent: The destination folder to copy the file to. + :type parent: CopyFileParent + :param name: An optional new name for the copied file. + + There are some restrictions to the file name. Names containing + non-printable ASCII characters, forward and backward slashes + (`/`, `\`), and protected names like `.` and `..` are + automatically sanitized by removing the non-allowed + characters., defaults to None + :type name: Optional[str], optional + :param version: An optional ID of the specific file version to copy., defaults to None + :type version: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'name': name, 'version': version, 'parent': parent} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/copy', + ] + ), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileFull) + + def get_file_thumbnail_url( + self, + file_id: str, + extension: GetFileThumbnailUrlExtension, + *, + min_height: Optional[int] = None, + min_width: Optional[int] = None, + max_height: Optional[int] = None, + max_width: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> str: + """ + Retrieves a thumbnail, or smaller image representation, of a file. + + Sizes of `32x32`,`64x64`, `128x128`, and `256x256` can be returned in + + + the `.png` format and sizes of `32x32`, `160x160`, and `320x320` + + + can be returned in the `.jpg` format. + + + Thumbnails can be generated for the image and video file formats listed + + + [found on our community site][1]. + + + [1]: https://community.box.com/t5/Migrating-and-Previewing-Content/File-Types-and-Fonts-Supported-in-Box-Content-Preview/ta-p/327 + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extension: The file format for the thumbnail. + Example: "png" + :type extension: GetFileThumbnailUrlExtension + :param min_height: The minimum height of the thumbnail., defaults to None + :type min_height: Optional[int], optional + :param min_width: The minimum width of the thumbnail., defaults to None + :type min_width: Optional[int], optional + :param max_height: The maximum height of the thumbnail., defaults to None + :type max_height: Optional[int], optional + :param max_width: The maximum width of the thumbnail., defaults to None + :type max_width: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'min_height': to_string(min_height), + 'min_width': to_string(min_width), + 'max_height': to_string(max_height), + 'max_width': to_string(max_width), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/thumbnail.', + to_string(extension), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + follow_redirects=False, + ) + ) + if 'location' in response.headers: + return response.headers.get('location') + if 'Location' in response.headers: + return response.headers.get('Location') + raise BoxSDKError(message='No location header in response') + + def get_file_thumbnail_by_id( + self, + file_id: str, + extension: GetFileThumbnailByIdExtension, + *, + min_height: Optional[int] = None, + min_width: Optional[int] = None, + max_height: Optional[int] = None, + max_width: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Optional[ByteStream]: + """ + Retrieves a thumbnail, or smaller image representation, of a file. + + Sizes of `32x32`,`64x64`, `128x128`, and `256x256` can be returned in + + + the `.png` format and sizes of `32x32`, `160x160`, and `320x320` + + + can be returned in the `.jpg` format. + + + Thumbnails can be generated for the image and video file formats listed + + + [found on our community site][1]. + + + [1]: https://community.box.com/t5/Migrating-and-Previewing-Content/File-Types-and-Fonts-Supported-in-Box-Content-Preview/ta-p/327 + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extension: The file format for the thumbnail. + Example: "png" + :type extension: GetFileThumbnailByIdExtension + :param min_height: The minimum height of the thumbnail., defaults to None + :type min_height: Optional[int], optional + :param min_width: The minimum width of the thumbnail., defaults to None + :type min_width: Optional[int], optional + :param max_height: The maximum height of the thumbnail., defaults to None + :type max_height: Optional[int], optional + :param max_width: The maximum width of the thumbnail., defaults to None + :type max_width: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'min_height': to_string(min_height), + 'min_width': to_string(min_width), + 'max_height': to_string(max_height), + 'max_width': to_string(max_width), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/thumbnail.', + to_string(extension), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.BINARY, + auth=self.auth, + network_session=self.network_session, + ) + ) + if to_string(response.status) == '202': + return None + return response.content diff --git a/box_sdk_gen/managers/folder_classifications.py b/box_sdk_gen/managers/folder_classifications.py new file mode 100644 index 000000000..4192936a7 --- /dev/null +++ b/box_sdk_gen/managers/folder_classifications.py @@ -0,0 +1,331 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from typing import List + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.classification import Classification + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class UpdateClassificationOnFolderRequestBodyOpField(str, Enum): + REPLACE = 'replace' + + +class UpdateClassificationOnFolderRequestBodyPathField(str, Enum): + _BOX__SECURITY__CLASSIFICATION__KEY = '/Box__Security__Classification__Key' + + +class UpdateClassificationOnFolderRequestBody(BaseObject): + def __init__( + self, + value: str, + *, + op: UpdateClassificationOnFolderRequestBodyOpField = UpdateClassificationOnFolderRequestBodyOpField.REPLACE, + path: UpdateClassificationOnFolderRequestBodyPathField = UpdateClassificationOnFolderRequestBodyPathField._BOX__SECURITY__CLASSIFICATION__KEY, + **kwargs + ): + """ + :param value: The name of the classification to apply to this folder. + + To list the available classifications in an enterprise, + use the classification API to retrieve the + [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) + which lists all available classification keys. + :type value: str + :param op: The value will always be `replace`., defaults to UpdateClassificationOnFolderRequestBodyOpField.REPLACE + :type op: UpdateClassificationOnFolderRequestBodyOpField, optional + :param path: Defines classifications + available in the enterprise., defaults to UpdateClassificationOnFolderRequestBodyPathField._BOX__SECURITY__CLASSIFICATION__KEY + :type path: UpdateClassificationOnFolderRequestBodyPathField, optional + """ + super().__init__(**kwargs) + self.value = value + self.op = op + self.path = path + + +class FolderClassificationsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_classification_on_folder( + self, + folder_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Classification: + """ + Retrieves the classification metadata instance that + + has been applied to a folder. + + + This API can also be called by including the enterprise ID in the + + + URL explicitly, for example + + + `/folders/:id/enterprise_12345/securityClassification-6VMVochwUWo`. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/metadata/enterprise/securityClassification-6VMVochwUWo', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Classification) + + def add_classification_to_folder( + self, + folder_id: str, + *, + box_security_classification_key: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Classification: + """ + Adds a classification to a folder by specifying the label of the + + classification to add. + + + This API can also be called by including the enterprise ID in the + + + URL explicitly, for example + + + `/folders/:id/enterprise_12345/securityClassification-6VMVochwUWo`. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param box_security_classification_key: The name of the classification to apply to this folder. + + To list the available classifications in an enterprise, + use the classification API to retrieve the + [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) + which lists all available classification keys., defaults to None + :type box_security_classification_key: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'Box__Security__Classification__Key': box_security_classification_key + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/metadata/enterprise/securityClassification-6VMVochwUWo', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Classification) + + def update_classification_on_folder( + self, + folder_id: str, + request_body: List[UpdateClassificationOnFolderRequestBody], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Classification: + """ + Updates a classification on a folder. + + The classification can only be updated if a classification has already been + + + applied to the folder before. When editing classifications, only values are + + + defined for the enterprise will be accepted. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param request_body: Request body of updateClassificationOnFolder method + :type request_body: List[UpdateClassificationOnFolderRequestBody] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/metadata/enterprise/securityClassification-6VMVochwUWo', + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json-patch+json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Classification) + + def delete_classification_from_folder( + self, + folder_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes any classifications from a folder. + + This API can also be called by including the enterprise ID in the + + + URL explicitly, for example + + + `/folders/:id/enterprise_12345/securityClassification-6VMVochwUWo`. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/metadata/enterprise/securityClassification-6VMVochwUWo', + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/folder_locks.py b/box_sdk_gen/managers/folder_locks.py new file mode 100644 index 000000000..c7c7e8e39 --- /dev/null +++ b/box_sdk_gen/managers/folder_locks.py @@ -0,0 +1,216 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.folder_locks import FolderLocks + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.folder_lock import FolderLock + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateFolderLockLockedOperations(BaseObject): + def __init__(self, move: bool, delete: bool, **kwargs): + """ + :param move: Whether moving the folder should be locked. + :type move: bool + :param delete: Whether deleting the folder should be locked. + :type delete: bool + """ + super().__init__(**kwargs) + self.move = move + self.delete = delete + + +class CreateFolderLockFolder(BaseObject): + def __init__(self, type: str, id: str, **kwargs): + """ + :param type: The content type the lock is being applied to. Only `folder` + is supported. + :type type: str + :param id: The ID of the folder. + :type id: str + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class FolderLocksManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_folder_locks( + self, + folder_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderLocks: + """ + Retrieves folder lock details for a given folder. + + You must be authenticated as the owner or co-owner of the folder to + + + use this endpoint. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + :type folder_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'folder_id': to_string(folder_id)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/folder_locks'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderLocks) + + def create_folder_lock( + self, + folder: CreateFolderLockFolder, + *, + locked_operations: Optional[CreateFolderLockLockedOperations] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderLock: + """ + Creates a folder lock on a folder, preventing it from being moved and/or + + deleted. + + + You must be authenticated as the owner or co-owner of the folder to + + + use this endpoint. + + :param folder: The folder to apply the lock to. + :type folder: CreateFolderLockFolder + :param locked_operations: The operations to lock for the folder. If `locked_operations` is + included in the request, both `move` and `delete` must also be + included and both set to `true`., defaults to None + :type locked_operations: Optional[CreateFolderLockLockedOperations], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'locked_operations': locked_operations, 'folder': folder} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/folder_locks'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderLock) + + def delete_folder_lock_by_id( + self, + folder_lock_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a folder lock on a given folder. + + You must be authenticated as the owner or co-owner of the folder to + + + use this endpoint. + + :param folder_lock_id: The ID of the folder lock. + Example: "12345" + :type folder_lock_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folder_locks/', + to_string(folder_lock_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/folder_metadata.py b/box_sdk_gen/managers/folder_metadata.py new file mode 100644 index 000000000..602effef4 --- /dev/null +++ b/box_sdk_gen/managers/folder_metadata.py @@ -0,0 +1,450 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from typing import List + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.metadatas import Metadatas + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.metadata_full import MetadataFull + +from box_sdk_gen.schemas.metadata_error import MetadataError + +from box_sdk_gen.schemas.metadata_instance_value import MetadataInstanceValue + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetFolderMetadataByIdScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class CreateFolderMetadataByIdScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class UpdateFolderMetadataByIdScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class UpdateFolderMetadataByIdRequestBodyOpField(str, Enum): + ADD = 'add' + REPLACE = 'replace' + REMOVE = 'remove' + TEST = 'test' + MOVE = 'move' + COPY = 'copy' + + +class UpdateFolderMetadataByIdRequestBody(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'from_': 'from', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'from': 'from_', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + op: Optional[UpdateFolderMetadataByIdRequestBodyOpField] = None, + path: Optional[str] = None, + value: Optional[MetadataInstanceValue] = None, + from_: Optional[str] = None, + **kwargs + ): + """ + :param op: The type of change to perform on the template. Some + of these are hazardous as they will change existing templates., defaults to None + :type op: Optional[UpdateFolderMetadataByIdRequestBodyOpField], optional + :param path: The location in the metadata JSON object + to apply the changes to, in the format of a + [JSON-Pointer](https://tools.ietf.org/html/rfc6901). + + The path must always be prefixed with a `/` to represent the root + of the template. The characters `~` and `/` are reserved + characters and must be escaped in the key., defaults to None + :type path: Optional[str], optional + :param from_: The location in the metadata JSON object to move or copy a value + from. Required for `move` or `copy` operations and must be in the + format of a [JSON-Pointer](https://tools.ietf.org/html/rfc6901)., defaults to None + :type from_: Optional[str], optional + """ + super().__init__(**kwargs) + self.op = op + self.path = path + self.value = value + self.from_ = from_ + + +class DeleteFolderMetadataByIdScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class FolderMetadataManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_folder_metadata( + self, + folder_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Metadatas: + """ + Retrieves all metadata for a given folder. This can not be used on the root + + folder with ID `0`. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/metadata', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Metadatas) + + def get_folder_metadata_by_id( + self, + folder_id: str, + scope: GetFolderMetadataByIdScope, + template_key: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataFull: + """ + Retrieves the instance of a metadata template that has been applied to a + + folder. This can not be used on the root folder with ID `0`. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param scope: The scope of the metadata template. + Example: "global" + :type scope: GetFolderMetadataByIdScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/metadata/', + to_string(scope), + '/', + to_string(template_key), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataFull) + + def create_folder_metadata_by_id( + self, + folder_id: str, + scope: CreateFolderMetadataByIdScope, + template_key: str, + request_body: Dict, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataFull: + """ + Applies an instance of a metadata template to a folder. + + In most cases only values that are present in the metadata template + + + will be accepted, except for the `global.properties` template which accepts + + + any key-value pair. + + + To display the metadata template in the Box web app the enterprise needs to be + + + configured to enable **Cascading Folder Level Metadata** for the user in the + + + admin console. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param scope: The scope of the metadata template. + Example: "global" + :type scope: CreateFolderMetadataByIdScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param request_body: Request body of createFolderMetadataById method + :type request_body: Dict + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/metadata/', + to_string(scope), + '/', + to_string(template_key), + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataFull) + + def update_folder_metadata_by_id( + self, + folder_id: str, + scope: UpdateFolderMetadataByIdScope, + template_key: str, + request_body: List[UpdateFolderMetadataByIdRequestBody], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataFull: + """ + Updates a piece of metadata on a folder. + + The metadata instance can only be updated if the template has already been + + + applied to the folder before. When editing metadata, only values that match + + + the metadata template schema will be accepted. + + + The update is applied atomically. If any errors occur during the + + + application of the operations, the metadata instance will not be changed. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param scope: The scope of the metadata template. + Example: "global" + :type scope: UpdateFolderMetadataByIdScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param request_body: Request body of updateFolderMetadataById method + :type request_body: List[UpdateFolderMetadataByIdRequestBody] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/metadata/', + to_string(scope), + '/', + to_string(template_key), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json-patch+json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataFull) + + def delete_folder_metadata_by_id( + self, + folder_id: str, + scope: DeleteFolderMetadataByIdScope, + template_key: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a piece of folder metadata. + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param scope: The scope of the metadata template. + Example: "global" + :type scope: DeleteFolderMetadataByIdScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/metadata/', + to_string(scope), + '/', + to_string(template_key), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/folder_watermarks.py b/box_sdk_gen/managers/folder_watermarks.py new file mode 100644 index 000000000..56ac61a99 --- /dev/null +++ b/box_sdk_gen/managers/folder_watermarks.py @@ -0,0 +1,214 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.watermark import Watermark + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class UpdateFolderWatermarkWatermarkImprintField(str, Enum): + DEFAULT = 'default' + + +class UpdateFolderWatermarkWatermark(BaseObject): + def __init__( + self, + *, + imprint: UpdateFolderWatermarkWatermarkImprintField = UpdateFolderWatermarkWatermarkImprintField.DEFAULT, + **kwargs + ): + """ + :param imprint: The type of watermark to apply. + + Currently only supports one option., defaults to UpdateFolderWatermarkWatermarkImprintField.DEFAULT + :type imprint: UpdateFolderWatermarkWatermarkImprintField, optional + """ + super().__init__(**kwargs) + self.imprint = imprint + + +class FolderWatermarksManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_folder_watermark( + self, + folder_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Watermark: + """ + Retrieve the watermark for a folder. + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/watermark', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Watermark) + + def update_folder_watermark( + self, + folder_id: str, + watermark: UpdateFolderWatermarkWatermark, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Watermark: + """ + Applies or update a watermark on a folder. + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param watermark: The watermark to imprint on the folder. + :type watermark: UpdateFolderWatermarkWatermark + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'watermark': watermark} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/watermark', + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Watermark) + + def delete_folder_watermark( + self, + folder_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes the watermark from a folder. + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/watermark', + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/folders.py b/box_sdk_gen/managers/folders.py new file mode 100644 index 000000000..50772507b --- /dev/null +++ b/box_sdk_gen/managers/folders.py @@ -0,0 +1,966 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.items import Items + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import DateTime + + +class GetFolderByIdSort(str, Enum): + ID = 'id' + NAME = 'name' + DATE = 'date' + SIZE = 'size' + + +class GetFolderByIdDirection(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class UpdateFolderByIdSyncState(str, Enum): + SYNCED = 'synced' + NOT_SYNCED = 'not_synced' + PARTIALLY_SYNCED = 'partially_synced' + + +class UpdateFolderByIdParent(BaseObject): + def __init__( + self, *, id: Optional[str] = None, user_id: Optional[str] = None, **kwargs + ): + """ + :param id: The ID of parent item., defaults to None + :type id: Optional[str], optional + :param user_id: The input for `user_id` is optional. Moving to non-root folder is not allowed when `user_id` is present. Parent folder id should be zero when `user_id` is provided., defaults to None + :type user_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.user_id = user_id + + +class UpdateFolderByIdSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class UpdateFolderByIdSharedLinkPermissionsField(BaseObject): + def __init__(self, *, can_download: Optional[bool] = None, **kwargs): + """ + :param can_download: If the shared link allows for downloading of files. + This can only be set when `access` is set to + `open` or `company`., defaults to None + :type can_download: Optional[bool], optional + """ + super().__init__(**kwargs) + self.can_download = can_download + + +class UpdateFolderByIdSharedLink(BaseObject): + def __init__( + self, + *, + access: Optional[UpdateFolderByIdSharedLinkAccessField] = None, + password: Optional[str] = None, + vanity_name: Optional[str] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[UpdateFolderByIdSharedLinkPermissionsField] = None, + **kwargs + ): + """ + :param access: The level of access for the shared link. This can be + restricted to anyone with the link (`open`), only people + within the company (`company`) and only those who + have been invited to the folder (`collaborators`). + + If not set, this field defaults to the access level specified + by the enterprise admin. To create a shared link with this + default setting pass the `shared_link` object with + no `access` field, for example `{ "shared_link": {} }`. + + The `company` access level is only available to paid + accounts., defaults to None + :type access: Optional[UpdateFolderByIdSharedLinkAccessField], optional + :param password: The password required to access the shared link. Set the + password to `null` to remove it. + Passwords must now be at least eight characters + long and include a number, upper case letter, or + a non-numeric or non-alphabetic character. + A password can only be set when `access` is set to `open`., defaults to None + :type password: Optional[str], optional + :param vanity_name: Defines a custom vanity name to use in the shared link URL, + for example `https://app.box.com/v/my-shared-link`. + + Custom URLs should not be used when sharing sensitive content + as vanity URLs are a lot easier to guess than regular shared links., defaults to None + :type vanity_name: Optional[str], optional + :param unshared_at: The timestamp at which this shared link will + expire. This field can only be set by + users with paid accounts., defaults to None + :type unshared_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.access = access + self.password = password + self.vanity_name = vanity_name + self.unshared_at = unshared_at + self.permissions = permissions + + +class UpdateFolderByIdFolderUploadEmailAccessField(str, Enum): + OPEN = 'open' + COLLABORATORS = 'collaborators' + + +class UpdateFolderByIdFolderUploadEmail(BaseObject): + def __init__( + self, + *, + access: Optional[UpdateFolderByIdFolderUploadEmailAccessField] = None, + **kwargs + ): + """ + :param access: When this parameter has been set, users can email files + to the email address that has been automatically + created for this folder. + + To create an email address, set this property either when + creating or updating the folder. + + When set to `collaborators`, only emails from registered email + addresses for collaborators will be accepted. This includes + any email aliases a user might have registered. + + When set to `open` it will accept emails from any email + address., defaults to None + :type access: Optional[UpdateFolderByIdFolderUploadEmailAccessField], optional + """ + super().__init__(**kwargs) + self.access = access + + +class UpdateFolderByIdCollections(BaseObject): + def __init__( + self, *, id: Optional[str] = None, type: Optional[str] = None, **kwargs + ): + """ + :param id: The unique identifier for this object., defaults to None + :type id: Optional[str], optional + :param type: The type for this object., defaults to None + :type type: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class GetFolderItemsSort(str, Enum): + ID = 'id' + NAME = 'name' + DATE = 'date' + SIZE = 'size' + + +class GetFolderItemsDirection(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class CreateFolderParent(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The ID of parent folder. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class CreateFolderFolderUploadEmailAccessField(str, Enum): + OPEN = 'open' + COLLABORATORS = 'collaborators' + + +class CreateFolderFolderUploadEmail(BaseObject): + def __init__( + self, + *, + access: Optional[CreateFolderFolderUploadEmailAccessField] = None, + **kwargs + ): + """ + :param access: When this parameter has been set, users can email files + to the email address that has been automatically + created for this folder. + + To create an email address, set this property either when + creating or updating the folder. + + When set to `collaborators`, only emails from registered email + addresses for collaborators will be accepted. This includes + any email aliases a user might have registered. + + When set to `open` it will accept emails from any email + address., defaults to None + :type access: Optional[CreateFolderFolderUploadEmailAccessField], optional + """ + super().__init__(**kwargs) + self.access = access + + +class CreateFolderSyncState(str, Enum): + SYNCED = 'synced' + NOT_SYNCED = 'not_synced' + PARTIALLY_SYNCED = 'partially_synced' + + +class CopyFolderParent(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The ID of parent folder. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class FoldersManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_folder_by_id( + self, + folder_id: str, + *, + fields: Optional[List[str]] = None, + sort: Optional[GetFolderByIdSort] = None, + direction: Optional[GetFolderByIdDirection] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + if_none_match: Optional[str] = None, + boxapi: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderFull: + """ + Retrieves details for a folder, including the first 100 entries + + in the folder. + + + Passing `sort`, `direction`, `offset`, and `limit` + + + parameters in query allows you to manage the + + + list of returned + + + [folder items](r://folder--full#param-item-collection). + + + To fetch more items within the folder, use the + + + [Get items in a folder](e://get-folders-id-items) endpoint. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested. + + Additionally this field can be used to query any metadata + applied to the file by specifying the `metadata` field as well + as the scope and key of the template to retrieve, for example + `?fields=metadata.enterprise_12345.contractTemplate`., defaults to None + :type fields: Optional[List[str]], optional + :param sort: Defines the **second** attribute by which items + are sorted. + + The folder type affects the way the items + are sorted: + + * **Standard folder**: + Items are always sorted by + their `type` first, with + folders listed before files, + and files listed + before web links. + + * **Root folder**: + This parameter is not supported + for marker-based pagination + on the root folder + + (the folder with an `id` of `0`). + + * **Shared folder with parent path + to the associated folder visible to + the collaborator**: + Items are always sorted by + their `type` first, with + folders listed before files, + and files listed + before web links., defaults to None + :type sort: Optional[GetFolderByIdSort], optional + :param direction: The direction to sort results in. This can be either in alphabetical ascending + (`ASC`) or descending (`DESC`) order., defaults to None + :type direction: Optional[GetFolderByIdDirection], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param if_none_match: Ensures an item is only returned if it has changed. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `304 Not Modified` if the item has not + changed since., defaults to None + :type if_none_match: Optional[str], optional + :param boxapi: The URL, and optional password, for the shared link of this item. + + This header can be used to access items that have not been + explicitly shared with a user. + + Use the format `shared_link=[link]` or if a password is required then + use `shared_link=[link]&shared_link_password=[password]`. + + This header can be used on the file or folder shared, as well as on any files + or folders nested within the item., defaults to None + :type boxapi: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'sort': to_string(sort), + 'direction': to_string(direction), + 'offset': to_string(offset), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params( + { + 'if-none-match': to_string(if_none_match), + 'boxapi': to_string(boxapi), + **extra_headers, + } + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderFull) + + def update_folder_by_id( + self, + folder_id: str, + *, + name: Optional[str] = None, + description: Optional[str] = None, + sync_state: Optional[UpdateFolderByIdSyncState] = None, + can_non_owners_invite: Optional[bool] = None, + parent: Optional[UpdateFolderByIdParent] = None, + shared_link: Optional[UpdateFolderByIdSharedLink] = None, + folder_upload_email: Union[ + Optional[UpdateFolderByIdFolderUploadEmail], NullValue + ] = None, + tags: Optional[List[str]] = None, + is_collaboration_restricted_to_enterprise: Optional[bool] = None, + collections: Union[ + Optional[List[UpdateFolderByIdCollections]], NullValue + ] = None, + can_non_owners_view_collaborators: Optional[bool] = None, + fields: Optional[List[str]] = None, + if_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderFull: + r""" + Updates a folder. This can be also be used to move the folder, + + create shared links, update collaborations, and more. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param name: The optional new name for this folder. + + The following restrictions to folder names apply: names containing + non-printable ASCII characters, forward and backward slashes + (`/`, `\`), names with trailing spaces, and names `.` and `..` are + not allowed. + + Folder names must be unique within their parent folder. The name check is case-insensitive, + so a folder named `New Folder` cannot be created in a parent folder that already contains + a folder named `new folder`., defaults to None + :type name: Optional[str], optional + :param description: The optional description of this folder., defaults to None + :type description: Optional[str], optional + :param sync_state: Specifies whether a folder should be synced to a + user's device or not. This is used by Box Sync + (discontinued) and is not used by Box Drive., defaults to None + :type sync_state: Optional[UpdateFolderByIdSyncState], optional + :param can_non_owners_invite: Specifies if users who are not the owner + of the folder can invite new collaborators to the folder., defaults to None + :type can_non_owners_invite: Optional[bool], optional + :param tags: The tags for this item. These tags are shown in + the Box web app and mobile apps next to an item. + + To add or remove a tag, retrieve the item's current tags, + modify them, and then update this field. + + There is a limit of 100 tags per item, and 10,000 + unique tags per enterprise., defaults to None + :type tags: Optional[List[str]], optional + :param is_collaboration_restricted_to_enterprise: Specifies if new invites to this folder are restricted to users + within the enterprise. This does not affect existing + collaborations., defaults to None + :type is_collaboration_restricted_to_enterprise: Optional[bool], optional + :param collections: An array of collections to make this folder + a member of. Currently + we only support the `favorites` collection. + + To get the ID for a collection, use the + [List all collections][1] endpoint. + + Passing an empty array `[]` or `null` will remove + the folder from all collections. + + [1]: e://get-collections, defaults to None + :type collections: Union[Optional[List[UpdateFolderByIdCollections]], NullValue], optional + :param can_non_owners_view_collaborators: Restricts collaborators who are not the owner of + this folder from viewing other collaborations on + this folder. + + It also restricts non-owners from inviting new + collaborators. + + When setting this field to `false`, it is required + to also set `can_non_owners_invite_collaborators` to + `false` if it has not already been set., defaults to None + :type can_non_owners_view_collaborators: Optional[bool], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param if_match: Ensures this item hasn't recently changed before + making changes. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `412 Precondition Failed` if it + has changed since., defaults to None + :type if_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'name': name, + 'description': description, + 'sync_state': sync_state, + 'can_non_owners_invite': can_non_owners_invite, + 'parent': parent, + 'shared_link': shared_link, + 'folder_upload_email': folder_upload_email, + 'tags': tags, + 'is_collaboration_restricted_to_enterprise': ( + is_collaboration_restricted_to_enterprise + ), + 'collections': collections, + 'can_non_owners_view_collaborators': can_non_owners_view_collaborators, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params( + {'if-match': to_string(if_match), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderFull) + + def delete_folder_by_id( + self, + folder_id: str, + *, + recursive: Optional[bool] = None, + if_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a folder, either permanently or by moving it to + + the trash. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param recursive: Delete a folder that is not empty by recursively deleting the + folder and all of its content., defaults to None + :type recursive: Optional[bool], optional + :param if_match: Ensures this item hasn't recently changed before + making changes. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `412 Precondition Failed` if it + has changed since., defaults to None + :type if_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'recursive': to_string(recursive)} + ) + headers_map: Dict[str, str] = prepare_params( + {'if-match': to_string(if_match), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + ] + ), + method='DELETE', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_folder_items( + self, + folder_id: str, + *, + fields: Optional[List[str]] = None, + usemarker: Optional[bool] = None, + marker: Optional[str] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + sort: Optional[GetFolderItemsSort] = None, + direction: Optional[GetFolderItemsDirection] = None, + boxapi: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Items: + """ + Retrieves a page of items in a folder. These items can be files, + + folders, and web links. + + + To request more information about the folder itself, like its size, + + + use the [Get a folder](#get-folders-id) endpoint instead. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested. + + Additionally this field can be used to query any metadata + applied to the file by specifying the `metadata` field as well + as the scope and key of the template to retrieve, for example + `?fields=metadata.enterprise_12345.contractTemplate`., defaults to None + :type fields: Optional[List[str]], optional + :param usemarker: Specifies whether to use marker-based pagination instead of + offset-based pagination. Only one pagination method can + be used at a time. + + By setting this value to true, the API will return a `marker` field + that can be passed as a parameter to this endpoint to get the next + page of the response., defaults to None + :type usemarker: Optional[bool], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param sort: Defines the **second** attribute by which items + are sorted. + + The folder type affects the way the items + are sorted: + + * **Standard folder**: + Items are always sorted by + their `type` first, with + folders listed before files, + and files listed + before web links. + + * **Root folder**: + This parameter is not supported + for marker-based pagination + on the root folder + + (the folder with an `id` of `0`). + + * **Shared folder with parent path + to the associated folder visible to + the collaborator**: + Items are always sorted by + their `type` first, with + folders listed before files, + and files listed + before web links., defaults to None + :type sort: Optional[GetFolderItemsSort], optional + :param direction: The direction to sort results in. This can be either in alphabetical ascending + (`ASC`) or descending (`DESC`) order., defaults to None + :type direction: Optional[GetFolderItemsDirection], optional + :param boxapi: The URL, and optional password, for the shared link of this item. + + This header can be used to access items that have not been + explicitly shared with a user. + + Use the format `shared_link=[link]` or if a password is required then + use `shared_link=[link]&shared_link_password=[password]`. + + This header can be used on the file or folder shared, as well as on any files + or folders nested within the item., defaults to None + :type boxapi: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'usemarker': to_string(usemarker), + 'marker': to_string(marker), + 'offset': to_string(offset), + 'limit': to_string(limit), + 'sort': to_string(sort), + 'direction': to_string(direction), + } + ) + headers_map: Dict[str, str] = prepare_params( + {'boxapi': to_string(boxapi), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/items', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Items) + + def create_folder( + self, + name: str, + parent: CreateFolderParent, + *, + folder_upload_email: Optional[CreateFolderFolderUploadEmail] = None, + sync_state: Optional[CreateFolderSyncState] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderFull: + r""" + Creates a new empty folder within the specified parent folder. + :param name: The name for the new folder. + + The following restrictions to folder names apply: names containing + non-printable ASCII characters, forward and backward slashes + (`/`, `\`), names with trailing spaces, and names `.` and `..` are + not allowed. + + Folder names must be unique within their parent folder. The name check is case-insensitive, + so a folder named `New Folder` cannot be created in a parent folder that already contains + a folder named `new folder`. + :type name: str + :param parent: The parent folder to create the new folder within. + :type parent: CreateFolderParent + :param sync_state: Specifies whether a folder should be synced to a + user's device or not. This is used by Box Sync + (discontinued) and is not used by Box Drive., defaults to None + :type sync_state: Optional[CreateFolderSyncState], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'name': name, + 'parent': parent, + 'folder_upload_email': folder_upload_email, + 'sync_state': sync_state, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/folders']), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderFull) + + def copy_folder( + self, + folder_id: str, + parent: CopyFolderParent, + *, + name: Optional[str] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderFull: + r""" + Creates a copy of a folder within a destination folder. + + The original folder will not be changed. + + :param folder_id: The unique identifier of the folder to copy. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder with the ID `0` can not be copied. + Example: "0" + :type folder_id: str + :param parent: The destination folder to copy the folder to. + :type parent: CopyFolderParent + :param name: An optional new name for the copied folder. + + There are some restrictions to the file name. Names containing + non-printable ASCII characters, forward and backward slashes + (`/`, `\`), as well as names with trailing spaces are + prohibited. + + Additionally, the names `.` and `..` are + not allowed either., defaults to None + :type name: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'name': name, 'parent': parent} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/copy', + ] + ), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderFull) diff --git a/box_sdk_gen/managers/groups.py b/box_sdk_gen/managers/groups.py new file mode 100644 index 000000000..200116ba6 --- /dev/null +++ b/box_sdk_gen/managers/groups.py @@ -0,0 +1,450 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.groups import Groups + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.group_full import GroupFull + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateGroupInvitabilityLevel(str, Enum): + ADMINS_ONLY = 'admins_only' + ADMINS_AND_MEMBERS = 'admins_and_members' + ALL_MANAGED_USERS = 'all_managed_users' + + +class CreateGroupMemberViewabilityLevel(str, Enum): + ADMINS_ONLY = 'admins_only' + ADMINS_AND_MEMBERS = 'admins_and_members' + ALL_MANAGED_USERS = 'all_managed_users' + + +class UpdateGroupByIdInvitabilityLevel(str, Enum): + ADMINS_ONLY = 'admins_only' + ADMINS_AND_MEMBERS = 'admins_and_members' + ALL_MANAGED_USERS = 'all_managed_users' + + +class UpdateGroupByIdMemberViewabilityLevel(str, Enum): + ADMINS_ONLY = 'admins_only' + ADMINS_AND_MEMBERS = 'admins_and_members' + ALL_MANAGED_USERS = 'all_managed_users' + + +class GroupsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_groups( + self, + *, + filter_term: Optional[str] = None, + fields: Optional[List[str]] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Groups: + """ + Retrieves all of the groups for a given enterprise. The user + + must have admin permissions to inspect enterprise's groups. + + :param filter_term: Limits the results to only groups whose `name` starts + with the search term., defaults to None + :type filter_term: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'filter_term': to_string(filter_term), + 'fields': to_string(fields), + 'limit': to_string(limit), + 'offset': to_string(offset), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/groups']), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Groups) + + def create_group( + self, + name: str, + *, + provenance: Optional[str] = None, + external_sync_identifier: Optional[str] = None, + description: Optional[str] = None, + invitability_level: Optional[CreateGroupInvitabilityLevel] = None, + member_viewability_level: Optional[CreateGroupMemberViewabilityLevel] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> GroupFull: + """ + Creates a new group of users in an enterprise. Only users with admin + + permissions can create new groups. + + :param name: The name of the new group to be created. This name must be unique + within the enterprise. + :type name: str + :param provenance: Keeps track of which external source this group is + coming, for example `Active Directory`, or `Okta`. + + Setting this will also prevent Box admins from editing + the group name and its members directly via the Box + web application. + + This is desirable for one-way syncing of groups., defaults to None + :type provenance: Optional[str], optional + :param external_sync_identifier: An arbitrary identifier that can be used by + external group sync tools to link this Box Group to + an external group. + + Example values of this field + could be an **Active Directory Object ID** or a **Google + Group ID**. + + We recommend you use of this field in + order to avoid issues when group names are updated in + either Box or external systems., defaults to None + :type external_sync_identifier: Optional[str], optional + :param description: A human readable description of the group., defaults to None + :type description: Optional[str], optional + :param invitability_level: Specifies who can invite the group to collaborate + on folders. + + When set to `admins_only` the enterprise admin, co-admins, + and the group's admin can invite the group. + + When set to `admins_and_members` all the admins listed + above and group members can invite the group. + + When set to `all_managed_users` all managed users in the + enterprise can invite the group., defaults to None + :type invitability_level: Optional[CreateGroupInvitabilityLevel], optional + :param member_viewability_level: Specifies who can see the members of the group. + + * `admins_only` - the enterprise admin, co-admins, group's + group admin. + * `admins_and_members` - all admins and group members. + * `all_managed_users` - all managed users in the + enterprise., defaults to None + :type member_viewability_level: Optional[CreateGroupMemberViewabilityLevel], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'name': name, + 'provenance': provenance, + 'external_sync_identifier': external_sync_identifier, + 'description': description, + 'invitability_level': invitability_level, + 'member_viewability_level': member_viewability_level, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/groups']), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, GroupFull) + + def get_group_by_id( + self, + group_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> GroupFull: + """ + Retrieves information about a group. Only members of this + + group or users with admin-level permissions will be able to + + + use this API. + + :param group_id: The ID of the group. + Example: "57645" + :type group_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/groups/', + to_string(group_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, GroupFull) + + def update_group_by_id( + self, + group_id: str, + *, + name: Optional[str] = None, + provenance: Optional[str] = None, + external_sync_identifier: Optional[str] = None, + description: Optional[str] = None, + invitability_level: Optional[UpdateGroupByIdInvitabilityLevel] = None, + member_viewability_level: Optional[ + UpdateGroupByIdMemberViewabilityLevel + ] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> GroupFull: + """ + Updates a specific group. Only admins of this + + group or users with admin-level permissions will be able to + + + use this API. + + :param group_id: The ID of the group. + Example: "57645" + :type group_id: str + :param name: The name of the new group to be created. Must be unique within the + enterprise., defaults to None + :type name: Optional[str], optional + :param provenance: Keeps track of which external source this group is + coming, for example `Active Directory`, or `Okta`. + + Setting this will also prevent Box admins from editing + the group name and its members directly via the Box + web application. + + This is desirable for one-way syncing of groups., defaults to None + :type provenance: Optional[str], optional + :param external_sync_identifier: An arbitrary identifier that can be used by + external group sync tools to link this Box Group to + an external group. + + Example values of this field + could be an **Active Directory Object ID** or a **Google + Group ID**. + + We recommend you use of this field in + order to avoid issues when group names are updated in + either Box or external systems., defaults to None + :type external_sync_identifier: Optional[str], optional + :param description: A human readable description of the group., defaults to None + :type description: Optional[str], optional + :param invitability_level: Specifies who can invite the group to collaborate + on folders. + + When set to `admins_only` the enterprise admin, co-admins, + and the group's admin can invite the group. + + When set to `admins_and_members` all the admins listed + above and group members can invite the group. + + When set to `all_managed_users` all managed users in the + enterprise can invite the group., defaults to None + :type invitability_level: Optional[UpdateGroupByIdInvitabilityLevel], optional + :param member_viewability_level: Specifies who can see the members of the group. + + * `admins_only` - the enterprise admin, co-admins, group's + group admin. + * `admins_and_members` - all admins and group members. + * `all_managed_users` - all managed users in the + enterprise., defaults to None + :type member_viewability_level: Optional[UpdateGroupByIdMemberViewabilityLevel], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'name': name, + 'provenance': provenance, + 'external_sync_identifier': external_sync_identifier, + 'description': description, + 'invitability_level': invitability_level, + 'member_viewability_level': member_viewability_level, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/groups/', + to_string(group_id), + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, GroupFull) + + def delete_group_by_id( + self, group_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Permanently deletes a group. Only users with + + admin-level permissions will be able to use this API. + + :param group_id: The ID of the group. + Example: "57645" + :type group_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/groups/', + to_string(group_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/hub_collaborations.py b/box_sdk_gen/managers/hub_collaborations.py new file mode 100644 index 000000000..7950575f9 --- /dev/null +++ b/box_sdk_gen/managers/hub_collaborations.py @@ -0,0 +1,360 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2025_r0.hub_collaborations_v2025_r0 import ( + HubCollaborationsV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import ClientErrorV2025R0 + +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import ( + BoxVersionHeaderV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_v2025_r0 import ( + HubCollaborationV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_create_request_v2025_r0 import ( + HubCollaborationCreateRequestV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_update_request_v2025_r0 import ( + HubCollaborationUpdateRequestV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateHubCollaborationV2025R0HubTypeField(str, Enum): + HUBS = 'hubs' + + +class CreateHubCollaborationV2025R0Hub(BaseObject): + _discriminator = 'type', {'hubs'} + + def __init__( + self, + id: str, + *, + type: CreateHubCollaborationV2025R0HubTypeField = CreateHubCollaborationV2025R0HubTypeField.HUBS, + **kwargs + ): + """ + :param id: ID of the object. + :type id: str + :param type: The value will always be `hubs`., defaults to CreateHubCollaborationV2025R0HubTypeField.HUBS + :type type: CreateHubCollaborationV2025R0HubTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CreateHubCollaborationV2025R0AccessibleBy(BaseObject): + def __init__( + self, + type: str, + *, + id: Optional[str] = None, + login: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of collaborator to invite. + Possible values are `user` or `group`. + :type type: str + :param id: The ID of the user or group. + + Alternatively, use `login` to specify a user by email + address., defaults to None + :type id: Optional[str], optional + :param login: The email address of the user who gets access to the item. + + Alternatively, use `id` to specify a user by user ID., defaults to None + :type login: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.login = login + + +class HubCollaborationsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_hub_collaborations_v2025_r0( + self, + hub_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubCollaborationsV2025R0: + """ + Retrieves all collaborations for a hub. + :param hub_id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting this hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + :type hub_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'hub_id': to_string(hub_id), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/hub_collaborations'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubCollaborationsV2025R0) + + def create_hub_collaboration_v2025_r0( + self, + hub: CreateHubCollaborationV2025R0Hub, + accessible_by: CreateHubCollaborationV2025R0AccessibleBy, + role: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubCollaborationV2025R0: + """ + Adds a collaboration for a single user or a single group to a hub. + + Collaborations can be created using email address, user IDs, or group IDs. + + :param hub: Hubs reference. + :type hub: CreateHubCollaborationV2025R0Hub + :param accessible_by: The user or group who gets access to the item. + :type accessible_by: CreateHubCollaborationV2025R0AccessibleBy + :param role: The level of access granted to hub. + Possible values are `editor`, `viewer`, and `co-owner`. + :type role: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'hub': hub, 'accessible_by': accessible_by, 'role': role} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/hub_collaborations'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubCollaborationV2025R0) + + def get_hub_collaboration_by_id_v2025_r0( + self, + hub_collaboration_id: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubCollaborationV2025R0: + """ + Retrieves details for a hub collaboration by collaboration ID. + :param hub_collaboration_id: The ID of the hub collaboration. + Example: "1234" + :type hub_collaboration_id: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/hub_collaborations/', + to_string(hub_collaboration_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubCollaborationV2025R0) + + def update_hub_collaboration_by_id_v2025_r0( + self, + hub_collaboration_id: str, + *, + role: Optional[str] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubCollaborationV2025R0: + """ + Updates a hub collaboration. + + Can be used to change the hub role. + + :param hub_collaboration_id: The ID of the hub collaboration. + Example: "1234" + :type hub_collaboration_id: str + :param role: The level of access granted to hub. + Possible values are `editor`, `viewer`, and `co-owner`., defaults to None + :type role: Optional[str], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'role': role} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/hub_collaborations/', + to_string(hub_collaboration_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubCollaborationV2025R0) + + def delete_hub_collaboration_by_id_v2025_r0( + self, + hub_collaboration_id: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a single hub collaboration. + :param hub_collaboration_id: The ID of the hub collaboration. + Example: "1234" + :type hub_collaboration_id: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/hub_collaborations/', + to_string(hub_collaboration_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/hub_items.py b/box_sdk_gen/managers/hub_items.py new file mode 100644 index 000000000..4ad4e448f --- /dev/null +++ b/box_sdk_gen/managers/hub_items.py @@ -0,0 +1,177 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from typing import List + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2025_r0.hub_item_operation_v2025_r0 import ( + HubItemOperationV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_items_v2025_r0 import HubItemsV2025R0 + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import ClientErrorV2025R0 + +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import ( + BoxVersionHeaderV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_items_manage_response_v2025_r0 import ( + HubItemsManageResponseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_items_manage_request_v2025_r0 import ( + HubItemsManageRequestV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class HubItemsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_hub_items_v2025_r0( + self, + hub_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubItemsV2025R0: + """ + Retrieves all items associated with a Hub. + :param hub_id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting this hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + :type hub_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'hub_id': to_string(hub_id), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/hub_items'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubItemsV2025R0) + + def manage_hub_items_v2025_r0( + self, + hub_id: str, + *, + operations: Optional[List[HubItemOperationV2025R0]] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubItemsManageResponseV2025R0: + """ + Adds and/or removes Hub items from a Hub. + :param hub_id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting this hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + Example: "12345" + :type hub_id: str + :param operations: List of operations to perform on Hub items., defaults to None + :type operations: Optional[List[HubItemOperationV2025R0]], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'operations': operations} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/hubs/', + to_string(hub_id), + '/manage_items', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubItemsManageResponseV2025R0) diff --git a/box_sdk_gen/managers/hubs.py b/box_sdk_gen/managers/hubs.py new file mode 100644 index 000000000..549ad3566 --- /dev/null +++ b/box_sdk_gen/managers/hubs.py @@ -0,0 +1,475 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2025_r0.hubs_v2025_r0 import HubsV2025R0 + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import ClientErrorV2025R0 + +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import ( + BoxVersionHeaderV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_v2025_r0 import HubV2025R0 + +from box_sdk_gen.schemas.v2025_r0.hub_create_request_v2025_r0 import ( + HubCreateRequestV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_update_request_v2025_r0 import ( + HubUpdateRequestV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_copy_request_v2025_r0 import HubCopyRequestV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetHubsV2025R0Direction(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class GetEnterpriseHubsV2025R0Direction(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class HubsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_hubs_v2025_r0( + self, + *, + query: Optional[str] = None, + scope: Optional[str] = None, + sort: Optional[str] = None, + direction: Optional[GetHubsV2025R0Direction] = None, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubsV2025R0: + """ + Retrieves all hubs for requesting user. + :param query: The query string to search for hubs., defaults to None + :type query: Optional[str], optional + :param scope: The scope of the hubs to retrieve. Possible values include `editable`, + `view_only`, and `all`. Default is `all`., defaults to None + :type scope: Optional[str], optional + :param sort: The field to sort results by. + Possible values include `name`, `updated_at`, + `last_accessed_at`, `view_count`, and `relevance`. + Default is `relevance`., defaults to None + :type sort: Optional[str], optional + :param direction: The direction to sort results in. This can be either in alphabetical ascending + (`ASC`) or descending (`DESC`) order., defaults to None + :type direction: Optional[GetHubsV2025R0Direction], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'query': to_string(query), + 'scope': to_string(scope), + 'sort': to_string(sort), + 'direction': to_string(direction), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/hubs']), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubsV2025R0) + + def create_hub_v2025_r0( + self, + title: str, + *, + description: Optional[str] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubV2025R0: + """ + Creates a new Hub. + :param title: Title of the Hub. It cannot be empty and should be less than 50 characters. + :type title: str + :param description: Description of the Hub., defaults to None + :type description: Optional[str], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'title': title, 'description': description} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/hubs']), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubV2025R0) + + def get_enterprise_hubs_v2025_r0( + self, + *, + query: Optional[str] = None, + sort: Optional[str] = None, + direction: Optional[GetEnterpriseHubsV2025R0Direction] = None, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubsV2025R0: + """ + Retrieves all hubs for a given enterprise. + + Admins or Hub Co-admins of an enterprise + + + with GCM scope can make this call. + + :param query: The query string to search for hubs., defaults to None + :type query: Optional[str], optional + :param sort: The field to sort results by. + Possible values include `name`, `updated_at`, + `last_accessed_at`, `view_count`, and `relevance`. + Default is `relevance`., defaults to None + :type sort: Optional[str], optional + :param direction: The direction to sort results in. This can be either in alphabetical ascending + (`ASC`) or descending (`DESC`) order., defaults to None + :type direction: Optional[GetEnterpriseHubsV2025R0Direction], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'query': to_string(query), + 'sort': to_string(sort), + 'direction': to_string(direction), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/enterprise_hubs'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubsV2025R0) + + def get_hub_by_id_v2025_r0( + self, + hub_id: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubV2025R0: + """ + Retrieves details for a hub by its ID. + :param hub_id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting this hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + Example: "12345" + :type hub_id: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/hubs/', + to_string(hub_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubV2025R0) + + def update_hub_by_id_v2025_r0( + self, + hub_id: str, + *, + title: Optional[str] = None, + description: Optional[str] = None, + is_ai_enabled: Optional[bool] = None, + is_collaboration_restricted_to_enterprise: Optional[bool] = None, + can_non_owners_invite: Optional[bool] = None, + can_shared_link_be_created: Optional[bool] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubV2025R0: + """ + Updates a Hub. Can be used to change title, description, or Hub settings. + :param hub_id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting this hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + Example: "12345" + :type hub_id: str + :param title: Title of the Hub. It cannot be empty and should be less than 50 characters., defaults to None + :type title: Optional[str], optional + :param description: Description of the Hub., defaults to None + :type description: Optional[str], optional + :param is_ai_enabled: Indicates if AI features are enabled for the Hub., defaults to None + :type is_ai_enabled: Optional[bool], optional + :param is_collaboration_restricted_to_enterprise: Indicates if collaboration is restricted to the enterprise., defaults to None + :type is_collaboration_restricted_to_enterprise: Optional[bool], optional + :param can_non_owners_invite: Indicates if non-owners can invite others to the Hub., defaults to None + :type can_non_owners_invite: Optional[bool], optional + :param can_shared_link_be_created: Indicates if a shared link can be created for the Hub., defaults to None + :type can_shared_link_be_created: Optional[bool], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'title': title, + 'description': description, + 'is_ai_enabled': is_ai_enabled, + 'is_collaboration_restricted_to_enterprise': ( + is_collaboration_restricted_to_enterprise + ), + 'can_non_owners_invite': can_non_owners_invite, + 'can_shared_link_be_created': can_shared_link_be_created, + } + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/hubs/', + to_string(hub_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubV2025R0) + + def delete_hub_by_id_v2025_r0( + self, + hub_id: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a single hub. + :param hub_id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting this hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + Example: "12345" + :type hub_id: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/hubs/', + to_string(hub_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def copy_hub_v2025_r0( + self, + hub_id: str, + *, + title: Optional[str] = None, + description: Optional[str] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubV2025R0: + """ + Creates a copy of a Hub. + + The original Hub will not be modified. + + :param hub_id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting this hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + Example: "12345" + :type hub_id: str + :param title: Title of the Hub. It cannot be empty and should be less than 50 characters., defaults to None + :type title: Optional[str], optional + :param description: Description of the Hub., defaults to None + :type description: Optional[str], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'title': title, 'description': description} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/hubs/', + to_string(hub_id), + '/copy', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubV2025R0) diff --git a/box_sdk_gen/managers/integration_mappings.py b/box_sdk_gen/managers/integration_mappings.py new file mode 100644 index 000000000..49245b679 --- /dev/null +++ b/box_sdk_gen/managers/integration_mappings.py @@ -0,0 +1,504 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.integration_mapping_partner_item_slack import ( + IntegrationMappingPartnerItemSlack, +) + +from box_sdk_gen.schemas.integration_mapping_partner_item_teams_create_request import ( + IntegrationMappingPartnerItemTeamsCreateRequest, +) + +from box_sdk_gen.schemas.integration_mappings import IntegrationMappings + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.integration_mapping import IntegrationMapping + +from box_sdk_gen.schemas.integration_mapping_slack_create_request import ( + IntegrationMappingSlackCreateRequest, +) + +from box_sdk_gen.schemas.integration_mapping_box_item_slack import ( + IntegrationMappingBoxItemSlack, +) + +from box_sdk_gen.schemas.integration_mapping_slack_options import ( + IntegrationMappingSlackOptions, +) + +from box_sdk_gen.schemas.integration_mappings_teams import IntegrationMappingsTeams + +from box_sdk_gen.schemas.integration_mapping_teams import IntegrationMappingTeams + +from box_sdk_gen.schemas.integration_mapping_teams_create_request import ( + IntegrationMappingTeamsCreateRequest, +) + +from box_sdk_gen.schemas.folder_reference import FolderReference + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetSlackIntegrationMappingPartnerItemType(str, Enum): + CHANNEL = 'channel' + + +class GetSlackIntegrationMappingBoxItemType(str, Enum): + FOLDER = 'folder' + + +class GetTeamsIntegrationMappingPartnerItemType(str, Enum): + CHANNEL = 'channel' + TEAM = 'team' + + +class GetTeamsIntegrationMappingBoxItemType(str, Enum): + FOLDER = 'folder' + + +class IntegrationMappingsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_slack_integration_mapping( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + partner_item_type: Optional[GetSlackIntegrationMappingPartnerItemType] = None, + partner_item_id: Optional[str] = None, + box_item_id: Optional[str] = None, + box_item_type: Optional[GetSlackIntegrationMappingBoxItemType] = None, + is_manually_created: Optional[bool] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> IntegrationMappings: + """ + Lists [Slack integration mappings](https://support.box.com/hc/en-us/articles/4415585987859-Box-as-the-Content-Layer-for-Slack) in a users' enterprise. + + You need Admin or Co-Admin role to + + + use this endpoint. + + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param partner_item_type: Mapped item type, for which the mapping should be returned., defaults to None + :type partner_item_type: Optional[GetSlackIntegrationMappingPartnerItemType], optional + :param partner_item_id: ID of the mapped item, + for which the mapping should be returned., defaults to None + :type partner_item_id: Optional[str], optional + :param box_item_id: Box item ID, for which the mappings should be returned., defaults to None + :type box_item_id: Optional[str], optional + :param box_item_type: Box item type, for + which the mappings should be returned., defaults to None + :type box_item_type: Optional[GetSlackIntegrationMappingBoxItemType], optional + :param is_manually_created: Whether the mapping has been manually created., defaults to None + :type is_manually_created: Optional[bool], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'marker': to_string(marker), + 'limit': to_string(limit), + 'partner_item_type': to_string(partner_item_type), + 'partner_item_id': to_string(partner_item_id), + 'box_item_id': to_string(box_item_id), + 'box_item_type': to_string(box_item_type), + 'is_manually_created': to_string(is_manually_created), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/integration_mappings/slack', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, IntegrationMappings) + + def create_slack_integration_mapping( + self, + partner_item: IntegrationMappingPartnerItemSlack, + box_item: IntegrationMappingBoxItemSlack, + *, + options: Optional[IntegrationMappingSlackOptions] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> IntegrationMapping: + """ + Creates a [Slack integration mapping](https://support.box.com/hc/en-us/articles/4415585987859-Box-as-the-Content-Layer-for-Slack) + + by mapping a Slack channel to a Box item. + + + You need Admin or Co-Admin role to + + + use this endpoint. + + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'partner_item': partner_item, + 'box_item': box_item, + 'options': options, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/integration_mappings/slack', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, IntegrationMapping) + + def update_slack_integration_mapping_by_id( + self, + integration_mapping_id: str, + *, + box_item: Optional[IntegrationMappingBoxItemSlack] = None, + options: Optional[IntegrationMappingSlackOptions] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> IntegrationMapping: + """ + Updates a [Slack integration mapping](https://support.box.com/hc/en-us/articles/4415585987859-Box-as-the-Content-Layer-for-Slack). + + Supports updating the Box folder ID and options. + + + You need Admin or Co-Admin role to + + + use this endpoint. + + :param integration_mapping_id: An ID of an integration mapping. + Example: "11235432" + :type integration_mapping_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'box_item': box_item, 'options': options} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/integration_mappings/slack/', + to_string(integration_mapping_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, IntegrationMapping) + + def delete_slack_integration_mapping_by_id( + self, + integration_mapping_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a [Slack integration mapping](https://support.box.com/hc/en-us/articles/4415585987859-Box-as-the-Content-Layer-for-Slack). + + You need Admin or Co-Admin role to + + + use this endpoint. + + :param integration_mapping_id: An ID of an integration mapping. + Example: "11235432" + :type integration_mapping_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/integration_mappings/slack/', + to_string(integration_mapping_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_teams_integration_mapping( + self, + *, + partner_item_type: Optional[GetTeamsIntegrationMappingPartnerItemType] = None, + partner_item_id: Optional[str] = None, + box_item_id: Optional[str] = None, + box_item_type: Optional[GetTeamsIntegrationMappingBoxItemType] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> IntegrationMappingsTeams: + """ + Lists [Teams integration mappings](https://support.box.com/hc/en-us/articles/360044681474-Using-Box-for-Teams) in a users' enterprise. + + You need Admin or Co-Admin role to + + + use this endpoint. + + :param partner_item_type: Mapped item type, for which the mapping should be returned., defaults to None + :type partner_item_type: Optional[GetTeamsIntegrationMappingPartnerItemType], optional + :param partner_item_id: ID of the mapped item, + for which the mapping should be returned., defaults to None + :type partner_item_id: Optional[str], optional + :param box_item_id: Box item ID, for which the mappings should be returned., defaults to None + :type box_item_id: Optional[str], optional + :param box_item_type: Box item type, for + which the mappings should be returned., defaults to None + :type box_item_type: Optional[GetTeamsIntegrationMappingBoxItemType], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'partner_item_type': to_string(partner_item_type), + 'partner_item_id': to_string(partner_item_id), + 'box_item_id': to_string(box_item_id), + 'box_item_type': to_string(box_item_type), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/integration_mappings/teams', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, IntegrationMappingsTeams) + + def create_teams_integration_mapping( + self, + partner_item: IntegrationMappingPartnerItemTeamsCreateRequest, + box_item: FolderReference, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> IntegrationMappingTeams: + """ + Creates a [Teams integration mapping](https://support.box.com/hc/en-us/articles/360044681474-Using-Box-for-Teams) + + by mapping a Teams channel to a Box item. + + + You need Admin or Co-Admin role to + + + use this endpoint. + + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'partner_item': partner_item, 'box_item': box_item} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/integration_mappings/teams', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, IntegrationMappingTeams) + + def update_teams_integration_mapping_by_id( + self, + integration_mapping_id: str, + *, + box_item: Optional[FolderReference] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> IntegrationMappingTeams: + """ + Updates a [Teams integration mapping](https://support.box.com/hc/en-us/articles/360044681474-Using-Box-for-Teams). + + Supports updating the Box folder ID and options. + + + You need Admin or Co-Admin role to + + + use this endpoint. + + :param integration_mapping_id: An ID of an integration mapping. + Example: "11235432" + :type integration_mapping_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'box_item': box_item} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/integration_mappings/teams/', + to_string(integration_mapping_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, IntegrationMappingTeams) + + def delete_teams_integration_mapping_by_id( + self, + integration_mapping_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a [Teams integration mapping](https://support.box.com/hc/en-us/articles/360044681474-Using-Box-for-Teams). + + You need Admin or Co-Admin role to + + + use this endpoint. + + :param integration_mapping_id: An ID of an integration mapping. + Example: "11235432" + :type integration_mapping_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/integration_mappings/teams/', + to_string(integration_mapping_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/invites.py b/box_sdk_gen/managers/invites.py new file mode 100644 index 000000000..e143df6fd --- /dev/null +++ b/box_sdk_gen/managers/invites.py @@ -0,0 +1,185 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.invite import Invite + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateInviteEnterprise(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The ID of the enterprise. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class CreateInviteActionableBy(BaseObject): + def __init__(self, *, login: Optional[str] = None, **kwargs): + """ + :param login: The login of the invited user., defaults to None + :type login: Optional[str], optional + """ + super().__init__(**kwargs) + self.login = login + + +class InvitesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_invite( + self, + enterprise: CreateInviteEnterprise, + actionable_by: CreateInviteActionableBy, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Invite: + """ + Invites an existing external user to join an enterprise. + + The existing user can not be part of another enterprise and + + + must already have a Box account. Once invited, the user will receive an + + + email and are prompted to accept the invitation within the + + + Box web application. + + + This method requires the "Manage An Enterprise" scope enabled for + + + the application, which can be enabled within the developer console. + + :param enterprise: The enterprise to invite the user to. + :type enterprise: CreateInviteEnterprise + :param actionable_by: The user to invite. + :type actionable_by: CreateInviteActionableBy + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'enterprise': enterprise, 'actionable_by': actionable_by} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/invites']), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Invite) + + def get_invite_by_id( + self, + invite_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Invite: + """ + Returns the status of a user invite. + :param invite_id: The ID of an invite. + Example: "213723" + :type invite_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/invites/', + to_string(invite_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Invite) diff --git a/box_sdk_gen/managers/legal_hold_policies.py b/box_sdk_gen/managers/legal_hold_policies.py new file mode 100644 index 000000000..014b75ac3 --- /dev/null +++ b/box_sdk_gen/managers/legal_hold_policies.py @@ -0,0 +1,328 @@ +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.legal_hold_policies import LegalHoldPolicies + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.legal_hold_policy import LegalHoldPolicy + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import DateTime + + +class LegalHoldPoliciesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_legal_hold_policies( + self, + *, + policy_name: Optional[str] = None, + fields: Optional[List[str]] = None, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> LegalHoldPolicies: + """ + Retrieves a list of legal hold policies that belong to + + an enterprise. + + :param policy_name: Limits results to policies for which the names start with + this search term. This is a case-insensitive prefix., defaults to None + :type policy_name: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'policy_name': to_string(policy_name), + 'fields': to_string(fields), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/legal_hold_policies', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, LegalHoldPolicies) + + def create_legal_hold_policy( + self, + policy_name: str, + *, + description: Optional[str] = None, + filter_started_at: Optional[DateTime] = None, + filter_ended_at: Optional[DateTime] = None, + is_ongoing: Optional[bool] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> LegalHoldPolicy: + """ + Create a new legal hold policy. + :param policy_name: The name of the policy. + :type policy_name: str + :param description: A description for the policy., defaults to None + :type description: Optional[str], optional + :param filter_started_at: The filter start date. + + When this policy is applied using a `custodian` legal + hold assignments, it will only apply to file versions + created or uploaded inside of the + date range. Other assignment types, such as folders and + files, will ignore the date filter. + + Required if `is_ongoing` is set to `false`., defaults to None + :type filter_started_at: Optional[DateTime], optional + :param filter_ended_at: The filter end date. + + When this policy is applied using a `custodian` legal + hold assignments, it will only apply to file versions + created or uploaded inside of the + date range. Other assignment types, such as folders and + files, will ignore the date filter. + + Required if `is_ongoing` is set to `false`., defaults to None + :type filter_ended_at: Optional[DateTime], optional + :param is_ongoing: Whether new assignments under this policy should + continue applying to files even after initialization. + + When this policy is applied using a legal hold assignment, + it will continue applying the policy to any new file versions + even after it has been applied. + + For example, if a legal hold assignment is placed on a user + today, and that user uploads a file tomorrow, that file will + get held. This will continue until the policy is retired. + + Required if no filter dates are set., defaults to None + :type is_ongoing: Optional[bool], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'policy_name': policy_name, + 'description': description, + 'filter_started_at': filter_started_at, + 'filter_ended_at': filter_ended_at, + 'is_ongoing': is_ongoing, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/legal_hold_policies', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, LegalHoldPolicy) + + def get_legal_hold_policy_by_id( + self, + legal_hold_policy_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> LegalHoldPolicy: + """ + Retrieve a legal hold policy. + :param legal_hold_policy_id: The ID of the legal hold policy. + Example: "324432" + :type legal_hold_policy_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/legal_hold_policies/', + to_string(legal_hold_policy_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, LegalHoldPolicy) + + def update_legal_hold_policy_by_id( + self, + legal_hold_policy_id: str, + *, + policy_name: Optional[str] = None, + description: Optional[str] = None, + release_notes: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> LegalHoldPolicy: + """ + Update legal hold policy. + :param legal_hold_policy_id: The ID of the legal hold policy. + Example: "324432" + :type legal_hold_policy_id: str + :param policy_name: The name of the policy., defaults to None + :type policy_name: Optional[str], optional + :param description: A description for the policy., defaults to None + :type description: Optional[str], optional + :param release_notes: Notes around why the policy was released., defaults to None + :type release_notes: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'policy_name': policy_name, + 'description': description, + 'release_notes': release_notes, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/legal_hold_policies/', + to_string(legal_hold_policy_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, LegalHoldPolicy) + + def delete_legal_hold_policy_by_id( + self, + legal_hold_policy_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Delete an existing legal hold policy. + + This is an asynchronous process. The policy will not be + + + fully deleted yet when the response returns. + + :param legal_hold_policy_id: The ID of the legal hold policy. + Example: "324432" + :type legal_hold_policy_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/legal_hold_policies/', + to_string(legal_hold_policy_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/legal_hold_policy_assignments.py b/box_sdk_gen/managers/legal_hold_policy_assignments.py new file mode 100644 index 000000000..3e6812726 --- /dev/null +++ b/box_sdk_gen/managers/legal_hold_policy_assignments.py @@ -0,0 +1,393 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.legal_hold_policy_assignments import LegalHoldPolicyAssignments + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.legal_hold_policy_assignment import LegalHoldPolicyAssignment + +from box_sdk_gen.schemas.files_on_hold import FilesOnHold + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetLegalHoldPolicyAssignmentsAssignToType(str, Enum): + FILE = 'file' + FILE_VERSION = 'file_version' + FOLDER = 'folder' + USER = 'user' + OWNERSHIP = 'ownership' + INTERACTIONS = 'interactions' + + +class CreateLegalHoldPolicyAssignmentAssignToTypeField(str, Enum): + FILE = 'file' + FILE_VERSION = 'file_version' + FOLDER = 'folder' + USER = 'user' + OWNERSHIP = 'ownership' + INTERACTION = 'interaction' + + +class CreateLegalHoldPolicyAssignmentAssignTo(BaseObject): + _discriminator = 'type', { + 'file', + 'file_version', + 'folder', + 'user', + 'ownership', + 'interaction', + } + + def __init__( + self, type: CreateLegalHoldPolicyAssignmentAssignToTypeField, id: str, **kwargs + ): + """ + :param type: The type of item to assign the policy to. + :type type: CreateLegalHoldPolicyAssignmentAssignToTypeField + :param id: The ID of item to assign the policy to. + :type id: str + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class LegalHoldPolicyAssignmentsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_legal_hold_policy_assignments( + self, + policy_id: str, + *, + assign_to_type: Optional[GetLegalHoldPolicyAssignmentsAssignToType] = None, + assign_to_id: Optional[str] = None, + marker: Optional[str] = None, + limit: Optional[int] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> LegalHoldPolicyAssignments: + """ + Retrieves a list of items a legal hold policy has been assigned to. + :param policy_id: The ID of the legal hold policy. + :type policy_id: str + :param assign_to_type: Filters the results by the type of item the + policy was applied to., defaults to None + :type assign_to_type: Optional[GetLegalHoldPolicyAssignmentsAssignToType], optional + :param assign_to_id: Filters the results by the ID of item the + policy was applied to., defaults to None + :type assign_to_id: Optional[str], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'policy_id': to_string(policy_id), + 'assign_to_type': to_string(assign_to_type), + 'assign_to_id': to_string(assign_to_id), + 'marker': to_string(marker), + 'limit': to_string(limit), + 'fields': to_string(fields), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/legal_hold_policy_assignments', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, LegalHoldPolicyAssignments) + + def create_legal_hold_policy_assignment( + self, + policy_id: str, + assign_to: CreateLegalHoldPolicyAssignmentAssignTo, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> LegalHoldPolicyAssignment: + """ + Assign a legal hold to a file, file version, folder, or user. + :param policy_id: The ID of the policy to assign. + :type policy_id: str + :param assign_to: The item to assign the policy to. + :type assign_to: CreateLegalHoldPolicyAssignmentAssignTo + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'policy_id': policy_id, 'assign_to': assign_to} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/legal_hold_policy_assignments', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, LegalHoldPolicyAssignment) + + def get_legal_hold_policy_assignment_by_id( + self, + legal_hold_policy_assignment_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> LegalHoldPolicyAssignment: + """ + Retrieve a legal hold policy assignment. + :param legal_hold_policy_assignment_id: The ID of the legal hold policy assignment. + Example: "753465" + :type legal_hold_policy_assignment_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/legal_hold_policy_assignments/', + to_string(legal_hold_policy_assignment_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, LegalHoldPolicyAssignment) + + def delete_legal_hold_policy_assignment_by_id( + self, + legal_hold_policy_assignment_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Remove a legal hold from an item. + + This is an asynchronous process. The policy will not be + + + fully removed yet when the response returns. + + :param legal_hold_policy_assignment_id: The ID of the legal hold policy assignment. + Example: "753465" + :type legal_hold_policy_assignment_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/legal_hold_policy_assignments/', + to_string(legal_hold_policy_assignment_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_legal_hold_policy_assignment_file_on_hold( + self, + legal_hold_policy_assignment_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FilesOnHold: + """ + Get a list of files with current file versions for a legal hold + + assignment. + + + In some cases you may want to get previous file versions instead. In these + + + cases, use the `GET /legal_hold_policy_assignments/:id/file_versions_on_hold` + + + API instead to return any previous versions of a file for this legal hold + + + policy assignment. + + + Due to ongoing re-architecture efforts this API might not return all file + + + versions held for this policy ID. Instead, this API will only return the + + + latest file version held in the newly developed architecture. The `GET + + + /file_version_legal_holds` API can be used to fetch current and past versions + + + of files held within the legacy architecture. + + + This endpoint does not support returning any content that is on hold due to + + + a Custodian collaborating on a Hub. + + + The `GET /legal_hold_policy_assignments?policy_id={id}` API can be used to + + + find a list of policy assignments for a given policy ID. + + :param legal_hold_policy_assignment_id: The ID of the legal hold policy assignment. + Example: "753465" + :type legal_hold_policy_assignment_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'marker': to_string(marker), + 'limit': to_string(limit), + 'fields': to_string(fields), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/legal_hold_policy_assignments/', + to_string(legal_hold_policy_assignment_id), + '/files_on_hold', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FilesOnHold) diff --git a/box_sdk_gen/managers/list_collaborations.py b/box_sdk_gen/managers/list_collaborations.py new file mode 100644 index 000000000..b30238ad5 --- /dev/null +++ b/box_sdk_gen/managers/list_collaborations.py @@ -0,0 +1,329 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.collaborations import Collaborations + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.collaborations_offset_paginated import ( + CollaborationsOffsetPaginated, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetCollaborationsStatus(str, Enum): + PENDING = 'pending' + + +class ListCollaborationsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_collaborations( + self, + file_id: str, + *, + fields: Optional[List[str]] = None, + limit: Optional[int] = None, + marker: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Collaborations: + """ + Retrieves a list of pending and active collaborations for a + + file. This returns all the users that have access to the file + + + or have been invited to the file. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'limit': to_string(limit), + 'marker': to_string(marker), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/collaborations', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Collaborations) + + def get_folder_collaborations( + self, + folder_id: str, + *, + fields: Optional[List[str]] = None, + limit: Optional[int] = None, + marker: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Collaborations: + """ + Retrieves a list of pending and active collaborations for a + + folder. This returns all the users that have access to the folder + + + or have been invited to the folder. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + Example: "12345" + :type folder_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'limit': to_string(limit), + 'marker': to_string(marker), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/collaborations', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Collaborations) + + def get_collaborations( + self, + status: GetCollaborationsStatus, + *, + fields: Optional[List[str]] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CollaborationsOffsetPaginated: + """ + Retrieves all pending collaboration invites for this user. + :param status: The status of the collaborations to retrieve. + :type status: GetCollaborationsStatus + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'status': to_string(status), + 'fields': to_string(fields), + 'offset': to_string(offset), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/collaborations'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CollaborationsOffsetPaginated) + + def get_group_collaborations( + self, + group_id: str, + *, + limit: Optional[int] = None, + offset: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> CollaborationsOffsetPaginated: + """ + Retrieves all the collaborations for a group. The user + + must have admin permissions to inspect enterprise's groups. + + + Each collaboration object has details on which files or + + + folders the group has access to and with what role. + + :param group_id: The ID of the group. + Example: "57645" + :type group_id: str + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'limit': to_string(limit), 'offset': to_string(offset)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/groups/', + to_string(group_id), + '/collaborations', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, CollaborationsOffsetPaginated) diff --git a/box_sdk_gen/managers/memberships.py b/box_sdk_gen/managers/memberships.py new file mode 100644 index 000000000..7719acbc6 --- /dev/null +++ b/box_sdk_gen/managers/memberships.py @@ -0,0 +1,444 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from typing import List + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.group_memberships import GroupMemberships + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.group_membership import GroupMembership + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateGroupMembershipUser(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The ID of the user to add to the group. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class CreateGroupMembershipGroup(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The ID of the group to add the user to. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class CreateGroupMembershipRole(str, Enum): + MEMBER = 'member' + ADMIN = 'admin' + + +class UpdateGroupMembershipByIdRole(str, Enum): + MEMBER = 'member' + ADMIN = 'admin' + + +class MembershipsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_user_memberships( + self, + user_id: str, + *, + limit: Optional[int] = None, + offset: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> GroupMemberships: + """ + Retrieves all the groups for a user. Only members of this + + group or users with admin-level permissions will be able to + + + use this API. + + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'limit': to_string(limit), 'offset': to_string(offset)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + '/memberships', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, GroupMemberships) + + def get_group_memberships( + self, + group_id: str, + *, + limit: Optional[int] = None, + offset: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> GroupMemberships: + """ + Retrieves all the members for a group. Only members of this + + group or users with admin-level permissions will be able to + + + use this API. + + :param group_id: The ID of the group. + Example: "57645" + :type group_id: str + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'limit': to_string(limit), 'offset': to_string(offset)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/groups/', + to_string(group_id), + '/memberships', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, GroupMemberships) + + def create_group_membership( + self, + user: CreateGroupMembershipUser, + group: CreateGroupMembershipGroup, + *, + role: Optional[CreateGroupMembershipRole] = None, + configurable_permissions: Union[Optional[Dict[str, bool]], NullValue] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> GroupMembership: + """ + Creates a group membership. Only users with + + admin-level permissions will be able to use this API. + + :param user: The user to add to the group. + :type user: CreateGroupMembershipUser + :param group: The group to add the user to. + :type group: CreateGroupMembershipGroup + :param role: The role of the user in the group., defaults to None + :type role: Optional[CreateGroupMembershipRole], optional + :param configurable_permissions: Custom configuration for the permissions an admin + if a group will receive. This option has no effect + on members with a role of `member`. + + Setting these permissions overwrites the default + access levels of an admin. + + Specifying a value of `null` for this object will disable + all configurable permissions. Specifying permissions will set + them accordingly, omitted permissions will be enabled by default., defaults to None + :type configurable_permissions: Union[Optional[Dict[str, bool]], NullValue], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'user': user, + 'group': group, + 'role': role, + 'configurable_permissions': configurable_permissions, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/group_memberships'] + ), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, GroupMembership) + + def get_group_membership_by_id( + self, + group_membership_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> GroupMembership: + """ + Retrieves a specific group membership. Only admins of this + + group or users with admin-level permissions will be able to + + + use this API. + + :param group_membership_id: The ID of the group membership. + Example: "434534" + :type group_membership_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/group_memberships/', + to_string(group_membership_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, GroupMembership) + + def update_group_membership_by_id( + self, + group_membership_id: str, + *, + role: Optional[UpdateGroupMembershipByIdRole] = None, + configurable_permissions: Union[Optional[Dict[str, bool]], NullValue] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> GroupMembership: + """ + Updates a user's group membership. Only admins of this + + group or users with admin-level permissions will be able to + + + use this API. + + :param group_membership_id: The ID of the group membership. + Example: "434534" + :type group_membership_id: str + :param role: The role of the user in the group., defaults to None + :type role: Optional[UpdateGroupMembershipByIdRole], optional + :param configurable_permissions: Custom configuration for the permissions an admin + if a group will receive. This option has no effect + on members with a role of `member`. + + Setting these permissions overwrites the default + access levels of an admin. + + Specifying a value of `null` for this object will disable + all configurable permissions. Specifying permissions will set + them accordingly, omitted permissions will be enabled by default., defaults to None + :type configurable_permissions: Union[Optional[Dict[str, bool]], NullValue], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'role': role, + 'configurable_permissions': configurable_permissions, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/group_memberships/', + to_string(group_membership_id), + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, GroupMembership) + + def delete_group_membership_by_id( + self, + group_membership_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a specific group membership. Only admins of this + + group or users with admin-level permissions will be able to + + + use this API. + + :param group_membership_id: The ID of the group membership. + Example: "434534" + :type group_membership_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/group_memberships/', + to_string(group_membership_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/metadata_cascade_policies.py b/box_sdk_gen/managers/metadata_cascade_policies.py new file mode 100644 index 000000000..2d254c871 --- /dev/null +++ b/box_sdk_gen/managers/metadata_cascade_policies.py @@ -0,0 +1,333 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.metadata_cascade_policies import MetadataCascadePolicies + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.metadata_cascade_policy import MetadataCascadePolicy + +from box_sdk_gen.schemas.conflict_error import ConflictError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateMetadataCascadePolicyScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class ApplyMetadataCascadePolicyConflictResolution(str, Enum): + NONE = 'none' + OVERWRITE = 'overwrite' + + +class MetadataCascadePoliciesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_metadata_cascade_policies( + self, + folder_id: str, + *, + owner_enterprise_id: Optional[str] = None, + marker: Optional[str] = None, + offset: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataCascadePolicies: + """ + Retrieves a list of all the metadata cascade policies + + that are applied to a given folder. This can not be used on the root + + + folder with ID `0`. + + :param folder_id: Specifies which folder to return policies for. This can not be used on the + root folder with ID `0`. + :type folder_id: str + :param owner_enterprise_id: The ID of the enterprise ID for which to find metadata + cascade policies. If not specified, it defaults to the + current enterprise., defaults to None + :type owner_enterprise_id: Optional[str], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'folder_id': to_string(folder_id), + 'owner_enterprise_id': to_string(owner_enterprise_id), + 'marker': to_string(marker), + 'offset': to_string(offset), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_cascade_policies', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataCascadePolicies) + + def create_metadata_cascade_policy( + self, + folder_id: str, + scope: CreateMetadataCascadePolicyScope, + template_key: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataCascadePolicy: + """ + Creates a new metadata cascade policy that applies a given + + metadata template to a given folder and automatically + + + cascades it down to any files within that folder. + + + In order for the policy to be applied a metadata instance must first + + + be applied to the folder the policy is to be applied to. + + :param folder_id: The ID of the folder to apply the policy to. This folder will + need to already have an instance of the targeted metadata + template applied to it. + :type folder_id: str + :param scope: The scope of the targeted metadata template. This template will + need to already have an instance applied to the targeted folder. + :type scope: CreateMetadataCascadePolicyScope + :param template_key: The key of the targeted metadata template. This template will + need to already have an instance applied to the targeted folder. + + In many cases the template key is automatically derived + of its display name, for example `Contract Template` would + become `contractTemplate`. In some cases the creator of the + template will have provided its own template key. + + Please [list the templates for an enterprise][list], or + get all instances on a [file][file] or [folder][folder] + to inspect a template's key. + + [list]: e://get-metadata-templates-enterprise + [file]: e://get-files-id-metadata + [folder]: e://get-folders-id-metadata + :type template_key: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'folder_id': folder_id, + 'scope': scope, + 'templateKey': template_key, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_cascade_policies', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataCascadePolicy) + + def get_metadata_cascade_policy_by_id( + self, + metadata_cascade_policy_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataCascadePolicy: + """ + Retrieve a specific metadata cascade policy assigned to a folder. + :param metadata_cascade_policy_id: The ID of the metadata cascade policy. + Example: "6fd4ff89-8fc1-42cf-8b29-1890dedd26d7" + :type metadata_cascade_policy_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_cascade_policies/', + to_string(metadata_cascade_policy_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataCascadePolicy) + + def delete_metadata_cascade_policy_by_id( + self, + metadata_cascade_policy_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a metadata cascade policy. + :param metadata_cascade_policy_id: The ID of the metadata cascade policy. + Example: "6fd4ff89-8fc1-42cf-8b29-1890dedd26d7" + :type metadata_cascade_policy_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_cascade_policies/', + to_string(metadata_cascade_policy_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def apply_metadata_cascade_policy( + self, + metadata_cascade_policy_id: str, + conflict_resolution: ApplyMetadataCascadePolicyConflictResolution, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Force the metadata on a folder with a metadata cascade policy to be applied to + + all of its children. This can be used after creating a new cascade policy to + + + enforce the metadata to be cascaded down to all existing files within that + + + folder. + + :param metadata_cascade_policy_id: The ID of the cascade policy to force-apply. + Example: "6fd4ff89-8fc1-42cf-8b29-1890dedd26d7" + :type metadata_cascade_policy_id: str + :param conflict_resolution: Describes the desired behavior when dealing with the conflict + where a metadata template already has an instance applied + to a child. + + * `none` will preserve the existing value on the file + * `overwrite` will force-apply the templates values over + any existing values. + :type conflict_resolution: ApplyMetadataCascadePolicyConflictResolution + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'conflict_resolution': conflict_resolution} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_cascade_policies/', + to_string(metadata_cascade_policy_id), + '/apply', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/metadata_templates.py b/box_sdk_gen/managers/metadata_templates.py new file mode 100644 index 000000000..445c66a57 --- /dev/null +++ b/box_sdk_gen/managers/metadata_templates.py @@ -0,0 +1,636 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.metadata_templates import MetadataTemplates + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.metadata_template import MetadataTemplate + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetMetadataTemplateScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class UpdateMetadataTemplateScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class UpdateMetadataTemplateRequestBodyOpField(str, Enum): + EDITTEMPLATE = 'editTemplate' + ADDFIELD = 'addField' + REORDERFIELDS = 'reorderFields' + ADDENUMOPTION = 'addEnumOption' + REORDERENUMOPTIONS = 'reorderEnumOptions' + REORDERMULTISELECTOPTIONS = 'reorderMultiSelectOptions' + ADDMULTISELECTOPTION = 'addMultiSelectOption' + EDITFIELD = 'editField' + REMOVEFIELD = 'removeField' + EDITENUMOPTION = 'editEnumOption' + REMOVEENUMOPTION = 'removeEnumOption' + EDITMULTISELECTOPTION = 'editMultiSelectOption' + REMOVEMULTISELECTOPTION = 'removeMultiSelectOption' + + +class UpdateMetadataTemplateRequestBody(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'field_key': 'fieldKey', + 'field_keys': 'fieldKeys', + 'enum_option_key': 'enumOptionKey', + 'enum_option_keys': 'enumOptionKeys', + 'multi_select_option_key': 'multiSelectOptionKey', + 'multi_select_option_keys': 'multiSelectOptionKeys', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'fieldKey': 'field_key', + 'fieldKeys': 'field_keys', + 'enumOptionKey': 'enum_option_key', + 'enumOptionKeys': 'enum_option_keys', + 'multiSelectOptionKey': 'multi_select_option_key', + 'multiSelectOptionKeys': 'multi_select_option_keys', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + op: UpdateMetadataTemplateRequestBodyOpField, + *, + data: Optional[Dict] = None, + field_key: Optional[str] = None, + field_keys: Optional[List[str]] = None, + enum_option_key: Optional[str] = None, + enum_option_keys: Optional[List[str]] = None, + multi_select_option_key: Optional[str] = None, + multi_select_option_keys: Optional[List[str]] = None, + **kwargs + ): + """ + :param op: The type of change to perform on the template. Some + of these are hazardous as they will change existing templates. + :type op: UpdateMetadataTemplateRequestBodyOpField + :param data: The data for the operation. This will vary depending on the + operation being performed., defaults to None + :type data: Optional[Dict], optional + :param field_key: For operations that affect a single field this defines the key of + the field that is affected., defaults to None + :type field_key: Optional[str], optional + :param field_keys: For operations that affect multiple fields this defines the keys + of the fields that are affected., defaults to None + :type field_keys: Optional[List[str]], optional + :param enum_option_key: For operations that affect a single `enum` option this defines + the key of the option that is affected., defaults to None + :type enum_option_key: Optional[str], optional + :param enum_option_keys: For operations that affect multiple `enum` options this defines + the keys of the options that are affected., defaults to None + :type enum_option_keys: Optional[List[str]], optional + :param multi_select_option_key: For operations that affect a single multi select option this + defines the key of the option that is affected., defaults to None + :type multi_select_option_key: Optional[str], optional + :param multi_select_option_keys: For operations that affect multiple multi select options this + defines the keys of the options that are affected., defaults to None + :type multi_select_option_keys: Optional[List[str]], optional + """ + super().__init__(**kwargs) + self.op = op + self.data = data + self.field_key = field_key + self.field_keys = field_keys + self.enum_option_key = enum_option_key + self.enum_option_keys = enum_option_keys + self.multi_select_option_key = multi_select_option_key + self.multi_select_option_keys = multi_select_option_keys + + +class DeleteMetadataTemplateScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class CreateMetadataTemplateFieldsTypeField(str, Enum): + STRING = 'string' + FLOAT = 'float' + DATE = 'date' + ENUM = 'enum' + MULTISELECT = 'multiSelect' + + +class CreateMetadataTemplateFieldsOptionsField(BaseObject): + def __init__(self, key: str, **kwargs): + """ + :param key: The text value of the option. This represents both the display name of the + option and the internal key used when updating templates. + :type key: str + """ + super().__init__(**kwargs) + self.key = key + + +class CreateMetadataTemplateFields(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + **BaseObject._json_to_fields_mapping, + } + _discriminator = 'type', {'string', 'float', 'date', 'enum', 'multiSelect'} + + def __init__( + self, + type: CreateMetadataTemplateFieldsTypeField, + key: str, + display_name: str, + *, + description: Optional[str] = None, + hidden: Optional[bool] = None, + options: Optional[List[CreateMetadataTemplateFieldsOptionsField]] = None, + **kwargs + ): + """ + :param type: The type of field. The basic fields are a `string` field for text, a + `float` field for numbers, and a `date` fields to present the user with a + date-time picker. + + Additionally, metadata templates support an `enum` field for a basic list + of items, and ` multiSelect` field for a similar list of items where the + user can select more than one value. + :type type: CreateMetadataTemplateFieldsTypeField + :param key: A unique identifier for the field. The identifier must + be unique within the template to which it belongs. + :type key: str + :param display_name: The display name of the field as it is shown to the user in the web and + mobile apps. + :type display_name: str + :param description: A description of the field. This is not shown to the user., defaults to None + :type description: Optional[str], optional + :param hidden: Whether this field is hidden in the UI for the user and can only be set + through the API instead., defaults to None + :type hidden: Optional[bool], optional + :param options: A list of options for this field. This is used in combination with the + `enum` and `multiSelect` field types., defaults to None + :type options: Optional[List[CreateMetadataTemplateFieldsOptionsField]], optional + """ + super().__init__(**kwargs) + self.type = type + self.key = key + self.display_name = display_name + self.description = description + self.hidden = hidden + self.options = options + + +class MetadataTemplatesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_metadata_templates_by_instance_id( + self, + metadata_instance_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTemplates: + """ + Finds a metadata template by searching for the ID of an instance of the + + template. + + :param metadata_instance_id: The ID of an instance of the metadata template to find. + :type metadata_instance_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'metadata_instance_id': to_string(metadata_instance_id), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/metadata_templates'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTemplates) + + def get_metadata_template( + self, + scope: GetMetadataTemplateScope, + template_key: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTemplate: + """ + Retrieves a metadata template by its `scope` and `templateKey` values. + + To find the `scope` and `templateKey` for a template, list all templates for + + + an enterprise or globally, or list all templates applied to a file or folder. + + :param scope: The scope of the metadata template. + Example: "global" + :type scope: GetMetadataTemplateScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/', + to_string(scope), + '/', + to_string(template_key), + '/schema', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTemplate) + + def update_metadata_template( + self, + scope: UpdateMetadataTemplateScope, + template_key: str, + request_body: List[UpdateMetadataTemplateRequestBody], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTemplate: + """ + Updates a metadata template. + + The metadata template can only be updated if the template + + + already exists. + + + The update is applied atomically. If any errors occur during the + + + application of the operations, the metadata template will not be changed. + + :param scope: The scope of the metadata template. + Example: "global" + :type scope: UpdateMetadataTemplateScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param request_body: Request body of updateMetadataTemplate method + :type request_body: List[UpdateMetadataTemplateRequestBody] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/', + to_string(scope), + '/', + to_string(template_key), + '/schema', + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json-patch+json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTemplate) + + def delete_metadata_template( + self, + scope: DeleteMetadataTemplateScope, + template_key: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Delete a metadata template and its instances. + + This deletion is permanent and can not be reversed. + + :param scope: The scope of the metadata template. + Example: "global" + :type scope: DeleteMetadataTemplateScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/', + to_string(scope), + '/', + to_string(template_key), + '/schema', + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_metadata_template_by_id( + self, + template_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTemplate: + """ + Retrieves a metadata template by its ID. + :param template_id: The ID of the template. + Example: "f7a9891f" + :type template_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/', + to_string(template_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTemplate) + + def get_global_metadata_templates( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTemplates: + """ + Used to retrieve all generic, global metadata templates available to all + + enterprises using Box. + + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/global', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTemplates) + + def get_enterprise_metadata_templates( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTemplates: + """ + Used to retrieve all metadata templates created to be used specifically within + + the user's enterprise. + + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/enterprise', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTemplates) + + def create_metadata_template( + self, + scope: str, + display_name: str, + *, + template_key: Optional[str] = None, + hidden: Optional[bool] = None, + fields: Optional[List[CreateMetadataTemplateFields]] = None, + copy_instance_on_item_copy: Optional[bool] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTemplate: + """ + Creates a new metadata template that can be applied to + + files and folders. + + :param scope: The scope of the metadata template to create. Applications can + only create templates for use within the authenticated user's + enterprise. + + This value needs to be set to `enterprise`, as `global` scopes can + not be created by applications. + :type scope: str + :param display_name: The display name of the template. + :type display_name: str + :param template_key: A unique identifier for the template. This identifier needs to be + unique across the enterprise for which the metadata template is + being created. + + When not provided, the API will create a unique `templateKey` + based on the value of the `displayName`., defaults to None + :type template_key: Optional[str], optional + :param hidden: Defines if this template is visible in the Box web app UI, or if + it is purely intended for usage through the API., defaults to None + :type hidden: Optional[bool], optional + :param fields: An ordered list of template fields which are part of the template. + Each field can be a regular text field, date field, number field, + as well as a single or multi-select list., defaults to None + :type fields: Optional[List[CreateMetadataTemplateFields]], optional + :param copy_instance_on_item_copy: Whether or not to copy any metadata attached to a file or folder + when it is copied. By default, metadata is not copied along with a + file or folder when it is copied., defaults to None + :type copy_instance_on_item_copy: Optional[bool], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'scope': scope, + 'templateKey': template_key, + 'displayName': display_name, + 'hidden': hidden, + 'fields': fields, + 'copyInstanceOnItemCopy': copy_instance_on_item_copy, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/schema', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTemplate) diff --git a/box_sdk_gen/managers/recent_items.py b/box_sdk_gen/managers/recent_items.py new file mode 100644 index 000000000..83313a980 --- /dev/null +++ b/box_sdk_gen/managers/recent_items.py @@ -0,0 +1,109 @@ +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.recent_items import RecentItems + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class RecentItemsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_recent_items( + self, + *, + fields: Optional[List[str]] = None, + limit: Optional[int] = None, + marker: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> RecentItems: + """ + Returns information about the recent items accessed + + by a user, either in the last 90 days or up to the last + + + 1000 items accessed. + + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'limit': to_string(limit), + 'marker': to_string(marker), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/recent_items'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, RecentItems) diff --git a/box_sdk_gen/managers/retention_policies.py b/box_sdk_gen/managers/retention_policies.py new file mode 100644 index 000000000..9f5f5f723 --- /dev/null +++ b/box_sdk_gen/managers/retention_policies.py @@ -0,0 +1,443 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.retention_policies import RetentionPolicies + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.retention_policy import RetentionPolicy + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetRetentionPoliciesPolicyType(str, Enum): + FINITE = 'finite' + INDEFINITE = 'indefinite' + + +class CreateRetentionPolicyPolicyType(str, Enum): + FINITE = 'finite' + INDEFINITE = 'indefinite' + + +class CreateRetentionPolicyDispositionAction(str, Enum): + PERMANENTLY_DELETE = 'permanently_delete' + REMOVE_RETENTION = 'remove_retention' + + +class CreateRetentionPolicyRetentionType(str, Enum): + MODIFIABLE = 'modifiable' + NON_MODIFIABLE = 'non_modifiable' + + +class RetentionPoliciesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_retention_policies( + self, + *, + policy_name: Optional[str] = None, + policy_type: Optional[GetRetentionPoliciesPolicyType] = None, + created_by_user_id: Optional[str] = None, + fields: Optional[List[str]] = None, + limit: Optional[int] = None, + marker: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> RetentionPolicies: + """ + Retrieves all of the retention policies for an enterprise. + :param policy_name: Filters results by a case sensitive prefix of the name of + retention policies., defaults to None + :type policy_name: Optional[str], optional + :param policy_type: Filters results by the type of retention policy., defaults to None + :type policy_type: Optional[GetRetentionPoliciesPolicyType], optional + :param created_by_user_id: Filters results by the ID of the user who created policy., defaults to None + :type created_by_user_id: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination., defaults to None + :type marker: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'policy_name': to_string(policy_name), + 'policy_type': to_string(policy_type), + 'created_by_user_id': to_string(created_by_user_id), + 'fields': to_string(fields), + 'limit': to_string(limit), + 'marker': to_string(marker), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/retention_policies'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, RetentionPolicies) + + def create_retention_policy( + self, + policy_name: str, + policy_type: CreateRetentionPolicyPolicyType, + disposition_action: CreateRetentionPolicyDispositionAction, + *, + description: Optional[str] = None, + retention_length: Optional[str] = None, + retention_type: Optional[CreateRetentionPolicyRetentionType] = None, + can_owner_extend_retention: Optional[bool] = None, + are_owners_notified: Optional[bool] = None, + custom_notification_recipients: Optional[List[UserMini]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> RetentionPolicy: + """ + Creates a retention policy. + :param policy_name: The name for the retention policy. + :type policy_name: str + :param policy_type: The type of the retention policy. A retention + policy type can either be `finite`, where a + specific amount of time to retain the content is known + upfront, or `indefinite`, where the amount of time + to retain the content is still unknown. + :type policy_type: CreateRetentionPolicyPolicyType + :param disposition_action: The disposition action of the retention policy. + `permanently_delete` deletes the content + retained by the policy permanently. + `remove_retention` lifts retention policy + from the content, allowing it to be deleted + by users once the retention policy has expired. + :type disposition_action: CreateRetentionPolicyDispositionAction + :param description: The additional text description of the retention policy., defaults to None + :type description: Optional[str], optional + :param retention_length: The length of the retention policy. This value + specifies the duration in days that the retention + policy will be active for after being assigned to + content. If the policy has a `policy_type` of + `indefinite`, the `retention_length` will also be + `indefinite`., defaults to None + :type retention_length: Optional[str], optional + :param retention_type: Specifies the retention type: + + * `modifiable`: You can modify the retention policy. For example, + you can add or remove folders, shorten or lengthen + the policy duration, or delete the assignment. + Use this type if your retention policy + is not related to any regulatory purposes. + + * `non_modifiable`: You can modify the retention policy + only in a limited way: add a folder, lengthen the duration, + retire the policy, change the disposition action + or notification settings. You cannot perform other actions, + such as deleting the assignment or shortening the + policy duration. Use this type to ensure + compliance with regulatory retention policies., defaults to None + :type retention_type: Optional[CreateRetentionPolicyRetentionType], optional + :param can_owner_extend_retention: Whether the owner of a file will be allowed to + extend the retention., defaults to None + :type can_owner_extend_retention: Optional[bool], optional + :param are_owners_notified: Whether owner and co-owners of a file are notified + when the policy nears expiration., defaults to None + :type are_owners_notified: Optional[bool], optional + :param custom_notification_recipients: A list of users notified when + the retention policy duration is about to end., defaults to None + :type custom_notification_recipients: Optional[List[UserMini]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'policy_name': policy_name, + 'description': description, + 'policy_type': policy_type, + 'disposition_action': disposition_action, + 'retention_length': retention_length, + 'retention_type': retention_type, + 'can_owner_extend_retention': can_owner_extend_retention, + 'are_owners_notified': are_owners_notified, + 'custom_notification_recipients': custom_notification_recipients, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/retention_policies'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, RetentionPolicy) + + def get_retention_policy_by_id( + self, + retention_policy_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> RetentionPolicy: + """ + Retrieves a retention policy. + :param retention_policy_id: The ID of the retention policy. + Example: "982312" + :type retention_policy_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/retention_policies/', + to_string(retention_policy_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, RetentionPolicy) + + def update_retention_policy_by_id( + self, + retention_policy_id: str, + *, + policy_name: Union[Optional[str], NullValue] = None, + description: Union[Optional[str], NullValue] = None, + disposition_action: Optional[str] = None, + retention_type: Union[Optional[str], NullValue] = None, + retention_length: Optional[str] = None, + status: Union[Optional[str], NullValue] = None, + can_owner_extend_retention: Union[Optional[bool], NullValue] = None, + are_owners_notified: Union[Optional[bool], NullValue] = None, + custom_notification_recipients: Union[ + Optional[List[UserBase]], NullValue + ] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> RetentionPolicy: + """ + Updates a retention policy. + :param retention_policy_id: The ID of the retention policy. + Example: "982312" + :type retention_policy_id: str + :param policy_name: The name for the retention policy., defaults to None + :type policy_name: Union[Optional[str], NullValue], optional + :param description: The additional text description of the retention policy., defaults to None + :type description: Union[Optional[str], NullValue], optional + :param disposition_action: The disposition action of the retention policy. + This action can be `permanently_delete`, which + will cause the content retained by the policy + to be permanently deleted, or `remove_retention`, + which will lift the retention policy from the content, + allowing it to be deleted by users, + once the retention policy has expired. + You can use `null` if you don't want to change `disposition_action`., defaults to None + :type disposition_action: Optional[str], optional + :param retention_type: Specifies the retention type: + + * `modifiable`: You can modify the retention policy. For example, + you can add or remove folders, shorten or lengthen + the policy duration, or delete the assignment. + Use this type if your retention policy + is not related to any regulatory purposes. + * `non-modifiable`: You can modify the retention policy + only in a limited way: add a folder, lengthen the duration, + retire the policy, change the disposition action + or notification settings. You cannot perform other actions, + such as deleting the assignment or shortening the + policy duration. Use this type to ensure + compliance with regulatory retention policies. + + When updating a retention policy, you can use + `non-modifiable` type only. You can convert a + `modifiable` policy to `non-modifiable`, but + not the other way around., defaults to None + :type retention_type: Union[Optional[str], NullValue], optional + :param retention_length: The length of the retention policy. This value + specifies the duration in days that the retention + policy will be active for after being assigned to + content. If the policy has a `policy_type` of + `indefinite`, the `retention_length` will also be + `indefinite`., defaults to None + :type retention_length: Optional[str], optional + :param status: Used to retire a retention policy. + + If not retiring a policy, do not include this parameter + or set it to `null`., defaults to None + :type status: Union[Optional[str], NullValue], optional + :param can_owner_extend_retention: Determines if the owner of items under the policy + can extend the retention when the original retention + duration is about to end., defaults to None + :type can_owner_extend_retention: Union[Optional[bool], NullValue], optional + :param are_owners_notified: Determines if owners and co-owners of items + under the policy are notified when + the retention duration is about to end., defaults to None + :type are_owners_notified: Union[Optional[bool], NullValue], optional + :param custom_notification_recipients: A list of users notified when the retention duration is about to end., defaults to None + :type custom_notification_recipients: Union[Optional[List[UserBase]], NullValue], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'policy_name': policy_name, + 'description': description, + 'disposition_action': disposition_action, + 'retention_type': retention_type, + 'retention_length': retention_length, + 'status': status, + 'can_owner_extend_retention': can_owner_extend_retention, + 'are_owners_notified': are_owners_notified, + 'custom_notification_recipients': custom_notification_recipients, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/retention_policies/', + to_string(retention_policy_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, RetentionPolicy) + + def delete_retention_policy_by_id( + self, + retention_policy_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Permanently deletes a retention policy. + :param retention_policy_id: The ID of the retention policy. + Example: "982312" + :type retention_policy_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/retention_policies/', + to_string(retention_policy_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/retention_policy_assignments.py b/box_sdk_gen/managers/retention_policy_assignments.py new file mode 100644 index 000000000..fd612d1ec --- /dev/null +++ b/box_sdk_gen/managers/retention_policy_assignments.py @@ -0,0 +1,371 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.retention_policy_assignments import RetentionPolicyAssignments + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.retention_policy_assignment import RetentionPolicyAssignment + +from box_sdk_gen.schemas.files_under_retention import FilesUnderRetention + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetRetentionPolicyAssignmentsType(str, Enum): + FOLDER = 'folder' + ENTERPRISE = 'enterprise' + METADATA_TEMPLATE = 'metadata_template' + + +class CreateRetentionPolicyAssignmentAssignToTypeField(str, Enum): + ENTERPRISE = 'enterprise' + FOLDER = 'folder' + METADATA_TEMPLATE = 'metadata_template' + + +class CreateRetentionPolicyAssignmentAssignTo(BaseObject): + _discriminator = 'type', {'enterprise', 'folder', 'metadata_template'} + + def __init__( + self, + type: CreateRetentionPolicyAssignmentAssignToTypeField, + *, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of item to assign the policy to. + :type type: CreateRetentionPolicyAssignmentAssignToTypeField + :param id: The ID of item to assign the policy to. + Set to `null` or omit when `type` is set to + `enterprise`., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class CreateRetentionPolicyAssignmentFilterFields(BaseObject): + def __init__( + self, *, field: Optional[str] = None, value: Optional[str] = None, **kwargs + ): + """ + :param field: The metadata attribute key id., defaults to None + :type field: Optional[str], optional + :param value: The metadata attribute field id. For value, only + enum and multiselect types are supported., defaults to None + :type value: Optional[str], optional + """ + super().__init__(**kwargs) + self.field = field + self.value = value + + +class RetentionPolicyAssignmentsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_retention_policy_assignments( + self, + retention_policy_id: str, + *, + type: Optional[GetRetentionPolicyAssignmentsType] = None, + fields: Optional[List[str]] = None, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> RetentionPolicyAssignments: + """ + Returns a list of all retention policy assignments associated with a specified + + retention policy. + + :param retention_policy_id: The ID of the retention policy. + Example: "982312" + :type retention_policy_id: str + :param type: The type of the retention policy assignment to retrieve., defaults to None + :type type: Optional[GetRetentionPolicyAssignmentsType], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'type': to_string(type), + 'fields': to_string(fields), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/retention_policies/', + to_string(retention_policy_id), + '/assignments', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, RetentionPolicyAssignments) + + def create_retention_policy_assignment( + self, + policy_id: str, + assign_to: CreateRetentionPolicyAssignmentAssignTo, + *, + filter_fields: Optional[ + List[CreateRetentionPolicyAssignmentFilterFields] + ] = None, + start_date_field: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> RetentionPolicyAssignment: + """ + Assigns a retention policy to an item. + :param policy_id: The ID of the retention policy to assign. + :type policy_id: str + :param assign_to: The item to assign the policy to. + :type assign_to: CreateRetentionPolicyAssignmentAssignTo + :param filter_fields: If the `assign_to` type is `metadata_template`, + then optionally add the `filter_fields` parameter which will + require an array of objects with a field entry and a value entry. + Currently only one object of `field` and `value` is supported., defaults to None + :type filter_fields: Optional[List[CreateRetentionPolicyAssignmentFilterFields]], optional + :param start_date_field: The date the retention policy assignment begins. + + If the `assigned_to` type is `metadata_template`, + this field can be a date field's metadata attribute key id., defaults to None + :type start_date_field: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'policy_id': policy_id, + 'assign_to': assign_to, + 'filter_fields': filter_fields, + 'start_date_field': start_date_field, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/retention_policy_assignments', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, RetentionPolicyAssignment) + + def get_retention_policy_assignment_by_id( + self, + retention_policy_assignment_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> RetentionPolicyAssignment: + """ + Retrieves a retention policy assignment. + :param retention_policy_assignment_id: The ID of the retention policy assignment. + Example: "1233123" + :type retention_policy_assignment_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/retention_policy_assignments/', + to_string(retention_policy_assignment_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, RetentionPolicyAssignment) + + def delete_retention_policy_assignment_by_id( + self, + retention_policy_assignment_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes a retention policy assignment + + applied to content. + + :param retention_policy_assignment_id: The ID of the retention policy assignment. + Example: "1233123" + :type retention_policy_assignment_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/retention_policy_assignments/', + to_string(retention_policy_assignment_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_files_under_retention_policy_assignment( + self, + retention_policy_assignment_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FilesUnderRetention: + """ + Returns a list of files under retention for a retention policy assignment. + :param retention_policy_assignment_id: The ID of the retention policy assignment. + Example: "1233123" + :type retention_policy_assignment_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/retention_policy_assignments/', + to_string(retention_policy_assignment_id), + '/files_under_retention', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FilesUnderRetention) diff --git a/box_sdk_gen/managers/search.py b/box_sdk_gen/managers/search.py new file mode 100644 index 000000000..1d6ec33ed --- /dev/null +++ b/box_sdk_gen/managers/search.py @@ -0,0 +1,560 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Dict + +from typing import List + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.internal.utils import to_string + +from typing import Union + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.metadata_query_results import MetadataQueryResults + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.metadata_query import MetadataQuery + +from box_sdk_gen.schemas.search_results import SearchResults + +from box_sdk_gen.schemas.search_results_with_shared_links import ( + SearchResultsWithSharedLinks, +) + +from box_sdk_gen.schemas.metadata_filter import MetadataFilter + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.serialization.json import sd_to_json + + +class SearchByMetadataQueryOrderByDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class SearchByMetadataQueryOrderBy(BaseObject): + def __init__( + self, + *, + field_key: Optional[str] = None, + direction: Optional[SearchByMetadataQueryOrderByDirectionField] = None, + **kwargs + ): + """ + :param field_key: The metadata template field to order by. + + The `field_key` represents the `key` value of a field from the + metadata template being searched for., defaults to None + :type field_key: Optional[str], optional + :param direction: The direction to order by, either ascending or descending. + + The `ordering` direction must be the same for each item in the + array., defaults to None + :type direction: Optional[SearchByMetadataQueryOrderByDirectionField], optional + """ + super().__init__(**kwargs) + self.field_key = field_key + self.direction = direction + + +class SearchForContentScope(str, Enum): + USER_CONTENT = 'user_content' + ENTERPRISE_CONTENT = 'enterprise_content' + + +class SearchForContentContentTypes(str, Enum): + NAME = 'name' + DESCRIPTION = 'description' + FILE_CONTENT = 'file_content' + COMMENTS = 'comments' + TAG = 'tag' + + +class SearchForContentType(str, Enum): + FILE = 'file' + FOLDER = 'folder' + WEB_LINK = 'web_link' + + +class SearchForContentTrashContent(str, Enum): + NON_TRASHED_ONLY = 'non_trashed_only' + TRASHED_ONLY = 'trashed_only' + ALL_ITEMS = 'all_items' + + +class SearchForContentSort(str, Enum): + MODIFIED_AT = 'modified_at' + RELEVANCE = 'relevance' + + +class SearchForContentDirection(str, Enum): + DESC = 'DESC' + ASC = 'ASC' + + +class SearchManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def search_by_metadata_query( + self, + from_: str, + ancestor_folder_id: str, + *, + query: Optional[str] = None, + query_params: Optional[Dict] = None, + order_by: Optional[List[SearchByMetadataQueryOrderBy]] = None, + limit: Optional[int] = None, + marker: Optional[str] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataQueryResults: + """ + Create a search using SQL-like syntax to return items that match specific + + metadata. + + + By default, this endpoint returns only the most basic info about the items for + + + which the query matches. To get additional fields for each item, including any + + + of the metadata, use the `fields` attribute in the query. + + :param from_: Specifies the template used in the query. Must be in the form + `scope.templateKey`. Not all templates can be used in this field, + most notably the built-in, Box-provided classification templates + can not be used in a query. + :type from_: str + :param ancestor_folder_id: The ID of the folder that you are restricting the query to. A + value of zero will return results from all folders you have access + to. A non-zero value will only return results found in the folder + corresponding to the ID or in any of its subfolders. + :type ancestor_folder_id: str + :param query: The query to perform. A query is a logical expression that is very similar + to a SQL `SELECT` statement. Values in the search query can be turned into + parameters specified in the `query_param` arguments list to prevent having + to manually insert search values into the query string. + + For example, a value of `:amount` would represent the `amount` value in + `query_params` object., defaults to None + :type query: Optional[str], optional + :param query_params: Set of arguments corresponding to the parameters specified in the + `query`. The type of each parameter used in the `query_params` must match + the type of the corresponding metadata template field., defaults to None + :type query_params: Optional[Dict], optional + :param order_by: A list of template fields and directions to sort the metadata query + results by. + + The ordering `direction` must be the same for each item in the array., defaults to None + :type order_by: Optional[List[SearchByMetadataQueryOrderBy]], optional + :param limit: A value between 0 and 100 that indicates the maximum number of results + to return for a single request. This only specifies a maximum + boundary and will not guarantee the minimum number of results + returned., defaults to None + :type limit: Optional[int], optional + :param marker: Marker to use for requesting the next page., defaults to None + :type marker: Optional[str], optional + :param fields: By default, this endpoint returns only the most basic info about the items for + which the query matches. This attribute can be used to specify a list of + additional attributes to return for any item, including its metadata. + + This attribute takes a list of item fields, metadata template identifiers, + or metadata template field identifiers. + + For example: + + * `created_by` will add the details of the user who created the item to + the response. + * `metadata..` will return the mini-representation + of the metadata instance identified by the `scope` and `templateKey`. + * `metadata...` will return all the mini-representation + of the metadata instance identified by the `scope` and `templateKey` plus + the field specified by the `field` name. Multiple fields for the same + `scope` and `templateKey` can be defined., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'from': from_, + 'query': query, + 'query_params': query_params, + 'ancestor_folder_id': ancestor_folder_id, + 'order_by': order_by, + 'limit': limit, + 'marker': marker, + 'fields': fields, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_queries/execute_read', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataQueryResults) + + def search_for_content( + self, + *, + query: Optional[str] = None, + scope: Optional[SearchForContentScope] = None, + file_extensions: Optional[List[str]] = None, + created_at_range: Optional[List[str]] = None, + updated_at_range: Optional[List[str]] = None, + size_range: Optional[List[int]] = None, + owner_user_ids: Optional[List[str]] = None, + recent_updater_user_ids: Optional[List[str]] = None, + ancestor_folder_ids: Optional[List[str]] = None, + content_types: Optional[List[SearchForContentContentTypes]] = None, + type: Optional[SearchForContentType] = None, + trash_content: Optional[SearchForContentTrashContent] = None, + mdfilters: Optional[List[MetadataFilter]] = None, + sort: Optional[SearchForContentSort] = None, + direction: Optional[SearchForContentDirection] = None, + limit: Optional[int] = None, + include_recent_shared_links: Optional[bool] = None, + fields: Optional[List[str]] = None, + offset: Optional[int] = None, + deleted_user_ids: Optional[List[str]] = None, + deleted_at_range: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Union[SearchResults, SearchResultsWithSharedLinks]: + """ + Searches for files, folders, web links, and shared files across the + + users content or across the entire enterprise. + + :param query: The string to search for. This query is matched against item names, + descriptions, text content of files, and various other fields of + the different item types. + + This parameter supports a variety of operators to further refine + the results returns. + + * `""` - by wrapping a query in double quotes only exact matches are + returned by the API. Exact searches do not return search matches + based on specific character sequences. Instead, they return + matches based on phrases, that is, word sequences. For example: + A search for `"Blue-Box"` may return search results including + the sequence `"blue.box"`, `"Blue Box"`, and `"Blue-Box"`; + any item containing the words `Blue` and `Box` consecutively, in + the order specified. + * `AND` - returns items that contain both the search terms. For + example, a search for `marketing AND BoxWorks` returns items + that have both `marketing` and `BoxWorks` within its text in any order. + It does not return a result that only has `BoxWorks` in its text. + * `OR` - returns items that contain either of the search terms. For + example, a search for `marketing OR BoxWorks` returns a result that + has either `marketing` or `BoxWorks` within its text. Using this + operator is not necessary as we implicitly interpret multi-word + queries as `OR` unless another supported boolean term is used. + * `NOT` - returns items that do not contain the search term provided. + For example, a search for `marketing AND NOT BoxWorks` returns a result + that has only `marketing` within its text. Results containing + `BoxWorks` are omitted. + + We do not support lower case (that is, + `and`, `or`, and `not`) or mixed case (that is, `And`, `Or`, and `Not`) + operators. + + This field is required unless the `mdfilters` parameter is defined., defaults to None + :type query: Optional[str], optional + :param scope: Limits the search results to either the files that the user has + access to, or to files available to the entire enterprise. + + The scope defaults to `user_content`, which limits the search + results to content that is available to the currently authenticated + user. + + The `enterprise_content` can be requested by an admin through our + support channels. Once this scope has been enabled for a user, it + will allow that use to query for content across the entire + enterprise and not only the content that they have access to., defaults to None + :type scope: Optional[SearchForContentScope], optional + :param file_extensions: Limits the search results to any files that match any of the provided + file extensions. This list is a comma-separated list of file extensions + without the dots., defaults to None + :type file_extensions: Optional[List[str]], optional + :param created_at_range: Limits the search results to any items created within + a given date range. + + Date ranges are defined as comma separated RFC3339 + timestamps. + + If the the start date is omitted (`,2014-05-17T13:35:01-07:00`) + anything created before the end date will be returned. + + If the end date is omitted (`2014-05-15T13:35:01-07:00,`) the + current date will be used as the end date instead., defaults to None + :type created_at_range: Optional[List[str]], optional + :param updated_at_range: Limits the search results to any items updated within + a given date range. + + Date ranges are defined as comma separated RFC3339 + timestamps. + + If the start date is omitted (`,2014-05-17T13:35:01-07:00`) + anything updated before the end date will be returned. + + If the end date is omitted (`2014-05-15T13:35:01-07:00,`) the + current date will be used as the end date instead., defaults to None + :type updated_at_range: Optional[List[str]], optional + :param size_range: Limits the search results to any items with a size within + a given file size range. This applied to files and folders. + + Size ranges are defined as comma separated list of a lower + and upper byte size limit (inclusive). + + The upper and lower bound can be omitted to create open ranges., defaults to None + :type size_range: Optional[List[int]], optional + :param owner_user_ids: Limits the search results to any items that are owned + by the given list of owners, defined as a list of comma separated + user IDs. + + The items still need to be owned or shared with + the currently authenticated user for them to show up in the search + results. If the user does not have access to any files owned by any of + the users an empty result set will be returned. + + To search across an entire enterprise, we recommend using the + `enterprise_content` scope parameter which can be requested with our + support team., defaults to None + :type owner_user_ids: Optional[List[str]], optional + :param recent_updater_user_ids: Limits the search results to any items that have been updated + by the given list of users, defined as a list of comma separated + user IDs. + + The items still need to be owned or shared with + the currently authenticated user for them to show up in the search + results. If the user does not have access to any files owned by any of + the users an empty result set will be returned. + + This feature only searches back to the last 10 versions of an item., defaults to None + :type recent_updater_user_ids: Optional[List[str]], optional + :param ancestor_folder_ids: Limits the search results to items within the given + list of folders, defined as a comma separated lists + of folder IDs. + + Search results will also include items within any subfolders + of those ancestor folders. + + The folders still need to be owned or shared with + the currently authenticated user. If the folder is not accessible by this + user, or it does not exist, a `HTTP 404` error code will be returned + instead. + + To search across an entire enterprise, we recommend using the + `enterprise_content` scope parameter which can be requested with our + support team., defaults to None + :type ancestor_folder_ids: Optional[List[str]], optional + :param content_types: Limits the search results to any items that match the search query + for a specific part of the file, for example the file description. + + Content types are defined as a comma separated lists + of Box recognized content types. The allowed content types are as follows. + + * `name` - The name of the item, as defined by its `name` field. + * `description` - The description of the item, as defined by its + `description` field. + * `file_content` - The actual content of the file. + * `comments` - The content of any of the comments on a file or + folder. + * `tags` - Any tags that are applied to an item, as defined by its + `tags` field., defaults to None + :type content_types: Optional[List[SearchForContentContentTypes]], optional + :param type: Limits the search results to any items of this type. This + parameter only takes one value. By default the API returns + items that match any of these types. + + * `file` - Limits the search results to files, + * `folder` - Limits the search results to folders, + * `web_link` - Limits the search results to web links, also known + as bookmarks., defaults to None + :type type: Optional[SearchForContentType], optional + :param trash_content: 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., 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 is a list that specifies exactly **one** metadata template used to filter the search results. + The parameter 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 + 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., defaults to None + :type sort: Optional[SearchForContentSort], optional + :param direction: Defines the direction in which search results are ordered. This API + defaults to returning items in descending (`DESC`) order unless this + parameter is explicitly specified. + + When results are sorted by `relevance` the ordering is locked to returning + items in descending order of relevance, and this parameter is ignored., defaults to None + :type direction: Optional[SearchForContentDirection], optional + :param limit: Defines the maximum number of items to return as part of a page of + results., defaults to None + :type limit: Optional[int], optional + :param include_recent_shared_links: Defines whether the search results should include any items + that the user recently accessed through a shared link. + + When this parameter has been set to true, + the format of the response of this API changes to return + a list of [Search Results with + Shared Links](r://search_results_with_shared_links)., defaults to None + :type include_recent_shared_links: Optional[bool], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param deleted_user_ids: Limits the search results to items that were deleted by the given + list of users, defined as a list of comma separated user IDs. + + The `trash_content` parameter needs to be set to `trashed_only`. + + If searching in trash is not performed, an empty result set + is returned. The items need to be owned or shared with + the currently authenticated user for them to show up in the search + results. + + If the user does not have access to any files owned by + any of the users, an empty result set is returned. + + Data available from 2023-02-01 onwards., defaults to None + :type deleted_user_ids: Optional[List[str]], optional + :param deleted_at_range: Limits the search results to any items deleted within a given + date range. + + Date ranges are defined as comma separated RFC3339 timestamps. + + If the the start date is omitted (`2014-05-17T13:35:01-07:00`), + anything deleted before the end date will be returned. + + If the end date is omitted (`2014-05-15T13:35:01-07:00`), + the current date will be used as the end date instead. + + The `trash_content` parameter needs to be set to `trashed_only`. + + If searching in trash is not performed, then an empty result + is returned. + + Data available from 2023-02-01 onwards., defaults to None + :type deleted_at_range: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'query': to_string(query), + 'scope': to_string(scope), + 'file_extensions': to_string(file_extensions), + 'created_at_range': to_string(created_at_range), + 'updated_at_range': to_string(updated_at_range), + 'size_range': to_string(size_range), + 'owner_user_ids': to_string(owner_user_ids), + 'recent_updater_user_ids': to_string(recent_updater_user_ids), + 'ancestor_folder_ids': to_string(ancestor_folder_ids), + 'content_types': to_string(content_types), + 'type': to_string(type), + 'trash_content': to_string(trash_content), + 'mdfilters': to_string(mdfilters), + 'sort': to_string(sort), + 'direction': to_string(direction), + 'limit': to_string(limit), + 'include_recent_shared_links': to_string(include_recent_shared_links), + 'fields': to_string(fields), + 'offset': to_string(offset), + 'deleted_user_ids': to_string(deleted_user_ids), + 'deleted_at_range': to_string(deleted_at_range), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/search']), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize( + response.data, Union[SearchResults, SearchResultsWithSharedLinks] + ) diff --git a/box_sdk_gen/managers/session_termination.py b/box_sdk_gen/managers/session_termination.py new file mode 100644 index 000000000..756666a0c --- /dev/null +++ b/box_sdk_gen/managers/session_termination.py @@ -0,0 +1,139 @@ +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.session_termination_message import SessionTerminationMessage + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import SerializedData + + +class SessionTerminationManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def terminate_users_sessions( + self, + user_ids: List[str], + user_logins: List[str], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SessionTerminationMessage: + """ + Validates the roles and permissions of the user, + + and creates asynchronous jobs + + + to terminate the user's sessions. + + + Returns the status for the POST request. + + :param user_ids: A list of user IDs. + :type user_ids: List[str] + :param user_logins: A list of user logins. + :type user_logins: List[str] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'user_ids': user_ids, 'user_logins': user_logins} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/terminate_sessions', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SessionTerminationMessage) + + def terminate_groups_sessions( + self, + group_ids: List[str], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SessionTerminationMessage: + """ + Validates the roles and permissions of the group, + + and creates asynchronous jobs + + + to terminate the group's sessions. + + + Returns the status for the POST request. + + :param group_ids: A list of group IDs. + :type group_ids: List[str] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'group_ids': group_ids} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/groups/terminate_sessions', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SessionTerminationMessage) diff --git a/box_sdk_gen/managers/shared_links_app_items.py b/box_sdk_gen/managers/shared_links_app_items.py new file mode 100644 index 000000000..bb9017660 --- /dev/null +++ b/box_sdk_gen/managers/shared_links_app_items.py @@ -0,0 +1,84 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.app_item import AppItem + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class SharedLinksAppItemsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def find_app_item_for_shared_link( + self, boxapi: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AppItem: + """ + Returns the app item represented by a shared link. + + The link can originate from the current enterprise or another. + + :param boxapi: A header containing the shared link and optional password for the + shared link. + + The format for this header is `shared_link=[link]&shared_link_password=[password]`. + :type boxapi: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'boxapi': to_string(boxapi), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shared_items#app_items', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AppItem) diff --git a/box_sdk_gen/managers/shared_links_files.py b/box_sdk_gen/managers/shared_links_files.py new file mode 100644 index 000000000..0cfad2018 --- /dev/null +++ b/box_sdk_gen/managers/shared_links_files.py @@ -0,0 +1,540 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import DateTime + + +class AddShareLinkToFileSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class AddShareLinkToFileSharedLinkPermissionsField(BaseObject): + def __init__( + self, + *, + can_download: Optional[bool] = None, + can_preview: Optional[bool] = None, + can_edit: Optional[bool] = None, + **kwargs + ): + """ + :param can_download: If the shared link allows for downloading of files. + This can only be set when `access` is set to + `open` or `company`., defaults to None + :type can_download: Optional[bool], optional + :param can_preview: If the shared link allows for previewing of files. + This value is always `true`. For shared links on folders + this also applies to any items in the folder., defaults to None + :type can_preview: Optional[bool], optional + :param can_edit: If the shared link allows for editing of files. + This can only be set when `access` is set to + `open` or `company`. + This value can only be `true` is `can_download` is + also `true`., defaults to None + :type can_edit: Optional[bool], optional + """ + super().__init__(**kwargs) + self.can_download = can_download + self.can_preview = can_preview + self.can_edit = can_edit + + +class AddShareLinkToFileSharedLink(BaseObject): + def __init__( + self, + *, + access: Optional[AddShareLinkToFileSharedLinkAccessField] = None, + password: Optional[str] = None, + vanity_name: Optional[str] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[AddShareLinkToFileSharedLinkPermissionsField] = None, + **kwargs + ): + """ + :param access: The level of access for the shared link. This can be + restricted to anyone with the link (`open`), only people + within the company (`company`) and only those who + have been invited to the file (`collaborators`). + + If not set, this field defaults to the access level specified + by the enterprise admin. To create a shared link with this + default setting pass the `shared_link` object with + no `access` field, for example `{ "shared_link": {} }`. + + The `company` access level is only available to paid + accounts., defaults to None + :type access: Optional[AddShareLinkToFileSharedLinkAccessField], optional + :param password: The password required to access the shared link. Set the + password to `null` to remove it. + Passwords must now be at least eight characters + long and include a number, upper case letter, or + a non-numeric or non-alphabetic character. + A password can only be set when `access` is set to `open`., defaults to None + :type password: Optional[str], optional + :param vanity_name: Defines a custom vanity name to use in the shared link URL, + for example `https://app.box.com/v/my-shared-link`. + + Custom URLs should not be used when sharing sensitive content + as vanity URLs are a lot easier to guess than regular shared + links., defaults to None + :type vanity_name: Optional[str], optional + :param unshared_at: The timestamp at which this shared link will + expire. This field can only be set by + users with paid accounts. The value must be greater than the + current date and time., defaults to None + :type unshared_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.access = access + self.password = password + self.vanity_name = vanity_name + self.unshared_at = unshared_at + self.permissions = permissions + + +class UpdateSharedLinkOnFileSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class UpdateSharedLinkOnFileSharedLinkPermissionsField(BaseObject): + def __init__( + self, + *, + can_download: Optional[bool] = None, + can_preview: Optional[bool] = None, + can_edit: Optional[bool] = None, + **kwargs + ): + """ + :param can_download: If the shared link allows for downloading of files. + This can only be set when `access` is set to + `open` or `company`., defaults to None + :type can_download: Optional[bool], optional + :param can_preview: If the shared link allows for previewing of files. + This value is always `true`. For shared links on folders + this also applies to any items in the folder., defaults to None + :type can_preview: Optional[bool], optional + :param can_edit: If the shared link allows for editing of files. + This can only be set when `access` is set to + `open` or `company`. + This value can only be `true` is `can_download` is + also `true`., defaults to None + :type can_edit: Optional[bool], optional + """ + super().__init__(**kwargs) + self.can_download = can_download + self.can_preview = can_preview + self.can_edit = can_edit + + +class UpdateSharedLinkOnFileSharedLink(BaseObject): + def __init__( + self, + *, + access: Optional[UpdateSharedLinkOnFileSharedLinkAccessField] = None, + password: Optional[str] = None, + vanity_name: Optional[str] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[UpdateSharedLinkOnFileSharedLinkPermissionsField] = None, + **kwargs + ): + """ + :param access: The level of access for the shared link. This can be + restricted to anyone with the link (`open`), only people + within the company (`company`) and only those who + have been invited to the folder (`collaborators`). + + If not set, this field defaults to the access level specified + by the enterprise admin. To create a shared link with this + default setting pass the `shared_link` object with + no `access` field, for example `{ "shared_link": {} }`. + + The `company` access level is only available to paid + accounts., defaults to None + :type access: Optional[UpdateSharedLinkOnFileSharedLinkAccessField], optional + :param password: The password required to access the shared link. Set the + password to `null` to remove it. + Passwords must now be at least eight characters + long and include a number, upper case letter, or + a non-numeric or non-alphabetic character. + A password can only be set when `access` is set to `open`., defaults to None + :type password: Optional[str], optional + :param vanity_name: Defines a custom vanity name to use in the shared link URL, + for example `https://app.box.com/v/my-shared-link`. + + Custom URLs should not be used when sharing sensitive content + as vanity URLs are a lot easier to guess than regular shared + links., defaults to None + :type vanity_name: Optional[str], optional + :param unshared_at: The timestamp at which this shared link will + expire. This field can only be set by + users with paid accounts. The value must be greater than the + current date and time., defaults to None + :type unshared_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.access = access + self.password = password + self.vanity_name = vanity_name + self.unshared_at = unshared_at + self.permissions = permissions + + +class RemoveSharedLinkFromFileSharedLink(BaseObject): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +class SharedLinksFilesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def find_file_for_shared_link( + self, + boxapi: str, + *, + fields: Optional[List[str]] = None, + if_none_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileFull: + """ + Returns the file represented by a shared link. + + A shared file can be represented by a shared link, + + + which can originate within the current enterprise or within another. + + + This endpoint allows an application to retrieve information about a + + + shared file when only given a shared link. + + + The `shared_link_permission_options` array field can be returned + + + by requesting it in the `fields` query parameter. + + :param boxapi: A header containing the shared link and optional password for the + shared link. + + The format for this header is as follows: + + `shared_link=[link]&shared_link_password=[password]`. + :type boxapi: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param if_none_match: Ensures an item is only returned if it has changed. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `304 Not Modified` if the item has not + changed since., defaults to None + :type if_none_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params( + { + 'if-none-match': to_string(if_none_match), + 'boxapi': to_string(boxapi), + **extra_headers, + } + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/shared_items'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileFull) + + def get_shared_link_for_file( + self, + file_id: str, + fields: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileFull: + """ + Gets the information for a shared link on a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '#get_shared_link', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileFull) + + def add_share_link_to_file( + self, + file_id: str, + fields: str, + *, + shared_link: Optional[AddShareLinkToFileSharedLink] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileFull: + """ + Adds a shared link to a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param shared_link: The settings for the shared link to create on the file. + Use an empty object (`{}`) to use the default settings for shared + links., defaults to None + :type shared_link: Optional[AddShareLinkToFileSharedLink], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'shared_link': shared_link} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '#add_shared_link', + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileFull) + + def update_shared_link_on_file( + self, + file_id: str, + fields: str, + *, + shared_link: Optional[UpdateSharedLinkOnFileSharedLink] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileFull: + """ + Updates a shared link on a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param shared_link: The settings for the shared link to update., defaults to None + :type shared_link: Optional[UpdateSharedLinkOnFileSharedLink], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'shared_link': shared_link} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '#update_shared_link', + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileFull) + + def remove_shared_link_from_file( + self, + file_id: str, + fields: str, + *, + shared_link: Union[ + Optional[RemoveSharedLinkFromFileSharedLink], NullValue + ] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FileFull: + """ + Removes a shared link from a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param shared_link: By setting this value to `null`, the shared link + is removed from the file., defaults to None + :type shared_link: Union[Optional[RemoveSharedLinkFromFileSharedLink], NullValue], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'shared_link': shared_link} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '#remove_shared_link', + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FileFull) diff --git a/box_sdk_gen/managers/shared_links_folders.py b/box_sdk_gen/managers/shared_links_folders.py new file mode 100644 index 000000000..94be61488 --- /dev/null +++ b/box_sdk_gen/managers/shared_links_folders.py @@ -0,0 +1,546 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import DateTime + + +class AddShareLinkToFolderSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class AddShareLinkToFolderSharedLinkPermissionsField(BaseObject): + def __init__( + self, + *, + can_download: Optional[bool] = None, + can_preview: Optional[bool] = None, + can_edit: Optional[bool] = None, + **kwargs + ): + """ + :param can_download: If the shared link allows for downloading of files. + This can only be set when `access` is set to + `open` or `company`., defaults to None + :type can_download: Optional[bool], optional + :param can_preview: If the shared link allows for previewing of files. + This value is always `true`. For shared links on folders + this also applies to any items in the folder., defaults to None + :type can_preview: Optional[bool], optional + :param can_edit: This value can only be `false` for items + with a `type` of `folder`., defaults to None + :type can_edit: Optional[bool], optional + """ + super().__init__(**kwargs) + self.can_download = can_download + self.can_preview = can_preview + self.can_edit = can_edit + + +class AddShareLinkToFolderSharedLink(BaseObject): + def __init__( + self, + *, + access: Optional[AddShareLinkToFolderSharedLinkAccessField] = None, + password: Optional[str] = None, + vanity_name: Optional[str] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[AddShareLinkToFolderSharedLinkPermissionsField] = None, + **kwargs + ): + """ + :param access: The level of access for the shared link. This can be + restricted to anyone with the link (`open`), only people + within the company (`company`) and only those who + have been invited to the folder (`collaborators`). + + If not set, this field defaults to the access level specified + by the enterprise admin. To create a shared link with this + default setting pass the `shared_link` object with + no `access` field, for example `{ "shared_link": {} }`. + + The `company` access level is only available to paid + accounts., defaults to None + :type access: Optional[AddShareLinkToFolderSharedLinkAccessField], optional + :param password: The password required to access the shared link. Set the + password to `null` to remove it. + Passwords must now be at least eight characters + long and include a number, upper case letter, or + a non-numeric or non-alphabetic character. + A password can only be set when `access` is set to `open`., defaults to None + :type password: Optional[str], optional + :param vanity_name: Defines a custom vanity name to use in the shared link URL, + for example `https://app.box.com/v/my-shared-link`. + + Custom URLs should not be used when sharing sensitive content + as vanity URLs are a lot easier to guess than regular shared + links., defaults to None + :type vanity_name: Optional[str], optional + :param unshared_at: The timestamp at which this shared link will + expire. This field can only be set by + users with paid accounts. The value must be greater than the + current date and time., defaults to None + :type unshared_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.access = access + self.password = password + self.vanity_name = vanity_name + self.unshared_at = unshared_at + self.permissions = permissions + + +class UpdateSharedLinkOnFolderSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class UpdateSharedLinkOnFolderSharedLinkPermissionsField(BaseObject): + def __init__( + self, + *, + can_download: Optional[bool] = None, + can_preview: Optional[bool] = None, + can_edit: Optional[bool] = None, + **kwargs + ): + """ + :param can_download: If the shared link allows for downloading of files. + This can only be set when `access` is set to + `open` or `company`., defaults to None + :type can_download: Optional[bool], optional + :param can_preview: If the shared link allows for previewing of files. + This value is always `true`. For shared links on folders + this also applies to any items in the folder., defaults to None + :type can_preview: Optional[bool], optional + :param can_edit: This value can only be `false` for items + with a `type` of `folder`., defaults to None + :type can_edit: Optional[bool], optional + """ + super().__init__(**kwargs) + self.can_download = can_download + self.can_preview = can_preview + self.can_edit = can_edit + + +class UpdateSharedLinkOnFolderSharedLink(BaseObject): + def __init__( + self, + *, + access: Optional[UpdateSharedLinkOnFolderSharedLinkAccessField] = None, + password: Optional[str] = None, + vanity_name: Optional[str] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[ + UpdateSharedLinkOnFolderSharedLinkPermissionsField + ] = None, + **kwargs + ): + """ + :param access: The level of access for the shared link. This can be + restricted to anyone with the link (`open`), only people + within the company (`company`) and only those who + have been invited to the folder (`collaborators`). + + If not set, this field defaults to the access level specified + by the enterprise admin. To create a shared link with this + default setting pass the `shared_link` object with + no `access` field, for example `{ "shared_link": {} }`. + + The `company` access level is only available to paid + accounts., defaults to None + :type access: Optional[UpdateSharedLinkOnFolderSharedLinkAccessField], optional + :param password: The password required to access the shared link. Set the + password to `null` to remove it. + Passwords must now be at least eight characters + long and include a number, upper case letter, or + a non-numeric or non-alphabetic character. + A password can only be set when `access` is set to `open`., defaults to None + :type password: Optional[str], optional + :param vanity_name: Defines a custom vanity name to use in the shared link URL, + for example `https://app.box.com/v/my-shared-link`. + + Custom URLs should not be used when sharing sensitive content + as vanity URLs are a lot easier to guess than regular shared + links., defaults to None + :type vanity_name: Optional[str], optional + :param unshared_at: The timestamp at which this shared link will + expire. This field can only be set by + users with paid accounts. The value must be greater than the + current date and time., defaults to None + :type unshared_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.access = access + self.password = password + self.vanity_name = vanity_name + self.unshared_at = unshared_at + self.permissions = permissions + + +class RemoveSharedLinkFromFolderSharedLink(BaseObject): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +class SharedLinksFoldersManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def find_folder_for_shared_link( + self, + boxapi: str, + *, + fields: Optional[List[str]] = None, + if_none_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderFull: + """ + Return the folder represented by a shared link. + + A shared folder can be represented by a shared link, + + + which can originate within the current enterprise or within another. + + + This endpoint allows an application to retrieve information about a + + + shared folder when only given a shared link. + + :param boxapi: A header containing the shared link and optional password for the + shared link. + + The format for this header is as follows: + + `shared_link=[link]&shared_link_password=[password]`. + :type boxapi: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param if_none_match: Ensures an item is only returned if it has changed. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `304 Not Modified` if the item has not + changed since., defaults to None + :type if_none_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params( + { + 'if-none-match': to_string(if_none_match), + 'boxapi': to_string(boxapi), + **extra_headers, + } + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shared_items#folders', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderFull) + + def get_shared_link_for_folder( + self, + folder_id: str, + fields: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderFull: + """ + Gets the information for a shared link on a folder. + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '#get_shared_link', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderFull) + + def add_share_link_to_folder( + self, + folder_id: str, + fields: str, + *, + shared_link: Optional[AddShareLinkToFolderSharedLink] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderFull: + """ + Adds a shared link to a folder. + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param shared_link: The settings for the shared link to create on the folder. + + Use an empty object (`{}`) to use the default settings for shared + links., defaults to None + :type shared_link: Optional[AddShareLinkToFolderSharedLink], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'shared_link': shared_link} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '#add_shared_link', + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderFull) + + def update_shared_link_on_folder( + self, + folder_id: str, + fields: str, + *, + shared_link: Optional[UpdateSharedLinkOnFolderSharedLink] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderFull: + """ + Updates a shared link on a folder. + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param shared_link: The settings for the shared link to update., defaults to None + :type shared_link: Optional[UpdateSharedLinkOnFolderSharedLink], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'shared_link': shared_link} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '#update_shared_link', + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderFull) + + def remove_shared_link_from_folder( + self, + folder_id: str, + fields: str, + *, + shared_link: Union[ + Optional[RemoveSharedLinkFromFolderSharedLink], NullValue + ] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderFull: + """ + Removes a shared link from a folder. + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param shared_link: By setting this value to `null`, the shared link + is removed from the folder., defaults to None + :type shared_link: Union[Optional[RemoveSharedLinkFromFolderSharedLink], NullValue], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'shared_link': shared_link} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '#remove_shared_link', + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderFull) diff --git a/box_sdk_gen/managers/shared_links_web_links.py b/box_sdk_gen/managers/shared_links_web_links.py new file mode 100644 index 000000000..97ed09613 --- /dev/null +++ b/box_sdk_gen/managers/shared_links_web_links.py @@ -0,0 +1,508 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import DateTime + + +class AddShareLinkToWebLinkSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class AddShareLinkToWebLinkSharedLinkPermissionsField(BaseObject): + def __init__( + self, + *, + can_download: Optional[bool] = None, + can_preview: Optional[bool] = None, + can_edit: Optional[bool] = None, + **kwargs + ): + """ + :param can_download: If the shared link allows for downloading of files. + This can only be set when `access` is set to + `open` or `company`., defaults to None + :type can_download: Optional[bool], optional + :param can_preview: If the shared link allows for previewing of files. + This value is always `true`. For shared links on folders + this also applies to any items in the folder., defaults to None + :type can_preview: Optional[bool], optional + :param can_edit: This value can only be `true` is `type` is `file`., defaults to None + :type can_edit: Optional[bool], optional + """ + super().__init__(**kwargs) + self.can_download = can_download + self.can_preview = can_preview + self.can_edit = can_edit + + +class AddShareLinkToWebLinkSharedLink(BaseObject): + def __init__( + self, + *, + access: Optional[AddShareLinkToWebLinkSharedLinkAccessField] = None, + password: Optional[str] = None, + vanity_name: Optional[str] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[AddShareLinkToWebLinkSharedLinkPermissionsField] = None, + **kwargs + ): + """ + :param access: The level of access for the shared link. This can be + restricted to anyone with the link (`open`), only people + within the company (`company`) and only those who + have been invited to the file (`collaborators`). + + If not set, this field defaults to the access level specified + by the enterprise admin. To create a shared link with this + default setting pass the `shared_link` object with + no `access` field, for example `{ "shared_link": {} }`. + + The `company` access level is only available to paid + accounts., defaults to None + :type access: Optional[AddShareLinkToWebLinkSharedLinkAccessField], optional + :param password: The password required to access the shared link. Set the + password to `null` to remove it. + Passwords must now be at least eight characters + long and include a number, upper case letter, or + a non-numeric or non-alphabetic character. + A password can only be set when `access` is set to `open`., defaults to None + :type password: Optional[str], optional + :param vanity_name: Defines a custom vanity name to use in the shared link URL, + for example `https://app.box.com/v/my-shared-link`. + + Custom URLs should not be used when sharing sensitive content + as vanity URLs are a lot easier to guess than regular shared + links., defaults to None + :type vanity_name: Optional[str], optional + :param unshared_at: The timestamp at which this shared link will + expire. This field can only be set by + users with paid accounts. The value must be greater than the + current date and time., defaults to None + :type unshared_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.access = access + self.password = password + self.vanity_name = vanity_name + self.unshared_at = unshared_at + self.permissions = permissions + + +class UpdateSharedLinkOnWebLinkSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class UpdateSharedLinkOnWebLinkSharedLinkPermissionsField(BaseObject): + def __init__( + self, + *, + can_download: Optional[bool] = None, + can_preview: Optional[bool] = None, + can_edit: Optional[bool] = None, + **kwargs + ): + """ + :param can_download: If the shared link allows for downloading of files. + This can only be set when `access` is set to + `open` or `company`., defaults to None + :type can_download: Optional[bool], optional + :param can_preview: If the shared link allows for previewing of files. + This value is always `true`. For shared links on folders + this also applies to any items in the folder., defaults to None + :type can_preview: Optional[bool], optional + :param can_edit: This value can only be `true` is `type` is `file`., defaults to None + :type can_edit: Optional[bool], optional + """ + super().__init__(**kwargs) + self.can_download = can_download + self.can_preview = can_preview + self.can_edit = can_edit + + +class UpdateSharedLinkOnWebLinkSharedLink(BaseObject): + def __init__( + self, + *, + access: Optional[UpdateSharedLinkOnWebLinkSharedLinkAccessField] = None, + password: Optional[str] = None, + vanity_name: Optional[str] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[ + UpdateSharedLinkOnWebLinkSharedLinkPermissionsField + ] = None, + **kwargs + ): + """ + :param access: The level of access for the shared link. This can be + restricted to anyone with the link (`open`), only people + within the company (`company`) and only those who + have been invited to the folder (`collaborators`). + + If not set, this field defaults to the access level specified + by the enterprise admin. To create a shared link with this + default setting pass the `shared_link` object with + no `access` field, for example `{ "shared_link": {} }`. + + The `company` access level is only available to paid + accounts., defaults to None + :type access: Optional[UpdateSharedLinkOnWebLinkSharedLinkAccessField], optional + :param password: The password required to access the shared link. Set the + password to `null` to remove it. + Passwords must now be at least eight characters + long and include a number, upper case letter, or + a non-numeric or non-alphabetic character. + A password can only be set when `access` is set to `open`., defaults to None + :type password: Optional[str], optional + :param vanity_name: Defines a custom vanity name to use in the shared link URL, + for example `https://app.box.com/v/my-shared-link`. + + Custom URLs should not be used when sharing sensitive content + as vanity URLs are a lot easier to guess than regular shared + links., defaults to None + :type vanity_name: Optional[str], optional + :param unshared_at: The timestamp at which this shared link will + expire. This field can only be set by + users with paid accounts. The value must be greater than the + current date and time., defaults to None + :type unshared_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.access = access + self.password = password + self.vanity_name = vanity_name + self.unshared_at = unshared_at + self.permissions = permissions + + +class RemoveSharedLinkFromWebLinkSharedLink(BaseObject): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +class SharedLinksWebLinksManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def find_web_link_for_shared_link( + self, + boxapi: str, + *, + fields: Optional[List[str]] = None, + if_none_match: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> WebLink: + """ + Returns the web link represented by a shared link. + + A shared web link can be represented by a shared link, + + + which can originate within the current enterprise or within another. + + + This endpoint allows an application to retrieve information about a + + + shared web link when only given a shared link. + + :param boxapi: A header containing the shared link and optional password for the + shared link. + + The format for this header is as follows: + + `shared_link=[link]&shared_link_password=[password]`. + :type boxapi: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param if_none_match: Ensures an item is only returned if it has changed. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `304 Not Modified` if the item has not + changed since., defaults to None + :type if_none_match: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params( + { + 'if-none-match': to_string(if_none_match), + 'boxapi': to_string(boxapi), + **extra_headers, + } + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shared_items#web_links', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, WebLink) + + def get_shared_link_for_web_link( + self, + web_link_id: str, + fields: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> WebLink: + """ + Gets the information for a shared link on a web link. + :param web_link_id: The ID of the web link. + Example: "12345" + :type web_link_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/web_links/', + to_string(web_link_id), + '#get_shared_link', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, WebLink) + + def add_share_link_to_web_link( + self, + web_link_id: str, + fields: str, + *, + shared_link: Optional[AddShareLinkToWebLinkSharedLink] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> WebLink: + """ + Adds a shared link to a web link. + :param web_link_id: The ID of the web link. + Example: "12345" + :type web_link_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param shared_link: The settings for the shared link to create on the web link. + + Use an empty object (`{}`) to use the default settings for shared + links., defaults to None + :type shared_link: Optional[AddShareLinkToWebLinkSharedLink], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'shared_link': shared_link} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/web_links/', + to_string(web_link_id), + '#add_shared_link', + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, WebLink) + + def update_shared_link_on_web_link( + self, + web_link_id: str, + fields: str, + *, + shared_link: Optional[UpdateSharedLinkOnWebLinkSharedLink] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> WebLink: + """ + Updates a shared link on a web link. + :param web_link_id: The ID of the web link. + Example: "12345" + :type web_link_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param shared_link: The settings for the shared link to update., defaults to None + :type shared_link: Optional[UpdateSharedLinkOnWebLinkSharedLink], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'shared_link': shared_link} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/web_links/', + to_string(web_link_id), + '#update_shared_link', + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, WebLink) + + def remove_shared_link_from_web_link( + self, + web_link_id: str, + fields: str, + *, + shared_link: Union[ + Optional[RemoveSharedLinkFromWebLinkSharedLink], NullValue + ] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> WebLink: + """ + Removes a shared link from a web link. + :param web_link_id: The ID of the web link. + Example: "12345" + :type web_link_id: str + :param fields: Explicitly request the `shared_link` fields + to be returned for this item. + :type fields: str + :param shared_link: By setting this value to `null`, the shared link + is removed from the web link., defaults to None + :type shared_link: Union[Optional[RemoveSharedLinkFromWebLinkSharedLink], NullValue], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'shared_link': shared_link} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/web_links/', + to_string(web_link_id), + '#remove_shared_link', + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, WebLink) diff --git a/box_sdk_gen/managers/shield_information_barrier_reports.py b/box_sdk_gen/managers/shield_information_barrier_reports.py new file mode 100644 index 000000000..d25c0bce8 --- /dev/null +++ b/box_sdk_gen/managers/shield_information_barrier_reports.py @@ -0,0 +1,183 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.schemas.shield_information_barrier_reports import ( + ShieldInformationBarrierReports, +) + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.shield_information_barrier_report import ( + ShieldInformationBarrierReport, +) + +from box_sdk_gen.schemas.shield_information_barrier_reference import ( + ShieldInformationBarrierReference, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class ShieldInformationBarrierReportsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_shield_information_barrier_reports( + self, + shield_information_barrier_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierReports: + """ + Lists shield information barrier reports. + :param shield_information_barrier_id: The ID of the shield information barrier. + :type shield_information_barrier_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'shield_information_barrier_id': to_string( + shield_information_barrier_id + ), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_reports', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierReports) + + def create_shield_information_barrier_report( + self, + *, + shield_information_barrier: Optional[ShieldInformationBarrierBase] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierReport: + """ + Creates a shield information barrier report for a given barrier. + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'shield_information_barrier': shield_information_barrier} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_reports', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierReport) + + def get_shield_information_barrier_report_by_id( + self, + shield_information_barrier_report_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierReport: + """ + Retrieves a shield information barrier report by its ID. + :param shield_information_barrier_report_id: The ID of the shield information barrier Report. + Example: "3423" + :type shield_information_barrier_report_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_reports/', + to_string(shield_information_barrier_report_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierReport) diff --git a/box_sdk_gen/managers/shield_information_barrier_segment_members.py b/box_sdk_gen/managers/shield_information_barrier_segment_members.py new file mode 100644 index 000000000..8580d3d54 --- /dev/null +++ b/box_sdk_gen/managers/shield_information_barrier_segment_members.py @@ -0,0 +1,282 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.shield_information_barrier_segment_member import ( + ShieldInformationBarrierSegmentMember, +) + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.shield_information_barrier_segment_members import ( + ShieldInformationBarrierSegmentMembers, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateShieldInformationBarrierSegmentMemberType(str, Enum): + SHIELD_INFORMATION_BARRIER_SEGMENT_MEMBER = ( + 'shield_information_barrier_segment_member' + ) + + +class CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentTypeField( + str, Enum +): + SHIELD_INFORMATION_BARRIER_SEGMENT = 'shield_information_barrier_segment' + + +class CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegment( + BaseObject +): + _discriminator = 'type', {'shield_information_barrier_segment'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ + CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentTypeField + ] = None, + **kwargs + ): + """ + :param id: The ID reference of the + requesting shield information barrier segment., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield barrier segment for this member., defaults to None + :type type: Optional[CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class ShieldInformationBarrierSegmentMembersManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_shield_information_barrier_segment_member_by_id( + self, + shield_information_barrier_segment_member_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierSegmentMember: + """ + Retrieves a shield information barrier + + segment member by its ID. + + :param shield_information_barrier_segment_member_id: The ID of the shield information barrier segment Member. + Example: "7815" + :type shield_information_barrier_segment_member_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segment_members/', + to_string(shield_information_barrier_segment_member_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierSegmentMember) + + def delete_shield_information_barrier_segment_member_by_id( + self, + shield_information_barrier_segment_member_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a shield information barrier + + segment member based on provided ID. + + :param shield_information_barrier_segment_member_id: The ID of the shield information barrier segment Member. + Example: "7815" + :type shield_information_barrier_segment_member_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segment_members/', + to_string(shield_information_barrier_segment_member_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_shield_information_barrier_segment_members( + self, + shield_information_barrier_segment_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierSegmentMembers: + """ + Lists shield information barrier segment members + + based on provided segment IDs. + + :param shield_information_barrier_segment_id: The ID of the shield information barrier segment. + :type shield_information_barrier_segment_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'shield_information_barrier_segment_id': to_string( + shield_information_barrier_segment_id + ), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segment_members', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierSegmentMembers) + + def create_shield_information_barrier_segment_member( + self, + shield_information_barrier_segment: CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegment, + user: UserBase, + *, + type: Optional[CreateShieldInformationBarrierSegmentMemberType] = None, + shield_information_barrier: Optional[ShieldInformationBarrierBase] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierSegmentMember: + """ + Creates a new shield information barrier segment member. + :param shield_information_barrier_segment: The `type` and `id` of the + requested shield information barrier segment. + :type shield_information_barrier_segment: CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegment + :param user: User to which restriction will be applied. + :type user: UserBase + :param type: A type of the shield barrier segment member., defaults to None + :type type: Optional[CreateShieldInformationBarrierSegmentMemberType], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'type': type, + 'shield_information_barrier': shield_information_barrier, + 'shield_information_barrier_segment': shield_information_barrier_segment, + 'user': user, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segment_members', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierSegmentMember) diff --git a/box_sdk_gen/managers/shield_information_barrier_segment_restrictions.py b/box_sdk_gen/managers/shield_information_barrier_segment_restrictions.py new file mode 100644 index 000000000..e499efbe3 --- /dev/null +++ b/box_sdk_gen/managers/shield_information_barrier_segment_restrictions.py @@ -0,0 +1,316 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction import ( + ShieldInformationBarrierSegmentRestriction, +) + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.shield_information_barrier_segment_restrictions import ( + ShieldInformationBarrierSegmentRestrictions, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateShieldInformationBarrierSegmentRestrictionType(str, Enum): + SHIELD_INFORMATION_BARRIER_SEGMENT_RESTRICTION = ( + 'shield_information_barrier_segment_restriction' + ) + + +class CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegmentTypeField( + str, Enum +): + SHIELD_INFORMATION_BARRIER_SEGMENT = 'shield_information_barrier_segment' + + +class CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegment( + BaseObject +): + _discriminator = 'type', {'shield_information_barrier_segment'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ + CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegmentTypeField + ] = None, + **kwargs + ): + """ + :param id: The ID reference of the requesting + shield information barrier segment., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield barrier segment for this member., defaults to None + :type type: Optional[CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegmentTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CreateShieldInformationBarrierSegmentRestrictionRestrictedSegmentTypeField( + str, Enum +): + SHIELD_INFORMATION_BARRIER_SEGMENT = 'shield_information_barrier_segment' + + +class CreateShieldInformationBarrierSegmentRestrictionRestrictedSegment(BaseObject): + _discriminator = 'type', {'shield_information_barrier_segment'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ + CreateShieldInformationBarrierSegmentRestrictionRestrictedSegmentTypeField + ] = None, + **kwargs + ): + """ + :param id: The ID reference of the restricted + shield information barrier segment., defaults to None + :type id: Optional[str], optional + :param type: The type of the restricted shield + information barrier segment., defaults to None + :type type: Optional[CreateShieldInformationBarrierSegmentRestrictionRestrictedSegmentTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class ShieldInformationBarrierSegmentRestrictionsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_shield_information_barrier_segment_restriction_by_id( + self, + shield_information_barrier_segment_restriction_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierSegmentRestriction: + """ + Retrieves a shield information barrier segment + + restriction based on provided ID. + + :param shield_information_barrier_segment_restriction_id: The ID of the shield information barrier segment Restriction. + Example: "4563" + :type shield_information_barrier_segment_restriction_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segment_restrictions/', + to_string(shield_information_barrier_segment_restriction_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierSegmentRestriction) + + def delete_shield_information_barrier_segment_restriction_by_id( + self, + shield_information_barrier_segment_restriction_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Delete shield information barrier segment restriction + + based on provided ID. + + :param shield_information_barrier_segment_restriction_id: The ID of the shield information barrier segment Restriction. + Example: "4563" + :type shield_information_barrier_segment_restriction_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segment_restrictions/', + to_string(shield_information_barrier_segment_restriction_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_shield_information_barrier_segment_restrictions( + self, + shield_information_barrier_segment_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierSegmentRestrictions: + """ + Lists shield information barrier segment restrictions + + based on provided segment ID. + + :param shield_information_barrier_segment_id: The ID of the shield information barrier segment. + :type shield_information_barrier_segment_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'shield_information_barrier_segment_id': to_string( + shield_information_barrier_segment_id + ), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segment_restrictions', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierSegmentRestrictions) + + def create_shield_information_barrier_segment_restriction( + self, + shield_information_barrier_segment: CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegment, + restricted_segment: CreateShieldInformationBarrierSegmentRestrictionRestrictedSegment, + *, + type: CreateShieldInformationBarrierSegmentRestrictionType = CreateShieldInformationBarrierSegmentRestrictionType.SHIELD_INFORMATION_BARRIER_SEGMENT_RESTRICTION, + shield_information_barrier: Optional[ShieldInformationBarrierBase] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierSegmentRestriction: + """ + Creates a shield information barrier + + segment restriction object. + + :param shield_information_barrier_segment: The `type` and `id` of the requested + shield information barrier segment. + :type shield_information_barrier_segment: CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegment + :param restricted_segment: The `type` and `id` of the restricted + shield information barrier segment. + :type restricted_segment: CreateShieldInformationBarrierSegmentRestrictionRestrictedSegment + :param type: The type of the shield barrier segment + restriction for this member., defaults to CreateShieldInformationBarrierSegmentRestrictionType.SHIELD_INFORMATION_BARRIER_SEGMENT_RESTRICTION + :type type: CreateShieldInformationBarrierSegmentRestrictionType, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'type': type, + 'shield_information_barrier': shield_information_barrier, + 'shield_information_barrier_segment': shield_information_barrier_segment, + 'restricted_segment': restricted_segment, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segment_restrictions', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierSegmentRestriction) diff --git a/box_sdk_gen/managers/shield_information_barrier_segments.py b/box_sdk_gen/managers/shield_information_barrier_segments.py new file mode 100644 index 000000000..d258edeca --- /dev/null +++ b/box_sdk_gen/managers/shield_information_barrier_segments.py @@ -0,0 +1,279 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.shield_information_barrier_segment import ( + ShieldInformationBarrierSegment, +) + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.shield_information_barrier_segments import ( + ShieldInformationBarrierSegments, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class ShieldInformationBarrierSegmentsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_shield_information_barrier_segment_by_id( + self, + shield_information_barrier_segment_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierSegment: + """ + Retrieves shield information barrier segment based on provided ID.. + :param shield_information_barrier_segment_id: The ID of the shield information barrier segment. + Example: "3423" + :type shield_information_barrier_segment_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segments/', + to_string(shield_information_barrier_segment_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierSegment) + + def delete_shield_information_barrier_segment_by_id( + self, + shield_information_barrier_segment_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes the shield information barrier segment + + based on provided ID. + + :param shield_information_barrier_segment_id: The ID of the shield information barrier segment. + Example: "3423" + :type shield_information_barrier_segment_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segments/', + to_string(shield_information_barrier_segment_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def update_shield_information_barrier_segment_by_id( + self, + shield_information_barrier_segment_id: str, + *, + name: Optional[str] = None, + description: Union[Optional[str], NullValue] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierSegment: + """ + Updates the shield information barrier segment based on provided ID.. + :param shield_information_barrier_segment_id: The ID of the shield information barrier segment. + Example: "3423" + :type shield_information_barrier_segment_id: str + :param name: The updated name for the shield information barrier segment., defaults to None + :type name: Optional[str], optional + :param description: The updated description for + the shield information barrier segment., defaults to None + :type description: Union[Optional[str], NullValue], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'name': name, 'description': description} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segments/', + to_string(shield_information_barrier_segment_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierSegment) + + def get_shield_information_barrier_segments( + self, + shield_information_barrier_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierSegments: + """ + Retrieves a list of shield information barrier segment objects + + for the specified Information Barrier ID. + + :param shield_information_barrier_id: The ID of the shield information barrier. + :type shield_information_barrier_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'shield_information_barrier_id': to_string( + shield_information_barrier_id + ), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segments', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierSegments) + + def create_shield_information_barrier_segment( + self, + shield_information_barrier: ShieldInformationBarrierBase, + name: str, + *, + description: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrierSegment: + """ + Creates a shield information barrier segment. + :param name: Name of the shield information barrier segment. + :type name: str + :param description: Description of the shield information barrier segment., defaults to None + :type description: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'shield_information_barrier': shield_information_barrier, + 'name': name, + 'description': description, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barrier_segments', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrierSegment) diff --git a/box_sdk_gen/managers/shield_information_barriers.py b/box_sdk_gen/managers/shield_information_barriers.py new file mode 100644 index 000000000..b3784dae2 --- /dev/null +++ b/box_sdk_gen/managers/shield_information_barriers.py @@ -0,0 +1,219 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.shield_information_barrier import ShieldInformationBarrier + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.shield_information_barriers import ShieldInformationBarriers + +from box_sdk_gen.schemas.enterprise_base import EnterpriseBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class UpdateShieldInformationBarrierStatusStatus(str, Enum): + PENDING = 'pending' + DISABLED = 'disabled' + + +class ShieldInformationBarriersManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_shield_information_barrier_by_id( + self, + shield_information_barrier_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrier: + """ + Get shield information barrier based on provided ID. + :param shield_information_barrier_id: The ID of the shield information barrier. + Example: "1910967" + :type shield_information_barrier_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barriers/', + to_string(shield_information_barrier_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrier) + + def update_shield_information_barrier_status( + self, + id: str, + status: UpdateShieldInformationBarrierStatusStatus, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrier: + """ + Change status of shield information barrier with the specified ID. + :param id: The ID of the shield information barrier. + :type id: str + :param status: The desired status for the shield information barrier. + :type status: UpdateShieldInformationBarrierStatusStatus + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'id': id, 'status': status} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barriers/change_status', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrier) + + def get_shield_information_barriers( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarriers: + """ + Retrieves a list of shield information barrier objects + + for the enterprise of JWT. + + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barriers', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarriers) + + def create_shield_information_barrier( + self, + enterprise: EnterpriseBase, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldInformationBarrier: + """ + Creates a shield information barrier to + + separate individuals/groups within the same + + + firm and prevents confidential information passing between them. + + :param enterprise: The `type` and `id` of enterprise this barrier is under. + :type enterprise: EnterpriseBase + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'enterprise': enterprise} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_information_barriers', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldInformationBarrier) diff --git a/box_sdk_gen/managers/shield_lists.py b/box_sdk_gen/managers/shield_lists.py new file mode 100644 index 000000000..ec363b9a3 --- /dev/null +++ b/box_sdk_gen/managers/shield_lists.py @@ -0,0 +1,300 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_country_v2025_r0 import ( + ShieldListContentCountryV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_domain_v2025_r0 import ( + ShieldListContentDomainV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_email_v2025_r0 import ( + ShieldListContentEmailV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_ip_v2025_r0 import ( + ShieldListContentIpV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_request_v2025_r0 import ( + ShieldListContentRequestV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_lists_v2025_r0 import ShieldListsV2025R0 + +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import ( + BoxVersionHeaderV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_v2025_r0 import ShieldListV2025R0 + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import ClientErrorV2025R0 + +from box_sdk_gen.schemas.v2025_r0.shield_lists_create_v2025_r0 import ( + ShieldListsCreateV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_lists_update_v2025_r0 import ( + ShieldListsUpdateV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class ShieldListsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_shield_lists_v2025_r0( + self, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldListsV2025R0: + """ + Retrieves all shield lists in the enterprise. + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/shield_lists'] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldListsV2025R0) + + def create_shield_list_v2025_r0( + self, + name: str, + content: ShieldListContentRequestV2025R0, + *, + description: Optional[str] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldListV2025R0: + """ + Creates a shield list. + :param name: The name of the shield list. + :type name: str + :param description: Optional description of Shield List., defaults to None + :type description: Optional[str], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'name': name, + 'description': description, + 'content': content, + } + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/shield_lists'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldListV2025R0) + + def get_shield_list_by_id_v2025_r0( + self, + shield_list_id: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldListV2025R0: + """ + Retrieves a single shield list by its ID. + :param shield_list_id: The unique identifier that represents a shield list. + The ID for any Shield List can be determined by the response from the endpoint + fetching all shield lists for the enterprise. + Example: "90fb0e17-c332-40ed-b4f9-fa8908fbbb24 " + :type shield_list_id: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_lists/', + to_string(shield_list_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldListV2025R0) + + def delete_shield_list_by_id_v2025_r0( + self, + shield_list_id: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Delete a single shield list by its ID. + :param shield_list_id: The unique identifier that represents a shield list. + The ID for any Shield List can be determined by the response from the endpoint + fetching all shield lists for the enterprise. + Example: "90fb0e17-c332-40ed-b4f9-fa8908fbbb24 " + :type shield_list_id: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_lists/', + to_string(shield_list_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def update_shield_list_by_id_v2025_r0( + self, + shield_list_id: str, + name: str, + content: ShieldListContentRequestV2025R0, + *, + description: Optional[str] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ShieldListV2025R0: + """ + Updates a shield list. + :param shield_list_id: The unique identifier that represents a shield list. + The ID for any Shield List can be determined by the response from the endpoint + fetching all shield lists for the enterprise. + Example: "90fb0e17-c332-40ed-b4f9-fa8908fbbb24 " + :type shield_list_id: str + :param name: The name of the shield list. + :type name: str + :param description: Optional description of Shield List., defaults to None + :type description: Optional[str], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'name': name, + 'description': description, + 'content': content, + } + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/shield_lists/', + to_string(shield_list_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ShieldListV2025R0) diff --git a/box_sdk_gen/managers/sign_requests.py b/box_sdk_gen/managers/sign_requests.py new file mode 100644 index 000000000..bef15282c --- /dev/null +++ b/box_sdk_gen/managers/sign_requests.py @@ -0,0 +1,348 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from typing import List + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.file_base import FileBase + +from box_sdk_gen.schemas.sign_request_create_signer import SignRequestCreateSigner + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.sign_request_prefill_tag import SignRequestPrefillTag + +from box_sdk_gen.schemas.sign_request import SignRequest + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.sign_requests import SignRequests + +from box_sdk_gen.schemas.sign_request_create_request import SignRequestCreateRequest + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateSignRequestSignatureColor(str, Enum): + BLUE = 'blue' + BLACK = 'black' + RED = 'red' + + +class SignRequestsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def cancel_sign_request( + self, + sign_request_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SignRequest: + """ + Cancels a sign request. + :param sign_request_id: The ID of the signature request. + Example: "33243242" + :type sign_request_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/sign_requests/', + to_string(sign_request_id), + '/cancel', + ] + ), + method='POST', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SignRequest) + + def resend_sign_request( + self, + sign_request_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Resends a signature request email to all outstanding signers. + :param sign_request_id: The ID of the signature request. + Example: "33243242" + :type sign_request_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/sign_requests/', + to_string(sign_request_id), + '/resend', + ] + ), + method='POST', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_sign_request_by_id( + self, + sign_request_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SignRequest: + """ + Gets a sign request by ID. + :param sign_request_id: The ID of the signature request. + Example: "33243242" + :type sign_request_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/sign_requests/', + to_string(sign_request_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SignRequest) + + def get_sign_requests( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + senders: Optional[List[str]] = None, + shared_requests: Optional[bool] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SignRequests: + """ + Gets signature requests created by a user. If the `sign_files` and/or + + `parent_folder` are deleted, the signature request will not return in the list. + + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param senders: A list of sender emails to filter the signature requests by sender. + If provided, `shared_requests` must be set to `true`., defaults to None + :type senders: Optional[List[str]], optional + :param shared_requests: If set to `true`, only includes requests that user is not an owner, + but user is a collaborator. Collaborator access is determined by the + user access level of the sign files of the request. + Default is `false`. Must be set to `true` if `senders` are provided., defaults to None + :type shared_requests: Optional[bool], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'marker': to_string(marker), + 'limit': to_string(limit), + 'senders': to_string(senders), + 'shared_requests': to_string(shared_requests), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/sign_requests'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SignRequests) + + def create_sign_request( + self, + signers: List[SignRequestCreateSigner], + *, + source_files: Union[Optional[List[FileBase]], NullValue] = None, + signature_color: Union[ + Optional[CreateSignRequestSignatureColor], NullValue + ] = None, + parent_folder: Optional[FolderMini] = None, + is_document_preparation_needed: Optional[bool] = None, + redirect_url: Union[Optional[str], NullValue] = None, + declined_redirect_url: Union[Optional[str], NullValue] = None, + are_text_signatures_enabled: Optional[bool] = None, + email_subject: Union[Optional[str], NullValue] = None, + email_message: Union[Optional[str], NullValue] = None, + are_reminders_enabled: Optional[bool] = None, + name: Optional[str] = None, + prefill_tags: Optional[List[SignRequestPrefillTag]] = None, + days_valid: Union[Optional[int], NullValue] = None, + external_id: Union[Optional[str], NullValue] = None, + template_id: Union[Optional[str], NullValue] = None, + external_system_name: Union[Optional[str], NullValue] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SignRequest: + """ + Creates a signature request. This involves preparing a document for signing and + + sending the signature request to signers. + + :param signers: Array of signers for the signature request. 35 is the + max number of signers permitted. + + **Note**: It may happen that some signers belong to conflicting [segments](r://shield-information-barrier-segment-member) (user groups). + This means that due to the security policies, users are assigned to segments to prevent exchanges or communication that could lead to ethical conflicts. + In such a case, an attempt to send the sign request will result in an error. + + Read more about [segments and ethical walls](https://support.box.com/hc/en-us/articles/9920431507603-Understanding-Information-Barriers#h_01GFVJEHQA06N7XEZ4GCZ9GFAQ). + :type signers: List[SignRequestCreateSigner] + :param source_files: List of files to create a signing document from. This is currently limited to ten files. Only the ID and type fields are required for each file., defaults to None + :type source_files: Union[Optional[List[FileBase]], NullValue], optional + :param signature_color: Force a specific color for the signature (blue, black, or red)., defaults to None + :type signature_color: Union[Optional[CreateSignRequestSignatureColor], NullValue], optional + :param is_document_preparation_needed: Indicates if the sender should receive a `prepare_url` in the response to complete document preparation using the UI., defaults to None + :type is_document_preparation_needed: Optional[bool], optional + :param redirect_url: When specified, the signature request will be redirected to this url when a document is signed., defaults to None + :type redirect_url: Union[Optional[str], NullValue], optional + :param declined_redirect_url: The uri that a signer will be redirected to after declining to sign a document., defaults to None + :type declined_redirect_url: Union[Optional[str], NullValue], optional + :param are_text_signatures_enabled: Disables the usage of signatures generated by typing (text)., defaults to None + :type are_text_signatures_enabled: Optional[bool], optional + :param email_subject: Subject of sign request email. This is cleaned by sign request. If this field is not passed, a default subject will be used., defaults to None + :type email_subject: Union[Optional[str], NullValue], optional + :param email_message: Message to include in sign request email. The field is cleaned through sanitization of specific characters. However, some html tags are allowed. Links included in the message are also converted to hyperlinks in the email. The message may contain the following html tags including `a`, `abbr`, `acronym`, `b`, `blockquote`, `code`, `em`, `i`, `ul`, `li`, `ol`, and `strong`. Be aware that when the text to html ratio is too high, the email may end up in spam filters. Custom styles on these tags are not allowed. If this field is not passed, a default message will be used., defaults to None + :type email_message: Union[Optional[str], NullValue], optional + :param are_reminders_enabled: Reminds signers to sign a document on day 3, 8, 13 and 18. Reminders are only sent to outstanding signers., defaults to None + :type are_reminders_enabled: Optional[bool], optional + :param name: Name of the signature request., defaults to None + :type name: Optional[str], optional + :param prefill_tags: When a document contains sign-related tags in the content, you can prefill them using this `prefill_tags` by referencing the 'id' of the tag as the `external_id` field of the prefill tag., defaults to None + :type prefill_tags: Optional[List[SignRequestPrefillTag]], optional + :param days_valid: Set the number of days after which the created signature request will automatically expire if not completed. By default, we do not apply any expiration date on signature requests, and the signature request does not expire., defaults to None + :type days_valid: Union[Optional[int], NullValue], optional + :param external_id: This can be used to reference an ID in an external system that the sign request is related to., defaults to None + :type external_id: Union[Optional[str], NullValue], optional + :param template_id: When a signature request is created from a template this field will indicate the id of that template., defaults to None + :type template_id: Union[Optional[str], NullValue], optional + :param external_system_name: Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`., defaults to None + :type external_system_name: Union[Optional[str], NullValue], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'source_files': source_files, + 'signature_color': signature_color, + 'signers': signers, + 'parent_folder': parent_folder, + 'is_document_preparation_needed': is_document_preparation_needed, + 'redirect_url': redirect_url, + 'declined_redirect_url': declined_redirect_url, + 'are_text_signatures_enabled': are_text_signatures_enabled, + 'email_subject': email_subject, + 'email_message': email_message, + 'are_reminders_enabled': are_reminders_enabled, + 'name': name, + 'prefill_tags': prefill_tags, + 'days_valid': days_valid, + 'external_id': external_id, + 'template_id': template_id, + 'external_system_name': external_system_name, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/sign_requests'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SignRequest) diff --git a/box_sdk_gen/managers/sign_templates.py b/box_sdk_gen/managers/sign_templates.py new file mode 100644 index 000000000..531c1f0c6 --- /dev/null +++ b/box_sdk_gen/managers/sign_templates.py @@ -0,0 +1,123 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.sign_templates import SignTemplates + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.sign_template import SignTemplate + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class SignTemplatesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_sign_templates( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SignTemplates: + """ + Gets Box Sign templates created by a user. + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/sign_templates'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SignTemplates) + + def get_sign_template_by_id( + self, + template_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SignTemplate: + """ + Fetches details of a specific Box Sign template. + :param template_id: The ID of a Box Sign template. + Example: "123075213-7d117509-8f05-42e4-a5ef-5190a319d41d" + :type template_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/sign_templates/', + to_string(template_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SignTemplate) diff --git a/box_sdk_gen/managers/skills.py b/box_sdk_gen/managers/skills.py new file mode 100644 index 000000000..26ae00b73 --- /dev/null +++ b/box_sdk_gen/managers/skills.py @@ -0,0 +1,442 @@ +from enum import Enum + +from typing import Optional + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.skill_cards_metadata import SkillCardsMetadata + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCard + +from box_sdk_gen.schemas.timeline_skill_card import TimelineSkillCard + +from box_sdk_gen.schemas.transcript_skill_card import TranscriptSkillCard + +from box_sdk_gen.schemas.status_skill_card import StatusSkillCard + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class UpdateBoxSkillCardsOnFileRequestBodyOpField(str, Enum): + REPLACE = 'replace' + + +class UpdateBoxSkillCardsOnFileRequestBody(BaseObject): + def __init__( + self, + *, + op: Optional[UpdateBoxSkillCardsOnFileRequestBodyOpField] = None, + path: Optional[str] = None, + value: Optional[ + Union[ + KeywordSkillCard, + TimelineSkillCard, + TranscriptSkillCard, + StatusSkillCard, + ] + ] = None, + **kwargs + ): + """ + :param op: The value will always be `replace`., defaults to None + :type op: Optional[UpdateBoxSkillCardsOnFileRequestBodyOpField], optional + :param path: The JSON Path that represents the card to replace. In most cases + this will be in the format `/cards/{index}` where `index` is the + zero-indexed position of the card in the list of cards., defaults to None + :type path: Optional[str], optional + """ + super().__init__(**kwargs) + self.op = op + self.path = path + self.value = value + + +class UpdateAllSkillCardsOnFileStatus(str, Enum): + INVOKED = 'invoked' + PROCESSING = 'processing' + SUCCESS = 'success' + TRANSIENT_FAILURE = 'transient_failure' + PERMANENT_FAILURE = 'permanent_failure' + + +class UpdateAllSkillCardsOnFileMetadata(BaseObject): + def __init__( + self, + *, + cards: Optional[ + List[ + Union[ + KeywordSkillCard, + TimelineSkillCard, + TranscriptSkillCard, + StatusSkillCard, + ] + ] + ] = None, + **kwargs + ): + """ + :param cards: A list of Box Skill cards to apply to this file., defaults to None + :type cards: Optional[List[Union[KeywordSkillCard, TimelineSkillCard, TranscriptSkillCard, StatusSkillCard]]], optional + """ + super().__init__(**kwargs) + self.cards = cards + + +class UpdateAllSkillCardsOnFileFileTypeField(str, Enum): + FILE = 'file' + + +class UpdateAllSkillCardsOnFileFile(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + *, + type: Optional[UpdateAllSkillCardsOnFileFileTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `file`., defaults to None + :type type: Optional[UpdateAllSkillCardsOnFileFileTypeField], optional + :param id: The ID of the file., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class UpdateAllSkillCardsOnFileFileVersionTypeField(str, Enum): + FILE_VERSION = 'file_version' + + +class UpdateAllSkillCardsOnFileFileVersion(BaseObject): + _discriminator = 'type', {'file_version'} + + def __init__( + self, + *, + type: Optional[UpdateAllSkillCardsOnFileFileVersionTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `file_version`., defaults to None + :type type: Optional[UpdateAllSkillCardsOnFileFileVersionTypeField], optional + :param id: The ID of the file version., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class UpdateAllSkillCardsOnFileUsage(BaseObject): + def __init__( + self, *, unit: Optional[str] = None, value: Optional[float] = None, **kwargs + ): + """ + :param unit: The value will always be `file`., defaults to None + :type unit: Optional[str], optional + :param value: Number of resources affected., defaults to None + :type value: Optional[float], optional + """ + super().__init__(**kwargs) + self.unit = unit + self.value = value + + +class SkillsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_box_skill_cards_on_file( + self, file_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SkillCardsMetadata: + """ + List the Box Skills metadata cards that are attached to a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/global/boxSkillsCards', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SkillCardsMetadata) + + def create_box_skill_cards_on_file( + self, + file_id: str, + cards: List[ + Union[ + KeywordSkillCard, + TimelineSkillCard, + TranscriptSkillCard, + StatusSkillCard, + ] + ], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SkillCardsMetadata: + """ + Applies one or more Box Skills metadata cards to a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param cards: A list of Box Skill cards to apply to this file. + :type cards: List[Union[KeywordSkillCard, TimelineSkillCard, TranscriptSkillCard, StatusSkillCard]] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'cards': cards} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/global/boxSkillsCards', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SkillCardsMetadata) + + def update_box_skill_cards_on_file( + self, + file_id: str, + request_body: List[UpdateBoxSkillCardsOnFileRequestBody], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> SkillCardsMetadata: + """ + Updates one or more Box Skills metadata cards to a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param request_body: Request body of updateBoxSkillCardsOnFile method + :type request_body: List[UpdateBoxSkillCardsOnFileRequestBody] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/global/boxSkillsCards', + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json-patch+json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, SkillCardsMetadata) + + def delete_box_skill_cards_from_file( + self, file_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes any Box Skills cards metadata from a file. + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/metadata/global/boxSkillsCards', + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def update_all_skill_cards_on_file( + self, + skill_id: str, + status: UpdateAllSkillCardsOnFileStatus, + metadata: UpdateAllSkillCardsOnFileMetadata, + file: UpdateAllSkillCardsOnFileFile, + *, + file_version: Optional[UpdateAllSkillCardsOnFileFileVersion] = None, + usage: Optional[UpdateAllSkillCardsOnFileUsage] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + An alternative method that can be used to overwrite and update all Box Skill + + metadata cards on a file. + + :param skill_id: The ID of the skill to apply this metadata for. + Example: "33243242" + :type skill_id: str + :param status: Defines the status of this invocation. Set this to `success` when setting Skill cards. + :type status: UpdateAllSkillCardsOnFileStatus + :param metadata: The metadata to set for this skill. This is a list of + Box Skills cards. These cards will overwrite any existing Box + skill cards on the file. + :type metadata: UpdateAllSkillCardsOnFileMetadata + :param file: The file to assign the cards to. + :type file: UpdateAllSkillCardsOnFileFile + :param file_version: The optional file version to assign the cards to., defaults to None + :type file_version: Optional[UpdateAllSkillCardsOnFileFileVersion], optional + :param usage: A descriptor that defines what items are affected by this call. + + Set this to the default values when setting a card to a `success` + state, and leave it out in most other situations., defaults to None + :type usage: Optional[UpdateAllSkillCardsOnFileUsage], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'status': status, + 'metadata': metadata, + 'file': file, + 'file_version': file_version, + 'usage': usage, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/skill_invocations/', + to_string(skill_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/storage_policies.py b/box_sdk_gen/managers/storage_policies.py new file mode 100644 index 000000000..54f08cb97 --- /dev/null +++ b/box_sdk_gen/managers/storage_policies.py @@ -0,0 +1,140 @@ +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.storage_policies import StoragePolicies + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.storage_policy import StoragePolicy + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class StoragePoliciesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_storage_policies( + self, + *, + fields: Optional[List[str]] = None, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> StoragePolicies: + """ + Fetches all the storage policies in the enterprise. + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/storage_policies'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, StoragePolicies) + + def get_storage_policy_by_id( + self, + storage_policy_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> StoragePolicy: + """ + Fetches a specific storage policy. + :param storage_policy_id: The ID of the storage policy. + Example: "34342" + :type storage_policy_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/storage_policies/', + to_string(storage_policy_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, StoragePolicy) diff --git a/box_sdk_gen/managers/storage_policy_assignments.py b/box_sdk_gen/managers/storage_policy_assignments.py new file mode 100644 index 000000000..be0f88b2a --- /dev/null +++ b/box_sdk_gen/managers/storage_policy_assignments.py @@ -0,0 +1,352 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.storage_policy_assignments import StoragePolicyAssignments + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.storage_policy_assignment import StoragePolicyAssignment + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetStoragePolicyAssignmentsResolvedForType(str, Enum): + USER = 'user' + ENTERPRISE = 'enterprise' + + +class CreateStoragePolicyAssignmentStoragePolicyTypeField(str, Enum): + STORAGE_POLICY = 'storage_policy' + + +class CreateStoragePolicyAssignmentStoragePolicy(BaseObject): + _discriminator = 'type', {'storage_policy'} + + def __init__( + self, + id: str, + *, + type: CreateStoragePolicyAssignmentStoragePolicyTypeField = CreateStoragePolicyAssignmentStoragePolicyTypeField.STORAGE_POLICY, + **kwargs + ): + """ + :param id: The ID of the storage policy to assign. + :type id: str + :param type: The type to assign., defaults to CreateStoragePolicyAssignmentStoragePolicyTypeField.STORAGE_POLICY + :type type: CreateStoragePolicyAssignmentStoragePolicyTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CreateStoragePolicyAssignmentAssignedToTypeField(str, Enum): + USER = 'user' + ENTERPRISE = 'enterprise' + + +class CreateStoragePolicyAssignmentAssignedTo(BaseObject): + _discriminator = 'type', {'user', 'enterprise'} + + def __init__( + self, type: CreateStoragePolicyAssignmentAssignedToTypeField, id: str, **kwargs + ): + """ + :param type: The type to assign the policy to. + :type type: CreateStoragePolicyAssignmentAssignedToTypeField + :param id: The ID of the user or enterprise. + :type id: str + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class UpdateStoragePolicyAssignmentByIdStoragePolicyTypeField(str, Enum): + STORAGE_POLICY = 'storage_policy' + + +class UpdateStoragePolicyAssignmentByIdStoragePolicy(BaseObject): + _discriminator = 'type', {'storage_policy'} + + def __init__( + self, + id: str, + *, + type: UpdateStoragePolicyAssignmentByIdStoragePolicyTypeField = UpdateStoragePolicyAssignmentByIdStoragePolicyTypeField.STORAGE_POLICY, + **kwargs + ): + """ + :param id: The ID of the storage policy to assign. + :type id: str + :param type: The type to assign., defaults to UpdateStoragePolicyAssignmentByIdStoragePolicyTypeField.STORAGE_POLICY + :type type: UpdateStoragePolicyAssignmentByIdStoragePolicyTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class StoragePolicyAssignmentsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_storage_policy_assignments( + self, + resolved_for_type: GetStoragePolicyAssignmentsResolvedForType, + resolved_for_id: str, + *, + marker: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> StoragePolicyAssignments: + """ + Fetches all the storage policy assignment for an enterprise or user. + :param resolved_for_type: The target type to return assignments for. + :type resolved_for_type: GetStoragePolicyAssignmentsResolvedForType + :param resolved_for_id: The ID of the user or enterprise to return assignments for. + :type resolved_for_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'marker': to_string(marker), + 'resolved_for_type': to_string(resolved_for_type), + 'resolved_for_id': to_string(resolved_for_id), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/storage_policy_assignments', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, StoragePolicyAssignments) + + def create_storage_policy_assignment( + self, + storage_policy: CreateStoragePolicyAssignmentStoragePolicy, + assigned_to: CreateStoragePolicyAssignmentAssignedTo, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> StoragePolicyAssignment: + """ + Creates a storage policy assignment for an enterprise or user. + :param storage_policy: The storage policy to assign to the user or + enterprise. + :type storage_policy: CreateStoragePolicyAssignmentStoragePolicy + :param assigned_to: The user or enterprise to assign the storage + policy to. + :type assigned_to: CreateStoragePolicyAssignmentAssignedTo + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'storage_policy': storage_policy, + 'assigned_to': assigned_to, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/storage_policy_assignments', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, StoragePolicyAssignment) + + def get_storage_policy_assignment_by_id( + self, + storage_policy_assignment_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> StoragePolicyAssignment: + """ + Fetches a specific storage policy assignment. + :param storage_policy_assignment_id: The ID of the storage policy assignment. + Example: "932483" + :type storage_policy_assignment_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/storage_policy_assignments/', + to_string(storage_policy_assignment_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, StoragePolicyAssignment) + + def update_storage_policy_assignment_by_id( + self, + storage_policy_assignment_id: str, + storage_policy: UpdateStoragePolicyAssignmentByIdStoragePolicy, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> StoragePolicyAssignment: + """ + Updates a specific storage policy assignment. + :param storage_policy_assignment_id: The ID of the storage policy assignment. + Example: "932483" + :type storage_policy_assignment_id: str + :param storage_policy: The storage policy to assign to the user or + enterprise. + :type storage_policy: UpdateStoragePolicyAssignmentByIdStoragePolicy + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'storage_policy': storage_policy} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/storage_policy_assignments/', + to_string(storage_policy_assignment_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, StoragePolicyAssignment) + + def delete_storage_policy_assignment_by_id( + self, + storage_policy_assignment_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Delete a storage policy assignment. + + Deleting a storage policy assignment on a user + + + will have the user inherit the enterprise's default + + + storage policy. + + + There is a rate limit for calling this endpoint of only + + + twice per user in a 24 hour time frame. + + :param storage_policy_assignment_id: The ID of the storage policy assignment. + Example: "932483" + :type storage_policy_assignment_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/storage_policy_assignments/', + to_string(storage_policy_assignment_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/task_assignments.py b/box_sdk_gen/managers/task_assignments.py new file mode 100644 index 000000000..f7d5a8139 --- /dev/null +++ b/box_sdk_gen/managers/task_assignments.py @@ -0,0 +1,303 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.task_assignments import TaskAssignments + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.task_assignment import TaskAssignment + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateTaskAssignmentTaskTypeField(str, Enum): + TASK = 'task' + + +class CreateTaskAssignmentTask(BaseObject): + _discriminator = 'type', {'task'} + + def __init__( + self, + id: str, + *, + type: CreateTaskAssignmentTaskTypeField = CreateTaskAssignmentTaskTypeField.TASK, + **kwargs + ): + """ + :param id: The ID of the task. + :type id: str + :param type: The type of the item to assign., defaults to CreateTaskAssignmentTaskTypeField.TASK + :type type: CreateTaskAssignmentTaskTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CreateTaskAssignmentAssignTo(BaseObject): + def __init__( + self, *, id: Optional[str] = None, login: Optional[str] = None, **kwargs + ): + """ + :param id: The ID of the user to assign to the + task. + + To specify a user by their email + address use the `login` parameter., defaults to None + :type id: Optional[str], optional + :param login: The email address of the user to assign to the task. + To specify a user by their user ID please use the `id` parameter., defaults to None + :type login: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.login = login + + +class UpdateTaskAssignmentByIdResolutionState(str, Enum): + COMPLETED = 'completed' + INCOMPLETE = 'incomplete' + APPROVED = 'approved' + REJECTED = 'rejected' + + +class TaskAssignmentsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_task_assignments( + self, task_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TaskAssignments: + """ + Lists all of the assignments for a given task. + :param task_id: The ID of the task. + Example: "12345" + :type task_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/tasks/', + to_string(task_id), + '/assignments', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TaskAssignments) + + def create_task_assignment( + self, + task: CreateTaskAssignmentTask, + assign_to: CreateTaskAssignmentAssignTo, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TaskAssignment: + """ + Assigns a task to a user. + + A task can be assigned to more than one user by creating multiple + + + assignments. + + :param task: The task to assign to a user. + :type task: CreateTaskAssignmentTask + :param assign_to: The user to assign the task to. + :type assign_to: CreateTaskAssignmentAssignTo + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'task': task, 'assign_to': assign_to} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/task_assignments'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TaskAssignment) + + def get_task_assignment_by_id( + self, + task_assignment_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TaskAssignment: + """ + Retrieves information about a task assignment. + :param task_assignment_id: The ID of the task assignment. + Example: "12345" + :type task_assignment_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/task_assignments/', + to_string(task_assignment_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TaskAssignment) + + def update_task_assignment_by_id( + self, + task_assignment_id: str, + *, + message: Optional[str] = None, + resolution_state: Optional[UpdateTaskAssignmentByIdResolutionState] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TaskAssignment: + """ + Updates a task assignment. This endpoint can be + + used to update the state of a task assigned to a user. + + :param task_assignment_id: The ID of the task assignment. + Example: "12345" + :type task_assignment_id: str + :param message: An optional message by the assignee that can be added to the task., defaults to None + :type message: Optional[str], optional + :param resolution_state: The state of the task assigned to the user. + + * For a task with an `action` value of `complete` this can be + `incomplete` or `completed`. + * For a task with an `action` of `review` this can be + `incomplete`, `approved`, or `rejected`., defaults to None + :type resolution_state: Optional[UpdateTaskAssignmentByIdResolutionState], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'message': message, 'resolution_state': resolution_state} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/task_assignments/', + to_string(task_assignment_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TaskAssignment) + + def delete_task_assignment_by_id( + self, + task_assignment_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a specific task assignment. + :param task_assignment_id: The ID of the task assignment. + Example: "12345" + :type task_assignment_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/task_assignments/', + to_string(task_assignment_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/tasks.py b/box_sdk_gen/managers/tasks.py new file mode 100644 index 000000000..47becd4f6 --- /dev/null +++ b/box_sdk_gen/managers/tasks.py @@ -0,0 +1,338 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.tasks import Tasks + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.task import Task + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import DateTime + + +class CreateTaskItemTypeField(str, Enum): + FILE = 'file' + + +class CreateTaskItem(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[CreateTaskItemTypeField] = None, + **kwargs + ): + """ + :param id: The ID of the file., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `file`., defaults to None + :type type: Optional[CreateTaskItemTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CreateTaskAction(str, Enum): + REVIEW = 'review' + COMPLETE = 'complete' + + +class CreateTaskCompletionRule(str, Enum): + ALL_ASSIGNEES = 'all_assignees' + ANY_ASSIGNEE = 'any_assignee' + + +class UpdateTaskByIdAction(str, Enum): + REVIEW = 'review' + COMPLETE = 'complete' + + +class UpdateTaskByIdCompletionRule(str, Enum): + ALL_ASSIGNEES = 'all_assignees' + ANY_ASSIGNEE = 'any_assignee' + + +class TasksManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_file_tasks( + self, file_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Tasks: + """ + Retrieves a list of all the tasks for a file. This + + endpoint does not support pagination. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/tasks', + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Tasks) + + def create_task( + self, + item: CreateTaskItem, + *, + action: Optional[CreateTaskAction] = None, + message: Optional[str] = None, + due_at: Optional[DateTime] = None, + completion_rule: Optional[CreateTaskCompletionRule] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Task: + """ + Creates a single task on a file. This task is not assigned to any user and + + will need to be assigned separately. + + :param item: The file to attach the task to. + :type item: CreateTaskItem + :param action: The action the task assignee will be prompted to do. Must be + + * `review` defines an approval task that can be approved or, + rejected + * `complete` defines a general task which can be completed., defaults to None + :type action: Optional[CreateTaskAction], optional + :param message: An optional message to include with the task., defaults to None + :type message: Optional[str], optional + :param due_at: Defines when the task is due. Defaults to `null` if not + provided., defaults to None + :type due_at: Optional[DateTime], optional + :param completion_rule: Defines which assignees need to complete this task before the task + is considered completed. + + * `all_assignees` (default) requires all assignees to review or + approve the the task in order for it to be considered completed. + * `any_assignee` accepts any one assignee to review or + approve the the task in order for it to be considered completed., defaults to None + :type completion_rule: Optional[CreateTaskCompletionRule], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'item': item, + 'action': action, + 'message': message, + 'due_at': due_at, + 'completion_rule': completion_rule, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/tasks']), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Task) + + def get_task_by_id( + self, task_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Task: + """ + Retrieves information about a specific task. + :param task_id: The ID of the task. + Example: "12345" + :type task_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/tasks/', + to_string(task_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Task) + + def update_task_by_id( + self, + task_id: str, + *, + action: Optional[UpdateTaskByIdAction] = None, + message: Optional[str] = None, + due_at: Optional[DateTime] = None, + completion_rule: Optional[UpdateTaskByIdCompletionRule] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Task: + """ + Updates a task. This can be used to update a task's configuration, or to + + update its completion state. + + :param task_id: The ID of the task. + Example: "12345" + :type task_id: str + :param action: The action the task assignee will be prompted to do. Must be + + * `review` defines an approval task that can be approved or + rejected, + * `complete` defines a general task which can be completed., defaults to None + :type action: Optional[UpdateTaskByIdAction], optional + :param message: The message included with the task., defaults to None + :type message: Optional[str], optional + :param due_at: When the task is due at., defaults to None + :type due_at: Optional[DateTime], optional + :param completion_rule: Defines which assignees need to complete this task before the task + is considered completed. + + * `all_assignees` (default) requires all assignees to review or + approve the the task in order for it to be considered completed. + * `any_assignee` accepts any one assignee to review or + approve the the task in order for it to be considered completed., defaults to None + :type completion_rule: Optional[UpdateTaskByIdCompletionRule], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'action': action, + 'message': message, + 'due_at': due_at, + 'completion_rule': completion_rule, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/tasks/', + to_string(task_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Task) + + def delete_task_by_id( + self, task_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Removes a task from a file. + :param task_id: The ID of the task. + Example: "12345" + :type task_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/tasks/', + to_string(task_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/terms_of_service_user_statuses.py b/box_sdk_gen/managers/terms_of_service_user_statuses.py new file mode 100644 index 000000000..9374d3c21 --- /dev/null +++ b/box_sdk_gen/managers/terms_of_service_user_statuses.py @@ -0,0 +1,235 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.terms_of_service_user_statuses import ( + TermsOfServiceUserStatuses, +) + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.terms_of_service_user_status import TermsOfServiceUserStatus + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateTermsOfServiceStatusForUserTosTypeField(str, Enum): + TERMS_OF_SERVICE = 'terms_of_service' + + +class CreateTermsOfServiceStatusForUserTos(BaseObject): + _discriminator = 'type', {'terms_of_service'} + + def __init__( + self, + id: str, + *, + type: CreateTermsOfServiceStatusForUserTosTypeField = CreateTermsOfServiceStatusForUserTosTypeField.TERMS_OF_SERVICE, + **kwargs + ): + """ + :param id: The ID of terms of service. + :type id: str + :param type: The type of object., defaults to CreateTermsOfServiceStatusForUserTosTypeField.TERMS_OF_SERVICE + :type type: CreateTermsOfServiceStatusForUserTosTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CreateTermsOfServiceStatusForUserUserTypeField(str, Enum): + USER = 'user' + + +class CreateTermsOfServiceStatusForUserUser(BaseObject): + _discriminator = 'type', {'user'} + + def __init__( + self, + id: str, + *, + type: CreateTermsOfServiceStatusForUserUserTypeField = CreateTermsOfServiceStatusForUserUserTypeField.USER, + **kwargs + ): + """ + :param id: The ID of user. + :type id: str + :param type: The type of object., defaults to CreateTermsOfServiceStatusForUserUserTypeField.USER + :type type: CreateTermsOfServiceStatusForUserUserTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class TermsOfServiceUserStatusesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_terms_of_service_user_statuses( + self, + tos_id: str, + *, + user_id: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TermsOfServiceUserStatuses: + """ + Retrieves an overview of users and their status for a + + terms of service, including Whether they have accepted + + + the terms and when. + + :param tos_id: The ID of the terms of service. + :type tos_id: str + :param user_id: Limits results to the given user ID., defaults to None + :type user_id: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'tos_id': to_string(tos_id), 'user_id': to_string(user_id)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/terms_of_service_user_statuses', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TermsOfServiceUserStatuses) + + def create_terms_of_service_status_for_user( + self, + tos: CreateTermsOfServiceStatusForUserTos, + user: CreateTermsOfServiceStatusForUserUser, + is_accepted: bool, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TermsOfServiceUserStatus: + """ + Sets the status for a terms of service for a user. + :param tos: The terms of service to set the status for. + :type tos: CreateTermsOfServiceStatusForUserTos + :param user: The user to set the status for. + :type user: CreateTermsOfServiceStatusForUserUser + :param is_accepted: Whether the user has accepted the terms. + :type is_accepted: bool + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'tos': tos, 'user': user, 'is_accepted': is_accepted} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/terms_of_service_user_statuses', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TermsOfServiceUserStatus) + + def update_terms_of_service_status_for_user_by_id( + self, + terms_of_service_user_status_id: str, + is_accepted: bool, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TermsOfServiceUserStatus: + """ + Updates the status for a terms of service for a user. + :param terms_of_service_user_status_id: The ID of the terms of service status. + Example: "324234" + :type terms_of_service_user_status_id: str + :param is_accepted: Whether the user has accepted the terms. + :type is_accepted: bool + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'is_accepted': is_accepted} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/terms_of_service_user_statuses/', + to_string(terms_of_service_user_status_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TermsOfServiceUserStatus) diff --git a/box_sdk_gen/managers/terms_of_services.py b/box_sdk_gen/managers/terms_of_services.py new file mode 100644 index 000000000..2440409f1 --- /dev/null +++ b/box_sdk_gen/managers/terms_of_services.py @@ -0,0 +1,235 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.terms_of_services import TermsOfServices + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.terms_of_service import TermsOfService + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetTermsOfServiceTosType(str, Enum): + EXTERNAL = 'external' + MANAGED = 'managed' + + +class CreateTermsOfServiceStatus(str, Enum): + ENABLED = 'enabled' + DISABLED = 'disabled' + + +class CreateTermsOfServiceTosType(str, Enum): + EXTERNAL = 'external' + MANAGED = 'managed' + + +class UpdateTermsOfServiceByIdStatus(str, Enum): + ENABLED = 'enabled' + DISABLED = 'disabled' + + +class TermsOfServicesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_terms_of_service( + self, + *, + tos_type: Optional[GetTermsOfServiceTosType] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TermsOfServices: + """ + Returns the current terms of service text and settings + + for the enterprise. + + :param tos_type: Limits the results to the terms of service of the given type., defaults to None + :type tos_type: Optional[GetTermsOfServiceTosType], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'tos_type': to_string(tos_type)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/terms_of_services'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TermsOfServices) + + def create_terms_of_service( + self, + status: CreateTermsOfServiceStatus, + text: str, + *, + tos_type: Optional[CreateTermsOfServiceTosType] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TermsOfService: + """ + Creates a terms of service for a given enterprise + + and type of user. + + :param status: Whether this terms of service is active. + :type status: CreateTermsOfServiceStatus + :param text: The terms of service text to display to users. + + The text can be set to empty if the `status` is set to `disabled`. + :type text: str + :param tos_type: The type of user to set the terms of + service for., defaults to None + :type tos_type: Optional[CreateTermsOfServiceTosType], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'status': status, 'tos_type': tos_type, 'text': text} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/terms_of_services'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TermsOfService) + + def get_terms_of_service_by_id( + self, + terms_of_service_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TermsOfService: + """ + Fetches a specific terms of service. + :param terms_of_service_id: The ID of the terms of service. + Example: "324234" + :type terms_of_service_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/terms_of_services/', + to_string(terms_of_service_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TermsOfService) + + def update_terms_of_service_by_id( + self, + terms_of_service_id: str, + status: UpdateTermsOfServiceByIdStatus, + text: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TermsOfService: + """ + Updates a specific terms of service. + :param terms_of_service_id: The ID of the terms of service. + Example: "324234" + :type terms_of_service_id: str + :param status: Whether this terms of service is active. + :type status: UpdateTermsOfServiceByIdStatus + :param text: The terms of service text to display to users. + + The text can be set to empty if the `status` is set to `disabled`. + :type text: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'status': status, 'text': text} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/terms_of_services/', + to_string(terms_of_service_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TermsOfService) diff --git a/box_sdk_gen/managers/transfer.py b/box_sdk_gen/managers/transfer.py new file mode 100644 index 000000000..866934f48 --- /dev/null +++ b/box_sdk_gen/managers/transfer.py @@ -0,0 +1,193 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class TransferOwnedFolderOwnedBy(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The ID of the user who the folder will be + transferred to. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class TransferManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def transfer_owned_folder( + self, + user_id: str, + owned_by: TransferOwnedFolderOwnedBy, + *, + fields: Optional[List[str]] = None, + notify: Optional[bool] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> FolderFull: + """ + Move all of the items (files, folders and workflows) owned by a user into + + another user's account + + + Only the root folder (`0`) can be transferred. + + + Folders can only be moved across users by users with administrative + + + permissions. + + + All existing shared links and folder-level collaborations are transferred + + + during the operation. Please note that while collaborations at the individual + + + file-level are transferred during the operation, the collaborations are + + + deleted when the original user is deleted. + + + If the user has a large number of items across all folders, the call will + + + be run asynchronously. If the operation is not completed within 10 minutes, + + + the user will receive a 200 OK response, and the operation will continue running. + + + If the destination path has a metadata cascade policy attached to any of + + + the parent folders, a metadata cascade operation will be kicked off + + + asynchronously. + + + There is currently no way to check for when this operation is finished. + + + The destination folder's name will be in the format `{User}'s Files and + + + Folders`, where `{User}` is the display name of the user. + + + To make this API call your application will need to have the "Read and write + + + all files and folders stored in Box" scope enabled. + + + Please make sure the destination user has access to `Relay` or `Relay Lite`, + + + and has access to the files and folders involved in the workflows being + + + transferred. + + + Admins will receive an email when the operation is completed. + + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param owned_by: The user who the folder will be transferred to. + :type owned_by: TransferOwnedFolderOwnedBy + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param notify: Determines if users should receive email notification + for the action performed., defaults to None + :type notify: Optional[bool], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'owned_by': owned_by} + query_params_map: Dict[str, str] = prepare_params( + {'fields': to_string(fields), 'notify': to_string(notify)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + '/folders/0', + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, FolderFull) diff --git a/box_sdk_gen/managers/trashed_files.py b/box_sdk_gen/managers/trashed_files.py new file mode 100644 index 000000000..340d93b32 --- /dev/null +++ b/box_sdk_gen/managers/trashed_files.py @@ -0,0 +1,252 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.trash_file_restored import TrashFileRestored + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.trash_file import TrashFile + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class RestoreFileFromTrashParent(BaseObject): + def __init__(self, *, id: Optional[str] = None, **kwargs): + """ + :param id: The ID of parent item., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + + +class TrashedFilesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def restore_file_from_trash( + self, + file_id: str, + *, + name: Optional[str] = None, + parent: Optional[RestoreFileFromTrashParent] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TrashFileRestored: + """ + Restores a file that has been moved to the trash. + + An optional new parent ID can be provided to restore the file to in case the + + + original folder has been deleted. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param name: An optional new name for the file., defaults to None + :type name: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'name': name, 'parent': parent} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + ] + ), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TrashFileRestored) + + def get_trashed_file_by_id( + self, + file_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TrashFile: + """ + Retrieves a file that has been moved to the trash. + + Please note that only if the file itself has been moved to the + + + trash can it be retrieved with this API call. If instead one of + + + its parent folders was moved to the trash, only that folder + + + can be inspected using the + + + [`GET /folders/:id/trash`](e://get_folders_id_trash) API. + + + To list all items that have been moved to the trash, please + + + use the [`GET /folders/trash/items`](e://get-folders-trash-items/) + + + API. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/trash', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TrashFile) + + def delete_trashed_file_by_id( + self, file_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Permanently deletes a file that is in the trash. + + This action cannot be undone. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/files/', + to_string(file_id), + '/trash', + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/trashed_folders.py b/box_sdk_gen/managers/trashed_folders.py new file mode 100644 index 000000000..b41e66dd1 --- /dev/null +++ b/box_sdk_gen/managers/trashed_folders.py @@ -0,0 +1,279 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.trash_folder_restored import TrashFolderRestored + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.trash_folder import TrashFolder + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class RestoreFolderFromTrashParent(BaseObject): + def __init__(self, *, id: Optional[str] = None, **kwargs): + """ + :param id: The ID of parent item., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + + +class TrashedFoldersManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def restore_folder_from_trash( + self, + folder_id: str, + *, + name: Optional[str] = None, + parent: Optional[RestoreFolderFromTrashParent] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TrashFolderRestored: + """ + Restores a folder that has been moved to the trash. + + An optional new parent ID can be provided to restore the folder to in case the + + + original folder has been deleted. + + + During this operation, part of the file tree will be locked, mainly + + + the source folder and all of its descendants, as well as the destination + + + folder. + + + For the duration of the operation, no other move, copy, delete, or restore + + + operation can performed on any of the locked folders. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param name: An optional new name for the folder., defaults to None + :type name: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'name': name, 'parent': parent} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + ] + ), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TrashFolderRestored) + + def get_trashed_folder_by_id( + self, + folder_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TrashFolder: + """ + Retrieves a folder that has been moved to the trash. + + Please note that only if the folder itself has been moved to the + + + trash can it be retrieved with this API call. If instead one of + + + its parent folders was moved to the trash, only that folder + + + can be inspected using the + + + [`GET /folders/:id/trash`](e://get_folders_id_trash) API. + + + To list all items that have been moved to the trash, please + + + use the [`GET /folders/trash/items`](e://get-folders-trash-items/) + + + API. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/trash', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TrashFolder) + + def delete_trashed_folder_by_id( + self, + folder_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Permanently deletes a folder that is in the trash. + + This action cannot be undone. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + Example: "12345" + :type folder_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/', + to_string(folder_id), + '/trash', + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/trashed_items.py b/box_sdk_gen/managers/trashed_items.py new file mode 100644 index 000000000..242cd2ed3 --- /dev/null +++ b/box_sdk_gen/managers/trashed_items.py @@ -0,0 +1,171 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.items import Items + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetTrashedItemsDirection(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class GetTrashedItemsSort(str, Enum): + NAME = 'name' + DATE = 'date' + SIZE = 'size' + + +class TrashedItemsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_trashed_items( + self, + *, + fields: Optional[List[str]] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + usemarker: Optional[bool] = None, + marker: Optional[str] = None, + direction: Optional[GetTrashedItemsDirection] = None, + sort: Optional[GetTrashedItemsSort] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Items: + """ + Retrieves the files and folders that have been moved + + to the trash. + + + Any attribute in the full files or folders objects can be passed + + + in with the `fields` parameter to retrieve those specific + + + attributes that are not returned by default. + + + This endpoint defaults to use offset-based pagination, yet also supports + + + marker-based pagination using the `marker` parameter. + + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param usemarker: Specifies whether to use marker-based pagination instead of + offset-based pagination. Only one pagination method can + be used at a time. + + By setting this value to true, the API will return a `marker` field + that can be passed as a parameter to this endpoint to get the next + page of the response., defaults to None + :type usemarker: Optional[bool], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param direction: The direction to sort results in. This can be either in alphabetical ascending + (`ASC`) or descending (`DESC`) order., defaults to None + :type direction: Optional[GetTrashedItemsDirection], optional + :param sort: Defines the **second** attribute by which items + are sorted. + + Items are always sorted by their `type` first, with + folders listed before files, and files listed + before web links. + + This parameter is not supported when using marker-based pagination., defaults to None + :type sort: Optional[GetTrashedItemsSort], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'fields': to_string(fields), + 'limit': to_string(limit), + 'offset': to_string(offset), + 'usemarker': to_string(usemarker), + 'marker': to_string(marker), + 'direction': to_string(direction), + 'sort': to_string(sort), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/folders/trash/items', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Items) diff --git a/box_sdk_gen/managers/trashed_web_links.py b/box_sdk_gen/managers/trashed_web_links.py new file mode 100644 index 000000000..de1cbe168 --- /dev/null +++ b/box_sdk_gen/managers/trashed_web_links.py @@ -0,0 +1,213 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.trash_web_link_restored import TrashWebLinkRestored + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.trash_web_link import TrashWebLink + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class RestoreWeblinkFromTrashParent(BaseObject): + def __init__(self, *, id: Optional[str] = None, **kwargs): + """ + :param id: The ID of parent item., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + + +class TrashedWebLinksManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def restore_weblink_from_trash( + self, + web_link_id: str, + *, + name: Optional[str] = None, + parent: Optional[RestoreWeblinkFromTrashParent] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TrashWebLinkRestored: + """ + Restores a web link that has been moved to the trash. + + An optional new parent ID can be provided to restore the web link to in case + + + the original folder has been deleted. + + :param web_link_id: The ID of the web link. + Example: "12345" + :type web_link_id: str + :param name: An optional new name for the web link., defaults to None + :type name: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'name': name, 'parent': parent} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/web_links/', + to_string(web_link_id), + ] + ), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TrashWebLinkRestored) + + def get_trashed_web_link_by_id( + self, + web_link_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> TrashWebLink: + """ + Retrieves a web link that has been moved to the trash. + :param web_link_id: The ID of the web link. + Example: "12345" + :type web_link_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/web_links/', + to_string(web_link_id), + '/trash', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, TrashWebLink) + + def delete_trashed_web_link_by_id( + self, + web_link_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Permanently deletes a web link that is in the trash. + + This action cannot be undone. + + :param web_link_id: The ID of the web link. + Example: "12345" + :type web_link_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/web_links/', + to_string(web_link_id), + '/trash', + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/uploads.py b/box_sdk_gen/managers/uploads.py new file mode 100644 index 000000000..1bdb269a6 --- /dev/null +++ b/box_sdk_gen/managers/uploads.py @@ -0,0 +1,546 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.upload_url import UploadUrl + +from box_sdk_gen.schemas.conflict_error import ConflictError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.networking.fetch_options import MultipartItem + +from box_sdk_gen.serialization.json import SerializedData + + +class UploadFileVersionAttributes(BaseObject): + def __init__( + self, name: str, *, content_modified_at: Optional[DateTime] = None, **kwargs + ): + """ + :param name: An optional new name for the file. If specified, the file + will be renamed when the new version is uploaded. + :type name: str + :param content_modified_at: Defines the time the file was last modified at. + + If not set, the upload time will be used., defaults to None + :type content_modified_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.name = name + self.content_modified_at = content_modified_at + + +class PreflightFileUploadCheckParent(BaseObject): + def __init__(self, *, id: Optional[str] = None, **kwargs): + """ + :param id: The ID of parent item., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + + +class UploadFileAttributesParentField(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The id of the parent folder. Use + `0` for the user's root folder. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class UploadFileAttributes(BaseObject): + def __init__( + self, + name: str, + parent: UploadFileAttributesParentField, + *, + content_created_at: Optional[DateTime] = None, + content_modified_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param name: The name of the file. + + File names must be unique within their parent folder. The name check is case-insensitive, so a file + named `New File` cannot be created in a parent folder that already contains a folder named `new file`. + :type name: str + :param parent: The parent folder to upload the file to. + :type parent: UploadFileAttributesParentField + :param content_created_at: Defines the time the file was originally created at. + + If not set, the upload time will be used., defaults to None + :type content_created_at: Optional[DateTime], optional + :param content_modified_at: Defines the time the file was last modified at. + + If not set, the upload time will be used., defaults to None + :type content_modified_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.name = name + self.parent = parent + self.content_created_at = content_created_at + self.content_modified_at = content_modified_at + + +class UploadWithPreflightCheckAttributesParentField(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The id of the parent folder. Use + `0` for the user's root folder. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class UploadWithPreflightCheckAttributes(BaseObject): + def __init__( + self, + name: str, + parent: UploadWithPreflightCheckAttributesParentField, + size: int, + *, + content_created_at: Optional[DateTime] = None, + content_modified_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param name: The name of the file. + + File names must be unique within their parent folder. The name check is case-insensitive, so a file + named `New File` cannot be created in a parent folder that already contains a folder named `new file`. + :type name: str + :param parent: The parent folder to upload the file to. + :type parent: UploadWithPreflightCheckAttributesParentField + :param size: The size of the file in bytes + :type size: int + :param content_created_at: Defines the time the file was originally created at. + + If not set, the upload time will be used., defaults to None + :type content_created_at: Optional[DateTime], optional + :param content_modified_at: Defines the time the file was last modified at. + + If not set, the upload time will be used., defaults to None + :type content_modified_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.name = name + self.parent = parent + self.size = size + self.content_created_at = content_created_at + self.content_modified_at = content_modified_at + + +class UploadsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def upload_file_version( + self, + file_id: str, + attributes: UploadFileVersionAttributes, + file: ByteStream, + *, + file_file_name: Optional[str] = None, + file_content_type: Optional[str] = None, + fields: Optional[List[str]] = None, + if_match: Optional[str] = None, + content_md_5: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Files: + """ + Update a file's content. For file sizes over 50MB we recommend + + using the Chunk Upload APIs. + + + The `attributes` part of the body must come **before** the + + + `file` part. Requests that do not follow this format when + + + uploading the file will receive a HTTP `400` error with a + + + `metadata_after_file_contents` error code. + + :param file_id: The unique identifier that represents a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + Example: "12345" + :type file_id: str + :param attributes: The additional attributes of the file being uploaded. Mainly the + name and the parent folder. These attributes are part of the multi + part request body and are in JSON format. + + + + The `attributes` part of the body must come **before** the + `file` part. Requests that do not follow this format when + uploading the file will receive a HTTP `400` error with a + `metadata_after_file_contents` error code. + + + :type attributes: UploadFileVersionAttributes + :param file: The content of the file to upload to Box. + + + + The `attributes` part of the body must come **before** the + `file` part. Requests that do not follow this format when + uploading the file will receive a HTTP `400` error with a + `metadata_after_file_contents` error code. + + + :type file: ByteStream + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param if_match: Ensures this item hasn't recently changed before + making changes. + + Pass in the item's last observed `etag` value + into this header and the endpoint will fail + with a `412 Precondition Failed` if it + has changed since., defaults to None + :type if_match: Optional[str], optional + :param content_md_5: An optional header containing the SHA1 hash of the file to + ensure that the file was not corrupted in transit., defaults to None + :type content_md_5: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'attributes': attributes, + 'file': file, + 'file_file_name': file_file_name, + 'file_content_type': file_content_type, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params( + { + 'if-match': to_string(if_match), + 'content-md5': to_string(content_md_5), + **extra_headers, + } + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.upload_url, + '/2.0/files/', + to_string(file_id), + '/content', + ] + ), + method='POST', + params=query_params_map, + headers=headers_map, + multipart_data=[ + MultipartItem(part_name='attributes', data=serialize(attributes)), + MultipartItem( + part_name='file', + file_stream=file, + file_name=file_file_name, + content_type=file_content_type, + ), + ], + content_type='multipart/form-data', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Files) + + def preflight_file_upload_check( + self, + *, + name: Optional[str] = None, + size: Optional[int] = None, + parent: Optional[PreflightFileUploadCheckParent] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UploadUrl: + """ + Performs a check to verify that a file will be accepted by Box + + before you upload the entire file. + + :param name: The name for the file., defaults to None + :type name: Optional[str], optional + :param size: The size of the file in bytes., defaults to None + :type size: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'name': name, 'size': size, 'parent': parent} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/files/content'] + ), + method='OPTIONS', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UploadUrl) + + def upload_file( + self, + attributes: UploadFileAttributes, + file: ByteStream, + *, + file_file_name: Optional[str] = None, + file_content_type: Optional[str] = None, + fields: Optional[List[str]] = None, + content_md_5: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Files: + """ + Uploads a small file to Box. For file sizes over 50MB we recommend + + using the Chunk Upload APIs. + + + The `attributes` part of the body must come **before** the + + + `file` part. Requests that do not follow this format when + + + uploading the file will receive a HTTP `400` error with a + + + `metadata_after_file_contents` error code. + + :param attributes: The additional attributes of the file being uploaded. Mainly the + name and the parent folder. These attributes are part of the multi + part request body and are in JSON format. + + + + The `attributes` part of the body must come **before** the + `file` part. Requests that do not follow this format when + uploading the file will receive a HTTP `400` error with a + `metadata_after_file_contents` error code. + + + :type attributes: UploadFileAttributes + :param file: The content of the file to upload to Box. + + + + The `attributes` part of the body must come **before** the + `file` part. Requests that do not follow this format when + uploading the file will receive a HTTP `400` error with a + `metadata_after_file_contents` error code. + + + :type file: ByteStream + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param content_md_5: An optional header containing the SHA1 hash of the file to + ensure that the file was not corrupted in transit., defaults to None + :type content_md_5: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'attributes': attributes, + 'file': file, + 'file_file_name': file_file_name, + 'file_content_type': file_content_type, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params( + {'content-md5': to_string(content_md_5), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.upload_url, '/2.0/files/content'] + ), + method='POST', + params=query_params_map, + headers=headers_map, + multipart_data=[ + MultipartItem(part_name='attributes', data=serialize(attributes)), + MultipartItem( + part_name='file', + file_stream=file, + file_name=file_file_name, + content_type=file_content_type, + ), + ], + content_type='multipart/form-data', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Files) + + def upload_with_preflight_check( + self, + attributes: UploadWithPreflightCheckAttributes, + file: ByteStream, + *, + file_file_name: Optional[str] = None, + file_content_type: Optional[str] = None, + fields: Optional[List[str]] = None, + content_md_5: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Files: + """ + Upload a file with a preflight check + :param file: The content of the file to upload to Box. + + + + The `attributes` part of the body must come **before** the + `file` part. Requests that do not follow this format when + uploading the file will receive a HTTP `400` error with a + `metadata_after_file_contents` error code. + + + :type file: ByteStream + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param content_md_5: An optional header containing the SHA1 hash of the file to + ensure that the file was not corrupted in transit., defaults to None + :type content_md_5: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'attributes': attributes, + 'file': file, + 'file_file_name': file_file_name, + 'file_content_type': file_content_type, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params( + {'content-md5': to_string(content_md_5), **extra_headers} + ) + preflight_upload_url: UploadUrl = self.preflight_file_upload_check( + name=attributes.name, + size=attributes.size, + parent=PreflightFileUploadCheckParent(id=attributes.parent.id), + extra_headers=extra_headers, + ) + if ( + preflight_upload_url.upload_url == None + or not 'http' in preflight_upload_url.upload_url + ): + raise BoxSDKError(message='Unable to get preflight upload URL') + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=preflight_upload_url.upload_url, + method='POST', + params=query_params_map, + headers=headers_map, + multipart_data=[ + MultipartItem(part_name='attributes', data=serialize(attributes)), + MultipartItem( + part_name='file', + file_stream=file, + file_name=file_file_name, + content_type=file_content_type, + ), + ], + content_type='multipart/form-data', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Files) diff --git a/box_sdk_gen/managers/user_collaborations.py b/box_sdk_gen/managers/user_collaborations.py new file mode 100644 index 000000000..2357844ca --- /dev/null +++ b/box_sdk_gen/managers/user_collaborations.py @@ -0,0 +1,444 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.collaboration import Collaboration + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import DateTime + + +class UpdateCollaborationByIdRole(str, Enum): + EDITOR = 'editor' + VIEWER = 'viewer' + PREVIEWER = 'previewer' + UPLOADER = 'uploader' + PREVIEWER_UPLOADER = 'previewer uploader' + VIEWER_UPLOADER = 'viewer uploader' + CO_OWNER = 'co-owner' + OWNER = 'owner' + + +class UpdateCollaborationByIdStatus(str, Enum): + PENDING = 'pending' + ACCEPTED = 'accepted' + REJECTED = 'rejected' + + +class CreateCollaborationItemTypeField(str, Enum): + FILE = 'file' + FOLDER = 'folder' + + +class CreateCollaborationItem(BaseObject): + _discriminator = 'type', {'file', 'folder'} + + def __init__( + self, + *, + type: Optional[CreateCollaborationItemTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of the item that this collaboration will be + granted access to., defaults to None + :type type: Optional[CreateCollaborationItemTypeField], optional + :param id: The ID of the item that will be granted access to., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class CreateCollaborationAccessibleByTypeField(str, Enum): + USER = 'user' + GROUP = 'group' + + +class CreateCollaborationAccessibleBy(BaseObject): + _discriminator = 'type', {'user', 'group'} + + def __init__( + self, + type: CreateCollaborationAccessibleByTypeField, + *, + id: Optional[str] = None, + login: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of collaborator to invite. + :type type: CreateCollaborationAccessibleByTypeField + :param id: The ID of the user or group. + + Alternatively, use `login` to specify a user by email + address., defaults to None + :type id: Optional[str], optional + :param login: The email address of the user to grant access to the item. + + Alternatively, use `id` to specify a user by user ID., defaults to None + :type login: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.login = login + + +class CreateCollaborationRole(str, Enum): + EDITOR = 'editor' + VIEWER = 'viewer' + PREVIEWER = 'previewer' + UPLOADER = 'uploader' + PREVIEWER_UPLOADER = 'previewer uploader' + VIEWER_UPLOADER = 'viewer uploader' + CO_OWNER = 'co-owner' + + +class UserCollaborationsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_collaboration_by_id( + self, + collaboration_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Collaboration: + """ + Retrieves a single collaboration. + :param collaboration_id: The ID of the collaboration. + Example: "1234" + :type collaboration_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaborations/', + to_string(collaboration_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Collaboration) + + def update_collaboration_by_id( + self, + collaboration_id: str, + role: UpdateCollaborationByIdRole, + *, + status: Optional[UpdateCollaborationByIdStatus] = None, + expires_at: Optional[DateTime] = None, + can_view_path: Optional[bool] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Optional[Collaboration]: + """ + Updates a collaboration. + + Can be used to change the owner of an item, or to + + + accept collaboration invites. + + :param collaboration_id: The ID of the collaboration. + Example: "1234" + :type collaboration_id: str + :param role: The level of access granted. + :type role: UpdateCollaborationByIdRole + :param status: Set the status of a `pending` collaboration invitation, + effectively accepting, or rejecting the invite., defaults to None + :type status: Optional[UpdateCollaborationByIdStatus], optional + :param expires_at: Update the expiration date for the collaboration. At this date, + the collaboration will be automatically removed from the item. + + This feature will only work if the **Automatically remove invited + collaborators: Allow folder owners to extend the expiry date** + setting has been enabled in the **Enterprise Settings** + of the **Admin Console**. When the setting is not enabled, + collaborations can not have an expiry date and a value for this + field will be result in an error. + + Additionally, a collaboration can only be given an + expiration if it was created after the **Automatically remove + invited collaborator** setting was enabled., defaults to None + :type expires_at: Optional[DateTime], optional + :param can_view_path: Determines if the invited users can see the entire parent path to + the associated folder. The user will not gain privileges in any + parent folder and therefore can not see content the user is not + collaborated on. + + Be aware that this meaningfully increases the time required to load the + invitee's **All Files** page. We recommend you limit the number of + collaborations with `can_view_path` enabled to 1,000 per user. + + Only owner or co-owners can invite collaborators with a `can_view_path` of + `true`. + + `can_view_path` can only be used for folder collaborations., defaults to None + :type can_view_path: Optional[bool], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'role': role, + 'status': status, + 'expires_at': expires_at, + 'can_view_path': can_view_path, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaborations/', + to_string(collaboration_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + if to_string(response.status) == '204': + return None + return deserialize(response.data, Collaboration) + + def delete_collaboration_by_id( + self, + collaboration_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a single collaboration. + :param collaboration_id: The ID of the collaboration. + Example: "1234" + :type collaboration_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/collaborations/', + to_string(collaboration_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def create_collaboration( + self, + item: CreateCollaborationItem, + accessible_by: CreateCollaborationAccessibleBy, + role: CreateCollaborationRole, + *, + is_access_only: Optional[bool] = None, + can_view_path: Optional[bool] = None, + expires_at: Optional[DateTime] = None, + fields: Optional[List[str]] = None, + notify: Optional[bool] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Collaboration: + """ + Adds a collaboration for a single user or a single group to a file + + or folder. + + + Collaborations can be created using email address, user IDs, or a + + + group IDs. + + + If a collaboration is being created with a group, access to + + + this endpoint is dependent on the group's ability to be invited. + + + If collaboration is in `pending` status, the following fields + + + are redacted: + + + - `login` and `name` are hidden if a collaboration was created + + + using `user_id`, + + + - `name` is hidden if a collaboration was created using `login`. + + :param item: The item to attach the comment to. + :type item: CreateCollaborationItem + :param accessible_by: The user or group to give access to the item. + :type accessible_by: CreateCollaborationAccessibleBy + :param role: The level of access granted. + :type role: CreateCollaborationRole + :param is_access_only: If set to `true`, collaborators have access to + shared items, but such items won't be visible in the + All Files list. Additionally, collaborators won't + see the the path to the root folder for the + shared item., defaults to None + :type is_access_only: Optional[bool], optional + :param can_view_path: Determines if the invited users can see the entire parent path to + the associated folder. The user will not gain privileges in any + parent folder and therefore can not see content the user is not + collaborated on. + + Be aware that this meaningfully increases the time required to load the + invitee's **All Files** page. We recommend you limit the number of + collaborations with `can_view_path` enabled to 1,000 per user. + + Only owner or co-owners can invite collaborators with a `can_view_path` of + `true`. + + `can_view_path` can only be used for folder collaborations., defaults to None + :type can_view_path: Optional[bool], optional + :param expires_at: Set the expiration date for the collaboration. At this date, the + collaboration will be automatically removed from the item. + + This feature will only work if the **Automatically remove invited + collaborators: Allow folder owners to extend the expiry date** + setting has been enabled in the **Enterprise Settings** + of the **Admin Console**. When the setting is not enabled, + collaborations can not have an expiry date and a value for this + field will be result in an error., defaults to None + :type expires_at: Optional[DateTime], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param notify: Determines if users should receive email notification + for the action performed., defaults to None + :type notify: Optional[bool], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'item': item, + 'accessible_by': accessible_by, + 'role': role, + 'is_access_only': is_access_only, + 'can_view_path': can_view_path, + 'expires_at': expires_at, + } + query_params_map: Dict[str, str] = prepare_params( + {'fields': to_string(fields), 'notify': to_string(notify)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/collaborations'] + ), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Collaboration) diff --git a/box_sdk_gen/managers/users.py b/box_sdk_gen/managers/users.py new file mode 100644 index 000000000..52df23cb1 --- /dev/null +++ b/box_sdk_gen/managers/users.py @@ -0,0 +1,701 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.internal.null_value import NullValue + +from typing import Union + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.users import Users + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.tracking_code import TrackingCode + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class GetUsersUserType(str, Enum): + ALL = 'all' + MANAGED = 'managed' + EXTERNAL = 'external' + + +class CreateUserRole(str, Enum): + COADMIN = 'coadmin' + USER = 'user' + + +class CreateUserStatus(str, Enum): + ACTIVE = 'active' + INACTIVE = 'inactive' + CANNOT_DELETE_EDIT = 'cannot_delete_edit' + CANNOT_DELETE_EDIT_UPLOAD = 'cannot_delete_edit_upload' + + +class UpdateUserByIdRole(str, Enum): + COADMIN = 'coadmin' + USER = 'user' + + +class UpdateUserByIdStatus(str, Enum): + ACTIVE = 'active' + INACTIVE = 'inactive' + CANNOT_DELETE_EDIT = 'cannot_delete_edit' + CANNOT_DELETE_EDIT_UPLOAD = 'cannot_delete_edit_upload' + + +class UpdateUserByIdNotificationEmail(BaseObject): + def __init__(self, *, email: Optional[str] = None, **kwargs): + """ + :param email: The email address to send the notifications to., defaults to None + :type email: Optional[str], optional + """ + super().__init__(**kwargs) + self.email = email + + +class UsersManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_users( + self, + *, + filter_term: Optional[str] = None, + user_type: Optional[GetUsersUserType] = None, + external_app_user_id: Optional[str] = None, + fields: Optional[List[str]] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + usemarker: Optional[bool] = None, + marker: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Users: + """ + Returns a list of all users for the Enterprise along with their `user_id`, + + `public_name`, and `login`. + + + The application and the authenticated user need to + + + have the permission to look up users in the entire + + + enterprise. + + :param filter_term: Limits the results to only users who's `name` or + `login` start with the search term. + + For externally managed users, the search term needs + to completely match the in order to find the user, and + it will only return one user at a time., defaults to None + :type filter_term: Optional[str], optional + :param user_type: Limits the results to the kind of user specified. + + * `all` returns every kind of user for whom the + `login` or `name` partially matches the + `filter_term`. It will only return an external user + if the login matches the `filter_term` completely, + and in that case it will only return that user. + * `managed` returns all managed and app users for whom + the `login` or `name` partially matches the + `filter_term`. + * `external` returns all external users for whom the + `login` matches the `filter_term` exactly., defaults to None + :type user_type: Optional[GetUsersUserType], optional + :param external_app_user_id: Limits the results to app users with the given + `external_app_user_id` value. + + When creating an app user, an + `external_app_user_id` value can be set. This value can + then be used in this endpoint to find any users that + match that `external_app_user_id` value., defaults to None + :type external_app_user_id: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param offset: The offset of the item at which to begin the response. + + Queries with offset parameter value + exceeding 10000 will be rejected + with a 400 response., defaults to None + :type offset: Optional[int], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param usemarker: Specifies whether to use marker-based pagination instead of + offset-based pagination. Only one pagination method can + be used at a time. + + By setting this value to true, the API will return a `marker` field + that can be passed as a parameter to this endpoint to get the next + page of the response., defaults to None + :type usemarker: Optional[bool], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'filter_term': to_string(filter_term), + 'user_type': to_string(user_type), + 'external_app_user_id': to_string(external_app_user_id), + 'fields': to_string(fields), + 'offset': to_string(offset), + 'limit': to_string(limit), + 'usemarker': to_string(usemarker), + 'marker': to_string(marker), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/users']), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Users) + + def create_user( + self, + name: str, + *, + login: Optional[str] = None, + is_platform_access_only: Optional[bool] = None, + role: Optional[CreateUserRole] = None, + language: Optional[str] = None, + is_sync_enabled: Optional[bool] = None, + job_title: Optional[str] = None, + phone: Optional[str] = None, + address: Optional[str] = None, + space_amount: Optional[int] = None, + tracking_codes: Optional[List[TrackingCode]] = None, + can_see_managed_users: Optional[bool] = None, + timezone: Optional[str] = None, + is_external_collab_restricted: Optional[bool] = None, + is_exempt_from_device_limits: Optional[bool] = None, + is_exempt_from_login_verification: Optional[bool] = None, + status: Optional[CreateUserStatus] = None, + external_app_user_id: Optional[str] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UserFull: + """ + Creates a new managed user in an enterprise. This endpoint + + is only available to users and applications with the right + + + admin permissions. + + :param name: The name of the user. + :type name: str + :param login: The email address the user uses to log in + + Required, unless `is_platform_access_only` + is set to `true`., defaults to None + :type login: Optional[str], optional + :param is_platform_access_only: Specifies that the user is an app user., defaults to None + :type is_platform_access_only: Optional[bool], optional + :param role: The user’s enterprise role., defaults to None + :type role: Optional[CreateUserRole], optional + :param language: The language of the user, formatted in modified version of the + [ISO 639-1](/guides/api-calls/language-codes) format., defaults to None + :type language: Optional[str], optional + :param is_sync_enabled: Whether the user can use Box Sync., defaults to None + :type is_sync_enabled: Optional[bool], optional + :param job_title: The user’s job title., defaults to None + :type job_title: Optional[str], optional + :param phone: The user’s phone number., defaults to None + :type phone: Optional[str], optional + :param address: The user’s address., defaults to None + :type address: Optional[str], optional + :param space_amount: The user’s total available space in bytes. Set this to `-1` to + indicate unlimited storage., defaults to None + :type space_amount: Optional[int], optional + :param tracking_codes: Tracking codes allow an admin to generate reports from the + admin console and assign an attribute to a specific group + of users. This setting must be enabled for an enterprise before it + can be used., defaults to None + :type tracking_codes: Optional[List[TrackingCode]], optional + :param can_see_managed_users: Whether the user can see other enterprise users in their + contact list., defaults to None + :type can_see_managed_users: Optional[bool], optional + :param timezone: The user's timezone., defaults to None + :type timezone: Optional[str], optional + :param is_external_collab_restricted: Whether the user is allowed to collaborate with users outside + their enterprise., defaults to None + :type is_external_collab_restricted: Optional[bool], optional + :param is_exempt_from_device_limits: Whether to exempt the user from enterprise device limits., defaults to None + :type is_exempt_from_device_limits: Optional[bool], optional + :param is_exempt_from_login_verification: Whether the user must use two-factor authentication., defaults to None + :type is_exempt_from_login_verification: Optional[bool], optional + :param status: The user's account status., defaults to None + :type status: Optional[CreateUserStatus], optional + :param external_app_user_id: An external identifier for an app user, which can be used to look + up the user. This can be used to tie user IDs from external + identity providers to Box users., defaults to None + :type external_app_user_id: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'name': name, + 'login': login, + 'is_platform_access_only': is_platform_access_only, + 'role': role, + 'language': language, + 'is_sync_enabled': is_sync_enabled, + 'job_title': job_title, + 'phone': phone, + 'address': address, + 'space_amount': space_amount, + 'tracking_codes': tracking_codes, + 'can_see_managed_users': can_see_managed_users, + 'timezone': timezone, + 'is_external_collab_restricted': is_external_collab_restricted, + 'is_exempt_from_device_limits': is_exempt_from_device_limits, + 'is_exempt_from_login_verification': is_exempt_from_login_verification, + 'status': status, + 'external_app_user_id': external_app_user_id, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/users']), + method='POST', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UserFull) + + def get_user_me( + self, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UserFull: + """ + Retrieves information about the user who is currently authenticated. + + In the case of a client-side authenticated OAuth 2.0 application + + + this will be the user who authorized the app. + + + In the case of a JWT, server-side authenticated application + + + this will be the service account that belongs to the application + + + by default. + + + Use the `As-User` header to change who this API call is made on behalf of. + + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/users/me']), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UserFull) + + def get_user_by_id( + self, + user_id: str, + *, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UserFull: + """ + Retrieves information about a user in the enterprise. + + The application and the authenticated user need to + + + have the permission to look up users in the entire + + + enterprise. + + + This endpoint also returns a limited set of information + + + for external users who are collaborated on content + + + owned by the enterprise for authenticated users with the + + + right scopes. In this case, disallowed fields will return + + + null instead. + + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UserFull) + + def update_user_by_id( + self, + user_id: str, + *, + enterprise: Union[Optional[str], NullValue] = None, + notify: Optional[bool] = None, + name: Optional[str] = None, + login: Optional[str] = None, + role: Optional[UpdateUserByIdRole] = None, + language: Optional[str] = None, + is_sync_enabled: Optional[bool] = None, + job_title: Optional[str] = None, + phone: Optional[str] = None, + address: Optional[str] = None, + tracking_codes: Optional[List[TrackingCode]] = None, + can_see_managed_users: Optional[bool] = None, + timezone: Optional[str] = None, + is_external_collab_restricted: Optional[bool] = None, + is_exempt_from_device_limits: Optional[bool] = None, + is_exempt_from_login_verification: Optional[bool] = None, + is_password_reset_required: Optional[bool] = None, + status: Optional[UpdateUserByIdStatus] = None, + space_amount: Optional[int] = None, + notification_email: Union[ + Optional[UpdateUserByIdNotificationEmail], NullValue + ] = None, + external_app_user_id: Optional[str] = None, + fields: Optional[List[str]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> UserFull: + """ + Updates a managed or app user in an enterprise. This endpoint + + is only available to users and applications with the right + + + admin permissions. + + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param enterprise: Set this to `null` to roll the user out of the enterprise + and make them a free user., defaults to None + :type enterprise: Union[Optional[str], NullValue], optional + :param notify: Whether the user should receive an email when they + are rolled out of an enterprise., defaults to None + :type notify: Optional[bool], optional + :param name: The name of the user., defaults to None + :type name: Optional[str], optional + :param login: The email address the user uses to log in + + Note: If the target user's email is not confirmed, then the + primary login address cannot be changed., defaults to None + :type login: Optional[str], optional + :param role: The user’s enterprise role., defaults to None + :type role: Optional[UpdateUserByIdRole], optional + :param language: The language of the user, formatted in modified version of the + [ISO 639-1](/guides/api-calls/language-codes) format., defaults to None + :type language: Optional[str], optional + :param is_sync_enabled: Whether the user can use Box Sync., defaults to None + :type is_sync_enabled: Optional[bool], optional + :param job_title: The user’s job title., defaults to None + :type job_title: Optional[str], optional + :param phone: The user’s phone number., defaults to None + :type phone: Optional[str], optional + :param address: The user’s address., defaults to None + :type address: Optional[str], optional + :param tracking_codes: Tracking codes allow an admin to generate reports from the + admin console and assign an attribute to a specific group + of users. This setting must be enabled for an enterprise before it + can be used., defaults to None + :type tracking_codes: Optional[List[TrackingCode]], optional + :param can_see_managed_users: Whether the user can see other enterprise users in their + contact list., defaults to None + :type can_see_managed_users: Optional[bool], optional + :param timezone: The user's timezone., defaults to None + :type timezone: Optional[str], optional + :param is_external_collab_restricted: Whether the user is allowed to collaborate with users outside + their enterprise., defaults to None + :type is_external_collab_restricted: Optional[bool], optional + :param is_exempt_from_device_limits: Whether to exempt the user from enterprise device limits., defaults to None + :type is_exempt_from_device_limits: Optional[bool], optional + :param is_exempt_from_login_verification: Whether the user must use two-factor authentication., defaults to None + :type is_exempt_from_login_verification: Optional[bool], optional + :param is_password_reset_required: Whether the user is required to reset their password., defaults to None + :type is_password_reset_required: Optional[bool], optional + :param status: The user's account status., defaults to None + :type status: Optional[UpdateUserByIdStatus], optional + :param space_amount: The user’s total available space in bytes. Set this to `-1` to + indicate unlimited storage., defaults to None + :type space_amount: Optional[int], optional + :param notification_email: An alternate notification email address to which email + notifications are sent. When it's confirmed, this will be + the email address to which notifications are sent instead of + to the primary email address. + + Set this value to `null` to remove the notification email., defaults to None + :type notification_email: Union[Optional[UpdateUserByIdNotificationEmail], NullValue], optional + :param external_app_user_id: An external identifier for an app user, which can be used to look + up the user. This can be used to tie user IDs from external + identity providers to Box users. + + Note: In order to update this field, you need to request a token + using the application that created the app user., defaults to None + :type external_app_user_id: Optional[str], optional + :param fields: A comma-separated list of attributes to include in the + response. This can be used to request fields that are + not normally returned in a standard response. + + Be aware that specifying this parameter will have the + effect that none of the standard fields are returned in + the response unless explicitly specified, instead only + fields for the mini representation are returned, additional + to the fields requested., defaults to None + :type fields: Optional[List[str]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'enterprise': enterprise, + 'notify': notify, + 'name': name, + 'login': login, + 'role': role, + 'language': language, + 'is_sync_enabled': is_sync_enabled, + 'job_title': job_title, + 'phone': phone, + 'address': address, + 'tracking_codes': tracking_codes, + 'can_see_managed_users': can_see_managed_users, + 'timezone': timezone, + 'is_external_collab_restricted': is_external_collab_restricted, + 'is_exempt_from_device_limits': is_exempt_from_device_limits, + 'is_exempt_from_login_verification': is_exempt_from_login_verification, + 'is_password_reset_required': is_password_reset_required, + 'status': status, + 'space_amount': space_amount, + 'notification_email': notification_email, + 'external_app_user_id': external_app_user_id, + } + query_params_map: Dict[str, str] = prepare_params({'fields': to_string(fields)}) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + ] + ), + method='PUT', + params=query_params_map, + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, UserFull) + + def delete_user_by_id( + self, + user_id: str, + *, + notify: Optional[bool] = None, + force: Optional[bool] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a user. By default this will fail if the user + + still owns any content. Move their owned content first + + + before proceeding, or use the `force` field to delete + + + the user and their files. + + :param user_id: The ID of the user. + Example: "12345" + :type user_id: str + :param notify: Whether the user will receive email notification of + the deletion., defaults to None + :type notify: Optional[bool], optional + :param force: Whether the user should be deleted even if this user + still own files., defaults to None + :type force: Optional[bool], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'notify': to_string(notify), 'force': to_string(force)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/users/', + to_string(user_id), + ] + ), + method='DELETE', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/web_links.py b/box_sdk_gen/managers/web_links.py new file mode 100644 index 000000000..1ee5165c2 --- /dev/null +++ b/box_sdk_gen/managers/web_links.py @@ -0,0 +1,326 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from enum import Enum + +from typing import Dict + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.internal.utils import DateTime + + +class CreateWebLinkParent(BaseObject): + def __init__(self, id: str, **kwargs): + """ + :param id: The ID of parent folder. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + + +class UpdateWebLinkByIdParent(BaseObject): + def __init__( + self, *, id: Optional[str] = None, user_id: Optional[str] = None, **kwargs + ): + """ + :param id: The ID of parent item., defaults to None + :type id: Optional[str], optional + :param user_id: The input for `user_id` is optional. Moving to non-root folder is not allowed when `user_id` is present. Parent folder id should be zero when `user_id` is provided., defaults to None + :type user_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.user_id = user_id + + +class UpdateWebLinkByIdSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class UpdateWebLinkByIdSharedLink(BaseObject): + def __init__( + self, + *, + access: Optional[UpdateWebLinkByIdSharedLinkAccessField] = None, + password: Optional[str] = None, + vanity_name: Optional[str] = None, + unshared_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param access: The level of access for the shared link. This can be + restricted to anyone with the link (`open`), only people + within the company (`company`) and only those who + have been invited to the folder (`collaborators`). + + If not set, this field defaults to the access level specified + by the enterprise admin. To create a shared link with this + default setting pass the `shared_link` object with + no `access` field, for example `{ "shared_link": {} }`. + + The `company` access level is only available to paid + accounts., defaults to None + :type access: Optional[UpdateWebLinkByIdSharedLinkAccessField], optional + :param password: The password required to access the shared link. Set the + password to `null` to remove it. + Passwords must now be at least eight characters + long and include a number, upper case letter, or + a non-numeric or non-alphabetic character. + A password can only be set when `access` is set to `open`., defaults to None + :type password: Optional[str], optional + :param vanity_name: Defines a custom vanity name to use in the shared link URL, + for example `https://app.box.com/v/my-shared-link`. + + Custom URLs should not be used when sharing sensitive content + as vanity URLs are a lot easier to guess than regular shared + links., defaults to None + :type vanity_name: Optional[str], optional + :param unshared_at: The timestamp at which this shared link will + expire. This field can only be set by + users with paid accounts. The value must be greater than the + current date and time., defaults to None + :type unshared_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.access = access + self.password = password + self.vanity_name = vanity_name + self.unshared_at = unshared_at + + +class WebLinksManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_web_link( + self, + url: str, + parent: CreateWebLinkParent, + *, + name: Optional[str] = None, + description: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> WebLink: + """ + Creates a web link object within a folder. + :param url: The URL that this web link links to. Must start with + `"http://"` or `"https://"`. + :type url: str + :param parent: The parent folder to create the web link within. + :type parent: CreateWebLinkParent + :param name: Name of the web link. Defaults to the URL if not set., defaults to None + :type name: Optional[str], optional + :param description: Description of the web link., defaults to None + :type description: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'url': url, + 'parent': parent, + 'name': name, + 'description': description, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/web_links'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, WebLink) + + def get_web_link_by_id( + self, + web_link_id: str, + *, + boxapi: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> WebLink: + """ + Retrieve information about a web link. + :param web_link_id: The ID of the web link. + Example: "12345" + :type web_link_id: str + :param boxapi: The URL, and optional password, for the shared link of this item. + + This header can be used to access items that have not been + explicitly shared with a user. + + Use the format `shared_link=[link]` or if a password is required then + use `shared_link=[link]&shared_link_password=[password]`. + + This header can be used on the file or folder shared, as well as on any files + or folders nested within the item., defaults to None + :type boxapi: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params( + {'boxapi': to_string(boxapi), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/web_links/', + to_string(web_link_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, WebLink) + + def update_web_link_by_id( + self, + web_link_id: str, + *, + url: Optional[str] = None, + parent: Optional[UpdateWebLinkByIdParent] = None, + name: Optional[str] = None, + description: Optional[str] = None, + shared_link: Optional[UpdateWebLinkByIdSharedLink] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> WebLink: + """ + Updates a web link object. + :param web_link_id: The ID of the web link. + Example: "12345" + :type web_link_id: str + :param url: The new URL that the web link links to. Must start with + `"http://"` or `"https://"`., defaults to None + :type url: Optional[str], optional + :param name: A new name for the web link. Defaults to the URL if not set., defaults to None + :type name: Optional[str], optional + :param description: A new description of the web link., defaults to None + :type description: Optional[str], optional + :param shared_link: The settings for the shared link to update., defaults to None + :type shared_link: Optional[UpdateWebLinkByIdSharedLink], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'url': url, + 'parent': parent, + 'name': name, + 'description': description, + 'shared_link': shared_link, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/web_links/', + to_string(web_link_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, WebLink) + + def delete_web_link_by_id( + self, + web_link_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a web link. + :param web_link_id: The ID of the web link. + Example: "12345" + :type web_link_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/web_links/', + to_string(web_link_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/webhooks.py b/box_sdk_gen/managers/webhooks.py new file mode 100644 index 000000000..accecd298 --- /dev/null +++ b/box_sdk_gen/managers/webhooks.py @@ -0,0 +1,491 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from typing import List + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.webhooks import Webhooks + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.webhook import Webhook + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import compute_webhook_signature + +from box_sdk_gen.internal.utils import compare_signatures + +from box_sdk_gen.internal.utils import date_time_from_string + +from box_sdk_gen.internal.utils import get_epoch_time_in_seconds + +from box_sdk_gen.internal.utils import date_time_to_epoch_seconds + + +class CreateWebhookTargetTypeField(str, Enum): + FILE = 'file' + FOLDER = 'folder' + + +class CreateWebhookTarget(BaseObject): + _discriminator = 'type', {'file', 'folder'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[CreateWebhookTargetTypeField] = None, + **kwargs + ): + """ + :param id: The ID of the item to trigger a webhook., defaults to None + :type id: Optional[str], optional + :param type: The type of item to trigger a webhook., defaults to None + :type type: Optional[CreateWebhookTargetTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CreateWebhookTriggers(str, Enum): + FILE_UPLOADED = 'FILE.UPLOADED' + FILE_PREVIEWED = 'FILE.PREVIEWED' + FILE_DOWNLOADED = 'FILE.DOWNLOADED' + FILE_TRASHED = 'FILE.TRASHED' + FILE_DELETED = 'FILE.DELETED' + FILE_RESTORED = 'FILE.RESTORED' + FILE_COPIED = 'FILE.COPIED' + FILE_MOVED = 'FILE.MOVED' + FILE_LOCKED = 'FILE.LOCKED' + FILE_UNLOCKED = 'FILE.UNLOCKED' + FILE_RENAMED = 'FILE.RENAMED' + COMMENT_CREATED = 'COMMENT.CREATED' + COMMENT_UPDATED = 'COMMENT.UPDATED' + COMMENT_DELETED = 'COMMENT.DELETED' + TASK_ASSIGNMENT_CREATED = 'TASK_ASSIGNMENT.CREATED' + TASK_ASSIGNMENT_UPDATED = 'TASK_ASSIGNMENT.UPDATED' + METADATA_INSTANCE_CREATED = 'METADATA_INSTANCE.CREATED' + METADATA_INSTANCE_UPDATED = 'METADATA_INSTANCE.UPDATED' + METADATA_INSTANCE_DELETED = 'METADATA_INSTANCE.DELETED' + FOLDER_CREATED = 'FOLDER.CREATED' + FOLDER_RENAMED = 'FOLDER.RENAMED' + FOLDER_DOWNLOADED = 'FOLDER.DOWNLOADED' + FOLDER_RESTORED = 'FOLDER.RESTORED' + FOLDER_DELETED = 'FOLDER.DELETED' + FOLDER_COPIED = 'FOLDER.COPIED' + FOLDER_MOVED = 'FOLDER.MOVED' + FOLDER_TRASHED = 'FOLDER.TRASHED' + WEBHOOK_DELETED = 'WEBHOOK.DELETED' + COLLABORATION_CREATED = 'COLLABORATION.CREATED' + COLLABORATION_ACCEPTED = 'COLLABORATION.ACCEPTED' + COLLABORATION_REJECTED = 'COLLABORATION.REJECTED' + COLLABORATION_REMOVED = 'COLLABORATION.REMOVED' + COLLABORATION_UPDATED = 'COLLABORATION.UPDATED' + SHARED_LINK_DELETED = 'SHARED_LINK.DELETED' + SHARED_LINK_CREATED = 'SHARED_LINK.CREATED' + SHARED_LINK_UPDATED = 'SHARED_LINK.UPDATED' + SIGN_REQUEST_COMPLETED = 'SIGN_REQUEST.COMPLETED' + SIGN_REQUEST_DECLINED = 'SIGN_REQUEST.DECLINED' + SIGN_REQUEST_EXPIRED = 'SIGN_REQUEST.EXPIRED' + SIGN_REQUEST_SIGNER_EMAIL_BOUNCED = 'SIGN_REQUEST.SIGNER_EMAIL_BOUNCED' + + +class UpdateWebhookByIdTargetTypeField(str, Enum): + FILE = 'file' + FOLDER = 'folder' + + +class UpdateWebhookByIdTarget(BaseObject): + _discriminator = 'type', {'file', 'folder'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[UpdateWebhookByIdTargetTypeField] = None, + **kwargs + ): + """ + :param id: The ID of the item to trigger a webhook., defaults to None + :type id: Optional[str], optional + :param type: The type of item to trigger a webhook., defaults to None + :type type: Optional[UpdateWebhookByIdTargetTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class UpdateWebhookByIdTriggers(str, Enum): + FILE_UPLOADED = 'FILE.UPLOADED' + FILE_PREVIEWED = 'FILE.PREVIEWED' + FILE_DOWNLOADED = 'FILE.DOWNLOADED' + FILE_TRASHED = 'FILE.TRASHED' + FILE_DELETED = 'FILE.DELETED' + FILE_RESTORED = 'FILE.RESTORED' + FILE_COPIED = 'FILE.COPIED' + FILE_MOVED = 'FILE.MOVED' + FILE_LOCKED = 'FILE.LOCKED' + FILE_UNLOCKED = 'FILE.UNLOCKED' + FILE_RENAMED = 'FILE.RENAMED' + COMMENT_CREATED = 'COMMENT.CREATED' + COMMENT_UPDATED = 'COMMENT.UPDATED' + COMMENT_DELETED = 'COMMENT.DELETED' + TASK_ASSIGNMENT_CREATED = 'TASK_ASSIGNMENT.CREATED' + TASK_ASSIGNMENT_UPDATED = 'TASK_ASSIGNMENT.UPDATED' + METADATA_INSTANCE_CREATED = 'METADATA_INSTANCE.CREATED' + METADATA_INSTANCE_UPDATED = 'METADATA_INSTANCE.UPDATED' + METADATA_INSTANCE_DELETED = 'METADATA_INSTANCE.DELETED' + FOLDER_CREATED = 'FOLDER.CREATED' + FOLDER_RENAMED = 'FOLDER.RENAMED' + FOLDER_DOWNLOADED = 'FOLDER.DOWNLOADED' + FOLDER_RESTORED = 'FOLDER.RESTORED' + FOLDER_DELETED = 'FOLDER.DELETED' + FOLDER_COPIED = 'FOLDER.COPIED' + FOLDER_MOVED = 'FOLDER.MOVED' + FOLDER_TRASHED = 'FOLDER.TRASHED' + WEBHOOK_DELETED = 'WEBHOOK.DELETED' + COLLABORATION_CREATED = 'COLLABORATION.CREATED' + COLLABORATION_ACCEPTED = 'COLLABORATION.ACCEPTED' + COLLABORATION_REJECTED = 'COLLABORATION.REJECTED' + COLLABORATION_REMOVED = 'COLLABORATION.REMOVED' + COLLABORATION_UPDATED = 'COLLABORATION.UPDATED' + SHARED_LINK_DELETED = 'SHARED_LINK.DELETED' + SHARED_LINK_CREATED = 'SHARED_LINK.CREATED' + SHARED_LINK_UPDATED = 'SHARED_LINK.UPDATED' + SIGN_REQUEST_COMPLETED = 'SIGN_REQUEST.COMPLETED' + SIGN_REQUEST_DECLINED = 'SIGN_REQUEST.DECLINED' + SIGN_REQUEST_EXPIRED = 'SIGN_REQUEST.EXPIRED' + SIGN_REQUEST_SIGNER_EMAIL_BOUNCED = 'SIGN_REQUEST.SIGNER_EMAIL_BOUNCED' + + +class WebhooksManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_webhooks( + self, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Webhooks: + """ + Returns all defined webhooks for the requesting application. + + This API only returns webhooks that are applied to files or folders that are + + + owned by the authenticated user. This means that an admin can not see webhooks + + + created by a service account unless the admin has access to those folders, and + + + vice versa. + + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/webhooks']), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Webhooks) + + def create_webhook( + self, + target: CreateWebhookTarget, + address: str, + triggers: List[CreateWebhookTriggers], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Webhook: + """ + Creates a webhook. + :param target: The item that will trigger the webhook. + :type target: CreateWebhookTarget + :param address: The URL that is notified by this webhook. + :type address: str + :param triggers: An array of event names that this webhook is + to be triggered for. + :type triggers: List[CreateWebhookTriggers] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'target': target, + 'address': address, + 'triggers': triggers, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/webhooks']), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Webhook) + + def get_webhook_by_id( + self, + webhook_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Webhook: + """ + Retrieves a specific webhook. + :param webhook_id: The ID of the webhook. + Example: "3321123" + :type webhook_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/webhooks/', + to_string(webhook_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Webhook) + + def update_webhook_by_id( + self, + webhook_id: str, + *, + target: Optional[UpdateWebhookByIdTarget] = None, + address: Optional[str] = None, + triggers: Optional[List[UpdateWebhookByIdTriggers]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Webhook: + """ + Updates a webhook. + :param webhook_id: The ID of the webhook. + Example: "3321123" + :type webhook_id: str + :param target: The item that will trigger the webhook., defaults to None + :type target: Optional[UpdateWebhookByIdTarget], optional + :param address: The URL that is notified by this webhook., defaults to None + :type address: Optional[str], optional + :param triggers: An array of event names that this webhook is + to be triggered for., defaults to None + :type triggers: Optional[List[UpdateWebhookByIdTriggers]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'target': target, + 'address': address, + 'triggers': triggers, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/webhooks/', + to_string(webhook_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Webhook) + + def delete_webhook_by_id( + self, + webhook_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Deletes a webhook. + :param webhook_id: The ID of the webhook. + Example: "3321123" + :type webhook_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/webhooks/', + to_string(webhook_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + @staticmethod + def validate_message( + body: str, + headers: Dict[str, str], + primary_key: str, + *, + secondary_key: Optional[str] = None, + max_age: Optional[int] = 600 + ) -> bool: + """ + Validate a webhook message by verifying the signature and the delivery timestamp + :param body: The request body of the webhook message + :type body: str + :param headers: The headers of the webhook message + :type headers: Dict[str, str] + :param primary_key: The primary signature to verify the message with + :type primary_key: str + :param secondary_key: The secondary signature to verify the message with, defaults to None + :type secondary_key: Optional[str], optional + :param max_age: The maximum age of the message in seconds, defaults to 10 minutes, defaults to 600 + :type max_age: Optional[int], optional + """ + delivery_timestamp: DateTime = date_time_from_string( + headers.get('box-delivery-timestamp') + ) + current_epoch: int = get_epoch_time_in_seconds() + if ( + current_epoch - max_age > date_time_to_epoch_seconds(delivery_timestamp) + or date_time_to_epoch_seconds(delivery_timestamp) > current_epoch + ): + return False + if ( + not primary_key == None and not headers.get('box-signature-primary') == None + ) and compare_signatures( + expected_signature=compute_webhook_signature( + body, headers, primary_key, escape_body=False + ), + received_signature=headers.get('box-signature-primary'), + ): + return True + if ( + not primary_key == None and not headers.get('box-signature-primary') == None + ) and compare_signatures( + expected_signature=compute_webhook_signature( + body, headers, primary_key, escape_body=True + ), + received_signature=headers.get('box-signature-primary'), + ): + return True + if ( + not secondary_key == None + and not headers.get('box-signature-secondary') == None + ) and compare_signatures( + expected_signature=compute_webhook_signature( + body, headers, secondary_key, escape_body=False + ), + received_signature=headers.get('box-signature-secondary'), + ): + return True + if ( + not secondary_key == None + and not headers.get('box-signature-secondary') == None + ) and compare_signatures( + expected_signature=compute_webhook_signature( + body, headers, secondary_key, escape_body=True + ), + received_signature=headers.get('box-signature-secondary'), + ): + return True + return False diff --git a/box_sdk_gen/managers/workflows.py b/box_sdk_gen/managers/workflows.py new file mode 100644 index 000000000..7e0881040 --- /dev/null +++ b/box_sdk_gen/managers/workflows.py @@ -0,0 +1,261 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from typing import List + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.workflows import Workflows + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.outcome import Outcome + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class StartWorkflowType(str, Enum): + WORKFLOW_PARAMETERS = 'workflow_parameters' + + +class StartWorkflowFlow(BaseObject): + def __init__( + self, *, type: Optional[str] = None, id: Optional[str] = None, **kwargs + ): + """ + :param type: The type of the flow object., defaults to None + :type type: Optional[str], optional + :param id: The id of the flow., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class StartWorkflowFilesTypeField(str, Enum): + FILE = 'file' + + +class StartWorkflowFiles(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + *, + type: Optional[StartWorkflowFilesTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of the file object., defaults to None + :type type: Optional[StartWorkflowFilesTypeField], optional + :param id: The id of the file., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class StartWorkflowFolderTypeField(str, Enum): + FOLDER = 'folder' + + +class StartWorkflowFolder(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + *, + type: Optional[StartWorkflowFolderTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of the folder object., defaults to None + :type type: Optional[StartWorkflowFolderTypeField], optional + :param id: The id of the folder., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class WorkflowsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_workflows( + self, + folder_id: str, + *, + trigger_type: Optional[str] = None, + limit: Optional[int] = None, + marker: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> Workflows: + """ + Returns list of workflows that act on a given `folder ID`, and + + have a flow with a trigger type of `WORKFLOW_MANUAL_START`. + + + You application must be authorized to use the `Manage Box Relay` application + + + scope within the developer console in to use this endpoint. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + :type folder_id: str + :param trigger_type: Type of trigger to search for., defaults to None + :type trigger_type: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'folder_id': to_string(folder_id), + 'trigger_type': to_string(trigger_type), + 'limit': to_string(limit), + 'marker': to_string(marker), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/workflows'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, Workflows) + + def start_workflow( + self, + workflow_id: str, + flow: StartWorkflowFlow, + files: List[StartWorkflowFiles], + folder: StartWorkflowFolder, + *, + type: Optional[StartWorkflowType] = None, + outcomes: Optional[List[Outcome]] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Initiates a flow with a trigger type of `WORKFLOW_MANUAL_START`. + + You application must be authorized to use the `Manage Box Relay` application + + + scope within the developer console. + + :param workflow_id: The ID of the workflow. + Example: "12345" + :type workflow_id: str + :param flow: The flow that will be triggered. + :type flow: StartWorkflowFlow + :param files: The array of files for which the workflow should start. All files + must be in the workflow's configured folder. + :type files: List[StartWorkflowFiles] + :param folder: The folder object for which the workflow is configured. + :type folder: StartWorkflowFolder + :param type: The type of the parameters object., defaults to None + :type type: Optional[StartWorkflowType], optional + :param outcomes: A configurable outcome the workflow should complete., defaults to None + :type outcomes: Optional[List[Outcome]], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'type': type, + 'flow': flow, + 'files': files, + 'folder': folder, + 'outcomes': outcomes, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/workflows/', + to_string(workflow_id), + '/start', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/managers/zip_downloads.py b/box_sdk_gen/managers/zip_downloads.py new file mode 100644 index 000000000..7813cbdc2 --- /dev/null +++ b/box_sdk_gen/managers/zip_downloads.py @@ -0,0 +1,318 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.zip_download import ZipDownload + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.zip_download_request import ZipDownloadRequest + +from box_sdk_gen.schemas.zip_download_status import ZipDownloadStatus + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.serialization.json import sd_to_json + + +class CreateZipDownloadItemsTypeField(str, Enum): + FILE = 'file' + FOLDER = 'folder' + + +class CreateZipDownloadItems(BaseObject): + _discriminator = 'type', {'file', 'folder'} + + def __init__(self, type: CreateZipDownloadItemsTypeField, id: str, **kwargs): + """ + :param type: The type of the item to add to the archive. + :type type: CreateZipDownloadItemsTypeField + :param id: The identifier of the item to add to the archive. When this item is + a folder then this can not be the root folder with ID `0`. + :type id: str + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class DownloadZipItemsTypeField(str, Enum): + FILE = 'file' + FOLDER = 'folder' + + +class DownloadZipItems(BaseObject): + _discriminator = 'type', {'file', 'folder'} + + def __init__(self, type: DownloadZipItemsTypeField, id: str, **kwargs): + """ + :param type: The type of the item to add to the archive. + :type type: DownloadZipItemsTypeField + :param id: The identifier of the item to add to the archive. When this item is + a folder then this can not be the root folder with ID `0`. + :type id: str + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class ZipDownloadsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_zip_download( + self, + items: List[CreateZipDownloadItems], + *, + download_file_name: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ZipDownload: + """ + Creates a request to download multiple files and folders as a single `zip` + + archive file. This API does not return the archive but instead performs all + + + the checks to ensure that the user has access to all the items, and then + + + returns a `download_url` and a `status_url` that can be used to download the + + + archive. + + + The limit for an archive is either the Account's upload limit or + + + 10,000 files, whichever is met first. + + + **Note**: Downloading a large file can be + + + affected by various + + + factors such as distance, network latency, + + + bandwidth, and congestion, as well as packet loss + + + ratio and current server load. + + + For these reasons we recommend that a maximum ZIP archive + + + total size does not exceed 25GB. + + :param items: A list of items to add to the `zip` archive. These can + be folders or files. + :type items: List[CreateZipDownloadItems] + :param download_file_name: The optional name of the `zip` archive. This name will be appended by the + `.zip` file extension, for example `January Financials.zip`., defaults to None + :type download_file_name: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'items': items, 'download_file_name': download_file_name} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/zip_downloads'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ZipDownload) + + def get_zip_download_content( + self, + download_url: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ByteStream: + """ + Returns the contents of a `zip` archive in binary format. This URL does not + + require any form of authentication and could be used in a user's browser to + + + download the archive to a user's device. + + + By default, this URL is only valid for a few seconds from the creation of + + + the request for this archive. Once a download has started it can not be + + + stopped and resumed, instead a new request for a zip archive would need to + + + be created. + + + The URL of this endpoint should not be considered as fixed. Instead, use + + + the [Create zip download](e://post_zip_downloads) API to request to create a + + + `zip` archive, and then follow the `download_url` field in the response to + + + this endpoint. + + :param download_url: The URL that can be used to download created `zip` archive. + Example: `https://dl.boxcloud.com/2.0/zip_downloads/29l00nfxDyHOt7RphI9zT_w==nDnZEDjY2S8iEWWCHEEiptFxwoWojjlibZjJ6geuE5xnXENDTPxzgbks_yY=/content` + :type download_url: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=download_url, + method='GET', + headers=headers_map, + response_format=ResponseFormat.BINARY, + auth=self.auth, + network_session=self.network_session, + ) + ) + return response.content + + def get_zip_download_status( + self, + status_url: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ZipDownloadStatus: + """ + Returns the download status of a `zip` archive, allowing an application to + + inspect the progress of the download as well as the number of items that + + + might have been skipped. + + + This endpoint can only be accessed once the download has started. + + + Subsequently this endpoint is valid for 12 hours from the start of the + + + download. + + + The URL of this endpoint should not be considered as fixed. Instead, use + + + the [Create zip download](e://post_zip_downloads) API to request to create a + + + `zip` archive, and then follow the `status_url` field in the response to + + + this endpoint. + + :param status_url: The URL that can be used to get the status of the `zip` archive being downloaded. + Example: `https://dl.boxcloud.com/2.0/zip_downloads/29l00nfxDyHOt7RphI9zT_w==nDnZEDjY2S8iEWWCHEEiptFxwoWojjlibZjJ6geuE5xnXENDTPxzgbks_yY=/status` + :type status_url: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=status_url, + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ZipDownloadStatus) + + def download_zip( + self, + items: List[DownloadZipItems], + *, + download_file_name: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ByteStream: + """ + Creates a zip and downloads its content + :param items: A list of items to add to the `zip` archive. These can + be folders or files. + :type items: List[DownloadZipItems] + :param download_file_name: The optional name of the `zip` archive. This name will be appended by the + `.zip` file extension, for example `January Financials.zip`., defaults to None + :type download_file_name: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'items': items, 'download_file_name': download_file_name} + zip_download_session: ZipDownload = self.create_zip_download( + items, download_file_name=download_file_name, extra_headers=extra_headers + ) + return self.get_zip_download_content( + zip_download_session.download_url, extra_headers=extra_headers + ) diff --git a/box_sdk_gen/networking/__init__.py b/box_sdk_gen/networking/__init__.py new file mode 100644 index 000000000..c12260c17 --- /dev/null +++ b/box_sdk_gen/networking/__init__.py @@ -0,0 +1,19 @@ +from box_sdk_gen.networking.box_network_client import * + +from box_sdk_gen.networking.proxy_config import * + +from box_sdk_gen.networking.network import * + +from box_sdk_gen.networking.auth import * + +from box_sdk_gen.networking.fetch_options import * + +from box_sdk_gen.networking.fetch_response import * + +from box_sdk_gen.networking.network_client import * + +from box_sdk_gen.networking.retries import * + +from box_sdk_gen.networking.base_urls import * + +from box_sdk_gen.networking.version import * diff --git a/box_sdk_gen/networking/auth.py b/box_sdk_gen/networking/auth.py new file mode 100644 index 000000000..61347ff3c --- /dev/null +++ b/box_sdk_gen/networking/auth.py @@ -0,0 +1,47 @@ +from typing import Optional + +from abc import abstractmethod + +from typing import List + +from box_sdk_gen.schemas.access_token import AccessToken + +from box_sdk_gen.networking.network import NetworkSession + + +class Authentication: + def __init__(self): + pass + + @abstractmethod + def retrieve_token( + self, *, network_session: Optional[NetworkSession] = None + ) -> AccessToken: + pass + + @abstractmethod + def refresh_token( + self, *, network_session: Optional[NetworkSession] = None + ) -> AccessToken: + pass + + @abstractmethod + def retrieve_authorization_header( + self, *, network_session: Optional[NetworkSession] = None + ) -> str: + pass + + @abstractmethod + def revoke_token(self, *, network_session: Optional[NetworkSession] = None) -> None: + pass + + @abstractmethod + def downscope_token( + self, + scopes: List[str], + *, + resource: Optional[str] = None, + shared_link: Optional[str] = None, + network_session: Optional[NetworkSession] = None + ) -> AccessToken: + pass diff --git a/box_sdk_gen/networking/base_urls.py b/box_sdk_gen/networking/base_urls.py new file mode 100644 index 000000000..6b59444f7 --- /dev/null +++ b/box_sdk_gen/networking/base_urls.py @@ -0,0 +1,29 @@ +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class BaseUrls(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'oauth_2_url': 'oauth2_url', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'oauth2_url': 'oauth_2_url', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + base_url: str = 'https://api.box.com', + upload_url: str = 'https://upload.box.com/api', + oauth_2_url: str = 'https://account.box.com/api/oauth2', + **kwargs + ): + super().__init__(**kwargs) + self.base_url = base_url + self.upload_url = upload_url + self.oauth_2_url = oauth_2_url diff --git a/box_sdk_gen/networking/box_network_client.py b/box_sdk_gen/networking/box_network_client.py new file mode 100644 index 000000000..7a4f44647 --- /dev/null +++ b/box_sdk_gen/networking/box_network_client.py @@ -0,0 +1,363 @@ +import io + +import time +from collections import OrderedDict +from dataclasses import dataclass +from typing import Optional, Dict, Union +from sys import version_info as py_version + +import requests +from requests import RequestException, Session, Response +from requests_toolbelt import MultipartEncoder + +from ..internal.logging import DataSanitizer +from .retries import BoxRetryStrategy +from ..networking.fetch_options import FetchOptions +from ..networking.fetch_response import FetchResponse +from ..box.errors import BoxAPIError, BoxSDKError, RequestInfo, ResponseInfo +from ..internal.utils import ByteStream, ResponseByteStream +from ..networking.network_client import NetworkClient +from ..serialization.json import ( + sd_to_json, + sd_to_url_params, + json_to_serialized_data, +) +from ..networking.version import __version__ + +SDK_VERSION = __version__ +USER_AGENT_HEADER = f'box-python-generated-sdk-{SDK_VERSION}' +X_BOX_UA_HEADER = ( + f'agent=box-python-generated-sdk/{SDK_VERSION}; ' + f'env=python/{py_version.major}.{py_version.minor}.{py_version.micro}' +) + + +@dataclass +class APIRequest: + method: str + url: str + headers: Dict[str, str] + params: Dict[str, str] + data: Optional[Union[str, ByteStream, MultipartEncoder]] + allow_redirects: bool = True + + +@dataclass +class APIResponse: + network_response: Optional[Response] = None + reauthentication_needed: Optional[bool] = False + raised_exception: Optional[Exception] = None + + def get_header( + self, header_name: str, default_value: Optional[str] = None + ) -> Optional[str]: + try: + return self.network_response.headers[header_name] + except (ValueError, KeyError, AttributeError): + return default_value + + +class BoxNetworkClient(NetworkClient): + def __init__(self, requests_session: Optional[Session] = None): + super().__init__() + self.requests_session = requests_session or requests.Session() + + def fetch(self, options: 'FetchOptions') -> FetchResponse: + retry_strategy = ( + options.network_session.retry_strategy + if options.network_session + else BoxRetryStrategy() + ) + data_sanitizer = ( + options.network_session.data_sanitizer + if options.network_session + else DataSanitizer() + ) + + attempt_nr = 1 + number_of_retries_on_exception = 0 + response = APIResponse() + + options_stream_position = self._get_options_stream_position(options) + multipart_streams_positions = self._get_multipart_stream_positions(options) + + while True: + request: APIRequest = self._prepare_request( + options=options, reauthenticate=response.reauthentication_needed + ) + response: APIResponse = self._make_request(request=request) + if response.network_response is not None: + attempt_for_retry = attempt_nr + network_response = response.network_response + + if options.response_format == 'binary': + fetch_response = FetchResponse( + url=network_response.url, + status=network_response.status_code, + headers=dict(response.network_response.headers), + content=ResponseByteStream( + response.network_response.iter_content(chunk_size=1024) + ), + ) + else: + fetch_response = FetchResponse( + url=network_response.url, + status=network_response.status_code, + headers=dict(response.network_response.headers), + data=(self._read_json_body(network_response.text)), + content=io.BytesIO(network_response.content), + ) + else: + number_of_retries_on_exception += 1 + attempt_for_retry = number_of_retries_on_exception + fetch_response = FetchResponse(status=0, headers={}) + + attempt_nr += 1 + should_retry = retry_strategy.should_retry( + fetch_options=options, + fetch_response=fetch_response, + attempt_number=attempt_for_retry, + ) + + if should_retry: + self._reset_options_stream( + options, options_stream_position, response.raised_exception + ) + self._reset_multipart_streams( + options, multipart_streams_positions, response.raised_exception + ) + time.sleep( + retry_strategy.retry_after( + fetch_options=options, + fetch_response=fetch_response, + attempt_number=attempt_for_retry, + ) + ) + continue + + if 200 <= fetch_response.status < 400: + return fetch_response + break + self._raise_on_unsuccessful_request( + request=request, response=response, data_sanitizer=data_sanitizer + ) + + def _prepare_request( + self, options: 'FetchOptions', reauthenticate: bool = False + ) -> APIRequest: + headers = self._prepare_headers(options, reauthenticate) + params = options.params or {} + data = self._prepare_body( + options.content_type, options.file_stream or options.data + ) + allow_redirects = options.follow_redirects + + if options.content_type: + if options.content_type == 'multipart/form-data': + fields = OrderedDict() + for part in options.multipart_data: + if part.data: + fields[part.part_name] = sd_to_json(part.data) + else: + fields[part.part_name] = ( + part.file_name or '', + part.file_stream, + part.content_type, + ) + + multipart_stream = MultipartEncoder(fields) + data = multipart_stream + headers['Content-Type'] = multipart_stream.content_type + else: + headers['Content-Type'] = options.content_type + + return APIRequest( + method=options.method, + url=options.url, + headers=headers, + params=params, + data=data, + allow_redirects=allow_redirects, + ) + + @staticmethod + def _prepare_headers( + options: 'FetchOptions', reauthenticate: bool = False + ) -> Dict[str, str]: + headers = {} + if options.network_session: + headers.update(options.network_session.additional_headers) + if options.headers: + headers.update(options.headers) + if options.auth: + if reauthenticate: + options.auth.refresh_token(network_session=options.network_session) + headers['Authorization'] = options.auth.retrieve_authorization_header( + network_session=options.network_session + ) + + headers['User-Agent'] = USER_AGENT_HEADER + headers['X-Box-UA'] = X_BOX_UA_HEADER + return headers + + @staticmethod + def _prepare_body( + content_type: str, data: Union[dict, ByteStream] + ) -> Optional[Union[str, ByteStream]]: + if ( + content_type == 'application/json' + or content_type == 'application/json-patch+json' + ): + return sd_to_json(data) if data else None + if content_type == 'application/x-www-form-urlencoded': + return sd_to_url_params(data) + if ( + content_type == 'multipart/form-data' + or content_type == 'application/octet-stream' + ): + return data + raise + + def _make_request(self, request: APIRequest) -> APIResponse: + raised_exception = None + reauthentication_needed = False + default_timeout = (5, 60) # connect, read timeout + try: + network_response = self.requests_session.request( + method=request.method, + url=request.url, + headers=request.headers, + data=request.data, + params=request.params, + allow_redirects=request.allow_redirects, + stream=True, + timeout=default_timeout, + ) + except RequestException as request_exc: + raised_exception = request_exc + network_response = None + + if 'EOF occurred in violation of protocol' in str(request_exc): + reauthentication_needed = True + + return APIResponse( + network_response=network_response, + reauthentication_needed=reauthentication_needed, + raised_exception=raised_exception, + ) + + @staticmethod + def _raise_on_unsuccessful_request( + request: APIRequest, response: APIResponse, data_sanitizer: DataSanitizer + ) -> None: + if response.raised_exception: + raise BoxSDKError( + message=str(response.raised_exception), error=response.raised_exception + ) + + network_response = response.network_response + response_json = BoxNetworkClient._read_json_body(network_response.text) + + raise BoxAPIError( + message=f'{network_response.status_code} {response_json.get("message", "")}; Request ID: {response_json.get("request_id", "")}', + request_info=RequestInfo( + method=request.method, + url=request.url, + query_params=request.params, + headers=request.headers, + body=request.data, + ), + response_info=ResponseInfo( + status_code=network_response.status_code, + headers=dict(network_response.headers), + body=response_json, + raw_body=network_response.text, + code=response_json.get("code", None), + context_info=response_json.get("context_info", {}), + request_id=response_json.get("request_id", None), + help_url=response_json.get("help_url", None), + ), + data_sanitizer=data_sanitizer, + ) + + @staticmethod + def _get_multipart_stream_positions(options: 'FetchOptions') -> dict: + multipart_streams_positions = {} + if options.multipart_data: + for part in options.multipart_data: + if part.file_stream and part.file_stream.seekable(): + multipart_streams_positions[part.part_name] = ( + part.file_stream.tell() + ) + return multipart_streams_positions + + @staticmethod + def _get_options_stream_position(options: 'FetchOptions') -> int: + filestream_position = 0 + if options.file_stream and options.file_stream.seekable(): + filestream_position = options.file_stream.tell() + return filestream_position + + @staticmethod + def _validate_seekable(stream: ByteStream, raised_exception: Optional[Exception]): + if not stream.seekable(): + raise BoxSDKError( + message='Request with non-seekable stream cannot be retried', + error=raised_exception, + ) + + @staticmethod + def _read_json_body(response_body: str) -> dict: + if not response_body: + return {} + try: + return json_to_serialized_data(response_body) + except (ValueError, TypeError): + return {} + + def _reset_stream( + self, + stream: ByteStream, + original_position: int, + raised_exception: Optional[Exception], + ): + self._validate_seekable(stream, raised_exception) + stream.seek(original_position) + + def _reset_options_stream( + self, + options: 'FetchOptions', + filestream_position: int, + raised_exception: Optional[Exception], + ): + if options.file_stream: + self._reset_stream( + options.file_stream, filestream_position, raised_exception + ) + + def _reset_multipart_streams( + self, + options: 'FetchOptions', + multipart_streams_positions: dict, + raised_exception: Optional[Exception], + ): + if not options.multipart_data: + return + + for part in options.multipart_data: + if not part.file_stream: + continue + + position = multipart_streams_positions.get(part.part_name) + # we didn't get position before sending request hence the stream must be non-seekable + if position is None: + raise BoxSDKError( + message='Request with non-seekable stream cannot be retried', + error=raised_exception, + ) + + self._reset_stream( + part.file_stream, + multipart_streams_positions[part.part_name], + raised_exception, + ) diff --git a/box_sdk_gen/networking/fetch_options.py b/box_sdk_gen/networking/fetch_options.py new file mode 100644 index 000000000..84d85f215 --- /dev/null +++ b/box_sdk_gen/networking/fetch_options.py @@ -0,0 +1,113 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from enum import Enum + +from typing import Optional + +from typing import Dict + +from typing import List + +if TYPE_CHECKING: + from box_sdk_gen.networking.auth import Authentication + +if TYPE_CHECKING: + from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import ByteStream + + +class ResponseFormat(str, Enum): + JSON = 'json' + BINARY = 'binary' + NO_CONTENT = 'no_content' + + +class MultipartItem: + def __init__( + self, + part_name: str, + *, + data: Optional[SerializedData] = None, + file_stream: Optional[ByteStream] = None, + file_name: Optional[str] = None, + content_type: Optional[str] = None, + ): + """ + :param part_name: Name of the part + :type part_name: str + :param data: Data of the part, defaults to None + :type data: Optional[SerializedData], optional + :param file_stream: File stream of the part, defaults to None + :type file_stream: Optional[ByteStream], optional + :param file_name: File name of the part, defaults to None + :type file_name: Optional[str], optional + :param content_type: Content type of the part, defaults to None + :type content_type: Optional[str], optional + """ + self.part_name = part_name + self.data = data + self.file_stream = file_stream + self.file_name = file_name + self.content_type = content_type + + +class FetchOptions: + def __init__( + self, + url: str, + method: str, + *, + params: Optional[Dict[str, str]] = None, + headers: Optional[Dict[str, str]] = None, + data: Optional[SerializedData] = None, + file_stream: Optional[ByteStream] = None, + multipart_data: Optional[List[MultipartItem]] = None, + content_type: str = 'application/json', + response_format: ResponseFormat = ResponseFormat.JSON, + auth: Optional[Authentication] = None, + network_session: Optional[NetworkSession] = None, + follow_redirects: Optional[bool] = True, + ): + """ + :param url: URL of the request + :type url: str + :param method: HTTP verb of the request + :type method: str + :param params: HTTP query parameters, defaults to None + :type params: Optional[Dict[str, str]], optional + :param headers: HTTP headers, defaults to None + :type headers: Optional[Dict[str, str]], optional + :param data: Request body of the request, defaults to None + :type data: Optional[SerializedData], optional + :param file_stream: Stream data of the request, defaults to None + :type file_stream: Optional[ByteStream], optional + :param multipart_data: Multipart data of the request, defaults to None + :type multipart_data: Optional[List[MultipartItem]], optional + :param content_type: Content type of the request body, defaults to 'application/json' + :type content_type: str, optional + :param response_format: Expected response format, defaults to ResponseFormat.JSON + :type response_format: ResponseFormat, optional + :param auth: Authentication object, defaults to None + :type auth: Optional[Authentication], optional + :param network_session: Network session object, defaults to None + :type network_session: Optional[NetworkSession], optional + :param follow_redirects: A boolean value indicate if the request should follow redirects. Defaults to True. Not supported in Browser environment., defaults to True + :type follow_redirects: Optional[bool], optional + """ + self.url = url + self.method = method + self.params = params + self.headers = headers + self.data = data + self.file_stream = file_stream + self.multipart_data = multipart_data + self.content_type = content_type + self.response_format = response_format + self.auth = auth + self.network_session = network_session + self.follow_redirects = follow_redirects diff --git a/box_sdk_gen/networking/fetch_response.py b/box_sdk_gen/networking/fetch_response.py new file mode 100644 index 000000000..48675119b --- /dev/null +++ b/box_sdk_gen/networking/fetch_response.py @@ -0,0 +1,36 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.internal.utils import ByteStream + + +class FetchResponse: + def __init__( + self, + status: int, + headers: Dict[str, str], + *, + url: Optional[str] = None, + data: Optional[SerializedData] = None, + content: Optional[ByteStream] = None + ): + """ + :param status: HTTP status code of the response + :type status: int + :param headers: HTTP headers of the response + :type headers: Dict[str, str] + :param url: URL of the response, defaults to None + :type url: Optional[str], optional + :param data: Response body of the response, defaults to None + :type data: Optional[SerializedData], optional + :param content: Streamed content of the response, defaults to None + :type content: Optional[ByteStream], optional + """ + self.status = status + self.headers = headers + self.url = url + self.data = data + self.content = content diff --git a/box_sdk_gen/networking/network.py b/box_sdk_gen/networking/network.py new file mode 100644 index 000000000..c51f8722e --- /dev/null +++ b/box_sdk_gen/networking/network.py @@ -0,0 +1,154 @@ +from typing import Dict + +from ..internal.logging import DataSanitizer +from .network_client import NetworkClient +from .box_network_client import BoxNetworkClient +from .proxy_config import ProxyConfig +from .base_urls import BaseUrls +from .retries import RetryStrategy, BoxRetryStrategy + + +class NetworkSession: + def __init__( + self, + *, + network_client: NetworkClient = None, + retry_strategy: RetryStrategy = None, + additional_headers: Dict[str, str] = None, + base_urls: BaseUrls = None, + proxy_url: str = None, + data_sanitizer: DataSanitizer = None, + ): + if additional_headers is None: + additional_headers = {} + if base_urls is None: + base_urls = BaseUrls() + if retry_strategy is None: + retry_strategy = BoxRetryStrategy() + if network_client is None: + network_client = BoxNetworkClient() + if ( + proxy_url + and hasattr(network_client, 'requests_session') + and network_client.requests_session + ): + network_client.requests_session.proxies = { + 'http': proxy_url, + 'https': proxy_url, + } + if data_sanitizer is None: + data_sanitizer = DataSanitizer() + self.additional_headers = additional_headers + self.base_urls = base_urls + self.proxy_url = proxy_url + self.network_client = network_client + self.retry_strategy = retry_strategy + self.data_sanitizer = data_sanitizer + + def with_additional_headers( + self, additional_headers: Dict[str, str] = None + ) -> 'NetworkSession': + """ + Generate a fresh network session by duplicating the existing configuration and network parameters, + while also including additional headers to be attached to every API call. + :param additional_headers: Dict of headers, which are appended to each API request + :return: a new instance of NetworkSession + """ + return NetworkSession( + network_client=self.network_client, + additional_headers={**self.additional_headers, **additional_headers}, + base_urls=self.base_urls, + proxy_url=self.proxy_url, + retry_strategy=self.retry_strategy, + data_sanitizer=self.data_sanitizer, + ) + + def with_custom_base_urls(self, base_urls: BaseUrls) -> 'NetworkSession': + """ + Generate a fresh network session by duplicating the existing configuration and network parameters, + while also including additional base urls to be used for each API call. + :param base_urls: Dict of base urls, which are appended to each API request + :return: a new instance of NetworkSession + """ + return NetworkSession( + network_client=self.network_client, + additional_headers=self.additional_headers, + base_urls=base_urls, + proxy_url=self.proxy_url, + retry_strategy=self.retry_strategy, + data_sanitizer=self.data_sanitizer, + ) + + def with_proxy(self, config: ProxyConfig) -> 'NetworkSession': + """ + Generate a fresh network session by duplicating the existing configuration and network parameters, + while also including a proxy to be used for each API call. + :param config: ProxyConfig object, which contains the proxy url, username, and password + :return: a new instance of NetworkSession + """ + if not config.url or not config.url.startswith("http"): + raise ValueError("Invalid proxy URL provided") + + proxy_host = config.url.split("//")[1] + proxy_auth = ( + f"{config.username}:{config.password}@" + if config.username and config.password + else "" + ) + proxy_url = f"http://{proxy_auth}{proxy_host}" + return NetworkSession( + network_client=self.network_client, + additional_headers=self.additional_headers, + base_urls=self.base_urls, + proxy_url=proxy_url, + retry_strategy=self.retry_strategy, + data_sanitizer=self.data_sanitizer, + ) + + def with_network_client(self, network_client: NetworkClient) -> 'NetworkSession': + """ + Generate a fresh network session by duplicating the existing configuration and network parameters, + while also including a new network client to be used for each API call. + :param network_client: NetworkClient object, which contains the fetch method + :return: a new instance of NetworkSession + """ + return NetworkSession( + network_client=network_client, + additional_headers=self.additional_headers, + base_urls=self.base_urls, + proxy_url=self.proxy_url, + retry_strategy=self.retry_strategy, + data_sanitizer=self.data_sanitizer, + ) + + def with_retry_strategy(self, retry_strategy: RetryStrategy) -> 'NetworkSession': + """ + Generate a fresh network session by duplicating the existing configuration and network parameters, + while also including a new retry options to be used for each API call. + :param retry_strategy: RetryStrategy object, which contains the retry logic + :return: a new instance of NetworkSession + """ + return NetworkSession( + network_client=self.network_client, + additional_headers=self.additional_headers, + base_urls=self.base_urls, + proxy_url=self.proxy_url, + retry_strategy=retry_strategy, + data_sanitizer=self.data_sanitizer, + ) + + def with_data_sanitizer(self, data_sanitizer: DataSanitizer) -> 'NetworkSession': + """ + Generate a fresh network session by duplicating the existing configuration and network parameters, + while also applying data sanitizer to sanitize sensitive data for logging. + :param data_sanitizer: + :return: + """ + return NetworkSession( + network_client=self.network_client, + additional_headers=self.additional_headers, + base_urls=self.base_urls, + proxy_url=self.proxy_url, + retry_strategy=self.retry_strategy, + data_sanitizer=data_sanitizer, + ) diff --git a/box_sdk_gen/networking/network_client.py b/box_sdk_gen/networking/network_client.py new file mode 100644 index 000000000..789354a42 --- /dev/null +++ b/box_sdk_gen/networking/network_client.py @@ -0,0 +1,14 @@ +from abc import abstractmethod + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + + +class NetworkClient: + def __init__(self): + pass + + @abstractmethod + def fetch(self, options: FetchOptions) -> FetchResponse: + pass diff --git a/box_sdk_gen/networking/proxy_config.py b/box_sdk_gen/networking/proxy_config.py new file mode 100644 index 000000000..05d6f1298 --- /dev/null +++ b/box_sdk_gen/networking/proxy_config.py @@ -0,0 +1,14 @@ +from typing import Optional + + +class ProxyConfig: + def __init__( + self, + url: str, + *, + username: Optional[str] = None, + password: Optional[str] = None + ): + self.url = url + self.username = username + self.password = password diff --git a/box_sdk_gen/networking/retries.py b/box_sdk_gen/networking/retries.py new file mode 100644 index 000000000..30539dcdd --- /dev/null +++ b/box_sdk_gen/networking/retries.py @@ -0,0 +1,94 @@ +from abc import abstractmethod + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import random + + +class RetryStrategy: + def __init__(self): + pass + + @abstractmethod + def should_retry( + self, + fetch_options: FetchOptions, + fetch_response: FetchResponse, + attempt_number: int, + ) -> bool: + pass + + @abstractmethod + def retry_after( + self, + fetch_options: FetchOptions, + fetch_response: FetchResponse, + attempt_number: int, + ) -> float: + pass + + +class BoxRetryStrategy(RetryStrategy): + def __init__( + self, + *, + max_attempts: int = 5, + retry_randomization_factor: float = 0.5, + retry_base_interval: float = 1, + max_retries_on_exception: int = 2, + **kwargs + ): + super().__init__(**kwargs) + self.max_attempts = max_attempts + self.retry_randomization_factor = retry_randomization_factor + self.retry_base_interval = retry_base_interval + self.max_retries_on_exception = max_retries_on_exception + + def should_retry( + self, + fetch_options: FetchOptions, + fetch_response: FetchResponse, + attempt_number: int, + ) -> bool: + if fetch_response.status == 0: + return attempt_number <= self.max_retries_on_exception + is_successful: bool = ( + fetch_response.status >= 200 and fetch_response.status < 400 + ) + retry_after_header: str = fetch_response.headers.get('Retry-After') + is_accepted_with_retry_after: bool = ( + fetch_response.status == 202 and not retry_after_header == None + ) + if attempt_number >= self.max_attempts: + return False + if is_accepted_with_retry_after: + return True + if fetch_response.status >= 500: + return True + if fetch_response.status == 429: + return True + if fetch_response.status == 401 and not fetch_options.auth == None: + fetch_options.auth.refresh_token( + network_session=fetch_options.network_session + ) + return True + if is_successful: + return False + return False + + def retry_after( + self, + fetch_options: FetchOptions, + fetch_response: FetchResponse, + attempt_number: int, + ) -> float: + retry_after_header: str = fetch_response.headers.get('Retry-After') + if not retry_after_header == None: + return float(retry_after_header) + randomization: float = random( + 1 - self.retry_randomization_factor, 1 + self.retry_randomization_factor + ) + exponential: float = 2**attempt_number + return (exponential * self.retry_base_interval) * randomization diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py new file mode 100644 index 000000000..b794fd409 --- /dev/null +++ b/box_sdk_gen/networking/version.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/box_sdk_gen/parameters/__init__.py b/box_sdk_gen/parameters/__init__.py new file mode 100644 index 000000000..6ee9acb7e --- /dev/null +++ b/box_sdk_gen/parameters/__init__.py @@ -0,0 +1 @@ +from box_sdk_gen.parameters.v2025_r0 import * diff --git a/box_sdk_gen/parameters/v2025_r0/__init__.py b/box_sdk_gen/parameters/v2025_r0/__init__.py new file mode 100644 index 000000000..72c18cf87 --- /dev/null +++ b/box_sdk_gen/parameters/v2025_r0/__init__.py @@ -0,0 +1 @@ +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import * diff --git a/box_sdk_gen/parameters/v2025_r0/box_version_header_v2025_r0.py b/box_sdk_gen/parameters/v2025_r0/box_version_header_v2025_r0.py new file mode 100644 index 000000000..ed66cfe5c --- /dev/null +++ b/box_sdk_gen/parameters/v2025_r0/box_version_header_v2025_r0.py @@ -0,0 +1,7 @@ +from enum import Enum + +from box_sdk_gen.box.errors import BoxSDKError + + +class BoxVersionHeaderV2025R0(str, Enum): + _2025_0 = '2025.0' diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py new file mode 100644 index 000000000..8c051af87 --- /dev/null +++ b/box_sdk_gen/schemas/__init__.py @@ -0,0 +1,523 @@ +from box_sdk_gen.schemas.ai_agent_info import * + +from box_sdk_gen.schemas.ai_response import * + +from box_sdk_gen.schemas.ai_agent_reference import * + +from box_sdk_gen.schemas.ai_citation import * + +from box_sdk_gen.schemas.ai_response_full import * + +from box_sdk_gen.schemas.ai_dialogue_history import * + +from box_sdk_gen.schemas.ai_extract_response import * + +from box_sdk_gen.schemas.ai_extract_structured_response import * + +from box_sdk_gen.schemas.ai_item_base import * + +from box_sdk_gen.schemas.ai_item_ask import * + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import * + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import * + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import * + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import * + +from box_sdk_gen.schemas.ai_llm_endpoint_params import * + +from box_sdk_gen.schemas.ai_agent_spreadsheet_tool import * + +from box_sdk_gen.schemas.ai_studio_agent_spreadsheet_tool import * + +from box_sdk_gen.schemas.ai_studio_agent_spreadsheet_tool_response import * + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import * + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_text_gen import * + +from box_sdk_gen.schemas.ai_agent_long_text_tool_text_gen import * + +from box_sdk_gen.schemas.ai_agent_basic_gen_tool import * + +from box_sdk_gen.schemas.ai_studio_agent_basic_gen_tool import * + +from box_sdk_gen.schemas.ai_studio_agent_text_gen import * + +from box_sdk_gen.schemas.ai_studio_agent_basic_gen_tool_response import * + +from box_sdk_gen.schemas.ai_studio_agent_text_gen_response import * + +from box_sdk_gen.schemas.ai_agent_text_gen import * + +from box_sdk_gen.schemas.ai_text_gen import * + +from box_sdk_gen.schemas.ai_agent_basic_text_tool import * + +from box_sdk_gen.schemas.ai_studio_agent_basic_text_tool import * + +from box_sdk_gen.schemas.ai_studio_agent_basic_text_tool_response import * + +from box_sdk_gen.schemas.ai_agent_long_text_tool import * + +from box_sdk_gen.schemas.ai_studio_agent_long_text_tool import * + +from box_sdk_gen.schemas.ai_studio_agent_long_text_tool_response import * + +from box_sdk_gen.schemas.ai_studio_agent_extract_response import * + +from box_sdk_gen.schemas.ai_studio_agent_ask_response import * + +from box_sdk_gen.schemas.ai_studio_agent_extract import * + +from box_sdk_gen.schemas.ai_studio_agent_ask import * + +from box_sdk_gen.schemas.ai_agent_extract_structured import * + +from box_sdk_gen.schemas.ai_extract_structured import * + +from box_sdk_gen.schemas.ai_agent_extract import * + +from box_sdk_gen.schemas.ai_extract import * + +from box_sdk_gen.schemas.ai_agent_ask import * + +from box_sdk_gen.schemas.ai_ask import * + +from box_sdk_gen.schemas.app_item import * + +from box_sdk_gen.schemas.classification import * + +from box_sdk_gen.schemas.classification_template import * + +from box_sdk_gen.schemas.client_error import * + +from box_sdk_gen.schemas.collaboration_allowlist_entry import * + +from box_sdk_gen.schemas.collaboration_allowlist_entries import * + +from box_sdk_gen.schemas.collaborator_variable import * + +from box_sdk_gen.schemas.collection import * + +from box_sdk_gen.schemas.collections import * + +from box_sdk_gen.schemas.comment_base import * + +from box_sdk_gen.schemas.completion_rule_variable import * + +from box_sdk_gen.schemas.email_alias import * + +from box_sdk_gen.schemas.email_aliases import * + +from box_sdk_gen.schemas.enterprise_base import * + +from box_sdk_gen.schemas.file_base import * + +from box_sdk_gen.schemas.file_request_update_request import * + +from box_sdk_gen.schemas.file_request_copy_request import * + +from box_sdk_gen.schemas.file_version_base import * + +from box_sdk_gen.schemas.file_version_mini import * + +from box_sdk_gen.schemas.file_mini import * + +from box_sdk_gen.schemas.files_under_retention import * + +from box_sdk_gen.schemas.files_on_hold import * + +from box_sdk_gen.schemas.file_conflict import * + +from box_sdk_gen.schemas.conflict_error import * + +from box_sdk_gen.schemas.folder_base import * + +from box_sdk_gen.schemas.folder_mini import * + +from box_sdk_gen.schemas.file_or_folder_scope import * + +from box_sdk_gen.schemas.access_token import * + +from box_sdk_gen.schemas.folder_reference import * + +from box_sdk_gen.schemas.generic_source import * + +from box_sdk_gen.schemas.group_base import * + +from box_sdk_gen.schemas.group_mini import * + +from box_sdk_gen.schemas.group import * + +from box_sdk_gen.schemas.group_full import * + +from box_sdk_gen.schemas.groups import * + +from box_sdk_gen.schemas.integration_mapping_base import * + +from box_sdk_gen.schemas.integration_mapping_box_item_slack import * + +from box_sdk_gen.schemas.integration_mapping_partner_item_slack import * + +from box_sdk_gen.schemas.integration_mapping_partner_item_teams import * + +from box_sdk_gen.schemas.integration_mapping_teams import * + +from box_sdk_gen.schemas.integration_mappings_teams import * + +from box_sdk_gen.schemas.integration_mapping_partner_item_teams_create_request import * + +from box_sdk_gen.schemas.integration_mapping_teams_create_request import * + +from box_sdk_gen.schemas.integration_mapping_slack_options import * + +from box_sdk_gen.schemas.integration_mapping_slack_create_request import * + +from box_sdk_gen.schemas.keyword_skill_card import * + +from box_sdk_gen.schemas.legal_hold_policy_mini import * + +from box_sdk_gen.schemas.legal_hold_policy_assignment_base import * + +from box_sdk_gen.schemas.metadata_base import * + +from box_sdk_gen.schemas.metadata import * + +from box_sdk_gen.schemas.metadatas import * + +from box_sdk_gen.schemas.metadata_full import * + +from box_sdk_gen.schemas.metadata_cascade_policy import * + +from box_sdk_gen.schemas.metadata_cascade_policies import * + +from box_sdk_gen.schemas.metadata_error import * + +from box_sdk_gen.schemas.metadata_field_filter_date_range import * + +from box_sdk_gen.schemas.metadata_field_filter_float_range import * + +from box_sdk_gen.schemas.metadata_filter import * + +from box_sdk_gen.schemas.metadata_instance_value import * + +from box_sdk_gen.schemas.metadata_query import * + +from box_sdk_gen.schemas.metadata_query_index import * + +from box_sdk_gen.schemas.metadata_template import * + +from box_sdk_gen.schemas.metadata_templates import * + +from box_sdk_gen.schemas.o_auth_2_error import * + +from box_sdk_gen.schemas.post_o_auth_2_revoke import * + +from box_sdk_gen.schemas.post_o_auth_2_token import * + +from box_sdk_gen.schemas.post_o_auth_2_token_refresh_access_token import * + +from box_sdk_gen.schemas.realtime_server import * + +from box_sdk_gen.schemas.realtime_servers import * + +from box_sdk_gen.schemas.retention_policy_base import * + +from box_sdk_gen.schemas.retention_policy_mini import * + +from box_sdk_gen.schemas.file_version_retention import * + +from box_sdk_gen.schemas.file_version_retentions import * + +from box_sdk_gen.schemas.retention_policy_assignment_base import * + +from box_sdk_gen.schemas.role_variable import * + +from box_sdk_gen.schemas.outcome import * + +from box_sdk_gen.schemas.session_termination_message import * + +from box_sdk_gen.schemas.shield_information_barrier_base import * + +from box_sdk_gen.schemas.shield_information_barrier_reference import * + +from box_sdk_gen.schemas.shield_information_barrier_report_base import * + +from box_sdk_gen.schemas.shield_information_barrier_report_details import * + +from box_sdk_gen.schemas.shield_information_barrier_segment_member_base import * + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction_base import * + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction_mini import * + +from box_sdk_gen.schemas.sign_request_create_signer import * + +from box_sdk_gen.schemas.sign_request_prefill_tag import * + +from box_sdk_gen.schemas.template_signer_input import * + +from box_sdk_gen.schemas.template_signer import * + +from box_sdk_gen.schemas.sign_template import * + +from box_sdk_gen.schemas.sign_templates import * + +from box_sdk_gen.schemas.sign_request_signer_input import * + +from box_sdk_gen.schemas.sign_request_signer import * + +from box_sdk_gen.schemas.sign_request_base import * + +from box_sdk_gen.schemas.sign_request_create_request import * + +from box_sdk_gen.schemas.sign_request import * + +from box_sdk_gen.schemas.sign_requests import * + +from box_sdk_gen.schemas.status_skill_card import * + +from box_sdk_gen.schemas.storage_policy_mini import * + +from box_sdk_gen.schemas.storage_policy_assignment import * + +from box_sdk_gen.schemas.storage_policy_assignments import * + +from box_sdk_gen.schemas.storage_policy import * + +from box_sdk_gen.schemas.storage_policies import * + +from box_sdk_gen.schemas.terms_of_service_base import * + +from box_sdk_gen.schemas.terms_of_service import * + +from box_sdk_gen.schemas.terms_of_services import * + +from box_sdk_gen.schemas.timeline_skill_card import * + +from box_sdk_gen.schemas.tracking_code import * + +from box_sdk_gen.schemas.transcript_skill_card import * + +from box_sdk_gen.schemas.skill_cards_metadata import * + +from box_sdk_gen.schemas.upload_part_mini import * + +from box_sdk_gen.schemas.upload_part import * + +from box_sdk_gen.schemas.uploaded_part import * + +from box_sdk_gen.schemas.upload_parts import * + +from box_sdk_gen.schemas.upload_session import * + +from box_sdk_gen.schemas.upload_url import * + +from box_sdk_gen.schemas.user_base import * + +from box_sdk_gen.schemas.user_integration_mappings import * + +from box_sdk_gen.schemas.integration_mapping import * + +from box_sdk_gen.schemas.integration_mappings import * + +from box_sdk_gen.schemas.user_mini import * + +from box_sdk_gen.schemas.user import * + +from box_sdk_gen.schemas.user_full import * + +from box_sdk_gen.schemas.users import * + +from box_sdk_gen.schemas.trash_web_link_restored import * + +from box_sdk_gen.schemas.trash_web_link import * + +from box_sdk_gen.schemas.trash_folder_restored import * + +from box_sdk_gen.schemas.trash_folder import * + +from box_sdk_gen.schemas.trash_file_restored import * + +from box_sdk_gen.schemas.trash_file import * + +from box_sdk_gen.schemas.terms_of_service_user_status import * + +from box_sdk_gen.schemas.terms_of_service_user_statuses import * + +from box_sdk_gen.schemas.task_assignment import * + +from box_sdk_gen.schemas.task_assignments import * + +from box_sdk_gen.schemas.task import * + +from box_sdk_gen.schemas.tasks import * + +from box_sdk_gen.schemas.retention_policy_assignment import * + +from box_sdk_gen.schemas.retention_policy_assignments import * + +from box_sdk_gen.schemas.retention_policy import * + +from box_sdk_gen.schemas.retention_policies import * + +from box_sdk_gen.schemas.legal_hold_policy import * + +from box_sdk_gen.schemas.legal_hold_policies import * + +from box_sdk_gen.schemas.invite import * + +from box_sdk_gen.schemas.group_membership import * + +from box_sdk_gen.schemas.group_memberships import * + +from box_sdk_gen.schemas.file_version import * + +from box_sdk_gen.schemas.file_version_full import * + +from box_sdk_gen.schemas.file_versions import * + +from box_sdk_gen.schemas.file_request import * + +from box_sdk_gen.schemas.file import * + +from box_sdk_gen.schemas.file_full import * + +from box_sdk_gen.schemas.files import * + +from box_sdk_gen.schemas.event_source import * + +from box_sdk_gen.schemas.device_pinner import * + +from box_sdk_gen.schemas.device_pinners import * + +from box_sdk_gen.schemas.comment import * + +from box_sdk_gen.schemas.comment_full import * + +from box_sdk_gen.schemas.comments import * + +from box_sdk_gen.schemas.collaboration_allowlist_exempt_target import * + +from box_sdk_gen.schemas.collaboration_allowlist_exempt_targets import * + +from box_sdk_gen.schemas.app_item_event_source import * + +from box_sdk_gen.schemas.user_collaborations import * + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction import * + +from box_sdk_gen.schemas.shield_information_barrier_segment_restrictions import * + +from box_sdk_gen.schemas.shield_information_barrier_segment_member_mini import * + +from box_sdk_gen.schemas.shield_information_barrier_segment_member import * + +from box_sdk_gen.schemas.shield_information_barrier_segment_members import * + +from box_sdk_gen.schemas.shield_information_barrier_segment import * + +from box_sdk_gen.schemas.shield_information_barrier_segments import * + +from box_sdk_gen.schemas.shield_information_barrier_report import * + +from box_sdk_gen.schemas.shield_information_barrier_reports import * + +from box_sdk_gen.schemas.shield_information_barrier import * + +from box_sdk_gen.schemas.shield_information_barriers import * + +from box_sdk_gen.schemas.folder_lock import * + +from box_sdk_gen.schemas.folder_locks import * + +from box_sdk_gen.schemas.ai_agent_allowed_entity import * + +from box_sdk_gen.schemas.create_ai_agent import * + +from box_sdk_gen.schemas.ai_single_agent_response import * + +from box_sdk_gen.schemas.ai_single_agent_response_full import * + +from box_sdk_gen.schemas.ai_multiple_agent_response import * + +from box_sdk_gen.schemas.user_avatar import * + +from box_sdk_gen.schemas.watermark import * + +from box_sdk_gen.schemas.web_link_base import * + +from box_sdk_gen.schemas.web_link_mini import * + +from box_sdk_gen.schemas.web_link import * + +from box_sdk_gen.schemas.items_offset_paginated import * + +from box_sdk_gen.schemas.items import * + +from box_sdk_gen.schemas.folder import * + +from box_sdk_gen.schemas.legal_hold_policy_assignment import * + +from box_sdk_gen.schemas.legal_hold_policy_assignments import * + +from box_sdk_gen.schemas.file_version_legal_hold import * + +from box_sdk_gen.schemas.file_version_legal_holds import * + +from box_sdk_gen.schemas.folder_full import * + +from box_sdk_gen.schemas.search_results import * + +from box_sdk_gen.schemas.search_result_with_shared_link import * + +from box_sdk_gen.schemas.search_results_with_shared_links import * + +from box_sdk_gen.schemas.recent_item import * + +from box_sdk_gen.schemas.recent_items import * + +from box_sdk_gen.schemas.metadata_query_results import * + +from box_sdk_gen.schemas.event import * + +from box_sdk_gen.schemas.skill_invocation import * + +from box_sdk_gen.schemas.events import * + +from box_sdk_gen.schemas.collaboration import * + +from box_sdk_gen.schemas.collaborations_offset_paginated import * + +from box_sdk_gen.schemas.collaborations import * + +from box_sdk_gen.schemas.app_item_association import * + +from box_sdk_gen.schemas.app_item_associations import * + +from box_sdk_gen.schemas.webhook_mini import * + +from box_sdk_gen.schemas.webhooks import * + +from box_sdk_gen.schemas.webhook import * + +from box_sdk_gen.schemas.webhook_invocation import * + +from box_sdk_gen.schemas.workflow_mini import * + +from box_sdk_gen.schemas.workflow import * + +from box_sdk_gen.schemas.workflows import * + +from box_sdk_gen.schemas.workflow_full import * + +from box_sdk_gen.schemas.zip_download import * + +from box_sdk_gen.schemas.zip_download_request import * + +from box_sdk_gen.schemas.zip_download_status import * + +from box_sdk_gen.schemas.v2025_r0 import * diff --git a/box_sdk_gen/schemas/access_token.py b/box_sdk_gen/schemas/access_token.py new file mode 100644 index 000000000..2b4082048 --- /dev/null +++ b/box_sdk_gen/schemas/access_token.py @@ -0,0 +1,60 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_or_folder_scope import FileOrFolderScope + +from box_sdk_gen.box.errors import BoxSDKError + + +class AccessTokenTokenTypeField(str, Enum): + BEARER = 'bearer' + + +class AccessTokenIssuedTokenTypeField(str, Enum): + URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ACCESS_TOKEN = ( + 'urn:ietf:params:oauth:token-type:access_token' + ) + + +class AccessToken(BaseObject): + def __init__( + self, + *, + access_token: Optional[str] = None, + expires_in: Optional[int] = None, + token_type: Optional[AccessTokenTokenTypeField] = None, + restricted_to: Optional[List[FileOrFolderScope]] = None, + refresh_token: Optional[str] = None, + issued_token_type: Optional[AccessTokenIssuedTokenTypeField] = None, + **kwargs + ): + """ + :param access_token: The requested access token., defaults to None + :type access_token: Optional[str], optional + :param expires_in: The time in seconds by which this token will expire., defaults to None + :type expires_in: Optional[int], optional + :param token_type: The type of access token returned., defaults to None + :type token_type: Optional[AccessTokenTokenTypeField], optional + :param restricted_to: The permissions that this access token permits, + providing a list of resources (files, folders, etc) + and the scopes permitted for each of those resources., defaults to None + :type restricted_to: Optional[List[FileOrFolderScope]], optional + :param refresh_token: The refresh token for this access token, which can be used + to request a new access token when the current one expires., defaults to None + :type refresh_token: Optional[str], optional + :param issued_token_type: The type of downscoped access token returned. This is only + returned if an access token has been downscoped., defaults to None + :type issued_token_type: Optional[AccessTokenIssuedTokenTypeField], optional + """ + super().__init__(**kwargs) + self.access_token = access_token + self.expires_in = expires_in + self.token_type = token_type + self.restricted_to = restricted_to + self.refresh_token = refresh_token + self.issued_token_type = issued_token_type diff --git a/box_sdk_gen/schemas/ai_agent_allowed_entity.py b/box_sdk_gen/schemas/ai_agent_allowed_entity.py new file mode 100644 index 000000000..202cf8649 --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_allowed_entity.py @@ -0,0 +1,9 @@ +from typing import Union + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.schemas.group_base import GroupBase + +from box_sdk_gen.box.errors import BoxSDKError + +AiAgentAllowedEntity = Union[UserBase, GroupBase] diff --git a/box_sdk_gen/schemas/ai_agent_ask.py b/box_sdk_gen/schemas/ai_agent_ask.py new file mode 100644 index 000000000..1193998b5 --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_ask.py @@ -0,0 +1,48 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_agent_long_text_tool import AiAgentLongTextTool + +from box_sdk_gen.schemas.ai_agent_basic_text_tool import AiAgentBasicTextTool + +from box_sdk_gen.schemas.ai_agent_spreadsheet_tool import AiAgentSpreadsheetTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentAskTypeField(str, Enum): + AI_AGENT_ASK = 'ai_agent_ask' + + +class AiAgentAsk(BaseObject): + _discriminator = 'type', {'ai_agent_ask'} + + def __init__( + self, + *, + type: AiAgentAskTypeField = AiAgentAskTypeField.AI_AGENT_ASK, + long_text: Optional[AiAgentLongTextTool] = None, + basic_text: Optional[AiAgentBasicTextTool] = None, + spreadsheet: Optional[AiAgentSpreadsheetTool] = None, + long_text_multi: Optional[AiAgentLongTextTool] = None, + basic_text_multi: Optional[AiAgentBasicTextTool] = None, + basic_image: Optional[AiAgentBasicTextTool] = None, + basic_image_multi: Optional[AiAgentBasicTextTool] = None, + **kwargs + ): + """ + :param type: The type of AI agent used to handle queries., defaults to AiAgentAskTypeField.AI_AGENT_ASK + :type type: AiAgentAskTypeField, optional + """ + super().__init__(**kwargs) + self.type = type + self.long_text = long_text + self.basic_text = basic_text + self.spreadsheet = spreadsheet + self.long_text_multi = long_text_multi + self.basic_text_multi = basic_text_multi + self.basic_image = basic_image + self.basic_image_multi = basic_image_multi diff --git a/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py b/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py new file mode 100644 index 000000000..6aef8bb0c --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py @@ -0,0 +1,69 @@ +from typing import Optional + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_text_gen import ( + AiAgentBasicTextToolTextGen, +) + +from box_sdk_gen.schemas.ai_agent_long_text_tool_text_gen import ( + AiAgentLongTextToolTextGenEmbeddingsField, +) + +from box_sdk_gen.schemas.ai_agent_long_text_tool_text_gen import ( + AiAgentLongTextToolTextGen, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentBasicGenTool(AiAgentLongTextToolTextGen): + def __init__( + self, + *, + content_template: Optional[str] = None, + embeddings: Optional[AiAgentLongTextToolTextGenEmbeddingsField] = None, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param content_template: How the content should be included in a request to the LLM. + Input for `{content}` is optional, depending on the use., defaults to None + :type content_template: Optional[str], optional + :param system_message: System messages aim at helping the LLM understand its role and what it is supposed to do. + The input for `{current_date}` is optional, depending on the use., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + + When using the `prompt_template` parameter, you **must include** input for `{user_question}`. + Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + embeddings=embeddings, + system_message=system_message, + prompt_template=prompt_template, + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.content_template = content_template diff --git a/box_sdk_gen/schemas/ai_agent_basic_text_tool.py b/box_sdk_gen/schemas/ai_agent_basic_text_tool.py new file mode 100644 index 000000000..5c48ae509 --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_basic_text_tool.py @@ -0,0 +1,48 @@ +from typing import Optional + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentBasicTextTool(AiAgentBasicTextToolBase): + def __init__( + self, + *, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param system_message: System messages try to help the LLM "understand" its role and what it is supposed to do., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. + `{current_date}` is optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.system_message = system_message + self.prompt_template = prompt_template diff --git a/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py b/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py new file mode 100644 index 000000000..0090d4218 --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py @@ -0,0 +1,36 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentBasicTextToolBase(BaseObject): + def __init__( + self, + *, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__(**kwargs) + self.model = model + self.num_tokens_for_completion = num_tokens_for_completion + self.llm_endpoint_params = llm_endpoint_params diff --git a/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py b/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py new file mode 100644 index 000000000..2d9a73f01 --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py @@ -0,0 +1,50 @@ +from typing import Optional + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentBasicTextToolTextGen(AiAgentBasicTextToolBase): + def __init__( + self, + *, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param system_message: System messages aim at helping the LLM understand its role and what it is supposed to do. + The input for `{current_date}` is optional, depending on the use., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + + When using the `prompt_template` parameter, you **must include** input for `{user_question}`. + Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.system_message = system_message + self.prompt_template = prompt_template diff --git a/box_sdk_gen/schemas/ai_agent_extract.py b/box_sdk_gen/schemas/ai_agent_extract.py new file mode 100644 index 000000000..02a2e34ce --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_extract.py @@ -0,0 +1,38 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_agent_long_text_tool import AiAgentLongTextTool + +from box_sdk_gen.schemas.ai_agent_basic_text_tool import AiAgentBasicTextTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentExtractTypeField(str, Enum): + AI_AGENT_EXTRACT = 'ai_agent_extract' + + +class AiAgentExtract(BaseObject): + _discriminator = 'type', {'ai_agent_extract'} + + def __init__( + self, + *, + type: AiAgentExtractTypeField = AiAgentExtractTypeField.AI_AGENT_EXTRACT, + long_text: Optional[AiAgentLongTextTool] = None, + basic_text: Optional[AiAgentBasicTextTool] = None, + basic_image: Optional[AiAgentBasicTextTool] = None, + **kwargs + ): + """ + :param type: The type of AI agent to be used for extraction., defaults to AiAgentExtractTypeField.AI_AGENT_EXTRACT + :type type: AiAgentExtractTypeField, optional + """ + super().__init__(**kwargs) + self.type = type + self.long_text = long_text + self.basic_text = basic_text + self.basic_image = basic_image diff --git a/box_sdk_gen/schemas/ai_agent_extract_structured.py b/box_sdk_gen/schemas/ai_agent_extract_structured.py new file mode 100644 index 000000000..142ae9961 --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_extract_structured.py @@ -0,0 +1,38 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_agent_long_text_tool import AiAgentLongTextTool + +from box_sdk_gen.schemas.ai_agent_basic_text_tool import AiAgentBasicTextTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentExtractStructuredTypeField(str, Enum): + AI_AGENT_EXTRACT_STRUCTURED = 'ai_agent_extract_structured' + + +class AiAgentExtractStructured(BaseObject): + _discriminator = 'type', {'ai_agent_extract_structured'} + + def __init__( + self, + *, + type: AiAgentExtractStructuredTypeField = AiAgentExtractStructuredTypeField.AI_AGENT_EXTRACT_STRUCTURED, + long_text: Optional[AiAgentLongTextTool] = None, + basic_text: Optional[AiAgentBasicTextTool] = None, + basic_image: Optional[AiAgentBasicTextTool] = None, + **kwargs + ): + """ + :param type: The type of AI agent to be used for extraction., defaults to AiAgentExtractStructuredTypeField.AI_AGENT_EXTRACT_STRUCTURED + :type type: AiAgentExtractStructuredTypeField, optional + """ + super().__init__(**kwargs) + self.type = type + self.long_text = long_text + self.basic_text = basic_text + self.basic_image = basic_image diff --git a/box_sdk_gen/schemas/ai_agent_info.py b/box_sdk_gen/schemas/ai_agent_info.py new file mode 100644 index 000000000..cca80e8f4 --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_info.py @@ -0,0 +1,49 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentInfoModelsField(BaseObject): + def __init__( + self, + *, + name: Optional[str] = None, + provider: Optional[str] = None, + supported_purpose: Optional[str] = None, + **kwargs + ): + """ + :param name: The name of the model used for the request., defaults to None + :type name: Optional[str], optional + :param provider: The provider that owns the model used for the request., defaults to None + :type provider: Optional[str], optional + :param supported_purpose: The supported purpose utilized by the model used for the request., defaults to None + :type supported_purpose: Optional[str], optional + """ + super().__init__(**kwargs) + self.name = name + self.provider = provider + self.supported_purpose = supported_purpose + + +class AiAgentInfo(BaseObject): + def __init__( + self, + *, + models: Optional[List[AiAgentInfoModelsField]] = None, + processor: Optional[str] = None, + **kwargs + ): + """ + :param models: The models used for the request., defaults to None + :type models: Optional[List[AiAgentInfoModelsField]], optional + :param processor: The processor used for the request., defaults to None + :type processor: Optional[str], optional + """ + super().__init__(**kwargs) + self.models = models + self.processor = processor diff --git a/box_sdk_gen/schemas/ai_agent_long_text_tool.py b/box_sdk_gen/schemas/ai_agent_long_text_tool.py new file mode 100644 index 000000000..57773078c --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_long_text_tool.py @@ -0,0 +1,90 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.schemas.ai_agent_basic_text_tool import AiAgentBasicTextTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentLongTextToolEmbeddingsStrategyField(BaseObject): + def __init__( + self, + *, + id: Optional[str] = None, + num_tokens_per_chunk: Optional[int] = None, + **kwargs + ): + """ + :param id: The strategy used for the AI agent for calculating embeddings., defaults to None + :type id: Optional[str], optional + :param num_tokens_per_chunk: The number of tokens per chunk., defaults to None + :type num_tokens_per_chunk: Optional[int], optional + """ + super().__init__(**kwargs) + self.id = id + self.num_tokens_per_chunk = num_tokens_per_chunk + + +class AiAgentLongTextToolEmbeddingsField(BaseObject): + def __init__( + self, + *, + model: Optional[str] = None, + strategy: Optional[AiAgentLongTextToolEmbeddingsStrategyField] = None, + **kwargs + ): + """ + :param model: The model used for the AI agent for calculating embeddings., defaults to None + :type model: Optional[str], optional + """ + super().__init__(**kwargs) + self.model = model + self.strategy = strategy + + +class AiAgentLongTextTool(AiAgentBasicTextTool): + def __init__( + self, + *, + embeddings: Optional[AiAgentLongTextToolEmbeddingsField] = None, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param system_message: System messages try to help the LLM "understand" its role and what it is supposed to do., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. + `{current_date}` is optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + system_message=system_message, + prompt_template=prompt_template, + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.embeddings = embeddings diff --git a/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py b/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py new file mode 100644 index 000000000..445162945 --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py @@ -0,0 +1,94 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_text_gen import ( + AiAgentBasicTextToolTextGen, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentLongTextToolTextGenEmbeddingsStrategyField(BaseObject): + def __init__( + self, + *, + id: Optional[str] = None, + num_tokens_per_chunk: Optional[int] = None, + **kwargs + ): + """ + :param id: The strategy used for the AI agent for calculating embeddings., defaults to None + :type id: Optional[str], optional + :param num_tokens_per_chunk: The number of tokens per chunk., defaults to None + :type num_tokens_per_chunk: Optional[int], optional + """ + super().__init__(**kwargs) + self.id = id + self.num_tokens_per_chunk = num_tokens_per_chunk + + +class AiAgentLongTextToolTextGenEmbeddingsField(BaseObject): + def __init__( + self, + *, + model: Optional[str] = None, + strategy: Optional[AiAgentLongTextToolTextGenEmbeddingsStrategyField] = None, + **kwargs + ): + """ + :param model: The model used for the AI agent for calculating embeddings., defaults to None + :type model: Optional[str], optional + """ + super().__init__(**kwargs) + self.model = model + self.strategy = strategy + + +class AiAgentLongTextToolTextGen(AiAgentBasicTextToolTextGen): + def __init__( + self, + *, + embeddings: Optional[AiAgentLongTextToolTextGenEmbeddingsField] = None, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param system_message: System messages aim at helping the LLM understand its role and what it is supposed to do. + The input for `{current_date}` is optional, depending on the use., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + + When using the `prompt_template` parameter, you **must include** input for `{user_question}`. + Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + system_message=system_message, + prompt_template=prompt_template, + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.embeddings = embeddings diff --git a/box_sdk_gen/schemas/ai_agent_reference.py b/box_sdk_gen/schemas/ai_agent_reference.py new file mode 100644 index 000000000..bf961999e --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_reference.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentReferenceTypeField(str, Enum): + AI_AGENT_ID = 'ai_agent_id' + + +class AiAgentReference(BaseObject): + _discriminator = 'type', {'ai_agent_id'} + + def __init__( + self, + *, + type: AiAgentReferenceTypeField = AiAgentReferenceTypeField.AI_AGENT_ID, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of AI agent used to handle queries., defaults to AiAgentReferenceTypeField.AI_AGENT_ID + :type type: AiAgentReferenceTypeField, optional + :param id: The ID of an Agent., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id diff --git a/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py b/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py new file mode 100644 index 000000000..fe7942124 --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py @@ -0,0 +1,36 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentSpreadsheetTool(BaseObject): + def __init__( + self, + *, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__(**kwargs) + self.model = model + self.num_tokens_for_completion = num_tokens_for_completion + self.llm_endpoint_params = llm_endpoint_params diff --git a/box_sdk_gen/schemas/ai_agent_text_gen.py b/box_sdk_gen/schemas/ai_agent_text_gen.py new file mode 100644 index 000000000..2b788eafe --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent_text_gen.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_agent_basic_gen_tool import AiAgentBasicGenTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAgentTextGenTypeField(str, Enum): + AI_AGENT_TEXT_GEN = 'ai_agent_text_gen' + + +class AiAgentTextGen(BaseObject): + _discriminator = 'type', {'ai_agent_text_gen'} + + def __init__( + self, + *, + type: AiAgentTextGenTypeField = AiAgentTextGenTypeField.AI_AGENT_TEXT_GEN, + basic_gen: Optional[AiAgentBasicGenTool] = None, + **kwargs + ): + """ + :param type: The type of AI agent used for generating text., defaults to AiAgentTextGenTypeField.AI_AGENT_TEXT_GEN + :type type: AiAgentTextGenTypeField, optional + """ + super().__init__(**kwargs) + self.type = type + self.basic_gen = basic_gen diff --git a/box_sdk_gen/schemas/ai_ask.py b/box_sdk_gen/schemas/ai_ask.py new file mode 100644 index 000000000..398b58d6a --- /dev/null +++ b/box_sdk_gen/schemas/ai_ask.py @@ -0,0 +1,64 @@ +from enum import Enum + +from typing import List + +from typing import Optional + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_item_ask import AiItemAsk + +from box_sdk_gen.schemas.ai_dialogue_history import AiDialogueHistory + +from box_sdk_gen.schemas.ai_agent_ask import AiAgentAsk + +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiAskModeField(str, Enum): + MULTIPLE_ITEM_QA = 'multiple_item_qa' + SINGLE_ITEM_QA = 'single_item_qa' + + +class AiAsk(BaseObject): + def __init__( + self, + mode: AiAskModeField, + prompt: str, + items: List[AiItemAsk], + *, + dialogue_history: Optional[List[AiDialogueHistory]] = None, + include_citations: Optional[bool] = None, + ai_agent: Optional[Union[AiAgentAsk, AiAgentReference]] = None, + **kwargs + ): + """ + :param mode: Box AI handles text documents with text representations up to 1MB in size, or a maximum of 25 files, + whichever comes first. If the text file size exceeds 1MB, the first 1MB of text representation will be processed. + Box AI handles image documents with a resolution of 1024 x 1024 pixels, with a maximum of 5 images or 5 pages + for multi-page images. If the number of image or image pages exceeds 5, the first 5 images or pages will + be processed. If you set mode parameter to `single_item_qa`, the items array can have one element only. + Currently Box AI does not support multi-modal requests. If both images and text are sent Box AI will only + process the text. + :type mode: AiAskModeField + :param prompt: The prompt provided by the client to be answered by the LLM. + The prompt's length is limited to 10000 characters. + :type prompt: str + :param items: The items to be processed by the LLM, often files. + :type items: List[AiItemAsk] + :param dialogue_history: The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response., defaults to None + :type dialogue_history: Optional[List[AiDialogueHistory]], optional + :param include_citations: A flag to indicate whether citations should be returned., defaults to None + :type include_citations: Optional[bool], optional + """ + super().__init__(**kwargs) + self.mode = mode + self.prompt = prompt + self.items = items + self.dialogue_history = dialogue_history + self.include_citations = include_citations + self.ai_agent = ai_agent diff --git a/box_sdk_gen/schemas/ai_citation.py b/box_sdk_gen/schemas/ai_citation.py new file mode 100644 index 000000000..b90376e95 --- /dev/null +++ b/box_sdk_gen/schemas/ai_citation.py @@ -0,0 +1,40 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiCitationTypeField(str, Enum): + FILE = 'file' + + +class AiCitation(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + *, + content: Optional[str] = None, + id: Optional[str] = None, + type: Optional[AiCitationTypeField] = None, + name: Optional[str] = None, + **kwargs + ): + """ + :param content: The specific content from where the answer was referenced., defaults to None + :type content: Optional[str], optional + :param id: The id of the item., defaults to None + :type id: Optional[str], optional + :param type: The type of the item., defaults to None + :type type: Optional[AiCitationTypeField], optional + :param name: The name of the item., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.content = content + self.id = id + self.type = type + self.name = name diff --git a/box_sdk_gen/schemas/ai_dialogue_history.py b/box_sdk_gen/schemas/ai_dialogue_history.py new file mode 100644 index 000000000..778304ef7 --- /dev/null +++ b/box_sdk_gen/schemas/ai_dialogue_history.py @@ -0,0 +1,30 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class AiDialogueHistory(BaseObject): + def __init__( + self, + *, + prompt: Optional[str] = None, + answer: Optional[str] = None, + created_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param prompt: The prompt previously provided by the client and answered by the LLM., defaults to None + :type prompt: Optional[str], optional + :param answer: The answer previously provided by the LLM., defaults to None + :type answer: Optional[str], optional + :param created_at: The ISO date formatted timestamp of when the previous answer to the prompt was created., defaults to None + :type created_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.prompt = prompt + self.answer = answer + self.created_at = created_at diff --git a/box_sdk_gen/schemas/ai_extract.py b/box_sdk_gen/schemas/ai_extract.py new file mode 100644 index 000000000..13096e498 --- /dev/null +++ b/box_sdk_gen/schemas/ai_extract.py @@ -0,0 +1,36 @@ +from typing import List + +from typing import Optional + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_item_base import AiItemBase + +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +from box_sdk_gen.schemas.ai_agent_extract import AiAgentExtract + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiExtract(BaseObject): + def __init__( + self, + prompt: str, + items: List[AiItemBase], + *, + ai_agent: Optional[Union[AiAgentReference, AiAgentExtract]] = None, + **kwargs + ): + """ + :param prompt: The prompt provided to a Large Language Model (LLM) in the request. The prompt can be up to 10000 characters long and it can be an XML or a JSON schema. + :type prompt: str + :param items: The items that LLM will process. Currently, you can use files only. + :type items: List[AiItemBase] + """ + super().__init__(**kwargs) + self.prompt = prompt + self.items = items + self.ai_agent = ai_agent diff --git a/box_sdk_gen/schemas/ai_extract_response.py b/box_sdk_gen/schemas/ai_extract_response.py new file mode 100644 index 000000000..e18207b35 --- /dev/null +++ b/box_sdk_gen/schemas/ai_extract_response.py @@ -0,0 +1,8 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiExtractResponse(BaseObject): + def __init__(self, **kwargs): + super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/ai_extract_structured.py b/box_sdk_gen/schemas/ai_extract_structured.py new file mode 100644 index 000000000..46d6e96ac --- /dev/null +++ b/box_sdk_gen/schemas/ai_extract_structured.py @@ -0,0 +1,134 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from typing import Union + +from box_sdk_gen.schemas.ai_item_base import AiItemBase + +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiExtractStructuredMetadataTemplateTypeField(str, Enum): + METADATA_TEMPLATE = 'metadata_template' + + +class AiExtractStructuredMetadataTemplateField(BaseObject): + _discriminator = 'type', {'metadata_template'} + + def __init__( + self, + *, + template_key: Optional[str] = None, + type: Optional[AiExtractStructuredMetadataTemplateTypeField] = None, + scope: Optional[str] = None, + **kwargs + ): + """ + :param template_key: The name of the metadata template., defaults to None + :type template_key: Optional[str], optional + :param type: Value is always `metadata_template`., defaults to None + :type type: Optional[AiExtractStructuredMetadataTemplateTypeField], optional + :param scope: The scope of the metadata template that can either be global or + enterprise. + * The **global** scope is used for templates that are + available to any Box enterprise. + * The **enterprise** scope represents templates created within a specific enterprise, + containing the ID of that enterprise., defaults to None + :type scope: Optional[str], optional + """ + super().__init__(**kwargs) + self.template_key = template_key + self.type = type + self.scope = scope + + +class AiExtractStructuredFieldsOptionsField(BaseObject): + def __init__(self, key: str, **kwargs): + """ + :param key: A unique identifier for the field. + :type key: str + """ + super().__init__(**kwargs) + self.key = key + + +class AiExtractStructuredFieldsField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + key: str, + *, + description: Optional[str] = None, + display_name: Optional[str] = None, + prompt: Optional[str] = None, + type: Optional[str] = None, + options: Optional[List[AiExtractStructuredFieldsOptionsField]] = None, + **kwargs + ): + """ + :param key: A unique identifier for the field. + :type key: str + :param description: A description of the field., defaults to None + :type description: Optional[str], optional + :param display_name: The display name of the field., defaults to None + :type display_name: Optional[str], optional + :param prompt: The context about the key that may include how to find and format it., defaults to None + :type prompt: Optional[str], optional + :param type: The type of the field. It include but is not limited to string, float, date, enum, and multiSelect., defaults to None + :type type: Optional[str], optional + :param options: A list of options for this field. This is most often used in combination with the enum and multiSelect field types., defaults to None + :type options: Optional[List[AiExtractStructuredFieldsOptionsField]], optional + """ + super().__init__(**kwargs) + self.key = key + self.description = description + self.display_name = display_name + self.prompt = prompt + self.type = type + self.options = options + + +class AiExtractStructured(BaseObject): + def __init__( + self, + items: List[AiItemBase], + *, + metadata_template: Optional[AiExtractStructuredMetadataTemplateField] = None, + fields: Optional[List[AiExtractStructuredFieldsField]] = None, + ai_agent: Optional[Union[AiAgentReference, AiAgentExtractStructured]] = None, + **kwargs + ): + """ + :param items: The items to be processed by the LLM. Currently you can use files only. + :type items: List[AiItemBase] + :param metadata_template: The metadata template containing the fields to extract. + For your request to work, you must provide either `metadata_template` or `fields`, but not both., defaults to None + :type metadata_template: Optional[AiExtractStructuredMetadataTemplateField], optional + :param fields: The fields to be extracted from the provided items. + For your request to work, you must provide either `metadata_template` or `fields`, but not both., defaults to None + :type fields: Optional[List[AiExtractStructuredFieldsField]], optional + """ + super().__init__(**kwargs) + self.items = items + self.metadata_template = metadata_template + self.fields = fields + self.ai_agent = ai_agent diff --git a/box_sdk_gen/schemas/ai_extract_structured_response.py b/box_sdk_gen/schemas/ai_extract_structured_response.py new file mode 100644 index 000000000..9c9e7f3ed --- /dev/null +++ b/box_sdk_gen/schemas/ai_extract_structured_response.py @@ -0,0 +1,34 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_extract_response import AiExtractResponse + +from box_sdk_gen.schemas.ai_agent_info import AiAgentInfo + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class AiExtractStructuredResponse(BaseObject): + def __init__( + self, + answer: AiExtractResponse, + created_at: DateTime, + *, + completion_reason: Optional[str] = None, + ai_agent_info: Optional[AiAgentInfo] = None, + **kwargs + ): + """ + :param created_at: The ISO date formatted timestamp of when the answer to the prompt was created. + :type created_at: DateTime + :param completion_reason: The reason the response finishes., defaults to None + :type completion_reason: Optional[str], optional + """ + super().__init__(**kwargs) + self.answer = answer + self.created_at = created_at + self.completion_reason = completion_reason + self.ai_agent_info = ai_agent_info diff --git a/box_sdk_gen/schemas/ai_item_ask.py b/box_sdk_gen/schemas/ai_item_ask.py new file mode 100644 index 000000000..d2702b88c --- /dev/null +++ b/box_sdk_gen/schemas/ai_item_ask.py @@ -0,0 +1,37 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiItemAskTypeField(str, Enum): + FILE = 'file' + HUBS = 'hubs' + + +class AiItemAsk(BaseObject): + _discriminator = 'type', {'file', 'hubs'} + + def __init__( + self, + id: str, + type: AiItemAskTypeField, + *, + content: Optional[str] = None, + **kwargs + ): + """ + :param id: The ID of the file. + :type id: str + :param type: The type of the item. A `hubs` item must be used as a single item. + :type type: AiItemAskTypeField + :param content: The content of the item, often the text representation., defaults to None + :type content: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.content = content diff --git a/box_sdk_gen/schemas/ai_item_base.py b/box_sdk_gen/schemas/ai_item_base.py new file mode 100644 index 000000000..53cd81822 --- /dev/null +++ b/box_sdk_gen/schemas/ai_item_base.py @@ -0,0 +1,36 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiItemBaseTypeField(str, Enum): + FILE = 'file' + + +class AiItemBase(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + id: str, + *, + type: AiItemBaseTypeField = AiItemBaseTypeField.FILE, + content: Optional[str] = None, + **kwargs + ): + """ + :param id: The ID of the file. + :type id: str + :param type: The type of the item. Currently the value can be `file` only., defaults to AiItemBaseTypeField.FILE + :type type: AiItemBaseTypeField, optional + :param content: The content of the item, often the text representation., defaults to None + :type content: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.content = content diff --git a/box_sdk_gen/schemas/ai_llm_endpoint_params.py b/box_sdk_gen/schemas/ai_llm_endpoint_params.py new file mode 100644 index 000000000..3425b0cd3 --- /dev/null +++ b/box_sdk_gen/schemas/ai_llm_endpoint_params.py @@ -0,0 +1,18 @@ +from typing import Union + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.box.errors import BoxSDKError + +AiLlmEndpointParams = Union[ + AiLlmEndpointParamsOpenAi, + AiLlmEndpointParamsGoogle, + AiLlmEndpointParamsAws, + AiLlmEndpointParamsIbm, +] diff --git a/box_sdk_gen/schemas/ai_llm_endpoint_params_aws.py b/box_sdk_gen/schemas/ai_llm_endpoint_params_aws.py new file mode 100644 index 000000000..c7ee05318 --- /dev/null +++ b/box_sdk_gen/schemas/ai_llm_endpoint_params_aws.py @@ -0,0 +1,41 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiLlmEndpointParamsAwsTypeField(str, Enum): + AWS_PARAMS = 'aws_params' + + +class AiLlmEndpointParamsAws(BaseObject): + _discriminator = 'type', {'aws_params'} + + def __init__( + self, + *, + type: AiLlmEndpointParamsAwsTypeField = AiLlmEndpointParamsAwsTypeField.AWS_PARAMS, + temperature: Optional[float] = None, + top_p: Optional[float] = None, + **kwargs + ): + """ + :param type: The type of the AI LLM endpoint params object for AWS. + This parameter is **required**., defaults to AiLlmEndpointParamsAwsTypeField.AWS_PARAMS + :type type: AiLlmEndpointParamsAwsTypeField, optional + :param temperature: What sampling temperature to use, between 0 and 1. Higher values like 0.8 will make the output more random, + while lower values like 0.2 will make it more focused and deterministic. + We generally recommend altering this or `top_p` but not both., defaults to None + :type temperature: Optional[float], optional + :param top_p: An alternative to sampling with temperature, called nucleus sampling, where the model considers the results + of the tokens with `top_p` probability mass. So 0.1 means only the tokens comprising the top 10% probability + mass are considered. We generally recommend altering this or temperature but not both., defaults to None + :type top_p: Optional[float], optional + """ + super().__init__(**kwargs) + self.type = type + self.temperature = temperature + self.top_p = top_p diff --git a/box_sdk_gen/schemas/ai_llm_endpoint_params_google.py b/box_sdk_gen/schemas/ai_llm_endpoint_params_google.py new file mode 100644 index 000000000..b30f28f4b --- /dev/null +++ b/box_sdk_gen/schemas/ai_llm_endpoint_params_google.py @@ -0,0 +1,43 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiLlmEndpointParamsGoogleTypeField(str, Enum): + GOOGLE_PARAMS = 'google_params' + + +class AiLlmEndpointParamsGoogle(BaseObject): + _discriminator = 'type', {'google_params'} + + def __init__( + self, + *, + type: AiLlmEndpointParamsGoogleTypeField = AiLlmEndpointParamsGoogleTypeField.GOOGLE_PARAMS, + temperature: Optional[float] = None, + top_p: Optional[float] = None, + top_k: Optional[float] = None, + **kwargs + ): + """ + :param type: The type of the AI LLM endpoint params object for Google. + This parameter is **required**., defaults to AiLlmEndpointParamsGoogleTypeField.GOOGLE_PARAMS + :type type: AiLlmEndpointParamsGoogleTypeField, optional + :param temperature: The temperature is used for sampling during response generation, which occurs when `top-P` and `top-K` are applied. Temperature controls the degree of randomness in the token selection., defaults to None + :type temperature: Optional[float], optional + :param top_p: `Top-P` changes how the model selects tokens for output. Tokens are selected from the most (see `top-K`) to least probable until the sum of their probabilities equals the `top-P` value., defaults to None + :type top_p: Optional[float], optional + :param top_k: `Top-K` changes how the model selects tokens for output. A low `top-K` means the next selected token is + the most probable among all tokens in the model's vocabulary (also called greedy decoding), + while a high `top-K` means that the next token is selected from among the three most probable tokens by using temperature., defaults to None + :type top_k: Optional[float], optional + """ + super().__init__(**kwargs) + self.type = type + self.temperature = temperature + self.top_p = top_p + self.top_k = top_k diff --git a/box_sdk_gen/schemas/ai_llm_endpoint_params_ibm.py b/box_sdk_gen/schemas/ai_llm_endpoint_params_ibm.py new file mode 100644 index 000000000..851bd34fe --- /dev/null +++ b/box_sdk_gen/schemas/ai_llm_endpoint_params_ibm.py @@ -0,0 +1,47 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiLlmEndpointParamsIbmTypeField(str, Enum): + IBM_PARAMS = 'ibm_params' + + +class AiLlmEndpointParamsIbm(BaseObject): + _discriminator = 'type', {'ibm_params'} + + def __init__( + self, + *, + type: AiLlmEndpointParamsIbmTypeField = AiLlmEndpointParamsIbmTypeField.IBM_PARAMS, + temperature: Optional[float] = None, + top_p: Optional[float] = None, + top_k: Optional[float] = None, + **kwargs + ): + """ + :param type: The type of the AI LLM endpoint params object for IBM. + This parameter is **required**., defaults to AiLlmEndpointParamsIbmTypeField.IBM_PARAMS + :type type: AiLlmEndpointParamsIbmTypeField, optional + :param temperature: What sampling temperature to use, between 0 and 1. Higher values like 0.8 will make the output more random, + while lower values like 0.2 will make it more focused and deterministic. + We generally recommend altering this or `top_p` but not both., defaults to None + :type temperature: Optional[float], optional + :param top_p: An alternative to sampling with temperature, called nucleus sampling, where the model considers the results + of the tokens with `top_p` probability mass. So 0.1 means only the tokens comprising the top 10% probability + mass are considered. We generally recommend altering this or temperature but not both., defaults to None + :type top_p: Optional[float], optional + :param top_k: `Top-K` changes how the model selects tokens for output. A low `top-K` means the next selected token is + the most probable among all tokens in the model's vocabulary (also called greedy decoding), + while a high `top-K` means that the next token is selected from among the three most probable tokens by using temperature., defaults to None + :type top_k: Optional[float], optional + """ + super().__init__(**kwargs) + self.type = type + self.temperature = temperature + self.top_p = top_p + self.top_k = top_k diff --git a/box_sdk_gen/schemas/ai_llm_endpoint_params_open_ai.py b/box_sdk_gen/schemas/ai_llm_endpoint_params_open_ai.py new file mode 100644 index 000000000..77264f885 --- /dev/null +++ b/box_sdk_gen/schemas/ai_llm_endpoint_params_open_ai.py @@ -0,0 +1,54 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiLlmEndpointParamsOpenAiTypeField(str, Enum): + OPENAI_PARAMS = 'openai_params' + + +class AiLlmEndpointParamsOpenAi(BaseObject): + _discriminator = 'type', {'openai_params'} + + def __init__( + self, + *, + type: AiLlmEndpointParamsOpenAiTypeField = AiLlmEndpointParamsOpenAiTypeField.OPENAI_PARAMS, + temperature: Optional[float] = None, + top_p: Optional[float] = None, + frequency_penalty: Optional[float] = None, + presence_penalty: Optional[float] = None, + stop: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of the AI LLM endpoint params object for OpenAI. + This parameter is **required**., defaults to AiLlmEndpointParamsOpenAiTypeField.OPENAI_PARAMS + :type type: AiLlmEndpointParamsOpenAiTypeField, optional + :param temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, + while lower values like 0.2 will make it more focused and deterministic. + We generally recommend altering this or `top_p` but not both., defaults to None + :type temperature: Optional[float], optional + :param top_p: An alternative to sampling with temperature, called nucleus sampling, where the model considers the results + of the tokens with `top_p` probability mass. So 0.1 means only the tokens comprising the top 10% probability + mass are considered. We generally recommend altering this or temperature but not both., defaults to None + :type top_p: Optional[float], optional + :param frequency_penalty: A number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the + text so far, decreasing the model's likelihood to repeat the same line verbatim., defaults to None + :type frequency_penalty: Optional[float], optional + :param presence_penalty: A number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics., defaults to None + :type presence_penalty: Optional[float], optional + :param stop: Up to 4 sequences where the API will stop generating further tokens., defaults to None + :type stop: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.temperature = temperature + self.top_p = top_p + self.frequency_penalty = frequency_penalty + self.presence_penalty = presence_penalty + self.stop = stop diff --git a/box_sdk_gen/schemas/ai_multiple_agent_response.py b/box_sdk_gen/schemas/ai_multiple_agent_response.py new file mode 100644 index 000000000..fad0593d5 --- /dev/null +++ b/box_sdk_gen/schemas/ai_multiple_agent_response.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_single_agent_response_full import AiSingleAgentResponseFull + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiMultipleAgentResponse(BaseObject): + def __init__( + self, + entries: List[AiSingleAgentResponseFull], + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: The list of AI Agents. + :type entries: List[AiSingleAgentResponseFull] + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker diff --git a/box_sdk_gen/schemas/ai_response.py b/box_sdk_gen/schemas/ai_response.py new file mode 100644 index 000000000..ff59efbc3 --- /dev/null +++ b/box_sdk_gen/schemas/ai_response.py @@ -0,0 +1,34 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_agent_info import AiAgentInfo + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class AiResponse(BaseObject): + def __init__( + self, + answer: str, + created_at: DateTime, + *, + completion_reason: Optional[str] = None, + ai_agent_info: Optional[AiAgentInfo] = None, + **kwargs + ): + """ + :param answer: The answer provided by the LLM. + :type answer: str + :param created_at: The ISO date formatted timestamp of when the answer to the prompt was created. + :type created_at: DateTime + :param completion_reason: The reason the response finishes., defaults to None + :type completion_reason: Optional[str], optional + """ + super().__init__(**kwargs) + self.answer = answer + self.created_at = created_at + self.completion_reason = completion_reason + self.ai_agent_info = ai_agent_info diff --git a/box_sdk_gen/schemas/ai_response_full.py b/box_sdk_gen/schemas/ai_response_full.py new file mode 100644 index 000000000..7c9a1c89f --- /dev/null +++ b/box_sdk_gen/schemas/ai_response_full.py @@ -0,0 +1,44 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.ai_agent_info import AiAgentInfo + +from box_sdk_gen.schemas.ai_response import AiResponse + +from box_sdk_gen.schemas.ai_citation import AiCitation + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiResponseFull(AiResponse): + def __init__( + self, + answer: str, + created_at: DateTime, + *, + citations: Optional[List[AiCitation]] = None, + completion_reason: Optional[str] = None, + ai_agent_info: Optional[AiAgentInfo] = None, + **kwargs + ): + """ + :param answer: The answer provided by the LLM. + :type answer: str + :param created_at: The ISO date formatted timestamp of when the answer to the prompt was created. + :type created_at: DateTime + :param citations: The citations of the LLM's answer reference., defaults to None + :type citations: Optional[List[AiCitation]], optional + :param completion_reason: The reason the response finishes., defaults to None + :type completion_reason: Optional[str], optional + """ + super().__init__( + answer=answer, + created_at=created_at, + completion_reason=completion_reason, + ai_agent_info=ai_agent_info, + **kwargs + ) + self.citations = citations diff --git a/box_sdk_gen/schemas/ai_single_agent_response.py b/box_sdk_gen/schemas/ai_single_agent_response.py new file mode 100644 index 000000000..e7d778b18 --- /dev/null +++ b/box_sdk_gen/schemas/ai_single_agent_response.py @@ -0,0 +1,78 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.group_base import GroupBase + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.schemas.ai_agent_allowed_entity import AiAgentAllowedEntity + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class AiSingleAgentResponseTypeField(str, Enum): + AI_AGENT = 'ai_agent' + + +class AiSingleAgentResponse(BaseObject): + _discriminator = 'type', {'ai_agent'} + + def __init__( + self, + id: str, + origin: str, + name: str, + access_state: str, + *, + type: Optional[AiSingleAgentResponseTypeField] = None, + created_by: Optional[UserBase] = None, + created_at: Optional[DateTime] = None, + modified_by: Optional[UserBase] = None, + modified_at: Optional[DateTime] = None, + icon_reference: Optional[str] = None, + allowed_entities: Optional[List[AiAgentAllowedEntity]] = None, + **kwargs + ): + """ + :param id: The unique identifier of the AI Agent. + :type id: str + :param origin: The provider of the AI Agent. + :type origin: str + :param name: The name of the AI Agent. + :type name: str + :param access_state: The state of the AI Agent. Possible values are: `enabled`, `disabled`, and `enabled_for_selected_users`. + :type access_state: str + :param type: The type of agent used to handle queries., defaults to None + :type type: Optional[AiSingleAgentResponseTypeField], optional + :param created_by: The user who created this agent., defaults to None + :type created_by: Optional[UserBase], optional + :param created_at: The ISO date-time formatted timestamp of when this AI agent was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_by: The user who most recently modified this agent., defaults to None + :type modified_by: Optional[UserBase], optional + :param modified_at: The ISO date-time formatted timestamp of when this AI agent was recently modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param icon_reference: The icon reference of the AI Agent., defaults to None + :type icon_reference: Optional[str], optional + :param allowed_entities: List of allowed users or groups., defaults to None + :type allowed_entities: Optional[List[AiAgentAllowedEntity]], optional + """ + super().__init__(**kwargs) + self.id = id + self.origin = origin + self.name = name + self.access_state = access_state + self.type = type + self.created_by = created_by + self.created_at = created_at + self.modified_by = modified_by + self.modified_at = modified_at + self.icon_reference = icon_reference + self.allowed_entities = allowed_entities diff --git a/box_sdk_gen/schemas/ai_single_agent_response_full.py b/box_sdk_gen/schemas/ai_single_agent_response_full.py new file mode 100644 index 000000000..1898fd5e7 --- /dev/null +++ b/box_sdk_gen/schemas/ai_single_agent_response_full.py @@ -0,0 +1,90 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.ai_single_agent_response import AiSingleAgentResponseTypeField + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.group_base import GroupBase + +from box_sdk_gen.schemas.ai_agent_allowed_entity import AiAgentAllowedEntity + +from box_sdk_gen.schemas.ai_single_agent_response import AiSingleAgentResponse + +from box_sdk_gen.schemas.ai_studio_agent_ask_response import AiStudioAgentAskResponse + +from box_sdk_gen.schemas.ai_studio_agent_text_gen_response import ( + AiStudioAgentTextGenResponse, +) + +from box_sdk_gen.schemas.ai_studio_agent_extract_response import ( + AiStudioAgentExtractResponse, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiSingleAgentResponseFull(AiSingleAgentResponse): + def __init__( + self, + id: str, + origin: str, + name: str, + access_state: str, + *, + ask: Optional[AiStudioAgentAskResponse] = None, + text_gen: Optional[AiStudioAgentTextGenResponse] = None, + extract: Optional[AiStudioAgentExtractResponse] = None, + type: Optional[AiSingleAgentResponseTypeField] = None, + created_by: Optional[UserBase] = None, + created_at: Optional[DateTime] = None, + modified_by: Optional[UserBase] = None, + modified_at: Optional[DateTime] = None, + icon_reference: Optional[str] = None, + allowed_entities: Optional[List[AiAgentAllowedEntity]] = None, + **kwargs + ): + """ + :param id: The unique identifier of the AI Agent. + :type id: str + :param origin: The provider of the AI Agent. + :type origin: str + :param name: The name of the AI Agent. + :type name: str + :param access_state: The state of the AI Agent. Possible values are: `enabled`, `disabled`, and `enabled_for_selected_users`. + :type access_state: str + :param type: The type of agent used to handle queries., defaults to None + :type type: Optional[AiSingleAgentResponseTypeField], optional + :param created_by: The user who created this agent., defaults to None + :type created_by: Optional[UserBase], optional + :param created_at: The ISO date-time formatted timestamp of when this AI agent was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_by: The user who most recently modified this agent., defaults to None + :type modified_by: Optional[UserBase], optional + :param modified_at: The ISO date-time formatted timestamp of when this AI agent was recently modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param icon_reference: The icon reference of the AI Agent., defaults to None + :type icon_reference: Optional[str], optional + :param allowed_entities: List of allowed users or groups., defaults to None + :type allowed_entities: Optional[List[AiAgentAllowedEntity]], optional + """ + super().__init__( + id=id, + origin=origin, + name=name, + access_state=access_state, + type=type, + created_by=created_by, + created_at=created_at, + modified_by=modified_by, + modified_at=modified_at, + icon_reference=icon_reference, + allowed_entities=allowed_entities, + **kwargs + ) + self.ask = ask + self.text_gen = text_gen + self.extract = extract diff --git a/box_sdk_gen/schemas/ai_studio_agent_ask.py b/box_sdk_gen/schemas/ai_studio_agent_ask.py new file mode 100644 index 000000000..43f92b4d8 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_ask.py @@ -0,0 +1,70 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_studio_agent_long_text_tool import AiStudioAgentLongTextTool + +from box_sdk_gen.schemas.ai_studio_agent_basic_text_tool import ( + AiStudioAgentBasicTextTool, +) + +from box_sdk_gen.schemas.ai_studio_agent_spreadsheet_tool import ( + AiStudioAgentSpreadsheetTool, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentAskTypeField(str, Enum): + AI_AGENT_ASK = 'ai_agent_ask' + + +class AiStudioAgentAsk(BaseObject): + _discriminator = 'type', {'ai_agent_ask'} + + def __init__( + self, + access_state: str, + description: str, + *, + type: AiStudioAgentAskTypeField = AiStudioAgentAskTypeField.AI_AGENT_ASK, + custom_instructions: Optional[str] = None, + suggested_questions: Optional[List[str]] = None, + long_text: Optional[AiStudioAgentLongTextTool] = None, + basic_text: Optional[AiStudioAgentBasicTextTool] = None, + basic_image: Optional[AiStudioAgentBasicTextTool] = None, + spreadsheet: Optional[AiStudioAgentSpreadsheetTool] = None, + long_text_multi: Optional[AiStudioAgentLongTextTool] = None, + basic_text_multi: Optional[AiStudioAgentBasicTextTool] = None, + basic_image_multi: Optional[AiStudioAgentBasicTextTool] = None, + **kwargs + ): + """ + :param access_state: The state of the AI Agent capability. Possible values are: `enabled` and `disabled`. + :type access_state: str + :param description: The description of the AI agent. + :type description: str + :param type: The type of AI agent used to handle queries., defaults to AiStudioAgentAskTypeField.AI_AGENT_ASK + :type type: AiStudioAgentAskTypeField, optional + :param custom_instructions: Custom instructions for the AI agent., defaults to None + :type custom_instructions: Optional[str], optional + :param suggested_questions: Suggested questions for the AI agent. If null, suggested question will be generated. If empty, no suggested questions will be displayed., defaults to None + :type suggested_questions: Optional[List[str]], optional + """ + super().__init__(**kwargs) + self.access_state = access_state + self.description = description + self.type = type + self.custom_instructions = custom_instructions + self.suggested_questions = suggested_questions + self.long_text = long_text + self.basic_text = basic_text + self.basic_image = basic_image + self.spreadsheet = spreadsheet + self.long_text_multi = long_text_multi + self.basic_text_multi = basic_text_multi + self.basic_image_multi = basic_image_multi diff --git a/box_sdk_gen/schemas/ai_studio_agent_ask_response.py b/box_sdk_gen/schemas/ai_studio_agent_ask_response.py new file mode 100644 index 000000000..89d100337 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_ask_response.py @@ -0,0 +1,72 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_studio_agent_long_text_tool_response import ( + AiStudioAgentLongTextToolResponse, +) + +from box_sdk_gen.schemas.ai_studio_agent_basic_text_tool_response import ( + AiStudioAgentBasicTextToolResponse, +) + +from box_sdk_gen.schemas.ai_studio_agent_spreadsheet_tool_response import ( + AiStudioAgentSpreadsheetToolResponse, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentAskResponseTypeField(str, Enum): + AI_AGENT_ASK = 'ai_agent_ask' + + +class AiStudioAgentAskResponse(BaseObject): + _discriminator = 'type', {'ai_agent_ask'} + + def __init__( + self, + access_state: str, + description: str, + *, + type: AiStudioAgentAskResponseTypeField = AiStudioAgentAskResponseTypeField.AI_AGENT_ASK, + custom_instructions: Optional[str] = None, + suggested_questions: Optional[List[str]] = None, + long_text: Optional[AiStudioAgentLongTextToolResponse] = None, + basic_text: Optional[AiStudioAgentBasicTextToolResponse] = None, + basic_image: Optional[AiStudioAgentBasicTextToolResponse] = None, + spreadsheet: Optional[AiStudioAgentSpreadsheetToolResponse] = None, + long_text_multi: Optional[AiStudioAgentLongTextToolResponse] = None, + basic_text_multi: Optional[AiStudioAgentBasicTextToolResponse] = None, + basic_image_multi: Optional[AiStudioAgentBasicTextToolResponse] = None, + **kwargs + ): + """ + :param access_state: The state of the AI Agent capability. Possible values are: `enabled` and `disabled`. + :type access_state: str + :param description: The description of the AI agent. + :type description: str + :param type: The type of AI agent used to ask questions., defaults to AiStudioAgentAskResponseTypeField.AI_AGENT_ASK + :type type: AiStudioAgentAskResponseTypeField, optional + :param custom_instructions: Custom instructions for the AI agent., defaults to None + :type custom_instructions: Optional[str], optional + :param suggested_questions: Suggested questions for the AI agent. If null, suggested question will be generated. If empty, no suggested questions will be displayed., defaults to None + :type suggested_questions: Optional[List[str]], optional + """ + super().__init__(**kwargs) + self.access_state = access_state + self.description = description + self.type = type + self.custom_instructions = custom_instructions + self.suggested_questions = suggested_questions + self.long_text = long_text + self.basic_text = basic_text + self.basic_image = basic_image + self.spreadsheet = spreadsheet + self.long_text_multi = long_text_multi + self.basic_text_multi = basic_text_multi + self.basic_image_multi = basic_image_multi diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py new file mode 100644 index 000000000..b3ed99e08 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py @@ -0,0 +1,75 @@ +from typing import Optional + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_text_gen import ( + AiAgentBasicTextToolTextGen, +) + +from box_sdk_gen.schemas.ai_agent_long_text_tool_text_gen import ( + AiAgentLongTextToolTextGenEmbeddingsField, +) + +from box_sdk_gen.schemas.ai_agent_long_text_tool_text_gen import ( + AiAgentLongTextToolTextGen, +) + +from box_sdk_gen.schemas.ai_agent_basic_gen_tool import AiAgentBasicGenTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentBasicGenTool(AiAgentBasicGenTool): + def __init__( + self, + *, + is_custom_instructions_included: Optional[bool] = None, + content_template: Optional[str] = None, + embeddings: Optional[AiAgentLongTextToolTextGenEmbeddingsField] = None, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param is_custom_instructions_included: True if system message contains custom instructions placeholder, false otherwise., defaults to None + :type is_custom_instructions_included: Optional[bool], optional + :param content_template: How the content should be included in a request to the LLM. + Input for `{content}` is optional, depending on the use., defaults to None + :type content_template: Optional[str], optional + :param system_message: System messages aim at helping the LLM understand its role and what it is supposed to do. + The input for `{current_date}` is optional, depending on the use., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + + When using the `prompt_template` parameter, you **must include** input for `{user_question}`. + Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + content_template=content_template, + embeddings=embeddings, + system_message=system_message, + prompt_template=prompt_template, + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.is_custom_instructions_included = is_custom_instructions_included diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py new file mode 100644 index 000000000..4e418497c --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py @@ -0,0 +1,83 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_text_gen import ( + AiAgentBasicTextToolTextGen, +) + +from box_sdk_gen.schemas.ai_agent_long_text_tool_text_gen import ( + AiAgentLongTextToolTextGenEmbeddingsField, +) + +from box_sdk_gen.schemas.ai_agent_long_text_tool_text_gen import ( + AiAgentLongTextToolTextGen, +) + +from box_sdk_gen.schemas.ai_agent_basic_gen_tool import AiAgentBasicGenTool + +from box_sdk_gen.schemas.ai_studio_agent_basic_gen_tool import AiStudioAgentBasicGenTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentBasicGenToolResponse(AiStudioAgentBasicGenTool): + def __init__( + self, + *, + warnings: Optional[List[str]] = None, + is_custom_instructions_included: Optional[bool] = None, + content_template: Optional[str] = None, + embeddings: Optional[AiAgentLongTextToolTextGenEmbeddingsField] = None, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param warnings: Warnings concerning tool., defaults to None + :type warnings: Optional[List[str]], optional + :param is_custom_instructions_included: True if system message contains custom instructions placeholder, false otherwise., defaults to None + :type is_custom_instructions_included: Optional[bool], optional + :param content_template: How the content should be included in a request to the LLM. + Input for `{content}` is optional, depending on the use., defaults to None + :type content_template: Optional[str], optional + :param system_message: System messages aim at helping the LLM understand its role and what it is supposed to do. + The input for `{current_date}` is optional, depending on the use., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + + When using the `prompt_template` parameter, you **must include** input for `{user_question}`. + Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + is_custom_instructions_included=is_custom_instructions_included, + content_template=content_template, + embeddings=embeddings, + system_message=system_message, + prompt_template=prompt_template, + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.warnings = warnings diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py new file mode 100644 index 000000000..bb1435fe5 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py @@ -0,0 +1,54 @@ +from typing import Optional + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.schemas.ai_agent_basic_text_tool import AiAgentBasicTextTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentBasicTextTool(AiAgentBasicTextTool): + def __init__( + self, + *, + is_custom_instructions_included: Optional[bool] = None, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param is_custom_instructions_included: True if system message contains custom instructions placeholder, false otherwise., defaults to None + :type is_custom_instructions_included: Optional[bool], optional + :param system_message: System messages try to help the LLM "understand" its role and what it is supposed to do., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. + `{current_date}` is optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + system_message=system_message, + prompt_template=prompt_template, + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.is_custom_instructions_included = is_custom_instructions_included diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py new file mode 100644 index 000000000..47b4eaf25 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py @@ -0,0 +1,64 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.schemas.ai_agent_basic_text_tool import AiAgentBasicTextTool + +from box_sdk_gen.schemas.ai_studio_agent_basic_text_tool import ( + AiStudioAgentBasicTextTool, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentBasicTextToolResponse(AiStudioAgentBasicTextTool): + def __init__( + self, + *, + warnings: Optional[List[str]] = None, + is_custom_instructions_included: Optional[bool] = None, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param warnings: Warnings concerning tool., defaults to None + :type warnings: Optional[List[str]], optional + :param is_custom_instructions_included: True if system message contains custom instructions placeholder, false otherwise., defaults to None + :type is_custom_instructions_included: Optional[bool], optional + :param system_message: System messages try to help the LLM "understand" its role and what it is supposed to do., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. + `{current_date}` is optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + is_custom_instructions_included=is_custom_instructions_included, + system_message=system_message, + prompt_template=prompt_template, + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.warnings = warnings diff --git a/box_sdk_gen/schemas/ai_studio_agent_extract.py b/box_sdk_gen/schemas/ai_studio_agent_extract.py new file mode 100644 index 000000000..1d090d2da --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_extract.py @@ -0,0 +1,52 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_studio_agent_long_text_tool import AiStudioAgentLongTextTool + +from box_sdk_gen.schemas.ai_studio_agent_basic_text_tool import ( + AiStudioAgentBasicTextTool, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentExtractTypeField(str, Enum): + AI_AGENT_EXTRACT = 'ai_agent_extract' + + +class AiStudioAgentExtract(BaseObject): + _discriminator = 'type', {'ai_agent_extract'} + + def __init__( + self, + access_state: str, + description: str, + *, + type: AiStudioAgentExtractTypeField = AiStudioAgentExtractTypeField.AI_AGENT_EXTRACT, + custom_instructions: Optional[str] = None, + long_text: Optional[AiStudioAgentLongTextTool] = None, + basic_text: Optional[AiStudioAgentBasicTextTool] = None, + basic_image: Optional[AiStudioAgentBasicTextTool] = None, + **kwargs + ): + """ + :param access_state: The state of the AI Agent capability. Possible values are: `enabled` and `disabled`. + :type access_state: str + :param description: The description of the AI agent. + :type description: str + :param type: The type of AI agent to be used for metadata extraction., defaults to AiStudioAgentExtractTypeField.AI_AGENT_EXTRACT + :type type: AiStudioAgentExtractTypeField, optional + :param custom_instructions: Custom instructions for the AI agent., defaults to None + :type custom_instructions: Optional[str], optional + """ + super().__init__(**kwargs) + self.access_state = access_state + self.description = description + self.type = type + self.custom_instructions = custom_instructions + self.long_text = long_text + self.basic_text = basic_text + self.basic_image = basic_image diff --git a/box_sdk_gen/schemas/ai_studio_agent_extract_response.py b/box_sdk_gen/schemas/ai_studio_agent_extract_response.py new file mode 100644 index 000000000..81ee75993 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_extract_response.py @@ -0,0 +1,54 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_studio_agent_long_text_tool_response import ( + AiStudioAgentLongTextToolResponse, +) + +from box_sdk_gen.schemas.ai_studio_agent_basic_text_tool_response import ( + AiStudioAgentBasicTextToolResponse, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentExtractResponseTypeField(str, Enum): + AI_AGENT_EXTRACT = 'ai_agent_extract' + + +class AiStudioAgentExtractResponse(BaseObject): + _discriminator = 'type', {'ai_agent_extract'} + + def __init__( + self, + access_state: str, + description: str, + *, + type: AiStudioAgentExtractResponseTypeField = AiStudioAgentExtractResponseTypeField.AI_AGENT_EXTRACT, + custom_instructions: Optional[str] = None, + long_text: Optional[AiStudioAgentLongTextToolResponse] = None, + basic_text: Optional[AiStudioAgentBasicTextToolResponse] = None, + basic_image: Optional[AiStudioAgentBasicTextToolResponse] = None, + **kwargs + ): + """ + :param access_state: The state of the AI Agent capability. Possible values are: `enabled` and `disabled`. + :type access_state: str + :param description: The description of the AI agent. + :type description: str + :param type: The type of AI agent to be used for metadata extraction., defaults to AiStudioAgentExtractResponseTypeField.AI_AGENT_EXTRACT + :type type: AiStudioAgentExtractResponseTypeField, optional + :param custom_instructions: Custom instructions for the AI agent., defaults to None + :type custom_instructions: Optional[str], optional + """ + super().__init__(**kwargs) + self.access_state = access_state + self.description = description + self.type = type + self.custom_instructions = custom_instructions + self.long_text = long_text + self.basic_text = basic_text + self.basic_image = basic_image diff --git a/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py new file mode 100644 index 000000000..5419f0b12 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py @@ -0,0 +1,62 @@ +from typing import Optional + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.schemas.ai_agent_basic_text_tool import AiAgentBasicTextTool + +from box_sdk_gen.schemas.ai_agent_long_text_tool import ( + AiAgentLongTextToolEmbeddingsField, +) + +from box_sdk_gen.schemas.ai_agent_long_text_tool import AiAgentLongTextTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentLongTextTool(AiAgentLongTextTool): + def __init__( + self, + *, + is_custom_instructions_included: Optional[bool] = None, + embeddings: Optional[AiAgentLongTextToolEmbeddingsField] = None, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param is_custom_instructions_included: True if system message contains custom instructions placeholder, false otherwise., defaults to None + :type is_custom_instructions_included: Optional[bool], optional + :param system_message: System messages try to help the LLM "understand" its role and what it is supposed to do., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. + `{current_date}` is optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + embeddings=embeddings, + system_message=system_message, + prompt_template=prompt_template, + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.is_custom_instructions_included = is_custom_instructions_included diff --git a/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py new file mode 100644 index 000000000..b521b3751 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py @@ -0,0 +1,70 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_basic_text_tool_base import AiAgentBasicTextToolBase + +from box_sdk_gen.schemas.ai_agent_basic_text_tool import AiAgentBasicTextTool + +from box_sdk_gen.schemas.ai_agent_long_text_tool import ( + AiAgentLongTextToolEmbeddingsField, +) + +from box_sdk_gen.schemas.ai_agent_long_text_tool import AiAgentLongTextTool + +from box_sdk_gen.schemas.ai_studio_agent_long_text_tool import AiStudioAgentLongTextTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentLongTextToolResponse(AiStudioAgentLongTextTool): + def __init__( + self, + *, + warnings: Optional[List[str]] = None, + is_custom_instructions_included: Optional[bool] = None, + embeddings: Optional[AiAgentLongTextToolEmbeddingsField] = None, + system_message: Optional[str] = None, + prompt_template: Optional[str] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param warnings: Warnings concerning tool., defaults to None + :type warnings: Optional[List[str]], optional + :param is_custom_instructions_included: True if system message contains custom instructions placeholder, false otherwise., defaults to None + :type is_custom_instructions_included: Optional[bool], optional + :param system_message: System messages try to help the LLM "understand" its role and what it is supposed to do., defaults to None + :type system_message: Optional[str], optional + :param prompt_template: The prompt template contains contextual information of the request and the user prompt. + When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. + `{current_date}` is optional, depending on the use., defaults to None + :type prompt_template: Optional[str], optional + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + is_custom_instructions_included=is_custom_instructions_included, + embeddings=embeddings, + system_message=system_message, + prompt_template=prompt_template, + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.warnings = warnings diff --git a/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py new file mode 100644 index 000000000..9d207998f --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py @@ -0,0 +1,38 @@ +from typing import Optional + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_spreadsheet_tool import AiAgentSpreadsheetTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentSpreadsheetTool(AiAgentSpreadsheetTool): + def __init__( + self, + *, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) diff --git a/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py new file mode 100644 index 000000000..bf6323140 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py @@ -0,0 +1,48 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.ai_llm_endpoint_params_open_ai import AiLlmEndpointParamsOpenAi + +from box_sdk_gen.schemas.ai_llm_endpoint_params_google import AiLlmEndpointParamsGoogle + +from box_sdk_gen.schemas.ai_llm_endpoint_params_aws import AiLlmEndpointParamsAws + +from box_sdk_gen.schemas.ai_llm_endpoint_params_ibm import AiLlmEndpointParamsIbm + +from box_sdk_gen.schemas.ai_llm_endpoint_params import AiLlmEndpointParams + +from box_sdk_gen.schemas.ai_agent_spreadsheet_tool import AiAgentSpreadsheetTool + +from box_sdk_gen.schemas.ai_studio_agent_spreadsheet_tool import ( + AiStudioAgentSpreadsheetTool, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentSpreadsheetToolResponse(AiStudioAgentSpreadsheetTool): + def __init__( + self, + *, + warnings: Optional[List[str]] = None, + model: Optional[str] = None, + num_tokens_for_completion: Optional[int] = None, + llm_endpoint_params: Optional[AiLlmEndpointParams] = None, + **kwargs + ): + """ + :param warnings: Warnings concerning tool., defaults to None + :type warnings: Optional[List[str]], optional + :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :type model: Optional[str], optional + :param num_tokens_for_completion: The number of tokens for completion., defaults to None + :type num_tokens_for_completion: Optional[int], optional + """ + super().__init__( + model=model, + num_tokens_for_completion=num_tokens_for_completion, + llm_endpoint_params=llm_endpoint_params, + **kwargs + ) + self.warnings = warnings diff --git a/box_sdk_gen/schemas/ai_studio_agent_text_gen.py b/box_sdk_gen/schemas/ai_studio_agent_text_gen.py new file mode 100644 index 000000000..c461a3761 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_text_gen.py @@ -0,0 +1,50 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_studio_agent_basic_gen_tool import AiStudioAgentBasicGenTool + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentTextGenTypeField(str, Enum): + AI_AGENT_TEXT_GEN = 'ai_agent_text_gen' + + +class AiStudioAgentTextGen(BaseObject): + _discriminator = 'type', {'ai_agent_text_gen'} + + def __init__( + self, + access_state: str, + description: str, + *, + type: AiStudioAgentTextGenTypeField = AiStudioAgentTextGenTypeField.AI_AGENT_TEXT_GEN, + custom_instructions: Optional[str] = None, + suggested_questions: Optional[List[str]] = None, + basic_gen: Optional[AiStudioAgentBasicGenTool] = None, + **kwargs + ): + """ + :param access_state: The state of the AI Agent capability. Possible values are: `enabled` and `disabled`. + :type access_state: str + :param description: The description of the AI agent. + :type description: str + :param type: The type of AI agent used for generating text., defaults to AiStudioAgentTextGenTypeField.AI_AGENT_TEXT_GEN + :type type: AiStudioAgentTextGenTypeField, optional + :param custom_instructions: Custom instructions for the AI agent., defaults to None + :type custom_instructions: Optional[str], optional + :param suggested_questions: Suggested questions for the AI agent. If null, suggested question will be generated. If empty, no suggested questions will be displayed., defaults to None + :type suggested_questions: Optional[List[str]], optional + """ + super().__init__(**kwargs) + self.access_state = access_state + self.description = description + self.type = type + self.custom_instructions = custom_instructions + self.suggested_questions = suggested_questions + self.basic_gen = basic_gen diff --git a/box_sdk_gen/schemas/ai_studio_agent_text_gen_response.py b/box_sdk_gen/schemas/ai_studio_agent_text_gen_response.py new file mode 100644 index 000000000..fdef65767 --- /dev/null +++ b/box_sdk_gen/schemas/ai_studio_agent_text_gen_response.py @@ -0,0 +1,52 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_studio_agent_basic_gen_tool_response import ( + AiStudioAgentBasicGenToolResponse, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiStudioAgentTextGenResponseTypeField(str, Enum): + AI_AGENT_TEXT_GEN = 'ai_agent_text_gen' + + +class AiStudioAgentTextGenResponse(BaseObject): + _discriminator = 'type', {'ai_agent_text_gen'} + + def __init__( + self, + access_state: str, + description: str, + *, + type: AiStudioAgentTextGenResponseTypeField = AiStudioAgentTextGenResponseTypeField.AI_AGENT_TEXT_GEN, + custom_instructions: Optional[str] = None, + suggested_questions: Optional[List[str]] = None, + basic_gen: Optional[AiStudioAgentBasicGenToolResponse] = None, + **kwargs + ): + """ + :param access_state: The state of the AI Agent capability. Possible values are: `enabled` and `disabled`. + :type access_state: str + :param description: The description of the AI agent. + :type description: str + :param type: The type of AI agent used for generating text., defaults to AiStudioAgentTextGenResponseTypeField.AI_AGENT_TEXT_GEN + :type type: AiStudioAgentTextGenResponseTypeField, optional + :param custom_instructions: Custom instructions for the AI agent., defaults to None + :type custom_instructions: Optional[str], optional + :param suggested_questions: Suggested questions for the AI agent. If null, suggested question will be generated. If empty, no suggested questions will be displayed., defaults to None + :type suggested_questions: Optional[List[str]], optional + """ + super().__init__(**kwargs) + self.access_state = access_state + self.description = description + self.type = type + self.custom_instructions = custom_instructions + self.suggested_questions = suggested_questions + self.basic_gen = basic_gen diff --git a/box_sdk_gen/schemas/ai_text_gen.py b/box_sdk_gen/schemas/ai_text_gen.py new file mode 100644 index 000000000..1d62f7dcc --- /dev/null +++ b/box_sdk_gen/schemas/ai_text_gen.py @@ -0,0 +1,75 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Union + +from box_sdk_gen.schemas.ai_dialogue_history import AiDialogueHistory + +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +from box_sdk_gen.schemas.ai_agent_text_gen import AiAgentTextGen + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiTextGenItemsTypeField(str, Enum): + FILE = 'file' + + +class AiTextGenItemsField(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + id: str, + *, + type: AiTextGenItemsTypeField = AiTextGenItemsTypeField.FILE, + content: Optional[str] = None, + **kwargs + ): + """ + :param id: The ID of the item. + :type id: str + :param type: The type of the item., defaults to AiTextGenItemsTypeField.FILE + :type type: AiTextGenItemsTypeField, optional + :param content: The content to use as context for generating new text or editing existing text., defaults to None + :type content: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.content = content + + +class AiTextGen(BaseObject): + def __init__( + self, + prompt: str, + items: List[AiTextGenItemsField], + *, + dialogue_history: Optional[List[AiDialogueHistory]] = None, + ai_agent: Optional[Union[AiAgentReference, AiAgentTextGen]] = None, + **kwargs + ): + """ + :param prompt: The prompt provided by the client to be answered by the LLM. The prompt's length is limited to 10000 characters. + :type prompt: str + :param items: The items to be processed by the LLM, often files. + The array can include **exactly one** element. + + **Note**: Box AI handles documents with text representations up to 1MB in size. + If the file size exceeds 1MB, the first 1MB of text representation will be processed. + :type items: List[AiTextGenItemsField] + :param dialogue_history: The history of prompts and answers previously passed to the LLM. This parameter provides the additional context to the LLM when generating the response., defaults to None + :type dialogue_history: Optional[List[AiDialogueHistory]], optional + """ + super().__init__(**kwargs) + self.prompt = prompt + self.items = items + self.dialogue_history = dialogue_history + self.ai_agent = ai_agent diff --git a/box_sdk_gen/schemas/app_item.py b/box_sdk_gen/schemas/app_item.py new file mode 100644 index 000000000..d218c94e7 --- /dev/null +++ b/box_sdk_gen/schemas/app_item.py @@ -0,0 +1,34 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AppItemTypeField(str, Enum): + APP_ITEM = 'app_item' + + +class AppItem(BaseObject): + _discriminator = 'type', {'app_item'} + + def __init__( + self, + id: str, + application_type: str, + *, + type: AppItemTypeField = AppItemTypeField.APP_ITEM, + **kwargs + ): + """ + :param id: The unique identifier for this app item. + :type id: str + :param application_type: The type of the app that owns this app item. + :type application_type: str + :param type: The value will always be `app_item`., defaults to AppItemTypeField.APP_ITEM + :type type: AppItemTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.application_type = application_type + self.type = type diff --git a/box_sdk_gen/schemas/app_item_association.py b/box_sdk_gen/schemas/app_item_association.py new file mode 100644 index 000000000..9981fae29 --- /dev/null +++ b/box_sdk_gen/schemas/app_item_association.py @@ -0,0 +1,44 @@ +from enum import Enum + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.app_item import AppItem + +from box_sdk_gen.schemas.file_base import FileBase + +from box_sdk_gen.schemas.folder_base import FolderBase + +from box_sdk_gen.schemas.web_link_base import WebLinkBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class AppItemAssociationTypeField(str, Enum): + APP_ITEM_ASSOCIATION = 'app_item_association' + + +class AppItemAssociation(BaseObject): + _discriminator = 'type', {'app_item_association'} + + def __init__( + self, + id: str, + app_item: AppItem, + item: Union[FileBase, FolderBase, WebLinkBase], + *, + type: AppItemAssociationTypeField = AppItemAssociationTypeField.APP_ITEM_ASSOCIATION, + **kwargs + ): + """ + :param id: The unique identifier for this app item association. + :type id: str + :param type: The value will always be `app_item_association`., defaults to AppItemAssociationTypeField.APP_ITEM_ASSOCIATION + :type type: AppItemAssociationTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.app_item = app_item + self.item = item + self.type = type diff --git a/box_sdk_gen/schemas/app_item_associations.py b/box_sdk_gen/schemas/app_item_associations.py new file mode 100644 index 000000000..4cb940a3b --- /dev/null +++ b/box_sdk_gen/schemas/app_item_associations.py @@ -0,0 +1,36 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.app_item_association import AppItemAssociation + +from box_sdk_gen.box.errors import BoxSDKError + + +class AppItemAssociations(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[AppItemAssociation]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/app_item_event_source.py b/box_sdk_gen/schemas/app_item_event_source.py new file mode 100644 index 000000000..640847d86 --- /dev/null +++ b/box_sdk_gen/schemas/app_item_event_source.py @@ -0,0 +1,44 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.group_mini import GroupMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class AppItemEventSourceTypeField(str, Enum): + APP_ITEM = 'app_item' + + +class AppItemEventSource(BaseObject): + _discriminator = 'type', {'app_item'} + + def __init__( + self, + id: str, + app_item_type: str, + *, + type: AppItemEventSourceTypeField = AppItemEventSourceTypeField.APP_ITEM, + user: Optional[UserMini] = None, + group: Optional[GroupMini] = None, + **kwargs + ): + """ + :param id: The id of the `AppItem`. + :type id: str + :param app_item_type: The type of the `AppItem`. + :type app_item_type: str + :param type: The type of the source that this event represents. Can only be `app_item`., defaults to AppItemEventSourceTypeField.APP_ITEM + :type type: AppItemEventSourceTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.app_item_type = app_item_type + self.type = type + self.user = user + self.group = group diff --git a/box_sdk_gen/schemas/classification.py b/box_sdk_gen/schemas/classification.py new file mode 100644 index 000000000..54feab5c4 --- /dev/null +++ b/box_sdk_gen/schemas/classification.py @@ -0,0 +1,88 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ClassificationTemplateField(str, Enum): + SECURITYCLASSIFICATION_6VMVOCHWUWO = 'securityClassification-6VMVochwUWo' + + +class Classification(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'box_security_classification_key': 'Box__Security__Classification__Key', + 'parent': '$parent', + 'template': '$template', + 'scope': '$scope', + 'version': '$version', + 'type': '$type', + 'type_version': '$typeVersion', + 'can_edit': '$canEdit', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'Box__Security__Classification__Key': 'box_security_classification_key', + '$parent': 'parent', + '$template': 'template', + '$scope': 'scope', + '$version': 'version', + '$type': 'type', + '$typeVersion': 'type_version', + '$canEdit': 'can_edit', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + box_security_classification_key: Optional[str] = None, + parent: Optional[str] = None, + template: Optional[ClassificationTemplateField] = None, + scope: Optional[str] = None, + version: Optional[int] = None, + type: Optional[str] = None, + type_version: Optional[float] = None, + can_edit: Optional[bool] = None, + **kwargs + ): + """ + :param box_security_classification_key: The name of the classification applied to the item., defaults to None + :type box_security_classification_key: Optional[str], optional + :param parent: The identifier of the item that this metadata instance + has been attached to. This combines the `type` and the `id` + of the parent in the form `{type}_{id}`., defaults to None + :type parent: Optional[str], optional + :param template: The value will always be `securityClassification-6VMVochwUWo`., defaults to None + :type template: Optional[ClassificationTemplateField], optional + :param scope: The scope of the enterprise that this classification has been + applied for. + + This will be in the format `enterprise_{enterprise_id}`., defaults to None + :type scope: Optional[str], optional + :param version: The version of the metadata instance. This version starts at 0 and + increases every time a classification is updated., defaults to None + :type version: Optional[int], optional + :param type: The unique ID of this classification instance. This will be include + the name of the classification template and a unique ID., defaults to None + :type type: Optional[str], optional + :param type_version: The version of the metadata template. This version starts at 0 and + increases every time the template is updated. This is mostly for internal + use., defaults to None + :type type_version: Optional[float], optional + :param can_edit: Whether an end user can change the classification., defaults to None + :type can_edit: Optional[bool], optional + """ + super().__init__(**kwargs) + self.box_security_classification_key = box_security_classification_key + self.parent = parent + self.template = template + self.scope = scope + self.version = version + self.type = type + self.type_version = type_version + self.can_edit = can_edit diff --git a/box_sdk_gen/schemas/classification_template.py b/box_sdk_gen/schemas/classification_template.py new file mode 100644 index 000000000..7bb03cb07 --- /dev/null +++ b/box_sdk_gen/schemas/classification_template.py @@ -0,0 +1,246 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + + +class ClassificationTemplateTypeField(str, Enum): + METADATA_TEMPLATE = 'metadata_template' + + +class ClassificationTemplateTemplateKeyField(str, Enum): + SECURITYCLASSIFICATION_6VMVOCHWUWO = 'securityClassification-6VMVochwUWo' + + +class ClassificationTemplateDisplayNameField(str, Enum): + CLASSIFICATION = 'Classification' + + +class ClassificationTemplateFieldsTypeField(str, Enum): + ENUM = 'enum' + + +class ClassificationTemplateFieldsKeyField(str, Enum): + BOX__SECURITY__CLASSIFICATION__KEY = 'Box__Security__Classification__Key' + + +class ClassificationTemplateFieldsDisplayNameField(str, Enum): + CLASSIFICATION = 'Classification' + + +class ClassificationTemplateFieldsOptionsStaticConfigClassificationField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'classification_definition': 'classificationDefinition', + 'color_id': 'colorID', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'classificationDefinition': 'classification_definition', + 'colorID': 'color_id', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + classification_definition: Optional[str] = None, + color_id: Optional[int] = None, + **kwargs + ): + """ + :param classification_definition: A longer description of the classification., defaults to None + :type classification_definition: Optional[str], optional + :param color_id: An internal Box identifier used to assign a color to + a classification label. + + Mapping between a `colorID` and a color may change + without notice. Currently, the color mappings are as + follows. + + * `0`: Yellow. + * `1`: Orange. + * `2`: Watermelon red. + * `3`: Purple rain. + * `4`: Light blue. + * `5`: Dark blue. + * `6`: Light green. + * `7`: Gray., defaults to None + :type color_id: Optional[int], optional + """ + super().__init__(**kwargs) + self.classification_definition = classification_definition + self.color_id = color_id + + +class ClassificationTemplateFieldsOptionsStaticConfigField(BaseObject): + def __init__( + self, + *, + classification: Optional[ + ClassificationTemplateFieldsOptionsStaticConfigClassificationField + ] = None, + **kwargs + ): + """ + :param classification: Additional information about the classification. + + This is not an exclusive list of properties, and + more object fields might be returned. These fields + are used for internal Box Shield and Box Governance + purposes and no additional value must be derived from + these fields., defaults to None + :type classification: Optional[ClassificationTemplateFieldsOptionsStaticConfigClassificationField], optional + """ + super().__init__(**kwargs) + self.classification = classification + + +class ClassificationTemplateFieldsOptionsField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'static_config': 'staticConfig', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'staticConfig': 'static_config', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + id: str, + key: str, + *, + static_config: Optional[ + ClassificationTemplateFieldsOptionsStaticConfigField + ] = None, + **kwargs + ): + """ + :param id: The unique ID of this classification. + :type id: str + :param key: The display name and key for this classification. + :type key: str + :param static_config: Additional information about the classification., defaults to None + :type static_config: Optional[ClassificationTemplateFieldsOptionsStaticConfigField], optional + """ + super().__init__(**kwargs) + self.id = id + self.key = key + self.static_config = static_config + + +class ClassificationTemplateFieldsField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + **BaseObject._json_to_fields_mapping, + } + _discriminator = 'type', {'enum'} + + def __init__( + self, + id: str, + options: List[ClassificationTemplateFieldsOptionsField], + *, + type: ClassificationTemplateFieldsTypeField = ClassificationTemplateFieldsTypeField.ENUM, + key: ClassificationTemplateFieldsKeyField = ClassificationTemplateFieldsKeyField.BOX__SECURITY__CLASSIFICATION__KEY, + display_name: ClassificationTemplateFieldsDisplayNameField = ClassificationTemplateFieldsDisplayNameField.CLASSIFICATION, + hidden: Optional[bool] = None, + **kwargs + ): + """ + :param id: The unique ID of the field. + :type id: str + :param options: A list of classifications available in this enterprise. + :type options: List[ClassificationTemplateFieldsOptionsField] + :param type: The array item type., defaults to ClassificationTemplateFieldsTypeField.ENUM + :type type: ClassificationTemplateFieldsTypeField, optional + :param key: Defines classifications + available in the enterprise., defaults to ClassificationTemplateFieldsKeyField.BOX__SECURITY__CLASSIFICATION__KEY + :type key: ClassificationTemplateFieldsKeyField, optional + :param display_name: The value will always be `Classification`., defaults to ClassificationTemplateFieldsDisplayNameField.CLASSIFICATION + :type display_name: ClassificationTemplateFieldsDisplayNameField, optional + :param hidden: Classifications are always visible to web and mobile users., defaults to None + :type hidden: Optional[bool], optional + """ + super().__init__(**kwargs) + self.id = id + self.options = options + self.type = type + self.key = key + self.display_name = display_name + self.hidden = hidden + + +class ClassificationTemplate(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'template_key': 'templateKey', + 'display_name': 'displayName', + 'copy_instance_on_item_copy': 'copyInstanceOnItemCopy', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'templateKey': 'template_key', + 'displayName': 'display_name', + 'copyInstanceOnItemCopy': 'copy_instance_on_item_copy', + **BaseObject._json_to_fields_mapping, + } + _discriminator = 'type', {'metadata_template'} + + def __init__( + self, + id: str, + scope: str, + fields: List[ClassificationTemplateFieldsField], + *, + type: ClassificationTemplateTypeField = ClassificationTemplateTypeField.METADATA_TEMPLATE, + template_key: ClassificationTemplateTemplateKeyField = ClassificationTemplateTemplateKeyField.SECURITYCLASSIFICATION_6VMVOCHWUWO, + display_name: ClassificationTemplateDisplayNameField = ClassificationTemplateDisplayNameField.CLASSIFICATION, + hidden: Optional[bool] = None, + copy_instance_on_item_copy: Optional[bool] = None, + **kwargs + ): + """ + :param id: The ID of the classification template. + :type id: str + :param scope: The scope of the classification template. This is in the format + `enterprise_{id}` where the `id` is the enterprise ID. + :type scope: str + :param fields: A list of fields for this classification template. This includes + only one field, the `Box__Security__Classification__Key`, which defines + the different classifications available in this enterprise. + :type fields: List[ClassificationTemplateFieldsField] + :param type: The value will always be `metadata_template`., defaults to ClassificationTemplateTypeField.METADATA_TEMPLATE + :type type: ClassificationTemplateTypeField, optional + :param template_key: The value will always be `securityClassification-6VMVochwUWo`., defaults to ClassificationTemplateTemplateKeyField.SECURITYCLASSIFICATION_6VMVOCHWUWO + :type template_key: ClassificationTemplateTemplateKeyField, optional + :param display_name: The name of this template as shown in web and mobile interfaces., defaults to ClassificationTemplateDisplayNameField.CLASSIFICATION + :type display_name: ClassificationTemplateDisplayNameField, optional + :param hidden: Determines if the + template is always available in web and mobile interfaces., defaults to None + :type hidden: Optional[bool], optional + :param copy_instance_on_item_copy: Determines if + classifications are + copied along when the file or folder is + copied., defaults to None + :type copy_instance_on_item_copy: Optional[bool], optional + """ + super().__init__(**kwargs) + self.id = id + self.scope = scope + self.fields = fields + self.type = type + self.template_key = template_key + self.display_name = display_name + self.hidden = hidden + self.copy_instance_on_item_copy = copy_instance_on_item_copy diff --git a/box_sdk_gen/schemas/client_error.py b/box_sdk_gen/schemas/client_error.py new file mode 100644 index 000000000..fce9bae17 --- /dev/null +++ b/box_sdk_gen/schemas/client_error.py @@ -0,0 +1,77 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ClientErrorTypeField(str, Enum): + ERROR = 'error' + + +class ClientErrorCodeField(str, Enum): + CREATED = 'created' + ACCEPTED = 'accepted' + NO_CONTENT = 'no_content' + REDIRECT = 'redirect' + NOT_MODIFIED = 'not_modified' + BAD_REQUEST = 'bad_request' + UNAUTHORIZED = 'unauthorized' + FORBIDDEN = 'forbidden' + NOT_FOUND = 'not_found' + METHOD_NOT_ALLOWED = 'method_not_allowed' + CONFLICT = 'conflict' + PRECONDITION_FAILED = 'precondition_failed' + TOO_MANY_REQUESTS = 'too_many_requests' + INTERNAL_SERVER_ERROR = 'internal_server_error' + UNAVAILABLE = 'unavailable' + ITEM_NAME_INVALID = 'item_name_invalid' + INSUFFICIENT_SCOPE = 'insufficient_scope' + + +class ClientError(BaseObject): + _discriminator = 'type', {'error'} + + def __init__( + self, + *, + type: Optional[ClientErrorTypeField] = None, + status: Optional[int] = None, + code: Optional[ClientErrorCodeField] = None, + message: Optional[str] = None, + context_info: Optional[Dict] = None, + help_url: Optional[str] = None, + request_id: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `error`., defaults to None + :type type: Optional[ClientErrorTypeField], optional + :param status: The HTTP status of the response., defaults to None + :type status: Optional[int], optional + :param code: A Box-specific error code., defaults to None + :type code: Optional[ClientErrorCodeField], optional + :param message: A short message describing the error., defaults to None + :type message: Optional[str], optional + :param context_info: A free-form object that contains additional context + about the error. The possible fields are defined on + a per-endpoint basis. `message` is only one example., defaults to None + :type context_info: Optional[Dict], optional + :param help_url: A URL that links to more information about why this error occurred., defaults to None + :type help_url: Optional[str], optional + :param request_id: A unique identifier for this response, which can be used + when contacting Box support., defaults to None + :type request_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.status = status + self.code = code + self.message = message + self.context_info = context_info + self.help_url = help_url + self.request_id = request_id diff --git a/box_sdk_gen/schemas/collaboration.py b/box_sdk_gen/schemas/collaboration.py new file mode 100644 index 000000000..7d81fae09 --- /dev/null +++ b/box_sdk_gen/schemas/collaboration.py @@ -0,0 +1,220 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Union + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.schemas.app_item import AppItem + +from box_sdk_gen.schemas.user_collaborations import UserCollaborations + +from box_sdk_gen.schemas.group_mini import GroupMini + +from box_sdk_gen.schemas.terms_of_service_base import TermsOfServiceBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class CollaborationTypeField(str, Enum): + COLLABORATION = 'collaboration' + + +class CollaborationRoleField(str, Enum): + EDITOR = 'editor' + VIEWER = 'viewer' + PREVIEWER = 'previewer' + UPLOADER = 'uploader' + PREVIEWER_UPLOADER = 'previewer uploader' + VIEWER_UPLOADER = 'viewer uploader' + CO_OWNER = 'co-owner' + OWNER = 'owner' + + +class CollaborationStatusField(str, Enum): + ACCEPTED = 'accepted' + PENDING = 'pending' + REJECTED = 'rejected' + + +class CollaborationAcceptanceRequirementsStatusTermsOfServiceRequirementField( + BaseObject +): + def __init__( + self, + *, + is_accepted: Optional[bool] = None, + terms_of_service: Optional[TermsOfServiceBase] = None, + **kwargs + ): + """ + :param is_accepted: Whether or not the terms of service have been accepted. The + field is `null` when there is no terms of service required., defaults to None + :type is_accepted: Optional[bool], optional + """ + super().__init__(**kwargs) + self.is_accepted = is_accepted + self.terms_of_service = terms_of_service + + +class CollaborationAcceptanceRequirementsStatusStrongPasswordRequirementField( + BaseObject +): + def __init__( + self, + *, + enterprise_has_strong_password_required_for_external_users: Optional[ + bool + ] = None, + user_has_strong_password: Optional[bool] = None, + **kwargs + ): + """ + :param enterprise_has_strong_password_required_for_external_users: Whether or not the enterprise that owns the content requires + a strong password to collaborate on the content, or enforces + an exposed password detection for the external collaborators., defaults to None + :type enterprise_has_strong_password_required_for_external_users: Optional[bool], optional + :param user_has_strong_password: Whether or not the user has a strong and not exposed password set + for their account. The field is `null` when a strong password is + not required., defaults to None + :type user_has_strong_password: Optional[bool], optional + """ + super().__init__(**kwargs) + self.enterprise_has_strong_password_required_for_external_users = ( + enterprise_has_strong_password_required_for_external_users + ) + self.user_has_strong_password = user_has_strong_password + + +class CollaborationAcceptanceRequirementsStatusTwoFactorAuthenticationRequirementField( + BaseObject +): + def __init__( + self, + *, + enterprise_has_two_factor_auth_enabled: Optional[bool] = None, + user_has_two_factor_authentication_enabled: Optional[bool] = None, + **kwargs + ): + """ + :param enterprise_has_two_factor_auth_enabled: Whether or not the enterprise that owns the content requires + two-factor authentication to be enabled in order to + collaborate on the content., defaults to None + :type enterprise_has_two_factor_auth_enabled: Optional[bool], optional + :param user_has_two_factor_authentication_enabled: Whether or not the user has two-factor authentication + enabled. The field is `null` when two-factor + authentication is not required., defaults to None + :type user_has_two_factor_authentication_enabled: Optional[bool], optional + """ + super().__init__(**kwargs) + self.enterprise_has_two_factor_auth_enabled = ( + enterprise_has_two_factor_auth_enabled + ) + self.user_has_two_factor_authentication_enabled = ( + user_has_two_factor_authentication_enabled + ) + + +class CollaborationAcceptanceRequirementsStatusField(BaseObject): + def __init__( + self, + *, + terms_of_service_requirement: Optional[ + CollaborationAcceptanceRequirementsStatusTermsOfServiceRequirementField + ] = None, + strong_password_requirement: Optional[ + CollaborationAcceptanceRequirementsStatusStrongPasswordRequirementField + ] = None, + two_factor_authentication_requirement: Optional[ + CollaborationAcceptanceRequirementsStatusTwoFactorAuthenticationRequirementField + ] = None, + **kwargs + ): + super().__init__(**kwargs) + self.terms_of_service_requirement = terms_of_service_requirement + self.strong_password_requirement = strong_password_requirement + self.two_factor_authentication_requirement = ( + two_factor_authentication_requirement + ) + + +class Collaboration(BaseObject): + _discriminator = 'type', {'collaboration'} + + def __init__( + self, + id: str, + *, + type: CollaborationTypeField = CollaborationTypeField.COLLABORATION, + item: Optional[Union[File, Folder, WebLink]] = None, + app_item: Optional[AppItem] = None, + accessible_by: Optional[Union[UserCollaborations, GroupMini]] = None, + invite_email: Optional[str] = None, + role: Optional[CollaborationRoleField] = None, + expires_at: Optional[DateTime] = None, + is_access_only: Optional[bool] = None, + status: Optional[CollaborationStatusField] = None, + acknowledged_at: Optional[DateTime] = None, + created_by: Optional[UserCollaborations] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + acceptance_requirements_status: Optional[ + CollaborationAcceptanceRequirementsStatusField + ] = None, + **kwargs + ): + """ + :param id: The unique identifier for this collaboration. + :type id: str + :param type: The value will always be `collaboration`., defaults to CollaborationTypeField.COLLABORATION + :type type: CollaborationTypeField, optional + :param invite_email: The email address used to invite an unregistered collaborator, if + they are not a registered user., defaults to None + :type invite_email: Optional[str], optional + :param role: The level of access granted., defaults to None + :type role: Optional[CollaborationRoleField], optional + :param expires_at: When the collaboration will expire, or `null` if no expiration + date is set., defaults to None + :type expires_at: Optional[DateTime], optional + :param is_access_only: If set to `true`, collaborators have access to + shared items, but such items won't be visible in the + All Files list. Additionally, collaborators won't + see the the path to the root folder for the + shared item., defaults to None + :type is_access_only: Optional[bool], optional + :param status: The status of the collaboration invitation. If the status + is `pending`, `login` and `name` return an empty string., defaults to None + :type status: Optional[CollaborationStatusField], optional + :param acknowledged_at: When the `status` of the collaboration object changed to + `accepted` or `rejected`., defaults to None + :type acknowledged_at: Optional[DateTime], optional + :param created_at: When the collaboration object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the collaboration object was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.item = item + self.app_item = app_item + self.accessible_by = accessible_by + self.invite_email = invite_email + self.role = role + self.expires_at = expires_at + self.is_access_only = is_access_only + self.status = status + self.acknowledged_at = acknowledged_at + self.created_by = created_by + self.created_at = created_at + self.modified_at = modified_at + self.acceptance_requirements_status = acceptance_requirements_status diff --git a/box_sdk_gen/schemas/collaboration_allowlist_entries.py b/box_sdk_gen/schemas/collaboration_allowlist_entries.py new file mode 100644 index 000000000..6d15e2629 --- /dev/null +++ b/box_sdk_gen/schemas/collaboration_allowlist_entries.py @@ -0,0 +1,40 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.collaboration_allowlist_entry import ( + CollaborationAllowlistEntry, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class CollaborationAllowlistEntries(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[CollaborationAllowlistEntry]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of allowed collaboration domains., defaults to None + :type entries: Optional[List[CollaborationAllowlistEntry]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/collaboration_allowlist_entry.py b/box_sdk_gen/schemas/collaboration_allowlist_entry.py new file mode 100644 index 000000000..341f79c9d --- /dev/null +++ b/box_sdk_gen/schemas/collaboration_allowlist_entry.py @@ -0,0 +1,83 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class CollaborationAllowlistEntryTypeField(str, Enum): + COLLABORATION_WHITELIST_ENTRY = 'collaboration_whitelist_entry' + + +class CollaborationAllowlistEntryDirectionField(str, Enum): + INBOUND = 'inbound' + OUTBOUND = 'outbound' + BOTH = 'both' + + +class CollaborationAllowlistEntryEnterpriseTypeField(str, Enum): + ENTERPRISE = 'enterprise' + + +class CollaborationAllowlistEntryEnterpriseField(BaseObject): + _discriminator = 'type', {'enterprise'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[CollaborationAllowlistEntryEnterpriseTypeField] = None, + name: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this enterprise., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `enterprise`., defaults to None + :type type: Optional[CollaborationAllowlistEntryEnterpriseTypeField], optional + :param name: The name of the enterprise., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + + +class CollaborationAllowlistEntry(BaseObject): + _discriminator = 'type', {'collaboration_whitelist_entry'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[CollaborationAllowlistEntryTypeField] = None, + domain: Optional[str] = None, + direction: Optional[CollaborationAllowlistEntryDirectionField] = None, + enterprise: Optional[CollaborationAllowlistEntryEnterpriseField] = None, + created_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param id: The unique identifier for this entry., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `collaboration_whitelist_entry`., defaults to None + :type type: Optional[CollaborationAllowlistEntryTypeField], optional + :param domain: The whitelisted domain., defaults to None + :type domain: Optional[str], optional + :param direction: The direction of the collaborations to allow., defaults to None + :type direction: Optional[CollaborationAllowlistEntryDirectionField], optional + :param created_at: The time the entry was created at., defaults to None + :type created_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.domain = domain + self.direction = direction + self.enterprise = enterprise + self.created_at = created_at diff --git a/box_sdk_gen/schemas/collaboration_allowlist_exempt_target.py b/box_sdk_gen/schemas/collaboration_allowlist_exempt_target.py new file mode 100644 index 000000000..ab1c17e38 --- /dev/null +++ b/box_sdk_gen/schemas/collaboration_allowlist_exempt_target.py @@ -0,0 +1,77 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class CollaborationAllowlistExemptTargetTypeField(str, Enum): + COLLABORATION_WHITELIST_EXEMPT_TARGET = 'collaboration_whitelist_exempt_target' + + +class CollaborationAllowlistExemptTargetEnterpriseTypeField(str, Enum): + ENTERPRISE = 'enterprise' + + +class CollaborationAllowlistExemptTargetEnterpriseField(BaseObject): + _discriminator = 'type', {'enterprise'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[CollaborationAllowlistExemptTargetEnterpriseTypeField] = None, + name: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this enterprise., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `enterprise`., defaults to None + :type type: Optional[CollaborationAllowlistExemptTargetEnterpriseTypeField], optional + :param name: The name of the enterprise., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + + +class CollaborationAllowlistExemptTarget(BaseObject): + _discriminator = 'type', {'collaboration_whitelist_exempt_target'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[CollaborationAllowlistExemptTargetTypeField] = None, + enterprise: Optional[CollaborationAllowlistExemptTargetEnterpriseField] = None, + user: Optional[UserMini] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param id: The unique identifier for this exemption., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `collaboration_whitelist_exempt_target`., defaults to None + :type type: Optional[CollaborationAllowlistExemptTargetTypeField], optional + :param created_at: The time the entry was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The time the entry was modified., defaults to None + :type modified_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.enterprise = enterprise + self.user = user + self.created_at = created_at + self.modified_at = modified_at diff --git a/box_sdk_gen/schemas/collaboration_allowlist_exempt_targets.py b/box_sdk_gen/schemas/collaboration_allowlist_exempt_targets.py new file mode 100644 index 000000000..0c5fbc3a4 --- /dev/null +++ b/box_sdk_gen/schemas/collaboration_allowlist_exempt_targets.py @@ -0,0 +1,42 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.collaboration_allowlist_exempt_target import ( + CollaborationAllowlistExemptTarget, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class CollaborationAllowlistExemptTargets(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[CollaborationAllowlistExemptTarget]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of users exempt from any of the restrictions + imposed by the list of allowed collaboration domains + for this enterprise., defaults to None + :type entries: Optional[List[CollaborationAllowlistExemptTarget]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/collaborations.py b/box_sdk_gen/schemas/collaborations.py new file mode 100644 index 000000000..5ee9a17f8 --- /dev/null +++ b/box_sdk_gen/schemas/collaborations.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.collaboration import Collaboration + +from box_sdk_gen.box.errors import BoxSDKError + + +class Collaborations(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[Collaboration]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of collaborations., defaults to None + :type entries: Optional[List[Collaboration]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/collaborations_offset_paginated.py b/box_sdk_gen/schemas/collaborations_offset_paginated.py new file mode 100644 index 000000000..91538ad37 --- /dev/null +++ b/box_sdk_gen/schemas/collaborations_offset_paginated.py @@ -0,0 +1,47 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.collaboration import Collaboration + +from box_sdk_gen.box.errors import BoxSDKError + + +class CollaborationsOffsetPaginated(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + entries: Optional[List[Collaboration]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type total_count: Optional[int], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type offset: Optional[int], optional + :param entries: A list of collaborations., defaults to None + :type entries: Optional[List[Collaboration]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.limit = limit + self.offset = offset + self.entries = entries diff --git a/box_sdk_gen/schemas/collaborator_variable.py b/box_sdk_gen/schemas/collaborator_variable.py new file mode 100644 index 000000000..440ccf79c --- /dev/null +++ b/box_sdk_gen/schemas/collaborator_variable.py @@ -0,0 +1,68 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + + +class CollaboratorVariableTypeField(str, Enum): + VARIABLE = 'variable' + + +class CollaboratorVariableVariableTypeField(str, Enum): + USER_LIST = 'user_list' + + +class CollaboratorVariableVariableValueTypeField(str, Enum): + USER = 'user' + + +class CollaboratorVariableVariableValueField(BaseObject): + _discriminator = 'type', {'user'} + + def __init__( + self, + id: str, + *, + type: CollaboratorVariableVariableValueTypeField = CollaboratorVariableVariableValueTypeField.USER, + **kwargs + ): + """ + :param id: User's ID. + :type id: str + :param type: The object type., defaults to CollaboratorVariableVariableValueTypeField.USER + :type type: CollaboratorVariableVariableValueTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class CollaboratorVariable(BaseObject): + _discriminator = 'type', {'variable'} + + def __init__( + self, + variable_value: List[CollaboratorVariableVariableValueField], + *, + type: CollaboratorVariableTypeField = CollaboratorVariableTypeField.VARIABLE, + variable_type: CollaboratorVariableVariableTypeField = CollaboratorVariableVariableTypeField.USER_LIST, + **kwargs + ): + """ + :param variable_value: A list of user IDs. + :type variable_value: List[CollaboratorVariableVariableValueField] + :param type: Collaborator + object type., defaults to CollaboratorVariableTypeField.VARIABLE + :type type: CollaboratorVariableTypeField, optional + :param variable_type: Variable type + for the Collaborator + object., defaults to CollaboratorVariableVariableTypeField.USER_LIST + :type variable_type: CollaboratorVariableVariableTypeField, optional + """ + super().__init__(**kwargs) + self.variable_value = variable_value + self.type = type + self.variable_type = variable_type diff --git a/box_sdk_gen/schemas/collection.py b/box_sdk_gen/schemas/collection.py new file mode 100644 index 000000000..d55609279 --- /dev/null +++ b/box_sdk_gen/schemas/collection.py @@ -0,0 +1,50 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class CollectionTypeField(str, Enum): + COLLECTION = 'collection' + + +class CollectionNameField(str, Enum): + FAVORITES = 'Favorites' + + +class CollectionCollectionTypeField(str, Enum): + FAVORITES = 'favorites' + + +class Collection(BaseObject): + _discriminator = 'type', {'collection'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[CollectionTypeField] = None, + name: Optional[CollectionNameField] = None, + collection_type: Optional[CollectionCollectionTypeField] = None, + **kwargs + ): + """ + :param id: The unique identifier for this collection., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `collection`., defaults to None + :type type: Optional[CollectionTypeField], optional + :param name: The name of the collection., defaults to None + :type name: Optional[CollectionNameField], optional + :param collection_type: The type of the collection. This is used to + determine the proper visual treatment for + collections., defaults to None + :type collection_type: Optional[CollectionCollectionTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + self.collection_type = collection_type diff --git a/box_sdk_gen/schemas/collections.py b/box_sdk_gen/schemas/collections.py new file mode 100644 index 000000000..b64669f5d --- /dev/null +++ b/box_sdk_gen/schemas/collections.py @@ -0,0 +1,80 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.collection import Collection + +from box_sdk_gen.box.errors import BoxSDKError + + +class CollectionsOrderDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class CollectionsOrderField(BaseObject): + def __init__( + self, + *, + by: Optional[str] = None, + direction: Optional[CollectionsOrderDirectionField] = None, + **kwargs + ): + """ + :param by: The field to order by., defaults to None + :type by: Optional[str], optional + :param direction: The direction to order by, either ascending or descending., defaults to None + :type direction: Optional[CollectionsOrderDirectionField], optional + """ + super().__init__(**kwargs) + self.by = by + self.direction = direction + + +class Collections(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + order: Optional[List[CollectionsOrderField]] = None, + entries: Optional[List[Collection]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type total_count: Optional[int], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type offset: Optional[int], optional + :param order: The order by which items are returned. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type order: Optional[List[CollectionsOrderField]], optional + :param entries: A list of collections., defaults to None + :type entries: Optional[List[Collection]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.limit = limit + self.offset = offset + self.order = order + self.entries = entries diff --git a/box_sdk_gen/schemas/comment.py b/box_sdk_gen/schemas/comment.py new file mode 100644 index 000000000..a67ba328f --- /dev/null +++ b/box_sdk_gen/schemas/comment.py @@ -0,0 +1,66 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.comment_base import CommentBaseTypeField + +from box_sdk_gen.schemas.comment_base import CommentBase + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class CommentItemField(BaseObject): + def __init__( + self, *, id: Optional[str] = None, type: Optional[str] = None, **kwargs + ): + """ + :param id: The unique identifier for this object., defaults to None + :type id: Optional[str], optional + :param type: The type for this object., defaults to None + :type type: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class Comment(CommentBase): + def __init__( + self, + *, + is_reply_comment: Optional[bool] = None, + message: Optional[str] = None, + created_by: Optional[UserMini] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + item: Optional[CommentItemField] = None, + id: Optional[str] = None, + type: Optional[CommentBaseTypeField] = None, + **kwargs + ): + """ + :param is_reply_comment: Whether or not this comment is a reply to another + comment., defaults to None + :type is_reply_comment: Optional[bool], optional + :param message: The text of the comment, as provided by the user., defaults to None + :type message: Optional[str], optional + :param created_at: The time this comment was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The time this comment was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param id: The unique identifier for this comment., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `comment`., defaults to None + :type type: Optional[CommentBaseTypeField], optional + """ + super().__init__(id=id, type=type, **kwargs) + self.is_reply_comment = is_reply_comment + self.message = message + self.created_by = created_by + self.created_at = created_at + self.modified_at = modified_at + self.item = item diff --git a/box_sdk_gen/schemas/comment_base.py b/box_sdk_gen/schemas/comment_base.py new file mode 100644 index 000000000..bb5761693 --- /dev/null +++ b/box_sdk_gen/schemas/comment_base.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class CommentBaseTypeField(str, Enum): + COMMENT = 'comment' + + +class CommentBase(BaseObject): + _discriminator = 'type', {'comment'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[CommentBaseTypeField] = None, + **kwargs + ): + """ + :param id: The unique identifier for this comment., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `comment`., defaults to None + :type type: Optional[CommentBaseTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/comment_full.py b/box_sdk_gen/schemas/comment_full.py new file mode 100644 index 000000000..1b3b7b69f --- /dev/null +++ b/box_sdk_gen/schemas/comment_full.py @@ -0,0 +1,64 @@ +from typing import Optional + +from box_sdk_gen.schemas.comment_base import CommentBaseTypeField + +from box_sdk_gen.schemas.comment_base import CommentBase + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.comment import CommentItemField + +from box_sdk_gen.schemas.comment import Comment + +from box_sdk_gen.box.errors import BoxSDKError + + +class CommentFull(Comment): + def __init__( + self, + *, + tagged_message: Optional[str] = None, + is_reply_comment: Optional[bool] = None, + message: Optional[str] = None, + created_by: Optional[UserMini] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + item: Optional[CommentItemField] = None, + id: Optional[str] = None, + type: Optional[CommentBaseTypeField] = None, + **kwargs + ): + """ + :param tagged_message: The string representing the comment text with + @mentions included. @mention format is @[id:username] + where `id` is user's Box ID and `username` is + their display name., defaults to None + :type tagged_message: Optional[str], optional + :param is_reply_comment: Whether or not this comment is a reply to another + comment., defaults to None + :type is_reply_comment: Optional[bool], optional + :param message: The text of the comment, as provided by the user., defaults to None + :type message: Optional[str], optional + :param created_at: The time this comment was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The time this comment was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param id: The unique identifier for this comment., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `comment`., defaults to None + :type type: Optional[CommentBaseTypeField], optional + """ + super().__init__( + is_reply_comment=is_reply_comment, + message=message, + created_by=created_by, + created_at=created_at, + modified_at=modified_at, + item=item, + id=id, + type=type, + **kwargs + ) + self.tagged_message = tagged_message diff --git a/box_sdk_gen/schemas/comments.py b/box_sdk_gen/schemas/comments.py new file mode 100644 index 000000000..979b5afb4 --- /dev/null +++ b/box_sdk_gen/schemas/comments.py @@ -0,0 +1,80 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.comment_full import CommentFull + +from box_sdk_gen.box.errors import BoxSDKError + + +class CommentsOrderDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class CommentsOrderField(BaseObject): + def __init__( + self, + *, + by: Optional[str] = None, + direction: Optional[CommentsOrderDirectionField] = None, + **kwargs + ): + """ + :param by: The field to order by., defaults to None + :type by: Optional[str], optional + :param direction: The direction to order by, either ascending or descending., defaults to None + :type direction: Optional[CommentsOrderDirectionField], optional + """ + super().__init__(**kwargs) + self.by = by + self.direction = direction + + +class Comments(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + order: Optional[List[CommentsOrderField]] = None, + entries: Optional[List[CommentFull]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type total_count: Optional[int], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type offset: Optional[int], optional + :param order: The order by which items are returned. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type order: Optional[List[CommentsOrderField]], optional + :param entries: A list of comments., defaults to None + :type entries: Optional[List[CommentFull]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.limit = limit + self.offset = offset + self.order = order + self.entries = entries diff --git a/box_sdk_gen/schemas/completion_rule_variable.py b/box_sdk_gen/schemas/completion_rule_variable.py new file mode 100644 index 000000000..67b818f3f --- /dev/null +++ b/box_sdk_gen/schemas/completion_rule_variable.py @@ -0,0 +1,48 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class CompletionRuleVariableTypeField(str, Enum): + VARIABLE = 'variable' + + +class CompletionRuleVariableVariableTypeField(str, Enum): + TASK_COMPLETION_RULE = 'task_completion_rule' + + +class CompletionRuleVariableVariableValueField(str, Enum): + ALL_ASSIGNEES = 'all_assignees' + ANY_ASSIGNEES = 'any_assignees' + + +class CompletionRuleVariable(BaseObject): + _discriminator = 'type', {'variable'} + + def __init__( + self, + variable_value: CompletionRuleVariableVariableValueField, + *, + type: CompletionRuleVariableTypeField = CompletionRuleVariableTypeField.VARIABLE, + variable_type: CompletionRuleVariableVariableTypeField = CompletionRuleVariableVariableTypeField.TASK_COMPLETION_RULE, + **kwargs + ): + """ + :param variable_value: Variable + values for a completion + rule. + :type variable_value: CompletionRuleVariableVariableValueField + :param type: Completion + Rule object type., defaults to CompletionRuleVariableTypeField.VARIABLE + :type type: CompletionRuleVariableTypeField, optional + :param variable_type: Variable type + for the Completion + Rule object., defaults to CompletionRuleVariableVariableTypeField.TASK_COMPLETION_RULE + :type variable_type: CompletionRuleVariableVariableTypeField, optional + """ + super().__init__(**kwargs) + self.variable_value = variable_value + self.type = type + self.variable_type = variable_type diff --git a/box_sdk_gen/schemas/conflict_error.py b/box_sdk_gen/schemas/conflict_error.py new file mode 100644 index 000000000..4ac1341c5 --- /dev/null +++ b/box_sdk_gen/schemas/conflict_error.py @@ -0,0 +1,71 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Dict + +from box_sdk_gen.schemas.client_error import ClientErrorTypeField + +from box_sdk_gen.schemas.client_error import ClientErrorCodeField + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.file_conflict import FileConflict + +from box_sdk_gen.box.errors import BoxSDKError + + +class ConflictErrorContextInfoField(BaseObject): + def __init__(self, *, conflicts: Optional[List[FileConflict]] = None, **kwargs): + """ + :param conflicts: A list of the file conflicts that caused this error., defaults to None + :type conflicts: Optional[List[FileConflict]], optional + """ + super().__init__(**kwargs) + self.conflicts = conflicts + + +class ConflictError(ClientError): + def __init__( + self, + *, + type: Optional[ClientErrorTypeField] = None, + status: Optional[int] = None, + code: Optional[ClientErrorCodeField] = None, + message: Optional[str] = None, + context_info: Optional[Dict] = None, + help_url: Optional[str] = None, + request_id: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `error`., defaults to None + :type type: Optional[ClientErrorTypeField], optional + :param status: The HTTP status of the response., defaults to None + :type status: Optional[int], optional + :param code: A Box-specific error code., defaults to None + :type code: Optional[ClientErrorCodeField], optional + :param message: A short message describing the error., defaults to None + :type message: Optional[str], optional + :param context_info: A free-form object that contains additional context + about the error. The possible fields are defined on + a per-endpoint basis. `message` is only one example., defaults to None + :type context_info: Optional[Dict], optional + :param help_url: A URL that links to more information about why this error occurred., defaults to None + :type help_url: Optional[str], optional + :param request_id: A unique identifier for this response, which can be used + when contacting Box support., defaults to None + :type request_id: Optional[str], optional + """ + super().__init__( + type=type, + status=status, + code=code, + message=message, + context_info=context_info, + help_url=help_url, + request_id=request_id, + **kwargs + ) diff --git a/box_sdk_gen/schemas/create_ai_agent.py b/box_sdk_gen/schemas/create_ai_agent.py new file mode 100644 index 000000000..caa2a694d --- /dev/null +++ b/box_sdk_gen/schemas/create_ai_agent.py @@ -0,0 +1,65 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.schemas.group_base import GroupBase + +from box_sdk_gen.schemas.ai_agent_allowed_entity import AiAgentAllowedEntity + +from box_sdk_gen.schemas.ai_studio_agent_ask import AiStudioAgentAsk + +from box_sdk_gen.schemas.ai_studio_agent_text_gen import AiStudioAgentTextGen + +from box_sdk_gen.schemas.ai_studio_agent_extract import AiStudioAgentExtract + +from box_sdk_gen.box.errors import BoxSDKError + + +class CreateAiAgentTypeField(str, Enum): + AI_AGENT = 'ai_agent' + + +class CreateAiAgent(BaseObject): + _discriminator = 'type', {'ai_agent'} + + def __init__( + self, + name: str, + access_state: str, + *, + type: CreateAiAgentTypeField = CreateAiAgentTypeField.AI_AGENT, + icon_reference: Optional[str] = None, + allowed_entities: Optional[List[AiAgentAllowedEntity]] = None, + ask: Optional[AiStudioAgentAsk] = None, + text_gen: Optional[AiStudioAgentTextGen] = None, + extract: Optional[AiStudioAgentExtract] = None, + **kwargs + ): + """ + :param name: The name of the AI Agent. + :type name: str + :param access_state: The state of the AI Agent. Possible values are: `enabled`, `disabled`, and `enabled_for_selected_users`. + :type access_state: str + :param type: The type of agent used to handle queries., defaults to CreateAiAgentTypeField.AI_AGENT + :type type: CreateAiAgentTypeField, optional + :param icon_reference: The icon reference of the AI Agent. It should have format of the URL `https://cdn01.boxcdn.net/app-assets/aistudio/avatars/` + where possible values of `file_name` are: `logo_boxAi.png`,`logo_stamp.png`,`logo_legal.png`,`logo_finance.png`,`logo_config.png`,`logo_handshake.png`,`logo_analytics.png`,`logo_classification.png`., defaults to None + :type icon_reference: Optional[str], optional + :param allowed_entities: List of allowed users or groups., defaults to None + :type allowed_entities: Optional[List[AiAgentAllowedEntity]], optional + """ + super().__init__(**kwargs) + self.name = name + self.access_state = access_state + self.type = type + self.icon_reference = icon_reference + self.allowed_entities = allowed_entities + self.ask = ask + self.text_gen = text_gen + self.extract = extract diff --git a/box_sdk_gen/schemas/device_pinner.py b/box_sdk_gen/schemas/device_pinner.py new file mode 100644 index 000000000..97652f005 --- /dev/null +++ b/box_sdk_gen/schemas/device_pinner.py @@ -0,0 +1,40 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class DevicePinnerTypeField(str, Enum): + DEVICE_PINNER = 'device_pinner' + + +class DevicePinner(BaseObject): + _discriminator = 'type', {'device_pinner'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[DevicePinnerTypeField] = None, + owned_by: Optional[UserMini] = None, + product_name: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this device pin., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `device_pinner`., defaults to None + :type type: Optional[DevicePinnerTypeField], optional + :param product_name: The type of device being pinned., defaults to None + :type product_name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.owned_by = owned_by + self.product_name = product_name diff --git a/box_sdk_gen/schemas/device_pinners.py b/box_sdk_gen/schemas/device_pinners.py new file mode 100644 index 000000000..3ad323efa --- /dev/null +++ b/box_sdk_gen/schemas/device_pinners.py @@ -0,0 +1,68 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.device_pinner import DevicePinner + +from box_sdk_gen.box.errors import BoxSDKError + + +class DevicePinnersOrderByField(str, Enum): + ID = 'id' + + +class DevicePinnersOrderDirectionField(str, Enum): + ASC = 'asc' + DESC = 'desc' + + +class DevicePinnersOrderField(BaseObject): + def __init__( + self, + *, + by: Optional[DevicePinnersOrderByField] = None, + direction: Optional[DevicePinnersOrderDirectionField] = None, + **kwargs + ): + """ + :param by: The field that is ordered by., defaults to None + :type by: Optional[DevicePinnersOrderByField], optional + :param direction: The direction to order by, either ascending or descending., defaults to None + :type direction: Optional[DevicePinnersOrderDirectionField], optional + """ + super().__init__(**kwargs) + self.by = by + self.direction = direction + + +class DevicePinners(BaseObject): + def __init__( + self, + *, + entries: Optional[List[DevicePinner]] = None, + limit: Optional[int] = None, + next_marker: Optional[int] = None, + order: Optional[List[DevicePinnersOrderField]] = None, + **kwargs + ): + """ + :param entries: A list of device pins., defaults to None + :type entries: Optional[List[DevicePinner]], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[int], optional + :param order: The order by which items are returned., defaults to None + :type order: Optional[List[DevicePinnersOrderField]], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker + self.order = order diff --git a/box_sdk_gen/schemas/email_alias.py b/box_sdk_gen/schemas/email_alias.py new file mode 100644 index 000000000..b8355f6e5 --- /dev/null +++ b/box_sdk_gen/schemas/email_alias.py @@ -0,0 +1,40 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class EmailAliasTypeField(str, Enum): + EMAIL_ALIAS = 'email_alias' + + +class EmailAlias(BaseObject): + _discriminator = 'type', {'email_alias'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[EmailAliasTypeField] = None, + email: Optional[str] = None, + is_confirmed: Optional[bool] = None, + **kwargs + ): + """ + :param id: The unique identifier for this object., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `email_alias`., defaults to None + :type type: Optional[EmailAliasTypeField], optional + :param email: The email address., defaults to None + :type email: Optional[str], optional + :param is_confirmed: Whether the email address has been confirmed., defaults to None + :type is_confirmed: Optional[bool], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.email = email + self.is_confirmed = is_confirmed diff --git a/box_sdk_gen/schemas/email_aliases.py b/box_sdk_gen/schemas/email_aliases.py new file mode 100644 index 000000000..d359665b3 --- /dev/null +++ b/box_sdk_gen/schemas/email_aliases.py @@ -0,0 +1,28 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.email_alias import EmailAlias + +from box_sdk_gen.box.errors import BoxSDKError + + +class EmailAliases(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + entries: Optional[List[EmailAlias]] = None, + **kwargs + ): + """ + :param total_count: The number of email aliases., defaults to None + :type total_count: Optional[int], optional + :param entries: A list of email aliases., defaults to None + :type entries: Optional[List[EmailAlias]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries diff --git a/box_sdk_gen/schemas/enterprise_base.py b/box_sdk_gen/schemas/enterprise_base.py new file mode 100644 index 000000000..70868168b --- /dev/null +++ b/box_sdk_gen/schemas/enterprise_base.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseBaseTypeField(str, Enum): + ENTERPRISE = 'enterprise' + + +class EnterpriseBase(BaseObject): + _discriminator = 'type', {'enterprise'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[EnterpriseBaseTypeField] = None, + **kwargs + ): + """ + :param id: The unique identifier for this enterprise., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `enterprise`., defaults to None + :type type: Optional[EnterpriseBaseTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/event.py b/box_sdk_gen/schemas/event.py new file mode 100644 index 000000000..9a1a87cec --- /dev/null +++ b/box_sdk_gen/schemas/event.py @@ -0,0 +1,241 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Union + +from typing import Dict + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.user import User + +from box_sdk_gen.schemas.event_source import EventSource + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.schemas.app_item_event_source import AppItemEventSource + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class EventEventTypeField(str, Enum): + ACCESS_GRANTED = 'ACCESS_GRANTED' + ACCESS_REVOKED = 'ACCESS_REVOKED' + ADD_DEVICE_ASSOCIATION = 'ADD_DEVICE_ASSOCIATION' + ADD_LOGIN_ACTIVITY_DEVICE = 'ADD_LOGIN_ACTIVITY_DEVICE' + ADMIN_LOGIN = 'ADMIN_LOGIN' + APPLICATION_CREATED = 'APPLICATION_CREATED' + APPLICATION_PUBLIC_KEY_ADDED = 'APPLICATION_PUBLIC_KEY_ADDED' + APPLICATION_PUBLIC_KEY_DELETED = 'APPLICATION_PUBLIC_KEY_DELETED' + CHANGE_ADMIN_ROLE = 'CHANGE_ADMIN_ROLE' + CHANGE_FOLDER_PERMISSION = 'CHANGE_FOLDER_PERMISSION' + COLLABORATION_ACCEPT = 'COLLABORATION_ACCEPT' + COLLABORATION_EXPIRATION = 'COLLABORATION_EXPIRATION' + COLLABORATION_INVITE = 'COLLABORATION_INVITE' + COLLABORATION_REMOVE = 'COLLABORATION_REMOVE' + COLLABORATION_ROLE_CHANGE = 'COLLABORATION_ROLE_CHANGE' + COLLAB_ADD_COLLABORATOR = 'COLLAB_ADD_COLLABORATOR' + COLLAB_INVITE_COLLABORATOR = 'COLLAB_INVITE_COLLABORATOR' + COLLAB_REMOVE_COLLABORATOR = 'COLLAB_REMOVE_COLLABORATOR' + COLLAB_ROLE_CHANGE = 'COLLAB_ROLE_CHANGE' + COMMENT_CREATE = 'COMMENT_CREATE' + COMMENT_DELETE = 'COMMENT_DELETE' + CONTENT_ACCESS = 'CONTENT_ACCESS' + CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY = ( + 'CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY' + ) + CONTENT_WORKFLOW_AUTOMATION_ADD = 'CONTENT_WORKFLOW_AUTOMATION_ADD' + CONTENT_WORKFLOW_AUTOMATION_DELETE = 'CONTENT_WORKFLOW_AUTOMATION_DELETE' + CONTENT_WORKFLOW_POLICY_ADD = 'CONTENT_WORKFLOW_POLICY_ADD' + CONTENT_WORKFLOW_SHARING_POLICY_VIOLATION = ( + 'CONTENT_WORKFLOW_SHARING_POLICY_VIOLATION' + ) + CONTENT_WORKFLOW_UPLOAD_POLICY_VIOLATION = ( + 'CONTENT_WORKFLOW_UPLOAD_POLICY_VIOLATION' + ) + COPY = 'COPY' + DATA_RETENTION_CREATE_RETENTION = 'DATA_RETENTION_CREATE_RETENTION' + DATA_RETENTION_REMOVE_RETENTION = 'DATA_RETENTION_REMOVE_RETENTION' + DELETE = 'DELETE' + DELETE_USER = 'DELETE_USER' + DEVICE_TRUST_CHECK_FAILED = 'DEVICE_TRUST_CHECK_FAILED' + DOWNLOAD = 'DOWNLOAD' + EDIT = 'EDIT' + EDIT_USER = 'EDIT_USER' + EDR_CROWDSTRIKE_DEVICE_DETECTED = 'EDR_CROWDSTRIKE_DEVICE_DETECTED' + EDR_CROWDSTRIKE_NO_BOX_TOOLS = 'EDR_CROWDSTRIKE_NO_BOX_TOOLS' + EDR_CROWDSTRIKE_BOX_TOOLS_OUTDATED = 'EDR_CROWDSTRIKE_BOX_TOOLS_OUTDATED' + EDR_CROWDSTRIKE_DRIVE_OUTDATED = 'EDR_CROWDSTRIKE_DRIVE_OUTDATED' + EDR_CROWDSTRIKE_ACCESS_ALLOWED_NO_CROWDSTRIKE_DEVICE = ( + 'EDR_CROWDSTRIKE_ACCESS_ALLOWED_NO_CROWDSTRIKE_DEVICE' + ) + EDR_CROWDSTRIKE_ACCESS_REVOKED = 'EDR_CROWDSTRIKE_ACCESS_REVOKED' + EMAIL_ALIAS_CONFIRM = 'EMAIL_ALIAS_CONFIRM' + EMAIL_ALIAS_REMOVE = 'EMAIL_ALIAS_REMOVE' + ENABLE_TWO_FACTOR_AUTH = 'ENABLE_TWO_FACTOR_AUTH' + ENTERPRISE_APP_AUTHORIZATION_UPDATE = 'ENTERPRISE_APP_AUTHORIZATION_UPDATE' + FAILED_LOGIN = 'FAILED_LOGIN' + FILE_MARKED_MALICIOUS = 'FILE_MARKED_MALICIOUS' + FILE_WATERMARKED_DOWNLOAD = 'FILE_WATERMARKED_DOWNLOAD' + GROUP_ADD_ITEM = 'GROUP_ADD_ITEM' + GROUP_ADD_USER = 'GROUP_ADD_USER' + GROUP_CREATION = 'GROUP_CREATION' + GROUP_DELETION = 'GROUP_DELETION' + GROUP_EDITED = 'GROUP_EDITED' + GROUP_REMOVE_ITEM = 'GROUP_REMOVE_ITEM' + GROUP_REMOVE_USER = 'GROUP_REMOVE_USER' + 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' + ITEM_OPEN = 'ITEM_OPEN' + ITEM_PREVIEW = 'ITEM_PREVIEW' + ITEM_RENAME = 'ITEM_RENAME' + ITEM_SHARED = 'ITEM_SHARED' + ITEM_SHARED_CREATE = 'ITEM_SHARED_CREATE' + ITEM_SHARED_UNSHARE = 'ITEM_SHARED_UNSHARE' + ITEM_SHARED_UPDATE = 'ITEM_SHARED_UPDATE' + ITEM_SYNC = 'ITEM_SYNC' + ITEM_TRASH = 'ITEM_TRASH' + ITEM_UNDELETE_VIA_TRASH = 'ITEM_UNDELETE_VIA_TRASH' + ITEM_UNSYNC = 'ITEM_UNSYNC' + ITEM_UPLOAD = 'ITEM_UPLOAD' + LEGAL_HOLD_ASSIGNMENT_CREATE = 'LEGAL_HOLD_ASSIGNMENT_CREATE' + LEGAL_HOLD_ASSIGNMENT_DELETE = 'LEGAL_HOLD_ASSIGNMENT_DELETE' + LEGAL_HOLD_POLICY_CREATE = 'LEGAL_HOLD_POLICY_CREATE' + LEGAL_HOLD_POLICY_DELETE = 'LEGAL_HOLD_POLICY_DELETE' + LEGAL_HOLD_POLICY_UPDATE = 'LEGAL_HOLD_POLICY_UPDATE' + LOCK = 'LOCK' + LOCK_CREATE = 'LOCK_CREATE' + LOCK_DESTROY = 'LOCK_DESTROY' + LOGIN = 'LOGIN' + MASTER_INVITE_ACCEPT = 'MASTER_INVITE_ACCEPT' + MASTER_INVITE_REJECT = 'MASTER_INVITE_REJECT' + METADATA_INSTANCE_CREATE = 'METADATA_INSTANCE_CREATE' + METADATA_INSTANCE_DELETE = 'METADATA_INSTANCE_DELETE' + METADATA_INSTANCE_UPDATE = 'METADATA_INSTANCE_UPDATE' + METADATA_TEMPLATE_CREATE = 'METADATA_TEMPLATE_CREATE' + METADATA_TEMPLATE_DELETE = 'METADATA_TEMPLATE_DELETE' + METADATA_TEMPLATE_UPDATE = 'METADATA_TEMPLATE_UPDATE' + MOVE = 'MOVE' + NEW_USER = 'NEW_USER' + PREVIEW = 'PREVIEW' + REMOVE_DEVICE_ASSOCIATION = 'REMOVE_DEVICE_ASSOCIATION' + REMOVE_LOGIN_ACTIVITY_DEVICE = 'REMOVE_LOGIN_ACTIVITY_DEVICE' + 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' + SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION = ( + 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION' + ) + SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED = 'SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED' + SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED_MISSING_JUSTIFICATION = ( + 'SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED_MISSING_JUSTIFICATION' + ) + SHIELD_JUSTIFICATION_APPROVAL = 'SHIELD_JUSTIFICATION_APPROVAL' + SHIELD_SHARED_LINK_ACCESS_BLOCKED = 'SHIELD_SHARED_LINK_ACCESS_BLOCKED' + SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE = ( + 'SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE' + ) + SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_UPDATE = ( + 'SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_UPDATE' + ) + SIGN_DOCUMENT_ASSIGNED = 'SIGN_DOCUMENT_ASSIGNED' + SIGN_DOCUMENT_CANCELLED = 'SIGN_DOCUMENT_CANCELLED' + SIGN_DOCUMENT_COMPLETED = 'SIGN_DOCUMENT_COMPLETED' + SIGN_DOCUMENT_CONVERTED = 'SIGN_DOCUMENT_CONVERTED' + SIGN_DOCUMENT_CREATED = 'SIGN_DOCUMENT_CREATED' + SIGN_DOCUMENT_DECLINED = 'SIGN_DOCUMENT_DECLINED' + SIGN_DOCUMENT_EXPIRED = 'SIGN_DOCUMENT_EXPIRED' + SIGN_DOCUMENT_SIGNED = 'SIGN_DOCUMENT_SIGNED' + SIGN_DOCUMENT_VIEWED_BY_SIGNED = 'SIGN_DOCUMENT_VIEWED_BY_SIGNED' + SIGNER_DOWNLOADED = 'SIGNER_DOWNLOADED' + SIGNER_FORWARDED = 'SIGNER_FORWARDED' + STORAGE_EXPIRATION = 'STORAGE_EXPIRATION' + TAG_ITEM_CREATE = 'TAG_ITEM_CREATE' + TASK_ASSIGNMENT_CREATE = 'TASK_ASSIGNMENT_CREATE' + TASK_ASSIGNMENT_DELETE = 'TASK_ASSIGNMENT_DELETE' + TASK_ASSIGNMENT_UPDATE = 'TASK_ASSIGNMENT_UPDATE' + TASK_CREATE = 'TASK_CREATE' + TASK_UPDATE = 'TASK_UPDATE' + TERMS_OF_SERVICE_ACCEPT = 'TERMS_OF_SERVICE_ACCEPT' + TERMS_OF_SERVICE_REJECT = 'TERMS_OF_SERVICE_REJECT' + UNDELETE = 'UNDELETE' + UNLOCK = 'UNLOCK' + UNSHARE = 'UNSHARE' + UPDATE_COLLABORATION_EXPIRATION = 'UPDATE_COLLABORATION_EXPIRATION' + UPDATE_SHARE_EXPIRATION = 'UPDATE_SHARE_EXPIRATION' + UPLOAD = 'UPLOAD' + USER_AUTHENTICATE_OAUTH2_ACCESS_TOKEN_CREATE = ( + 'USER_AUTHENTICATE_OAUTH2_ACCESS_TOKEN_CREATE' + ) + WATERMARK_LABEL_CREATE = 'WATERMARK_LABEL_CREATE' + WATERMARK_LABEL_DELETE = 'WATERMARK_LABEL_DELETE' + + +class EventAdditionalDetailsField(BaseObject): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +class Event(BaseObject): + def __init__( + self, + *, + type: Optional[str] = None, + created_at: Optional[DateTime] = None, + recorded_at: Optional[DateTime] = None, + event_id: Optional[str] = None, + created_by: Optional[UserMini] = None, + event_type: Optional[EventEventTypeField] = None, + session_id: Optional[str] = None, + source: Optional[ + Union[User, EventSource, File, Folder, Dict, AppItemEventSource] + ] = None, + additional_details: Optional[EventAdditionalDetailsField] = None, + **kwargs + ): + """ + :param type: The value will always be `event`., defaults to None + :type type: Optional[str], optional + :param created_at: When the event object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param recorded_at: When the event object was recorded in database., defaults to None + :type recorded_at: Optional[DateTime], optional + :param event_id: The ID of the event object. You can use this to detect duplicate events., defaults to None + :type event_id: Optional[str], optional + :param session_id: The session of the user that performed the action. Not all events will + populate this attribute., defaults to None + :type session_id: Optional[str], optional + :param additional_details: This object provides additional information about the event if available. + + This can include how a user performed an event as well as additional + information to correlate an event to external KeySafe logs. Not all events + have an `additional_details` object. This object is only available in the + Enterprise Events., defaults to None + :type additional_details: Optional[EventAdditionalDetailsField], optional + """ + super().__init__(**kwargs) + self.type = type + self.created_at = created_at + self.recorded_at = recorded_at + self.event_id = event_id + self.created_by = created_by + self.event_type = event_type + self.session_id = session_id + self.source = source + self.additional_details = additional_details diff --git a/box_sdk_gen/schemas/event_source.py b/box_sdk_gen/schemas/event_source.py new file mode 100644 index 000000000..513744f4c --- /dev/null +++ b/box_sdk_gen/schemas/event_source.py @@ -0,0 +1,63 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class EventSourceItemTypeField(str, Enum): + FILE = 'file' + FOLDER = 'folder' + + +class EventSourceClassificationField(BaseObject): + def __init__(self, *, name: Optional[str] = None, **kwargs): + """ + :param name: The classification's name., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.name = name + + +class EventSource(BaseObject): + _discriminator = 'item_type', {'file', 'folder'} + + def __init__( + self, + item_type: EventSourceItemTypeField, + item_id: str, + item_name: str, + *, + classification: Optional[EventSourceClassificationField] = None, + parent: Optional[FolderMini] = None, + owned_by: Optional[UserMini] = None, + **kwargs + ): + """ + :param item_type: The type of the item that the event + represents. Can be `file` or `folder`. + :type item_type: EventSourceItemTypeField + :param item_id: The unique identifier that represents the + item. + :type item_id: str + :param item_name: The name of the item. + :type item_name: str + :param classification: The object containing classification information for the item that + triggered the event. This field will not appear if the item does not + have a classification set., defaults to None + :type classification: Optional[EventSourceClassificationField], optional + """ + super().__init__(**kwargs) + self.item_type = item_type + self.item_id = item_id + self.item_name = item_name + self.classification = classification + self.parent = parent + self.owned_by = owned_by diff --git a/box_sdk_gen/schemas/events.py b/box_sdk_gen/schemas/events.py new file mode 100644 index 000000000..2e2e14a8d --- /dev/null +++ b/box_sdk_gen/schemas/events.py @@ -0,0 +1,35 @@ +from typing import Optional + +from typing import Union + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.event import Event + +from box_sdk_gen.box.errors import BoxSDKError + + +class Events(BaseObject): + def __init__( + self, + *, + chunk_size: Optional[int] = None, + next_stream_position: Optional[Union[str, int]] = None, + entries: Optional[List[Event]] = None, + **kwargs + ): + """ + :param chunk_size: The number of events returned in this response., defaults to None + :type chunk_size: Optional[int], optional + :param next_stream_position: The stream position of the start of the next page (chunk) + of events., defaults to None + :type next_stream_position: Optional[Union[str, int]], optional + :param entries: A list of events., defaults to None + :type entries: Optional[List[Event]], optional + """ + super().__init__(**kwargs) + self.chunk_size = chunk_size + self.next_stream_position = next_stream_position + self.entries = entries diff --git a/box_sdk_gen/schemas/file.py b/box_sdk_gen/schemas/file.py new file mode 100644 index 000000000..2e9344e88 --- /dev/null +++ b/box_sdk_gen/schemas/file.py @@ -0,0 +1,278 @@ +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.file_base import FileBaseTypeField + +from box_sdk_gen.schemas.file_base import FileBase + +from box_sdk_gen.schemas.file_version_mini import FileVersionMini + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class FilePathCollectionField(BaseObject): + def __init__(self, total_count: int, entries: List[FolderMini], **kwargs): + """ + :param total_count: The number of folders in this list. + :type total_count: int + :param entries: The parent folders for this item. + :type entries: List[FolderMini] + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries + + +class FileSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class FileSharedLinkEffectiveAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class FileSharedLinkEffectivePermissionField(str, Enum): + CAN_EDIT = 'can_edit' + CAN_DOWNLOAD = 'can_download' + CAN_PREVIEW = 'can_preview' + NO_ACCESS = 'no_access' + + +class FileSharedLinkPermissionsField(BaseObject): + def __init__(self, can_download: bool, can_preview: bool, can_edit: bool, **kwargs): + """ + :param can_download: Defines if the shared link allows for the item to be downloaded. For + shared links on folders, this also applies to any items in the folder. + + This value can be set to `true` when the effective access level is + set to `open` or `company`, not `collaborators`. + :type can_download: bool + :param can_preview: Defines if the shared link allows for the item to be previewed. + + This value is always `true`. For shared links on folders this also + applies to any items in the folder. + :type can_preview: bool + :param can_edit: Defines if the shared link allows for the item to be edited. + + This value can only be `true` if `can_download` is also `true` and if + the item has a type of `file`. + :type can_edit: bool + """ + super().__init__(**kwargs) + self.can_download = can_download + self.can_preview = can_preview + self.can_edit = can_edit + + +class FileSharedLinkField(BaseObject): + def __init__( + self, + url: str, + effective_access: FileSharedLinkEffectiveAccessField, + effective_permission: FileSharedLinkEffectivePermissionField, + is_password_enabled: bool, + download_count: int, + preview_count: int, + *, + download_url: Optional[str] = None, + vanity_url: Optional[str] = None, + vanity_name: Optional[str] = None, + access: Optional[FileSharedLinkAccessField] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[FileSharedLinkPermissionsField] = None, + **kwargs + ): + """ + :param url: The URL that can be used to access the item on Box. + + This URL will display the item in Box's preview UI where the file + can be downloaded if allowed. + + This URL will continue to work even when a custom `vanity_url` + has been set for this shared link. + :type url: str + :param effective_access: The effective access level for the shared link. This can be a more + restrictive access level than the value in the `access` field when the + enterprise settings restrict the allowed access levels. + :type effective_access: FileSharedLinkEffectiveAccessField + :param effective_permission: The effective permissions for this shared link. + These result in the more restrictive combination of + the share link permissions and the item permissions set + by the administrator, the owner, and any ancestor item + such as a folder. + :type effective_permission: FileSharedLinkEffectivePermissionField + :param is_password_enabled: Defines if the shared link requires a password to access the item. + :type is_password_enabled: bool + :param download_count: The number of times this item has been downloaded. + :type download_count: int + :param preview_count: The number of times this item has been previewed. + :type preview_count: int + :param download_url: A URL that can be used to download the file. This URL can be used in + a browser to download the file. This URL includes the file + extension so that the file will be saved with the right file type. + + This property will be `null` for folders., defaults to None + :type download_url: Optional[str], optional + :param vanity_url: The "Custom URL" that can also be used to preview the item on Box. Custom + URLs can only be created or modified in the Box Web application., defaults to None + :type vanity_url: Optional[str], optional + :param vanity_name: The custom name of a shared link, as used in the `vanity_url` field., defaults to None + :type vanity_name: Optional[str], optional + :param access: The access level for this shared link. + + * `open` - provides access to this item to anyone with this link + * `company` - only provides access to this item to people the same company + * `collaborators` - only provides access to this item to people who are + collaborators on this item + + If this field is omitted when creating the shared link, the access level + will be set to the default access level specified by the enterprise admin., defaults to None + :type access: Optional[FileSharedLinkAccessField], optional + :param unshared_at: The date and time when this link will be unshared. This field can only be + set by users with paid accounts., defaults to None + :type unshared_at: Optional[DateTime], optional + :param permissions: Defines if this link allows a user to preview, edit, and download an item. + These permissions refer to the shared link only and + do not supersede permissions applied to the item itself., defaults to None + :type permissions: Optional[FileSharedLinkPermissionsField], optional + """ + super().__init__(**kwargs) + self.url = url + self.effective_access = effective_access + self.effective_permission = effective_permission + self.is_password_enabled = is_password_enabled + self.download_count = download_count + self.preview_count = preview_count + self.download_url = download_url + self.vanity_url = vanity_url + self.vanity_name = vanity_name + self.access = access + self.unshared_at = unshared_at + self.permissions = permissions + + +class FileItemStatusField(str, Enum): + ACTIVE = 'active' + TRASHED = 'trashed' + DELETED = 'deleted' + + +class File(FileMini): + def __init__( + self, + id: str, + *, + description: Optional[str] = None, + size: Optional[int] = None, + path_collection: Optional[FilePathCollectionField] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + trashed_at: Optional[DateTime] = None, + purged_at: Optional[DateTime] = None, + content_created_at: Optional[DateTime] = None, + content_modified_at: Optional[DateTime] = None, + created_by: Optional[UserMini] = None, + modified_by: Optional[UserMini] = None, + owned_by: Optional[UserMini] = None, + shared_link: Optional[FileSharedLinkField] = None, + parent: Optional[FolderMini] = None, + item_status: Optional[FileItemStatusField] = None, + sequence_id: Optional[str] = None, + name: Optional[str] = None, + sha_1: Optional[str] = None, + file_version: Optional[FileVersionMini] = None, + etag: Optional[str] = None, + type: FileBaseTypeField = FileBaseTypeField.FILE, + **kwargs + ): + """ + :param id: The unique identifier that represent a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + :type id: str + :param description: The optional description of this file. + If the description exceeds 255 characters, the first 255 characters + are set as a file description and the rest of it is ignored., defaults to None + :type description: Optional[str], optional + :param size: The file size in bytes. Be careful parsing this integer as it can + get very large and cause an integer overflow., defaults to None + :type size: Optional[int], optional + :param created_at: The date and time when the file was created on Box., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The date and time when the file was last updated on Box., defaults to None + :type modified_at: Optional[DateTime], optional + :param trashed_at: The time at which this file was put in the trash., defaults to None + :type trashed_at: Optional[DateTime], optional + :param purged_at: The time at which this file is expected to be purged + from the trash., defaults to None + :type purged_at: Optional[DateTime], optional + :param content_created_at: The date and time at which this file was originally + created, which might be before it was uploaded to Box., defaults to None + :type content_created_at: Optional[DateTime], optional + :param content_modified_at: The date and time at which this file was last updated, + which might be before it was uploaded to Box., defaults to None + :type content_modified_at: Optional[DateTime], optional + :param item_status: Defines if this item has been deleted or not. + + * `active` when the item has is not in the trash + * `trashed` when the item has been moved to the trash but not deleted + * `deleted` when the item has been permanently deleted., defaults to None + :type item_status: Optional[FileItemStatusField], optional + :param name: The name of the file., defaults to None + :type name: Optional[str], optional + :param sha_1: The SHA1 hash of the file. This can be used to compare the contents + of a file on Box with a local file., defaults to None + :type sha_1: Optional[str], optional + :param etag: The HTTP `etag` of this file. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the file if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `file`., defaults to FileBaseTypeField.FILE + :type type: FileBaseTypeField, optional + """ + super().__init__( + id=id, + sequence_id=sequence_id, + name=name, + sha_1=sha_1, + file_version=file_version, + etag=etag, + type=type, + **kwargs + ) + self.description = description + self.size = size + self.path_collection = path_collection + self.created_at = created_at + self.modified_at = modified_at + self.trashed_at = trashed_at + self.purged_at = purged_at + self.content_created_at = content_created_at + self.content_modified_at = content_modified_at + self.created_by = created_by + self.modified_by = modified_by + self.owned_by = owned_by + self.shared_link = shared_link + self.parent = parent + self.item_status = item_status diff --git a/box_sdk_gen/schemas/file_base.py b/box_sdk_gen/schemas/file_base.py new file mode 100644 index 000000000..aa2c63a0f --- /dev/null +++ b/box_sdk_gen/schemas/file_base.py @@ -0,0 +1,44 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileBaseTypeField(str, Enum): + FILE = 'file' + + +class FileBase(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + id: str, + *, + etag: Optional[str] = None, + type: FileBaseTypeField = FileBaseTypeField.FILE, + **kwargs + ): + """ + :param id: The unique identifier that represent a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + :type id: str + :param etag: The HTTP `etag` of this file. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the file if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `file`., defaults to FileBaseTypeField.FILE + :type type: FileBaseTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.etag = etag + self.type = type diff --git a/box_sdk_gen/schemas/file_conflict.py b/box_sdk_gen/schemas/file_conflict.py new file mode 100644 index 000000000..5e88917fe --- /dev/null +++ b/box_sdk_gen/schemas/file_conflict.py @@ -0,0 +1,57 @@ +from typing import Optional + +from box_sdk_gen.schemas.file_base import FileBaseTypeField + +from box_sdk_gen.schemas.file_base import FileBase + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.schemas.file_version_mini import FileVersionMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileConflict(FileMini): + def __init__( + self, + id: str, + *, + sequence_id: Optional[str] = None, + name: Optional[str] = None, + sha_1: Optional[str] = None, + file_version: Optional[FileVersionMini] = None, + etag: Optional[str] = None, + type: FileBaseTypeField = FileBaseTypeField.FILE, + **kwargs + ): + """ + :param id: The unique identifier that represent a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + :type id: str + :param name: The name of the file., defaults to None + :type name: Optional[str], optional + :param sha_1: The SHA1 hash of the file. This can be used to compare the contents + of a file on Box with a local file., defaults to None + :type sha_1: Optional[str], optional + :param etag: The HTTP `etag` of this file. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the file if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `file`., defaults to FileBaseTypeField.FILE + :type type: FileBaseTypeField, optional + """ + super().__init__( + id=id, + sequence_id=sequence_id, + name=name, + sha_1=sha_1, + file_version=file_version, + etag=etag, + type=type, + **kwargs + ) diff --git a/box_sdk_gen/schemas/file_full.py b/box_sdk_gen/schemas/file_full.py new file mode 100644 index 000000000..ce0b75c1e --- /dev/null +++ b/box_sdk_gen/schemas/file_full.py @@ -0,0 +1,577 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.file_base import FileBaseTypeField + +from box_sdk_gen.schemas.file_base import FileBase + +from box_sdk_gen.schemas.file_version_mini import FileVersionMini + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.schemas.file import FilePathCollectionField + +from box_sdk_gen.schemas.file import FileSharedLinkField + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.file import FileItemStatusField + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.file_or_folder_scope import FileOrFolderScope + +from box_sdk_gen.schemas.metadata_full import MetadataFull + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class FileFullPermissionsField(BaseObject): + def __init__( + self, + can_delete: bool, + can_download: bool, + can_invite_collaborator: bool, + can_rename: bool, + can_set_share_access: bool, + can_share: bool, + can_annotate: bool, + can_comment: bool, + can_preview: bool, + can_upload: bool, + can_view_annotations_all: bool, + can_view_annotations_self: bool, + **kwargs + ): + """ + :param can_delete: Specifies if the current user can delete this item. + :type can_delete: bool + :param can_download: Specifies if the current user can download this item. + :type can_download: bool + :param can_invite_collaborator: Specifies if the current user can invite new + users to collaborate on this item, and if the user can + update the role of a user already collaborated on this + item. + :type can_invite_collaborator: bool + :param can_rename: Specifies if the user can rename this item. + :type can_rename: bool + :param can_set_share_access: Specifies if the user can change the access level of an + existing shared link on this item. + :type can_set_share_access: bool + :param can_share: Specifies if the user can create a shared link for this item. + :type can_share: bool + :param can_annotate: Specifies if the user can place annotations on this file. + :type can_annotate: bool + :param can_comment: Specifies if the user can place comments on this file. + :type can_comment: bool + :param can_preview: Specifies if the user can preview this file. + :type can_preview: bool + :param can_upload: Specifies if the user can upload a new version of this file. + :type can_upload: bool + :param can_view_annotations_all: Specifies if the user view all annotations placed on this file. + :type can_view_annotations_all: bool + :param can_view_annotations_self: Specifies if the user view annotations placed by themselves + on this file. + :type can_view_annotations_self: bool + """ + super().__init__(**kwargs) + self.can_delete = can_delete + self.can_download = can_download + self.can_invite_collaborator = can_invite_collaborator + self.can_rename = can_rename + self.can_set_share_access = can_set_share_access + self.can_share = can_share + self.can_annotate = can_annotate + self.can_comment = can_comment + self.can_preview = can_preview + self.can_upload = can_upload + self.can_view_annotations_all = can_view_annotations_all + self.can_view_annotations_self = can_view_annotations_self + + +class FileFullLockTypeField(str, Enum): + LOCK = 'lock' + + +class FileFullLockAppTypeField(str, Enum): + GSUITE = 'gsuite' + OFFICE_WOPI = 'office_wopi' + OFFICE_WOPIPLUS = 'office_wopiplus' + OTHER = 'other' + + +class FileFullLockField(BaseObject): + _discriminator = 'type', {'lock'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[FileFullLockTypeField] = None, + created_by: Optional[UserMini] = None, + created_at: Optional[DateTime] = None, + expired_at: Optional[DateTime] = None, + is_download_prevented: Optional[bool] = None, + app_type: Optional[FileFullLockAppTypeField] = None, + **kwargs + ): + """ + :param id: The unique identifier for this lock., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `lock`., defaults to None + :type type: Optional[FileFullLockTypeField], optional + :param created_at: The time this lock was created at., defaults to None + :type created_at: Optional[DateTime], optional + :param expired_at: The time this lock is to expire at, which might be in the past., defaults to None + :type expired_at: Optional[DateTime], optional + :param is_download_prevented: Whether or not the file can be downloaded while locked., defaults to None + :type is_download_prevented: Optional[bool], optional + :param app_type: If the lock is managed by an application rather than a user, this + field identifies the type of the application that holds the lock. + This is an open enum and may be extended with additional values in + the future., defaults to None + :type app_type: Optional[FileFullLockAppTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.created_by = created_by + self.created_at = created_at + self.expired_at = expired_at + self.is_download_prevented = is_download_prevented + self.app_type = app_type + + +class FileFullExpiringEmbedLinkTokenTypeField(str, Enum): + BEARER = 'bearer' + + +class FileFullExpiringEmbedLinkField(BaseObject): + def __init__( + self, + *, + access_token: Optional[str] = None, + expires_in: Optional[int] = None, + token_type: Optional[FileFullExpiringEmbedLinkTokenTypeField] = None, + restricted_to: Optional[List[FileOrFolderScope]] = None, + url: Optional[str] = None, + **kwargs + ): + """ + :param access_token: The requested access token., defaults to None + :type access_token: Optional[str], optional + :param expires_in: The time in seconds by which this token will expire., defaults to None + :type expires_in: Optional[int], optional + :param token_type: The type of access token returned., defaults to None + :type token_type: Optional[FileFullExpiringEmbedLinkTokenTypeField], optional + :param restricted_to: The permissions that this access token permits, + providing a list of resources (files, folders, etc) + and the scopes permitted for each of those resources., defaults to None + :type restricted_to: Optional[List[FileOrFolderScope]], optional + :param url: The actual expiring embed URL for this file, constructed + from the file ID and access tokens specified in this object., defaults to None + :type url: Optional[str], optional + """ + super().__init__(**kwargs) + self.access_token = access_token + self.expires_in = expires_in + self.token_type = token_type + self.restricted_to = restricted_to + self.url = url + + +class FileFullWatermarkInfoField(BaseObject): + def __init__(self, *, is_watermarked: Optional[bool] = None, **kwargs): + """ + :param is_watermarked: Specifies if this item has a watermark applied., defaults to None + :type is_watermarked: Optional[bool], optional + """ + super().__init__(**kwargs) + self.is_watermarked = is_watermarked + + +class FileFullAllowedInviteeRolesField(str, Enum): + EDITOR = 'editor' + VIEWER = 'viewer' + PREVIEWER = 'previewer' + UPLOADER = 'uploader' + PREVIEWER_UPLOADER = 'previewer uploader' + VIEWER_UPLOADER = 'viewer uploader' + CO_OWNER = 'co-owner' + + +class FileFullMetadataField(BaseObject): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.extra_data = kwargs + + +class FileFullRepresentationsEntriesContentField(BaseObject): + def __init__(self, *, url_template: Optional[str] = None, **kwargs): + """ + :param url_template: The download URL that can be used to fetch the representation. + Make sure to make an authenticated API call to this endpoint. + + This URL is a template and will require the `{+asset_path}` to + be replaced by a path. In general, for unpaged representations + it can be replaced by an empty string. + + For paged representations, replace the `{+asset_path}` with the + page to request plus the extension for the file, for example + `1.pdf`. + + When requesting the download URL the following additional + query params can be passed along. + + * `set_content_disposition_type` - Sets the + `Content-Disposition` header in the API response with the + specified disposition type of either `inline` or `attachment`. + If not supplied, the `Content-Disposition` header is not + included in the response. + + * `set_content_disposition_filename` - Allows the application to + define the representation's file name used in the + `Content-Disposition` header. If not defined, the filename + is derived from the source file name in Box combined with the + extension of the representation., defaults to None + :type url_template: Optional[str], optional + """ + super().__init__(**kwargs) + self.url_template = url_template + + +class FileFullRepresentationsEntriesInfoField(BaseObject): + def __init__(self, *, url: Optional[str] = None, **kwargs): + """ + :param url: The API URL that can be used to get more info on this file + representation. Make sure to make an authenticated API call + to this endpoint., defaults to None + :type url: Optional[str], optional + """ + super().__init__(**kwargs) + self.url = url + + +class FileFullRepresentationsEntriesPropertiesField(BaseObject): + def __init__( + self, + *, + dimensions: Optional[str] = None, + paged: Optional[str] = None, + thumb: Optional[str] = None, + **kwargs + ): + """ + :param dimensions: The width by height size of this representation in pixels., defaults to None + :type dimensions: Optional[str], optional + :param paged: Indicates if the representation is build up out of multiple + pages., defaults to None + :type paged: Optional[str], optional + :param thumb: Indicates if the representation can be used as a thumbnail of + the file., defaults to None + :type thumb: Optional[str], optional + """ + super().__init__(**kwargs) + self.dimensions = dimensions + self.paged = paged + self.thumb = thumb + + +class FileFullRepresentationsEntriesStatusStateField(str, Enum): + SUCCESS = 'success' + VIEWABLE = 'viewable' + PENDING = 'pending' + NONE = 'none' + + +class FileFullRepresentationsEntriesStatusField(BaseObject): + def __init__( + self, + *, + state: Optional[FileFullRepresentationsEntriesStatusStateField] = None, + **kwargs + ): + """ + :param state: The status of the representation. + + * `success` defines the representation as ready to be viewed. + * `viewable` defines a video to be ready for viewing. + * `pending` defines the representation as to be generated. Retry + this endpoint to re-check the status. + * `none` defines that the representation will be created when + requested. Request the URL defined in the `info` object to + trigger this generation., defaults to None + :type state: Optional[FileFullRepresentationsEntriesStatusStateField], optional + """ + super().__init__(**kwargs) + self.state = state + + +class FileFullRepresentationsEntriesField(BaseObject): + def __init__( + self, + *, + content: Optional[FileFullRepresentationsEntriesContentField] = None, + info: Optional[FileFullRepresentationsEntriesInfoField] = None, + properties: Optional[FileFullRepresentationsEntriesPropertiesField] = None, + representation: Optional[str] = None, + status: Optional[FileFullRepresentationsEntriesStatusField] = None, + **kwargs + ): + """ + :param content: An object containing the URL that can be used to actually fetch + the representation., defaults to None + :type content: Optional[FileFullRepresentationsEntriesContentField], optional + :param info: An object containing the URL that can be used to fetch more info + on this representation., defaults to None + :type info: Optional[FileFullRepresentationsEntriesInfoField], optional + :param properties: An object containing the size and type of this presentation., defaults to None + :type properties: Optional[FileFullRepresentationsEntriesPropertiesField], optional + :param representation: Indicates the file type of the returned representation., defaults to None + :type representation: Optional[str], optional + :param status: An object containing the status of this representation., defaults to None + :type status: Optional[FileFullRepresentationsEntriesStatusField], optional + """ + super().__init__(**kwargs) + self.content = content + self.info = info + self.properties = properties + self.representation = representation + self.status = status + + +class FileFullRepresentationsField(BaseObject): + def __init__( + self, + *, + entries: Optional[List[FileFullRepresentationsEntriesField]] = None, + **kwargs + ): + """ + :param entries: A list of files., defaults to None + :type entries: Optional[List[FileFullRepresentationsEntriesField]], optional + """ + super().__init__(**kwargs) + self.entries = entries + + +class FileFullClassificationField(BaseObject): + def __init__( + self, + *, + name: Optional[str] = None, + definition: Optional[str] = None, + color: Optional[str] = None, + **kwargs + ): + """ + :param name: The name of the classification., defaults to None + :type name: Optional[str], optional + :param definition: An explanation of the meaning of this classification., defaults to None + :type definition: Optional[str], optional + :param color: The color that is used to display the + classification label in a user-interface. Colors are defined by the admin + or co-admin who created the classification in the Box web app., defaults to None + :type color: Optional[str], optional + """ + super().__init__(**kwargs) + self.name = name + self.definition = definition + self.color = color + + +class FileFullSharedLinkPermissionOptionsField(str, Enum): + CAN_PREVIEW = 'can_preview' + CAN_DOWNLOAD = 'can_download' + CAN_EDIT = 'can_edit' + + +class FileFull(File): + def __init__( + self, + id: str, + *, + version_number: Optional[str] = None, + comment_count: Optional[int] = None, + permissions: Optional[FileFullPermissionsField] = None, + tags: Optional[List[str]] = None, + lock: Optional[FileFullLockField] = None, + extension: Optional[str] = None, + is_package: Optional[bool] = None, + expiring_embed_link: Optional[FileFullExpiringEmbedLinkField] = None, + watermark_info: Optional[FileFullWatermarkInfoField] = None, + is_accessible_via_shared_link: Optional[bool] = None, + allowed_invitee_roles: Optional[List[FileFullAllowedInviteeRolesField]] = None, + is_externally_owned: Optional[bool] = None, + has_collaborations: Optional[bool] = None, + metadata: Optional[FileFullMetadataField] = None, + expires_at: Optional[DateTime] = None, + representations: Optional[FileFullRepresentationsField] = None, + classification: Optional[FileFullClassificationField] = None, + uploader_display_name: Optional[str] = None, + disposition_at: Optional[DateTime] = None, + shared_link_permission_options: Optional[ + List[FileFullSharedLinkPermissionOptionsField] + ] = None, + is_associated_with_app_item: Optional[bool] = None, + description: Optional[str] = None, + size: Optional[int] = None, + path_collection: Optional[FilePathCollectionField] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + trashed_at: Optional[DateTime] = None, + purged_at: Optional[DateTime] = None, + content_created_at: Optional[DateTime] = None, + content_modified_at: Optional[DateTime] = None, + created_by: Optional[UserMini] = None, + modified_by: Optional[UserMini] = None, + owned_by: Optional[UserMini] = None, + shared_link: Optional[FileSharedLinkField] = None, + parent: Optional[FolderMini] = None, + item_status: Optional[FileItemStatusField] = None, + sequence_id: Optional[str] = None, + name: Optional[str] = None, + sha_1: Optional[str] = None, + file_version: Optional[FileVersionMini] = None, + etag: Optional[str] = None, + type: FileBaseTypeField = FileBaseTypeField.FILE, + **kwargs + ): + """ + :param id: The unique identifier that represent a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + :type id: str + :param version_number: The version number of this file., defaults to None + :type version_number: Optional[str], optional + :param comment_count: The number of comments on this file., defaults to None + :type comment_count: Optional[int], optional + :param extension: Indicates the (optional) file extension for this file. By default, + this is set to an empty string., defaults to None + :type extension: Optional[str], optional + :param is_package: Indicates if the file is a package. Packages are commonly used + by Mac Applications and can include iWork files., defaults to None + :type is_package: Optional[bool], optional + :param is_accessible_via_shared_link: Specifies if the file can be accessed + via the direct shared link or a shared link + to a parent folder., defaults to None + :type is_accessible_via_shared_link: Optional[bool], optional + :param allowed_invitee_roles: A list of the types of roles that user can be invited at + when sharing this file., defaults to None + :type allowed_invitee_roles: Optional[List[FileFullAllowedInviteeRolesField]], optional + :param is_externally_owned: Specifies if this file is owned by a user outside of the + authenticated enterprise., defaults to None + :type is_externally_owned: Optional[bool], optional + :param has_collaborations: Specifies if this file has any other collaborators., defaults to None + :type has_collaborations: Optional[bool], optional + :param expires_at: When the file will automatically be deleted., defaults to None + :type expires_at: Optional[DateTime], optional + :param disposition_at: The retention expiration timestamp for the given file., defaults to None + :type disposition_at: Optional[DateTime], optional + :param shared_link_permission_options: A list of the types of roles that user can be invited at + when sharing this file., defaults to None + :type shared_link_permission_options: Optional[List[FileFullSharedLinkPermissionOptionsField]], optional + :param is_associated_with_app_item: This field will return true if the file or any ancestor of the file + is associated with at least one app item. Note that this will return + true even if the context user does not have access to the app item(s) + associated with the file., defaults to None + :type is_associated_with_app_item: Optional[bool], optional + :param description: The optional description of this file. + If the description exceeds 255 characters, the first 255 characters + are set as a file description and the rest of it is ignored., defaults to None + :type description: Optional[str], optional + :param size: The file size in bytes. Be careful parsing this integer as it can + get very large and cause an integer overflow., defaults to None + :type size: Optional[int], optional + :param created_at: The date and time when the file was created on Box., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The date and time when the file was last updated on Box., defaults to None + :type modified_at: Optional[DateTime], optional + :param trashed_at: The time at which this file was put in the trash., defaults to None + :type trashed_at: Optional[DateTime], optional + :param purged_at: The time at which this file is expected to be purged + from the trash., defaults to None + :type purged_at: Optional[DateTime], optional + :param content_created_at: The date and time at which this file was originally + created, which might be before it was uploaded to Box., defaults to None + :type content_created_at: Optional[DateTime], optional + :param content_modified_at: The date and time at which this file was last updated, + which might be before it was uploaded to Box., defaults to None + :type content_modified_at: Optional[DateTime], optional + :param item_status: Defines if this item has been deleted or not. + + * `active` when the item has is not in the trash + * `trashed` when the item has been moved to the trash but not deleted + * `deleted` when the item has been permanently deleted., defaults to None + :type item_status: Optional[FileItemStatusField], optional + :param name: The name of the file., defaults to None + :type name: Optional[str], optional + :param sha_1: The SHA1 hash of the file. This can be used to compare the contents + of a file on Box with a local file., defaults to None + :type sha_1: Optional[str], optional + :param etag: The HTTP `etag` of this file. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the file if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `file`., defaults to FileBaseTypeField.FILE + :type type: FileBaseTypeField, optional + """ + super().__init__( + id=id, + description=description, + size=size, + path_collection=path_collection, + created_at=created_at, + modified_at=modified_at, + trashed_at=trashed_at, + purged_at=purged_at, + content_created_at=content_created_at, + content_modified_at=content_modified_at, + created_by=created_by, + modified_by=modified_by, + owned_by=owned_by, + shared_link=shared_link, + parent=parent, + item_status=item_status, + sequence_id=sequence_id, + name=name, + sha_1=sha_1, + file_version=file_version, + etag=etag, + type=type, + **kwargs + ) + self.version_number = version_number + self.comment_count = comment_count + self.permissions = permissions + self.tags = tags + self.lock = lock + self.extension = extension + self.is_package = is_package + self.expiring_embed_link = expiring_embed_link + self.watermark_info = watermark_info + self.is_accessible_via_shared_link = is_accessible_via_shared_link + self.allowed_invitee_roles = allowed_invitee_roles + self.is_externally_owned = is_externally_owned + self.has_collaborations = has_collaborations + self.metadata = metadata + self.expires_at = expires_at + self.representations = representations + self.classification = classification + self.uploader_display_name = uploader_display_name + self.disposition_at = disposition_at + self.shared_link_permission_options = shared_link_permission_options + self.is_associated_with_app_item = is_associated_with_app_item diff --git a/box_sdk_gen/schemas/file_mini.py b/box_sdk_gen/schemas/file_mini.py new file mode 100644 index 000000000..c8b72f4e7 --- /dev/null +++ b/box_sdk_gen/schemas/file_mini.py @@ -0,0 +1,61 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.schemas.file_base import FileBaseTypeField + +from box_sdk_gen.schemas.file_base import FileBase + +from box_sdk_gen.schemas.file_version_mini import FileVersionMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileMini(FileBase): + _fields_to_json_mapping: Dict[str, str] = { + 'sha_1': 'sha1', + **FileBase._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'sha1': 'sha_1', + **FileBase._json_to_fields_mapping, + } + + def __init__( + self, + id: str, + *, + sequence_id: Optional[str] = None, + name: Optional[str] = None, + sha_1: Optional[str] = None, + file_version: Optional[FileVersionMini] = None, + etag: Optional[str] = None, + type: FileBaseTypeField = FileBaseTypeField.FILE, + **kwargs + ): + """ + :param id: The unique identifier that represent a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + :type id: str + :param name: The name of the file., defaults to None + :type name: Optional[str], optional + :param sha_1: The SHA1 hash of the file. This can be used to compare the contents + of a file on Box with a local file., defaults to None + :type sha_1: Optional[str], optional + :param etag: The HTTP `etag` of this file. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the file if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `file`., defaults to FileBaseTypeField.FILE + :type type: FileBaseTypeField, optional + """ + super().__init__(id=id, etag=etag, type=type, **kwargs) + self.sequence_id = sequence_id + self.name = name + self.sha_1 = sha_1 + self.file_version = file_version diff --git a/box_sdk_gen/schemas/file_or_folder_scope.py b/box_sdk_gen/schemas/file_or_folder_scope.py new file mode 100644 index 000000000..fe5956598 --- /dev/null +++ b/box_sdk_gen/schemas/file_or_folder_scope.py @@ -0,0 +1,47 @@ +from enum import Enum + +from typing import Optional + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileOrFolderScopeScopeField(str, Enum): + ANNOTATION_EDIT = 'annotation_edit' + ANNOTATION_VIEW_ALL = 'annotation_view_all' + ANNOTATION_VIEW_SELF = 'annotation_view_self' + BASE_EXPLORER = 'base_explorer' + BASE_PICKER = 'base_picker' + BASE_PREVIEW = 'base_preview' + BASE_UPLOAD = 'base_upload' + ITEM_DELETE = 'item_delete' + ITEM_DOWNLOAD = 'item_download' + ITEM_PREVIEW = 'item_preview' + ITEM_RENAME = 'item_rename' + ITEM_SHARE = 'item_share' + ITEM_UPLOAD = 'item_upload' + ITEM_READ = 'item_read' + + +class FileOrFolderScope(BaseObject): + def __init__( + self, + *, + scope: Optional[FileOrFolderScopeScopeField] = None, + object: Optional[Union[FolderMini, FileMini]] = None, + **kwargs + ): + """ + :param scope: The scopes for the resource access., defaults to None + :type scope: Optional[FileOrFolderScopeScopeField], optional + """ + super().__init__(**kwargs) + self.scope = scope + self.object = object diff --git a/box_sdk_gen/schemas/file_request.py b/box_sdk_gen/schemas/file_request.py new file mode 100644 index 000000000..ea17ff10a --- /dev/null +++ b/box_sdk_gen/schemas/file_request.py @@ -0,0 +1,129 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class FileRequestTypeField(str, Enum): + FILE_REQUEST = 'file_request' + + +class FileRequestStatusField(str, Enum): + ACTIVE = 'active' + INACTIVE = 'inactive' + + +class FileRequest(BaseObject): + _discriminator = 'type', {'file_request'} + + def __init__( + self, + id: str, + folder: FolderMini, + created_at: DateTime, + updated_at: DateTime, + *, + type: FileRequestTypeField = FileRequestTypeField.FILE_REQUEST, + title: Optional[str] = None, + description: Optional[str] = None, + status: Optional[FileRequestStatusField] = None, + is_email_required: Optional[bool] = None, + is_description_required: Optional[bool] = None, + expires_at: Optional[DateTime] = None, + url: Optional[str] = None, + etag: Optional[str] = None, + created_by: Optional[UserMini] = None, + updated_by: Optional[UserMini] = None, + **kwargs + ): + """ + :param id: The unique identifier for this file request. + :type id: str + :param created_at: The date and time when the file request was created. + :type created_at: DateTime + :param updated_at: The date and time when the file request was last updated. + :type updated_at: DateTime + :param type: The value will always be `file_request`., defaults to FileRequestTypeField.FILE_REQUEST + :type type: FileRequestTypeField, optional + :param title: The title of file request. This is shown + in the Box UI to users uploading files. + + This defaults to title of the file request that was + copied to create this file request., defaults to None + :type title: Optional[str], optional + :param description: The optional description of this file request. This is + shown in the Box UI to users uploading files. + + This defaults to description of the file request that was + copied to create this file request., defaults to None + :type description: Optional[str], optional + :param status: The status of the file request. This defaults + to `active`. + + When the status is set to `inactive`, the file request + will no longer accept new submissions, and any visitor + to the file request URL will receive a `HTTP 404` status + code. + + This defaults to status of file request that was + copied to create this file request., defaults to None + :type status: Optional[FileRequestStatusField], optional + :param is_email_required: Whether a file request submitter is required to provide + their email address. + + When this setting is set to true, the Box UI will show + an email field on the file request form. + + This defaults to setting of file request that was + copied to create this file request., defaults to None + :type is_email_required: Optional[bool], optional + :param is_description_required: Whether a file request submitter is required to provide + a description of the files they are submitting. + + When this setting is set to true, the Box UI will show + a description field on the file request form. + + This defaults to setting of file request that was + copied to create this file request., defaults to None + :type is_description_required: Optional[bool], optional + :param expires_at: The date after which a file request will no longer accept new + submissions. + + After this date, the `status` will automatically be set to + `inactive`., defaults to None + :type expires_at: Optional[DateTime], optional + :param url: The generated URL for this file request. This URL can be shared + with users to let them upload files to the associated folder., defaults to None + :type url: Optional[str], optional + :param etag: The HTTP `etag` of this file. This can be used in combination with + the `If-Match` header when updating a file request. By providing that + header, a change will only be performed on the file request if the `etag` + on the file request still matches the `etag` provided in the `If-Match` + header., defaults to None + :type etag: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.folder = folder + self.created_at = created_at + self.updated_at = updated_at + self.type = type + self.title = title + self.description = description + self.status = status + self.is_email_required = is_email_required + self.is_description_required = is_description_required + self.expires_at = expires_at + self.url = url + self.etag = etag + self.created_by = created_by + self.updated_by = updated_by diff --git a/box_sdk_gen/schemas/file_request_copy_request.py b/box_sdk_gen/schemas/file_request_copy_request.py new file mode 100644 index 000000000..93fc86e18 --- /dev/null +++ b/box_sdk_gen/schemas/file_request_copy_request.py @@ -0,0 +1,113 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_request_update_request import ( + FileRequestUpdateRequestStatusField, +) + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.file_request_update_request import FileRequestUpdateRequest + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileRequestCopyRequestFolderTypeField(str, Enum): + FOLDER = 'folder' + + +class FileRequestCopyRequestFolderField(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + id: str, + *, + type: Optional[FileRequestCopyRequestFolderTypeField] = None, + **kwargs + ): + """ + :param id: The ID of the folder to associate the new + file request to. + :type id: str + :param type: The value will always be `folder`., defaults to None + :type type: Optional[FileRequestCopyRequestFolderTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class FileRequestCopyRequest(FileRequestUpdateRequest): + def __init__( + self, + folder: FileRequestCopyRequestFolderField, + *, + title: Optional[str] = None, + description: Optional[str] = None, + status: Optional[FileRequestUpdateRequestStatusField] = None, + is_email_required: Optional[bool] = None, + is_description_required: Optional[bool] = None, + expires_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param folder: The folder to associate the new file request to. + :type folder: FileRequestCopyRequestFolderField + :param title: An optional new title for the file request. This can be + used to change the title of the file request. + + This will default to the value on the existing file request., defaults to None + :type title: Optional[str], optional + :param description: An optional new description for the file request. This can be + used to change the description of the file request. + + This will default to the value on the existing file request., defaults to None + :type description: Optional[str], optional + :param status: An optional new status of the file request. + + When the status is set to `inactive`, the file request + will no longer accept new submissions, and any visitor + to the file request URL will receive a `HTTP 404` status + code. + + This will default to the value on the existing file request., defaults to None + :type status: Optional[FileRequestUpdateRequestStatusField], optional + :param is_email_required: Whether a file request submitter is required to provide + their email address. + + When this setting is set to true, the Box UI will show + an email field on the file request form. + + This will default to the value on the existing file request., defaults to None + :type is_email_required: Optional[bool], optional + :param is_description_required: Whether a file request submitter is required to provide + a description of the files they are submitting. + + When this setting is set to true, the Box UI will show + a description field on the file request form. + + This will default to the value on the existing file request., defaults to None + :type is_description_required: Optional[bool], optional + :param expires_at: The date after which a file request will no longer accept new + submissions. + + After this date, the `status` will automatically be set to + `inactive`. + + This will default to the value on the existing file request., defaults to None + :type expires_at: Optional[DateTime], optional + """ + super().__init__( + title=title, + description=description, + status=status, + is_email_required=is_email_required, + is_description_required=is_description_required, + expires_at=expires_at, + **kwargs + ) + self.folder = folder diff --git a/box_sdk_gen/schemas/file_request_update_request.py b/box_sdk_gen/schemas/file_request_update_request.py new file mode 100644 index 000000000..e496d6124 --- /dev/null +++ b/box_sdk_gen/schemas/file_request_update_request.py @@ -0,0 +1,80 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class FileRequestUpdateRequestStatusField(str, Enum): + ACTIVE = 'active' + INACTIVE = 'inactive' + + +class FileRequestUpdateRequest(BaseObject): + def __init__( + self, + *, + title: Optional[str] = None, + description: Optional[str] = None, + status: Optional[FileRequestUpdateRequestStatusField] = None, + is_email_required: Optional[bool] = None, + is_description_required: Optional[bool] = None, + expires_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param title: An optional new title for the file request. This can be + used to change the title of the file request. + + This will default to the value on the existing file request., defaults to None + :type title: Optional[str], optional + :param description: An optional new description for the file request. This can be + used to change the description of the file request. + + This will default to the value on the existing file request., defaults to None + :type description: Optional[str], optional + :param status: An optional new status of the file request. + + When the status is set to `inactive`, the file request + will no longer accept new submissions, and any visitor + to the file request URL will receive a `HTTP 404` status + code. + + This will default to the value on the existing file request., defaults to None + :type status: Optional[FileRequestUpdateRequestStatusField], optional + :param is_email_required: Whether a file request submitter is required to provide + their email address. + + When this setting is set to true, the Box UI will show + an email field on the file request form. + + This will default to the value on the existing file request., defaults to None + :type is_email_required: Optional[bool], optional + :param is_description_required: Whether a file request submitter is required to provide + a description of the files they are submitting. + + When this setting is set to true, the Box UI will show + a description field on the file request form. + + This will default to the value on the existing file request., defaults to None + :type is_description_required: Optional[bool], optional + :param expires_at: The date after which a file request will no longer accept new + submissions. + + After this date, the `status` will automatically be set to + `inactive`. + + This will default to the value on the existing file request., defaults to None + :type expires_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.title = title + self.description = description + self.status = status + self.is_email_required = is_email_required + self.is_description_required = is_description_required + self.expires_at = expires_at diff --git a/box_sdk_gen/schemas/file_version.py b/box_sdk_gen/schemas/file_version.py new file mode 100644 index 000000000..3b213fb07 --- /dev/null +++ b/box_sdk_gen/schemas/file_version.py @@ -0,0 +1,69 @@ +from typing import Optional + +from box_sdk_gen.schemas.file_version_base import FileVersionBaseTypeField + +from box_sdk_gen.schemas.file_version_base import FileVersionBase + +from box_sdk_gen.schemas.file_version_mini import FileVersionMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class FileVersion(FileVersionMini): + def __init__( + self, + id: str, + *, + name: Optional[str] = None, + size: Optional[int] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + modified_by: Optional[UserMini] = None, + trashed_at: Optional[DateTime] = None, + trashed_by: Optional[UserMini] = None, + restored_at: Optional[DateTime] = None, + restored_by: Optional[UserMini] = None, + purged_at: Optional[DateTime] = None, + uploader_display_name: Optional[str] = None, + sha_1: Optional[str] = None, + type: FileVersionBaseTypeField = FileVersionBaseTypeField.FILE_VERSION, + **kwargs + ): + """ + :param id: The unique identifier that represent a file version. + :type id: str + :param name: The name of the file version., defaults to None + :type name: Optional[str], optional + :param size: Size of the file version in bytes., defaults to None + :type size: Optional[int], optional + :param created_at: When the file version object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the file version object was last updated., defaults to None + :type modified_at: Optional[DateTime], optional + :param trashed_at: When the file version object was trashed., defaults to None + :type trashed_at: Optional[DateTime], optional + :param restored_at: When the file version was restored from the trash., defaults to None + :type restored_at: Optional[DateTime], optional + :param purged_at: When the file version object will be permanently deleted., defaults to None + :type purged_at: Optional[DateTime], optional + :param sha_1: The SHA1 hash of this version of the file., defaults to None + :type sha_1: Optional[str], optional + :param type: The value will always be `file_version`., defaults to FileVersionBaseTypeField.FILE_VERSION + :type type: FileVersionBaseTypeField, optional + """ + super().__init__(id=id, sha_1=sha_1, type=type, **kwargs) + self.name = name + self.size = size + self.created_at = created_at + self.modified_at = modified_at + self.modified_by = modified_by + self.trashed_at = trashed_at + self.trashed_by = trashed_by + self.restored_at = restored_at + self.restored_by = restored_by + self.purged_at = purged_at + self.uploader_display_name = uploader_display_name diff --git a/box_sdk_gen/schemas/file_version_base.py b/box_sdk_gen/schemas/file_version_base.py new file mode 100644 index 000000000..d6fba5be6 --- /dev/null +++ b/box_sdk_gen/schemas/file_version_base.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileVersionBaseTypeField(str, Enum): + FILE_VERSION = 'file_version' + + +class FileVersionBase(BaseObject): + _discriminator = 'type', {'file_version'} + + def __init__( + self, + id: str, + *, + type: FileVersionBaseTypeField = FileVersionBaseTypeField.FILE_VERSION, + **kwargs + ): + """ + :param id: The unique identifier that represent a file version. + :type id: str + :param type: The value will always be `file_version`., defaults to FileVersionBaseTypeField.FILE_VERSION + :type type: FileVersionBaseTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/file_version_full.py b/box_sdk_gen/schemas/file_version_full.py new file mode 100644 index 000000000..7d778bd3a --- /dev/null +++ b/box_sdk_gen/schemas/file_version_full.py @@ -0,0 +1,80 @@ +from typing import Optional + +from box_sdk_gen.schemas.file_version_base import FileVersionBaseTypeField + +from box_sdk_gen.schemas.file_version_base import FileVersionBase + +from box_sdk_gen.schemas.file_version_mini import FileVersionMini + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.file_version import FileVersion + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileVersionFull(FileVersion): + def __init__( + self, + id: str, + *, + version_number: Optional[str] = None, + name: Optional[str] = None, + size: Optional[int] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + modified_by: Optional[UserMini] = None, + trashed_at: Optional[DateTime] = None, + trashed_by: Optional[UserMini] = None, + restored_at: Optional[DateTime] = None, + restored_by: Optional[UserMini] = None, + purged_at: Optional[DateTime] = None, + uploader_display_name: Optional[str] = None, + sha_1: Optional[str] = None, + type: FileVersionBaseTypeField = FileVersionBaseTypeField.FILE_VERSION, + **kwargs + ): + """ + :param id: The unique identifier that represent a file version. + :type id: str + :param version_number: The version number of this file version., defaults to None + :type version_number: Optional[str], optional + :param name: The name of the file version., defaults to None + :type name: Optional[str], optional + :param size: Size of the file version in bytes., defaults to None + :type size: Optional[int], optional + :param created_at: When the file version object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the file version object was last updated., defaults to None + :type modified_at: Optional[DateTime], optional + :param trashed_at: When the file version object was trashed., defaults to None + :type trashed_at: Optional[DateTime], optional + :param restored_at: When the file version was restored from the trash., defaults to None + :type restored_at: Optional[DateTime], optional + :param purged_at: When the file version object will be permanently deleted., defaults to None + :type purged_at: Optional[DateTime], optional + :param sha_1: The SHA1 hash of this version of the file., defaults to None + :type sha_1: Optional[str], optional + :param type: The value will always be `file_version`., defaults to FileVersionBaseTypeField.FILE_VERSION + :type type: FileVersionBaseTypeField, optional + """ + super().__init__( + id=id, + name=name, + size=size, + created_at=created_at, + modified_at=modified_at, + modified_by=modified_by, + trashed_at=trashed_at, + trashed_by=trashed_by, + restored_at=restored_at, + restored_by=restored_by, + purged_at=purged_at, + uploader_display_name=uploader_display_name, + sha_1=sha_1, + type=type, + **kwargs + ) + self.version_number = version_number diff --git a/box_sdk_gen/schemas/file_version_legal_hold.py b/box_sdk_gen/schemas/file_version_legal_hold.py new file mode 100644 index 000000000..67da341f4 --- /dev/null +++ b/box_sdk_gen/schemas/file_version_legal_hold.py @@ -0,0 +1,55 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_version_mini import FileVersionMini + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.schemas.legal_hold_policy_assignment import LegalHoldPolicyAssignment + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class FileVersionLegalHoldTypeField(str, Enum): + FILE_VERSION_LEGAL_HOLD = 'file_version_legal_hold' + + +class FileVersionLegalHold(BaseObject): + _discriminator = 'type', {'file_version_legal_hold'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[FileVersionLegalHoldTypeField] = None, + file_version: Optional[FileVersionMini] = None, + file: Optional[FileMini] = None, + legal_hold_policy_assignments: Optional[List[LegalHoldPolicyAssignment]] = None, + deleted_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param id: The unique identifier for this file version legal hold., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `file_version_legal_hold`., defaults to None + :type type: Optional[FileVersionLegalHoldTypeField], optional + :param legal_hold_policy_assignments: List of assignments contributing to this Hold., defaults to None + :type legal_hold_policy_assignments: Optional[List[LegalHoldPolicyAssignment]], optional + :param deleted_at: Time that this File-Version-Legal-Hold was + deleted., defaults to None + :type deleted_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.file_version = file_version + self.file = file + self.legal_hold_policy_assignments = legal_hold_policy_assignments + self.deleted_at = deleted_at diff --git a/box_sdk_gen/schemas/file_version_legal_holds.py b/box_sdk_gen/schemas/file_version_legal_holds.py new file mode 100644 index 000000000..fcf4c11dd --- /dev/null +++ b/box_sdk_gen/schemas/file_version_legal_holds.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_version_legal_hold import FileVersionLegalHold + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileVersionLegalHolds(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[FileVersionLegalHold]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of file version legal holds., defaults to None + :type entries: Optional[List[FileVersionLegalHold]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/file_version_mini.py b/box_sdk_gen/schemas/file_version_mini.py new file mode 100644 index 000000000..d4c03e4c9 --- /dev/null +++ b/box_sdk_gen/schemas/file_version_mini.py @@ -0,0 +1,39 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.schemas.file_version_base import FileVersionBaseTypeField + +from box_sdk_gen.schemas.file_version_base import FileVersionBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileVersionMini(FileVersionBase): + _fields_to_json_mapping: Dict[str, str] = { + 'sha_1': 'sha1', + **FileVersionBase._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'sha1': 'sha_1', + **FileVersionBase._json_to_fields_mapping, + } + + def __init__( + self, + id: str, + *, + sha_1: Optional[str] = None, + type: FileVersionBaseTypeField = FileVersionBaseTypeField.FILE_VERSION, + **kwargs + ): + """ + :param id: The unique identifier that represent a file version. + :type id: str + :param sha_1: The SHA1 hash of this version of the file., defaults to None + :type sha_1: Optional[str], optional + :param type: The value will always be `file_version`., defaults to FileVersionBaseTypeField.FILE_VERSION + :type type: FileVersionBaseTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.sha_1 = sha_1 diff --git a/box_sdk_gen/schemas/file_version_retention.py b/box_sdk_gen/schemas/file_version_retention.py new file mode 100644 index 000000000..3321cc4f7 --- /dev/null +++ b/box_sdk_gen/schemas/file_version_retention.py @@ -0,0 +1,56 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_version_mini import FileVersionMini + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.schemas.retention_policy_mini import RetentionPolicyMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class FileVersionRetentionTypeField(str, Enum): + FILE_VERSION_RETENTION = 'file_version_retention' + + +class FileVersionRetention(BaseObject): + _discriminator = 'type', {'file_version_retention'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[FileVersionRetentionTypeField] = None, + file_version: Optional[FileVersionMini] = None, + file: Optional[FileMini] = None, + applied_at: Optional[DateTime] = None, + disposition_at: Optional[DateTime] = None, + winning_retention_policy: Optional[RetentionPolicyMini] = None, + **kwargs + ): + """ + :param id: The unique identifier for this file version retention., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `file_version_retention`., defaults to None + :type type: Optional[FileVersionRetentionTypeField], optional + :param applied_at: When this file version retention object was + created., defaults to None + :type applied_at: Optional[DateTime], optional + :param disposition_at: When the retention expires on this file + version retention., defaults to None + :type disposition_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.file_version = file_version + self.file = file + self.applied_at = applied_at + self.disposition_at = disposition_at + self.winning_retention_policy = winning_retention_policy diff --git a/box_sdk_gen/schemas/file_version_retentions.py b/box_sdk_gen/schemas/file_version_retentions.py new file mode 100644 index 000000000..e83034c38 --- /dev/null +++ b/box_sdk_gen/schemas/file_version_retentions.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_version_retention import FileVersionRetention + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileVersionRetentions(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[FileVersionRetention]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of file version retentions., defaults to None + :type entries: Optional[List[FileVersionRetention]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/file_versions.py b/box_sdk_gen/schemas/file_versions.py new file mode 100644 index 000000000..494f249cd --- /dev/null +++ b/box_sdk_gen/schemas/file_versions.py @@ -0,0 +1,80 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.file_version_full import FileVersionFull + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileVersionsOrderDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class FileVersionsOrderField(BaseObject): + def __init__( + self, + *, + by: Optional[str] = None, + direction: Optional[FileVersionsOrderDirectionField] = None, + **kwargs + ): + """ + :param by: The field to order by., defaults to None + :type by: Optional[str], optional + :param direction: The direction to order by, either ascending or descending., defaults to None + :type direction: Optional[FileVersionsOrderDirectionField], optional + """ + super().__init__(**kwargs) + self.by = by + self.direction = direction + + +class FileVersions(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + order: Optional[List[FileVersionsOrderField]] = None, + entries: Optional[List[FileVersionFull]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type total_count: Optional[int], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type offset: Optional[int], optional + :param order: The order by which items are returned. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type order: Optional[List[FileVersionsOrderField]], optional + :param entries: A list of file versions., defaults to None + :type entries: Optional[List[FileVersionFull]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.limit = limit + self.offset = offset + self.order = order + self.entries = entries diff --git a/box_sdk_gen/schemas/files.py b/box_sdk_gen/schemas/files.py new file mode 100644 index 000000000..428c1fcab --- /dev/null +++ b/box_sdk_gen/schemas/files.py @@ -0,0 +1,28 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.box.errors import BoxSDKError + + +class Files(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + entries: Optional[List[FileFull]] = None, + **kwargs + ): + """ + :param total_count: The number of files., defaults to None + :type total_count: Optional[int], optional + :param entries: A list of files., defaults to None + :type entries: Optional[List[FileFull]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries diff --git a/box_sdk_gen/schemas/files_on_hold.py b/box_sdk_gen/schemas/files_on_hold.py new file mode 100644 index 000000000..9918c0066 --- /dev/null +++ b/box_sdk_gen/schemas/files_on_hold.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class FilesOnHold(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[FileMini]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of files., defaults to None + :type entries: Optional[List[FileMini]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/files_under_retention.py b/box_sdk_gen/schemas/files_under_retention.py new file mode 100644 index 000000000..6b2211a75 --- /dev/null +++ b/box_sdk_gen/schemas/files_under_retention.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class FilesUnderRetention(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[FileMini]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of files., defaults to None + :type entries: Optional[List[FileMini]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/folder.py b/box_sdk_gen/schemas/folder.py new file mode 100644 index 000000000..a0330bec5 --- /dev/null +++ b/box_sdk_gen/schemas/folder.py @@ -0,0 +1,313 @@ +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.folder_base import FolderBaseTypeField + +from box_sdk_gen.schemas.folder_base import FolderBase + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.items import Items + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class FolderPathCollectionField(BaseObject): + def __init__(self, total_count: int, entries: List[FolderMini], **kwargs): + """ + :param total_count: The number of folders in this list. + :type total_count: int + :param entries: The parent folders for this item. + :type entries: List[FolderMini] + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries + + +class FolderSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class FolderSharedLinkEffectiveAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class FolderSharedLinkEffectivePermissionField(str, Enum): + CAN_EDIT = 'can_edit' + CAN_DOWNLOAD = 'can_download' + CAN_PREVIEW = 'can_preview' + NO_ACCESS = 'no_access' + + +class FolderSharedLinkPermissionsField(BaseObject): + def __init__(self, can_download: bool, can_preview: bool, can_edit: bool, **kwargs): + """ + :param can_download: Defines if the shared link allows for the item to be downloaded. For + shared links on folders, this also applies to any items in the folder. + + This value can be set to `true` when the effective access level is + set to `open` or `company`, not `collaborators`. + :type can_download: bool + :param can_preview: Defines if the shared link allows for the item to be previewed. + + This value is always `true`. For shared links on folders this also + applies to any items in the folder. + :type can_preview: bool + :param can_edit: Defines if the shared link allows for the item to be edited. + + This value can only be `true` if `can_download` is also `true` and if + the item has a type of `file`. + :type can_edit: bool + """ + super().__init__(**kwargs) + self.can_download = can_download + self.can_preview = can_preview + self.can_edit = can_edit + + +class FolderSharedLinkField(BaseObject): + def __init__( + self, + url: str, + effective_access: FolderSharedLinkEffectiveAccessField, + effective_permission: FolderSharedLinkEffectivePermissionField, + is_password_enabled: bool, + download_count: int, + preview_count: int, + *, + download_url: Optional[str] = None, + vanity_url: Optional[str] = None, + vanity_name: Optional[str] = None, + access: Optional[FolderSharedLinkAccessField] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[FolderSharedLinkPermissionsField] = None, + **kwargs + ): + """ + :param url: The URL that can be used to access the item on Box. + + This URL will display the item in Box's preview UI where the file + can be downloaded if allowed. + + This URL will continue to work even when a custom `vanity_url` + has been set for this shared link. + :type url: str + :param effective_access: The effective access level for the shared link. This can be a more + restrictive access level than the value in the `access` field when the + enterprise settings restrict the allowed access levels. + :type effective_access: FolderSharedLinkEffectiveAccessField + :param effective_permission: The effective permissions for this shared link. + These result in the more restrictive combination of + the share link permissions and the item permissions set + by the administrator, the owner, and any ancestor item + such as a folder. + :type effective_permission: FolderSharedLinkEffectivePermissionField + :param is_password_enabled: Defines if the shared link requires a password to access the item. + :type is_password_enabled: bool + :param download_count: The number of times this item has been downloaded. + :type download_count: int + :param preview_count: The number of times this item has been previewed. + :type preview_count: int + :param download_url: A URL that can be used to download the file. This URL can be used in + a browser to download the file. This URL includes the file + extension so that the file will be saved with the right file type. + + This property will be `null` for folders., defaults to None + :type download_url: Optional[str], optional + :param vanity_url: The "Custom URL" that can also be used to preview the item on Box. Custom + URLs can only be created or modified in the Box Web application., defaults to None + :type vanity_url: Optional[str], optional + :param vanity_name: The custom name of a shared link, as used in the `vanity_url` field., defaults to None + :type vanity_name: Optional[str], optional + :param access: The access level for this shared link. + + * `open` - provides access to this item to anyone with this link + * `company` - only provides access to this item to people the same company + * `collaborators` - only provides access to this item to people who are + collaborators on this item + + If this field is omitted when creating the shared link, the access level + will be set to the default access level specified by the enterprise admin., defaults to None + :type access: Optional[FolderSharedLinkAccessField], optional + :param unshared_at: The date and time when this link will be unshared. This field can only be + set by users with paid accounts., defaults to None + :type unshared_at: Optional[DateTime], optional + :param permissions: Defines if this link allows a user to preview, edit, and download an item. + These permissions refer to the shared link only and + do not supersede permissions applied to the item itself., defaults to None + :type permissions: Optional[FolderSharedLinkPermissionsField], optional + """ + super().__init__(**kwargs) + self.url = url + self.effective_access = effective_access + self.effective_permission = effective_permission + self.is_password_enabled = is_password_enabled + self.download_count = download_count + self.preview_count = preview_count + self.download_url = download_url + self.vanity_url = vanity_url + self.vanity_name = vanity_name + self.access = access + self.unshared_at = unshared_at + self.permissions = permissions + + +class FolderFolderUploadEmailAccessField(str, Enum): + OPEN = 'open' + COLLABORATORS = 'collaborators' + + +class FolderFolderUploadEmailField(BaseObject): + def __init__( + self, + *, + access: Optional[FolderFolderUploadEmailAccessField] = None, + email: Optional[str] = None, + **kwargs + ): + """ + :param access: When this parameter has been set, users can email files + to the email address that has been automatically + created for this folder. + + To create an email address, set this property either when + creating or updating the folder. + + When set to `collaborators`, only emails from registered email + addresses for collaborators will be accepted. This includes + any email aliases a user might have registered. + + When set to `open` it will accept emails from any email + address., defaults to None + :type access: Optional[FolderFolderUploadEmailAccessField], optional + :param email: The optional upload email address for this folder., defaults to None + :type email: Optional[str], optional + """ + super().__init__(**kwargs) + self.access = access + self.email = email + + +class FolderItemStatusField(str, Enum): + ACTIVE = 'active' + TRASHED = 'trashed' + DELETED = 'deleted' + + +class Folder(FolderMini): + def __init__( + self, + id: str, + *, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + description: Optional[str] = None, + size: Optional[int] = None, + path_collection: Optional[FolderPathCollectionField] = None, + created_by: Optional[UserMini] = None, + modified_by: Optional[UserMini] = None, + trashed_at: Optional[DateTime] = None, + purged_at: Optional[DateTime] = None, + content_created_at: Optional[DateTime] = None, + content_modified_at: Optional[DateTime] = None, + owned_by: Optional[UserMini] = None, + shared_link: Optional[FolderSharedLinkField] = None, + folder_upload_email: Optional[FolderFolderUploadEmailField] = None, + parent: Optional[FolderMini] = None, + item_status: Optional[FolderItemStatusField] = None, + item_collection: Optional[Items] = None, + sequence_id: Optional[str] = None, + name: Optional[str] = None, + etag: Optional[str] = None, + type: FolderBaseTypeField = FolderBaseTypeField.FOLDER, + **kwargs + ): + """ + :param id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting a folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folders/123` + the `folder_id` is `123`. + :type id: str + :param created_at: The date and time when the folder was created. This value may + be `null` for some folders such as the root folder or the trash + folder., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The date and time when the folder was last updated. This value may + be `null` for some folders such as the root folder or the trash + folder., defaults to None + :type modified_at: Optional[DateTime], optional + :param size: The folder size in bytes. + + Be careful parsing this integer as its + value can get very large., defaults to None + :type size: Optional[int], optional + :param trashed_at: The time at which this folder was put in the trash., defaults to None + :type trashed_at: Optional[DateTime], optional + :param purged_at: The time at which this folder is expected to be purged + from the trash., defaults to None + :type purged_at: Optional[DateTime], optional + :param content_created_at: The date and time at which this folder was originally + created., defaults to None + :type content_created_at: Optional[DateTime], optional + :param content_modified_at: The date and time at which this folder was last updated., defaults to None + :type content_modified_at: Optional[DateTime], optional + :param folder_upload_email: The `folder_upload_email` parameter is not `null` if one of the following options is **true**: + + * The **Allow uploads to this folder via email** and the **Only allow email uploads from collaborators in this folder** are [enabled for a folder in the Admin Console](https://support.box.com/hc/en-us/articles/360043697534-Upload-to-Box-Through-Email), and the user has at least **Upload** permissions granted. + + * The **Allow uploads to this folder via email** setting is enabled for a folder in the Admin Console, and the **Only allow email uploads from collaborators in this folder** setting is deactivated (unchecked). + + If the conditions are not met, the parameter will have the following value: `folder_upload_email: null`., defaults to None + :type folder_upload_email: Optional[FolderFolderUploadEmailField], optional + :param item_status: Defines if this item has been deleted or not. + + * `active` when the item has is not in the trash + * `trashed` when the item has been moved to the trash but not deleted + * `deleted` when the item has been permanently deleted., defaults to None + :type item_status: Optional[FolderItemStatusField], optional + :param name: The name of the folder., defaults to None + :type name: Optional[str], optional + :param etag: The HTTP `etag` of this folder. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the folder if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `folder`., defaults to FolderBaseTypeField.FOLDER + :type type: FolderBaseTypeField, optional + """ + super().__init__( + id=id, sequence_id=sequence_id, name=name, etag=etag, type=type, **kwargs + ) + self.created_at = created_at + self.modified_at = modified_at + self.description = description + self.size = size + self.path_collection = path_collection + self.created_by = created_by + self.modified_by = modified_by + self.trashed_at = trashed_at + self.purged_at = purged_at + self.content_created_at = content_created_at + self.content_modified_at = content_modified_at + self.owned_by = owned_by + self.shared_link = shared_link + self.folder_upload_email = folder_upload_email + self.parent = parent + self.item_status = item_status + self.item_collection = item_collection diff --git a/box_sdk_gen/schemas/folder_base.py b/box_sdk_gen/schemas/folder_base.py new file mode 100644 index 000000000..251e11c9d --- /dev/null +++ b/box_sdk_gen/schemas/folder_base.py @@ -0,0 +1,44 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class FolderBaseTypeField(str, Enum): + FOLDER = 'folder' + + +class FolderBase(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + id: str, + *, + etag: Optional[str] = None, + type: FolderBaseTypeField = FolderBaseTypeField.FOLDER, + **kwargs + ): + """ + :param id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting a folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folders/123` + the `folder_id` is `123`. + :type id: str + :param etag: The HTTP `etag` of this folder. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the folder if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `folder`., defaults to FolderBaseTypeField.FOLDER + :type type: FolderBaseTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.etag = etag + self.type = type diff --git a/box_sdk_gen/schemas/folder_full.py b/box_sdk_gen/schemas/folder_full.py new file mode 100644 index 000000000..dff8b9f69 --- /dev/null +++ b/box_sdk_gen/schemas/folder_full.py @@ -0,0 +1,314 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.folder_base import FolderBaseTypeField + +from box_sdk_gen.schemas.folder_base import FolderBase + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.folder import FolderPathCollectionField + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.folder import FolderSharedLinkField + +from box_sdk_gen.schemas.folder import FolderFolderUploadEmailField + +from box_sdk_gen.schemas.folder import FolderItemStatusField + +from box_sdk_gen.schemas.items import Items + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.schemas.metadata_full import MetadataFull + +from box_sdk_gen.box.errors import BoxSDKError + + +class FolderFullSyncStateField(str, Enum): + SYNCED = 'synced' + NOT_SYNCED = 'not_synced' + PARTIALLY_SYNCED = 'partially_synced' + + +class FolderFullPermissionsField(BaseObject): + def __init__( + self, + can_delete: bool, + can_download: bool, + can_invite_collaborator: bool, + can_rename: bool, + can_set_share_access: bool, + can_share: bool, + can_upload: bool, + **kwargs + ): + """ + :param can_delete: Specifies if the current user can delete this item. + :type can_delete: bool + :param can_download: Specifies if the current user can download this item. + :type can_download: bool + :param can_invite_collaborator: Specifies if the current user can invite new + users to collaborate on this item, and if the user can + update the role of a user already collaborated on this + item. + :type can_invite_collaborator: bool + :param can_rename: Specifies if the user can rename this item. + :type can_rename: bool + :param can_set_share_access: Specifies if the user can change the access level of an + existing shared link on this item. + :type can_set_share_access: bool + :param can_share: Specifies if the user can create a shared link for this item. + :type can_share: bool + :param can_upload: Specifies if the user can upload into this folder. + :type can_upload: bool + """ + super().__init__(**kwargs) + self.can_delete = can_delete + self.can_download = can_download + self.can_invite_collaborator = can_invite_collaborator + self.can_rename = can_rename + self.can_set_share_access = can_set_share_access + self.can_share = can_share + self.can_upload = can_upload + + +class FolderFullMetadataField(BaseObject): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.extra_data = kwargs + + +class FolderFullAllowedSharedLinkAccessLevelsField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class FolderFullAllowedInviteeRolesField(str, Enum): + EDITOR = 'editor' + VIEWER = 'viewer' + PREVIEWER = 'previewer' + UPLOADER = 'uploader' + PREVIEWER_UPLOADER = 'previewer uploader' + VIEWER_UPLOADER = 'viewer uploader' + CO_OWNER = 'co-owner' + + +class FolderFullWatermarkInfoField(BaseObject): + def __init__(self, *, is_watermarked: Optional[bool] = None, **kwargs): + """ + :param is_watermarked: Specifies if this item has a watermark applied., defaults to None + :type is_watermarked: Optional[bool], optional + """ + super().__init__(**kwargs) + self.is_watermarked = is_watermarked + + +class FolderFullClassificationField(BaseObject): + def __init__( + self, + *, + name: Optional[str] = None, + definition: Optional[str] = None, + color: Optional[str] = None, + **kwargs + ): + """ + :param name: The name of the classification., defaults to None + :type name: Optional[str], optional + :param definition: An explanation of the meaning of this classification., defaults to None + :type definition: Optional[str], optional + :param color: The color that is used to display the + classification label in a user-interface. Colors are defined by the admin + or co-admin who created the classification in the Box web app., defaults to None + :type color: Optional[str], optional + """ + super().__init__(**kwargs) + self.name = name + self.definition = definition + self.color = color + + +class FolderFull(Folder): + def __init__( + self, + id: str, + *, + sync_state: Optional[FolderFullSyncStateField] = None, + has_collaborations: Optional[bool] = None, + permissions: Optional[FolderFullPermissionsField] = None, + tags: Optional[List[str]] = None, + can_non_owners_invite: Optional[bool] = None, + is_externally_owned: Optional[bool] = None, + metadata: Optional[FolderFullMetadataField] = None, + is_collaboration_restricted_to_enterprise: Optional[bool] = None, + allowed_shared_link_access_levels: Optional[ + List[FolderFullAllowedSharedLinkAccessLevelsField] + ] = None, + allowed_invitee_roles: Optional[ + List[FolderFullAllowedInviteeRolesField] + ] = None, + watermark_info: Optional[FolderFullWatermarkInfoField] = None, + is_accessible_via_shared_link: Optional[bool] = None, + can_non_owners_view_collaborators: Optional[bool] = None, + classification: Optional[FolderFullClassificationField] = None, + is_associated_with_app_item: Optional[bool] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + description: Optional[str] = None, + size: Optional[int] = None, + path_collection: Optional[FolderPathCollectionField] = None, + created_by: Optional[UserMini] = None, + modified_by: Optional[UserMini] = None, + trashed_at: Optional[DateTime] = None, + purged_at: Optional[DateTime] = None, + content_created_at: Optional[DateTime] = None, + content_modified_at: Optional[DateTime] = None, + owned_by: Optional[UserMini] = None, + shared_link: Optional[FolderSharedLinkField] = None, + folder_upload_email: Optional[FolderFolderUploadEmailField] = None, + parent: Optional[FolderMini] = None, + item_status: Optional[FolderItemStatusField] = None, + item_collection: Optional[Items] = None, + sequence_id: Optional[str] = None, + name: Optional[str] = None, + etag: Optional[str] = None, + type: FolderBaseTypeField = FolderBaseTypeField.FOLDER, + **kwargs + ): + """ + :param id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting a folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folders/123` + the `folder_id` is `123`. + :type id: str + :param has_collaborations: Specifies if this folder has any other collaborators., defaults to None + :type has_collaborations: Optional[bool], optional + :param is_externally_owned: Specifies if this folder is owned by a user outside of the + authenticated enterprise., defaults to None + :type is_externally_owned: Optional[bool], optional + :param allowed_shared_link_access_levels: A list of access levels that are available + for this folder. + + For some folders, like the root folder, this will always + be an empty list as sharing is not allowed at that level., defaults to None + :type allowed_shared_link_access_levels: Optional[List[FolderFullAllowedSharedLinkAccessLevelsField]], optional + :param allowed_invitee_roles: A list of the types of roles that user can be invited at + when sharing this folder., defaults to None + :type allowed_invitee_roles: Optional[List[FolderFullAllowedInviteeRolesField]], optional + :param is_accessible_via_shared_link: Specifies if the folder can be accessed + with the direct shared link or a shared link + to a parent folder., defaults to None + :type is_accessible_via_shared_link: Optional[bool], optional + :param can_non_owners_view_collaborators: Specifies if collaborators who are not owners + of this folder are restricted from viewing other + collaborations on this folder. + + It also restricts non-owners from inviting new + collaborators., defaults to None + :type can_non_owners_view_collaborators: Optional[bool], optional + :param is_associated_with_app_item: This field will return true if the folder or any ancestor of the + folder is associated with at least one app item. Note that this will + return true even if the context user does not have access to the + app item(s) associated with the folder., defaults to None + :type is_associated_with_app_item: Optional[bool], optional + :param created_at: The date and time when the folder was created. This value may + be `null` for some folders such as the root folder or the trash + folder., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The date and time when the folder was last updated. This value may + be `null` for some folders such as the root folder or the trash + folder., defaults to None + :type modified_at: Optional[DateTime], optional + :param size: The folder size in bytes. + + Be careful parsing this integer as its + value can get very large., defaults to None + :type size: Optional[int], optional + :param trashed_at: The time at which this folder was put in the trash., defaults to None + :type trashed_at: Optional[DateTime], optional + :param purged_at: The time at which this folder is expected to be purged + from the trash., defaults to None + :type purged_at: Optional[DateTime], optional + :param content_created_at: The date and time at which this folder was originally + created., defaults to None + :type content_created_at: Optional[DateTime], optional + :param content_modified_at: The date and time at which this folder was last updated., defaults to None + :type content_modified_at: Optional[DateTime], optional + :param folder_upload_email: The `folder_upload_email` parameter is not `null` if one of the following options is **true**: + + * The **Allow uploads to this folder via email** and the **Only allow email uploads from collaborators in this folder** are [enabled for a folder in the Admin Console](https://support.box.com/hc/en-us/articles/360043697534-Upload-to-Box-Through-Email), and the user has at least **Upload** permissions granted. + + * The **Allow uploads to this folder via email** setting is enabled for a folder in the Admin Console, and the **Only allow email uploads from collaborators in this folder** setting is deactivated (unchecked). + + If the conditions are not met, the parameter will have the following value: `folder_upload_email: null`., defaults to None + :type folder_upload_email: Optional[FolderFolderUploadEmailField], optional + :param item_status: Defines if this item has been deleted or not. + + * `active` when the item has is not in the trash + * `trashed` when the item has been moved to the trash but not deleted + * `deleted` when the item has been permanently deleted., defaults to None + :type item_status: Optional[FolderItemStatusField], optional + :param name: The name of the folder., defaults to None + :type name: Optional[str], optional + :param etag: The HTTP `etag` of this folder. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the folder if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `folder`., defaults to FolderBaseTypeField.FOLDER + :type type: FolderBaseTypeField, optional + """ + super().__init__( + id=id, + created_at=created_at, + modified_at=modified_at, + description=description, + size=size, + path_collection=path_collection, + created_by=created_by, + modified_by=modified_by, + trashed_at=trashed_at, + purged_at=purged_at, + content_created_at=content_created_at, + content_modified_at=content_modified_at, + owned_by=owned_by, + shared_link=shared_link, + folder_upload_email=folder_upload_email, + parent=parent, + item_status=item_status, + item_collection=item_collection, + sequence_id=sequence_id, + name=name, + etag=etag, + type=type, + **kwargs + ) + self.sync_state = sync_state + self.has_collaborations = has_collaborations + self.permissions = permissions + self.tags = tags + self.can_non_owners_invite = can_non_owners_invite + self.is_externally_owned = is_externally_owned + self.metadata = metadata + self.is_collaboration_restricted_to_enterprise = ( + is_collaboration_restricted_to_enterprise + ) + self.allowed_shared_link_access_levels = allowed_shared_link_access_levels + self.allowed_invitee_roles = allowed_invitee_roles + self.watermark_info = watermark_info + self.is_accessible_via_shared_link = is_accessible_via_shared_link + self.can_non_owners_view_collaborators = can_non_owners_view_collaborators + self.classification = classification + self.is_associated_with_app_item = is_associated_with_app_item diff --git a/box_sdk_gen/schemas/folder_lock.py b/box_sdk_gen/schemas/folder_lock.py new file mode 100644 index 000000000..ab97830ae --- /dev/null +++ b/box_sdk_gen/schemas/folder_lock.py @@ -0,0 +1,61 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class FolderLockLockedOperationsField(BaseObject): + def __init__(self, move: bool, delete: bool, **kwargs): + """ + :param move: Whether moving the folder is restricted. + :type move: bool + :param delete: Whether deleting the folder is restricted. + :type delete: bool + """ + super().__init__(**kwargs) + self.move = move + self.delete = delete + + +class FolderLock(BaseObject): + def __init__( + self, + *, + folder: Optional[FolderMini] = None, + id: Optional[str] = None, + type: Optional[str] = None, + created_by: Optional[UserBase] = None, + created_at: Optional[DateTime] = None, + locked_operations: Optional[FolderLockLockedOperationsField] = None, + lock_type: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this folder lock., defaults to None + :type id: Optional[str], optional + :param type: The object type, always `folder_lock`., defaults to None + :type type: Optional[str], optional + :param created_at: When the folder lock object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param locked_operations: The operations that have been locked. Currently the `move` + and `delete` operations cannot be locked separately, and both need to be + set to `true`., defaults to None + :type locked_operations: Optional[FolderLockLockedOperationsField], optional + :param lock_type: The lock type, always `freeze`., defaults to None + :type lock_type: Optional[str], optional + """ + super().__init__(**kwargs) + self.folder = folder + self.id = id + self.type = type + self.created_by = created_by + self.created_at = created_at + self.locked_operations = locked_operations + self.lock_type = lock_type diff --git a/box_sdk_gen/schemas/folder_locks.py b/box_sdk_gen/schemas/folder_locks.py new file mode 100644 index 000000000..6f0ca8c5f --- /dev/null +++ b/box_sdk_gen/schemas/folder_locks.py @@ -0,0 +1,34 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.folder_lock import FolderLock + +from box_sdk_gen.box.errors import BoxSDKError + + +class FolderLocks(BaseObject): + def __init__( + self, + *, + entries: Optional[List[FolderLock]] = None, + limit: Optional[str] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: A list of folder locks., defaults to None + :type entries: Optional[List[FolderLock]], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[str], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/folder_mini.py b/box_sdk_gen/schemas/folder_mini.py new file mode 100644 index 000000000..1e7d71a14 --- /dev/null +++ b/box_sdk_gen/schemas/folder_mini.py @@ -0,0 +1,41 @@ +from typing import Optional + +from box_sdk_gen.schemas.folder_base import FolderBaseTypeField + +from box_sdk_gen.schemas.folder_base import FolderBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class FolderMini(FolderBase): + def __init__( + self, + id: str, + *, + sequence_id: Optional[str] = None, + name: Optional[str] = None, + etag: Optional[str] = None, + type: FolderBaseTypeField = FolderBaseTypeField.FOLDER, + **kwargs + ): + """ + :param id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting a folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folders/123` + the `folder_id` is `123`. + :type id: str + :param name: The name of the folder., defaults to None + :type name: Optional[str], optional + :param etag: The HTTP `etag` of this folder. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the folder if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `folder`., defaults to FolderBaseTypeField.FOLDER + :type type: FolderBaseTypeField, optional + """ + super().__init__(id=id, etag=etag, type=type, **kwargs) + self.sequence_id = sequence_id + self.name = name diff --git a/box_sdk_gen/schemas/folder_reference.py b/box_sdk_gen/schemas/folder_reference.py new file mode 100644 index 000000000..a99575569 --- /dev/null +++ b/box_sdk_gen/schemas/folder_reference.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class FolderReferenceTypeField(str, Enum): + FOLDER = 'folder' + + +class FolderReference(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + id: str, + *, + type: FolderReferenceTypeField = FolderReferenceTypeField.FOLDER, + **kwargs + ): + """ + :param id: ID of the folder. + :type id: str + :param type: The value will always be `folder`., defaults to FolderReferenceTypeField.FOLDER + :type type: FolderReferenceTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/generic_source.py b/box_sdk_gen/schemas/generic_source.py new file mode 100644 index 000000000..e476ab914 --- /dev/null +++ b/box_sdk_gen/schemas/generic_source.py @@ -0,0 +1 @@ +from box_sdk_gen.box.errors import BoxSDKError diff --git a/box_sdk_gen/schemas/group.py b/box_sdk_gen/schemas/group.py new file mode 100644 index 000000000..fbb2fdc88 --- /dev/null +++ b/box_sdk_gen/schemas/group.py @@ -0,0 +1,44 @@ +from typing import Optional + +from box_sdk_gen.schemas.group_base import GroupBaseTypeField + +from box_sdk_gen.schemas.group_base import GroupBase + +from box_sdk_gen.schemas.group_mini import GroupMiniGroupTypeField + +from box_sdk_gen.schemas.group_mini import GroupMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class Group(GroupMini): + def __init__( + self, + id: str, + *, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + name: Optional[str] = None, + group_type: Optional[GroupMiniGroupTypeField] = None, + type: GroupBaseTypeField = GroupBaseTypeField.GROUP, + **kwargs + ): + """ + :param id: The unique identifier for this object. + :type id: str + :param created_at: When the group object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the group object was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param name: The name of the group., defaults to None + :type name: Optional[str], optional + :param group_type: The type of the group., defaults to None + :type group_type: Optional[GroupMiniGroupTypeField], optional + :param type: The value will always be `group`., defaults to GroupBaseTypeField.GROUP + :type type: GroupBaseTypeField, optional + """ + super().__init__(id=id, name=name, group_type=group_type, type=type, **kwargs) + self.created_at = created_at + self.modified_at = modified_at diff --git a/box_sdk_gen/schemas/group_base.py b/box_sdk_gen/schemas/group_base.py new file mode 100644 index 000000000..38cf63cd1 --- /dev/null +++ b/box_sdk_gen/schemas/group_base.py @@ -0,0 +1,26 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class GroupBaseTypeField(str, Enum): + GROUP = 'group' + + +class GroupBase(BaseObject): + _discriminator = 'type', {'group'} + + def __init__( + self, id: str, *, type: GroupBaseTypeField = GroupBaseTypeField.GROUP, **kwargs + ): + """ + :param id: The unique identifier for this object. + :type id: str + :param type: The value will always be `group`., defaults to GroupBaseTypeField.GROUP + :type type: GroupBaseTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/group_full.py b/box_sdk_gen/schemas/group_full.py new file mode 100644 index 000000000..c0503abfa --- /dev/null +++ b/box_sdk_gen/schemas/group_full.py @@ -0,0 +1,128 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.group_base import GroupBaseTypeField + +from box_sdk_gen.schemas.group_base import GroupBase + +from box_sdk_gen.schemas.group_mini import GroupMiniGroupTypeField + +from box_sdk_gen.schemas.group_mini import GroupMini + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.group import Group + +from box_sdk_gen.box.errors import BoxSDKError + + +class GroupFullInvitabilityLevelField(str, Enum): + ADMINS_ONLY = 'admins_only' + ADMINS_AND_MEMBERS = 'admins_and_members' + ALL_MANAGED_USERS = 'all_managed_users' + + +class GroupFullMemberViewabilityLevelField(str, Enum): + ADMINS_ONLY = 'admins_only' + ADMINS_AND_MEMBERS = 'admins_and_members' + ALL_MANAGED_USERS = 'all_managed_users' + + +class GroupFullPermissionsField(BaseObject): + def __init__(self, *, can_invite_as_collaborator: Optional[bool] = None, **kwargs): + """ + :param can_invite_as_collaborator: Specifies if the user can invite the group to collaborate on any items., defaults to None + :type can_invite_as_collaborator: Optional[bool], optional + """ + super().__init__(**kwargs) + self.can_invite_as_collaborator = can_invite_as_collaborator + + +class GroupFull(Group): + def __init__( + self, + id: str, + *, + provenance: Optional[str] = None, + external_sync_identifier: Optional[str] = None, + description: Optional[str] = None, + invitability_level: Optional[GroupFullInvitabilityLevelField] = None, + member_viewability_level: Optional[GroupFullMemberViewabilityLevelField] = None, + permissions: Optional[GroupFullPermissionsField] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + name: Optional[str] = None, + group_type: Optional[GroupMiniGroupTypeField] = None, + type: GroupBaseTypeField = GroupBaseTypeField.GROUP, + **kwargs + ): + """ + :param id: The unique identifier for this object. + :type id: str + :param provenance: Keeps track of which external source this group is + coming from (e.g. "Active Directory", "Google Groups", + "Facebook Groups"). Setting this will + also prevent Box users from editing the group name + and its members directly via the Box web application. + This is desirable for one-way syncing of groups., defaults to None + :type provenance: Optional[str], optional + :param external_sync_identifier: An arbitrary identifier that can be used by + external group sync tools to link this Box Group to + an external group. Example values of this field + could be an Active Directory Object ID or a Google + Group ID. We recommend you use of this field in + order to avoid issues when group names are updated in + either Box or external systems., defaults to None + :type external_sync_identifier: Optional[str], optional + :param description: Human readable description of the group., defaults to None + :type description: Optional[str], optional + :param invitability_level: Specifies who can invite the group to collaborate + on items. + + When set to `admins_only` the enterprise admin, co-admins, + and the group's admin can invite the group. + + When set to `admins_and_members` all the admins listed + above and group members can invite the group. + + When set to `all_managed_users` all managed users in the + enterprise can invite the group., defaults to None + :type invitability_level: Optional[GroupFullInvitabilityLevelField], optional + :param member_viewability_level: Specifies who can view the members of the group + (Get Memberships for Group). + + * `admins_only` - the enterprise admin, co-admins, group's + group admin. + * `admins_and_members` - all admins and group members. + * `all_managed_users` - all managed users in the + enterprise., defaults to None + :type member_viewability_level: Optional[GroupFullMemberViewabilityLevelField], optional + :param created_at: When the group object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the group object was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param name: The name of the group., defaults to None + :type name: Optional[str], optional + :param group_type: The type of the group., defaults to None + :type group_type: Optional[GroupMiniGroupTypeField], optional + :param type: The value will always be `group`., defaults to GroupBaseTypeField.GROUP + :type type: GroupBaseTypeField, optional + """ + super().__init__( + id=id, + created_at=created_at, + modified_at=modified_at, + name=name, + group_type=group_type, + type=type, + **kwargs + ) + self.provenance = provenance + self.external_sync_identifier = external_sync_identifier + self.description = description + self.invitability_level = invitability_level + self.member_viewability_level = member_viewability_level + self.permissions = permissions diff --git a/box_sdk_gen/schemas/group_membership.py b/box_sdk_gen/schemas/group_membership.py new file mode 100644 index 000000000..48cc01ea6 --- /dev/null +++ b/box_sdk_gen/schemas/group_membership.py @@ -0,0 +1,59 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.group_mini import GroupMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class GroupMembershipTypeField(str, Enum): + GROUP_MEMBERSHIP = 'group_membership' + + +class GroupMembershipRoleField(str, Enum): + MEMBER = 'member' + ADMIN = 'admin' + + +class GroupMembership(BaseObject): + _discriminator = 'type', {'group_membership'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[GroupMembershipTypeField] = None, + user: Optional[UserMini] = None, + group: Optional[GroupMini] = None, + role: Optional[GroupMembershipRoleField] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param id: The unique identifier for this group membership., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `group_membership`., defaults to None + :type type: Optional[GroupMembershipTypeField], optional + :param role: The role of the user in the group., defaults to None + :type role: Optional[GroupMembershipRoleField], optional + :param created_at: The time this membership was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The time this membership was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.user = user + self.group = group + self.role = role + self.created_at = created_at + self.modified_at = modified_at diff --git a/box_sdk_gen/schemas/group_memberships.py b/box_sdk_gen/schemas/group_memberships.py new file mode 100644 index 000000000..a5b402504 --- /dev/null +++ b/box_sdk_gen/schemas/group_memberships.py @@ -0,0 +1,80 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.group_membership import GroupMembership + +from box_sdk_gen.box.errors import BoxSDKError + + +class GroupMembershipsOrderDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class GroupMembershipsOrderField(BaseObject): + def __init__( + self, + *, + by: Optional[str] = None, + direction: Optional[GroupMembershipsOrderDirectionField] = None, + **kwargs + ): + """ + :param by: The field to order by., defaults to None + :type by: Optional[str], optional + :param direction: The direction to order by, either ascending or descending., defaults to None + :type direction: Optional[GroupMembershipsOrderDirectionField], optional + """ + super().__init__(**kwargs) + self.by = by + self.direction = direction + + +class GroupMemberships(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + order: Optional[List[GroupMembershipsOrderField]] = None, + entries: Optional[List[GroupMembership]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type total_count: Optional[int], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type offset: Optional[int], optional + :param order: The order by which items are returned. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type order: Optional[List[GroupMembershipsOrderField]], optional + :param entries: A list of group memberships., defaults to None + :type entries: Optional[List[GroupMembership]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.limit = limit + self.offset = offset + self.order = order + self.entries = entries diff --git a/box_sdk_gen/schemas/group_mini.py b/box_sdk_gen/schemas/group_mini.py new file mode 100644 index 000000000..a05571868 --- /dev/null +++ b/box_sdk_gen/schemas/group_mini.py @@ -0,0 +1,39 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.group_base import GroupBaseTypeField + +from box_sdk_gen.schemas.group_base import GroupBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class GroupMiniGroupTypeField(str, Enum): + MANAGED_GROUP = 'managed_group' + ALL_USERS_GROUP = 'all_users_group' + + +class GroupMini(GroupBase): + def __init__( + self, + id: str, + *, + name: Optional[str] = None, + group_type: Optional[GroupMiniGroupTypeField] = None, + type: GroupBaseTypeField = GroupBaseTypeField.GROUP, + **kwargs + ): + """ + :param id: The unique identifier for this object. + :type id: str + :param name: The name of the group., defaults to None + :type name: Optional[str], optional + :param group_type: The type of the group., defaults to None + :type group_type: Optional[GroupMiniGroupTypeField], optional + :param type: The value will always be `group`., defaults to GroupBaseTypeField.GROUP + :type type: GroupBaseTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.name = name + self.group_type = group_type diff --git a/box_sdk_gen/schemas/groups.py b/box_sdk_gen/schemas/groups.py new file mode 100644 index 000000000..76f8a6c13 --- /dev/null +++ b/box_sdk_gen/schemas/groups.py @@ -0,0 +1,80 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.group_full import GroupFull + +from box_sdk_gen.box.errors import BoxSDKError + + +class GroupsOrderDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class GroupsOrderField(BaseObject): + def __init__( + self, + *, + by: Optional[str] = None, + direction: Optional[GroupsOrderDirectionField] = None, + **kwargs + ): + """ + :param by: The field to order by., defaults to None + :type by: Optional[str], optional + :param direction: The direction to order by, either ascending or descending., defaults to None + :type direction: Optional[GroupsOrderDirectionField], optional + """ + super().__init__(**kwargs) + self.by = by + self.direction = direction + + +class Groups(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + order: Optional[List[GroupsOrderField]] = None, + entries: Optional[List[GroupFull]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type total_count: Optional[int], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type offset: Optional[int], optional + :param order: The order by which items are returned. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type order: Optional[List[GroupsOrderField]], optional + :param entries: A list of groups., defaults to None + :type entries: Optional[List[GroupFull]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.limit = limit + self.offset = offset + self.order = order + self.entries = entries diff --git a/box_sdk_gen/schemas/integration_mapping.py b/box_sdk_gen/schemas/integration_mapping.py new file mode 100644 index 000000000..6c2df8e31 --- /dev/null +++ b/box_sdk_gen/schemas/integration_mapping.py @@ -0,0 +1,90 @@ +from enum import Enum + +from typing import Optional + +from typing import Union + +from box_sdk_gen.schemas.integration_mapping_base import IntegrationMappingBaseTypeField + +from box_sdk_gen.schemas.integration_mapping_base import IntegrationMappingBase + +from box_sdk_gen.schemas.integration_mapping_slack_options import ( + IntegrationMappingSlackOptions, +) + +from box_sdk_gen.schemas.user_integration_mappings import UserIntegrationMappings + +from box_sdk_gen.schemas.integration_mapping_partner_item_slack import ( + IntegrationMappingPartnerItemSlack, +) + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class IntegrationMappingIntegrationTypeField(str, Enum): + SLACK = 'slack' + + +class IntegrationMapping(IntegrationMappingBase): + def __init__( + self, + partner_item: Union[IntegrationMappingPartnerItemSlack], + box_item: FolderMini, + id: str, + *, + integration_type: Optional[IntegrationMappingIntegrationTypeField] = None, + is_manually_created: Optional[bool] = None, + options: Optional[IntegrationMappingSlackOptions] = None, + created_by: Optional[UserIntegrationMappings] = None, + modified_by: Optional[UserIntegrationMappings] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + type: IntegrationMappingBaseTypeField = IntegrationMappingBaseTypeField.INTEGRATION_MAPPING, + **kwargs + ): + """ + :param partner_item: Mapped item object for Slack. + :type partner_item: Union[IntegrationMappingPartnerItemSlack] + :param box_item: The Box folder, to which the object from the + partner app domain (referenced in `partner_item_id`) is mapped. + :type box_item: FolderMini + :param id: A unique identifier of a folder mapping + (part of a composite key together + with `integration_type`). + :type id: str + :param integration_type: Identifies the Box partner app, + with which the mapping is associated. + Currently only supports Slack. + (part of the composite key together with `id`)., defaults to None + :type integration_type: Optional[IntegrationMappingIntegrationTypeField], optional + :param is_manually_created: Identifies whether the mapping has + been manually set + (as opposed to being automatically created)., defaults to None + :type is_manually_created: Optional[bool], optional + :param created_by: An object representing the user who + created the integration mapping., defaults to None + :type created_by: Optional[UserIntegrationMappings], optional + :param modified_by: The user who + last modified the integration mapping., defaults to None + :type modified_by: Optional[UserIntegrationMappings], optional + :param created_at: When the integration mapping object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the integration mapping object was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param type: Mapping type., defaults to IntegrationMappingBaseTypeField.INTEGRATION_MAPPING + :type type: IntegrationMappingBaseTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.partner_item = partner_item + self.box_item = box_item + self.integration_type = integration_type + self.is_manually_created = is_manually_created + self.options = options + self.created_by = created_by + self.modified_by = modified_by + self.created_at = created_at + self.modified_at = modified_at diff --git a/box_sdk_gen/schemas/integration_mapping_base.py b/box_sdk_gen/schemas/integration_mapping_base.py new file mode 100644 index 000000000..eb53aadb0 --- /dev/null +++ b/box_sdk_gen/schemas/integration_mapping_base.py @@ -0,0 +1,32 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class IntegrationMappingBaseTypeField(str, Enum): + INTEGRATION_MAPPING = 'integration_mapping' + + +class IntegrationMappingBase(BaseObject): + _discriminator = 'type', {'integration_mapping'} + + def __init__( + self, + id: str, + *, + type: IntegrationMappingBaseTypeField = IntegrationMappingBaseTypeField.INTEGRATION_MAPPING, + **kwargs + ): + """ + :param id: A unique identifier of a folder mapping + (part of a composite key together + with `integration_type`). + :type id: str + :param type: Mapping type., defaults to IntegrationMappingBaseTypeField.INTEGRATION_MAPPING + :type type: IntegrationMappingBaseTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/integration_mapping_box_item_slack.py b/box_sdk_gen/schemas/integration_mapping_box_item_slack.py new file mode 100644 index 000000000..427835d3a --- /dev/null +++ b/box_sdk_gen/schemas/integration_mapping_box_item_slack.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class IntegrationMappingBoxItemSlackTypeField(str, Enum): + FOLDER = 'folder' + + +class IntegrationMappingBoxItemSlack(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + id: str, + *, + type: IntegrationMappingBoxItemSlackTypeField = IntegrationMappingBoxItemSlackTypeField.FOLDER, + **kwargs + ): + """ + :param id: ID of the mapped item (of type referenced in `type`). + :type id: str + :param type: Type of the mapped item referenced in `id`., defaults to IntegrationMappingBoxItemSlackTypeField.FOLDER + :type type: IntegrationMappingBoxItemSlackTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/integration_mapping_partner_item_slack.py b/box_sdk_gen/schemas/integration_mapping_partner_item_slack.py new file mode 100644 index 000000000..5d0731166 --- /dev/null +++ b/box_sdk_gen/schemas/integration_mapping_partner_item_slack.py @@ -0,0 +1,40 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class IntegrationMappingPartnerItemSlackTypeField(str, Enum): + CHANNEL = 'channel' + + +class IntegrationMappingPartnerItemSlack(BaseObject): + _discriminator = 'type', {'channel'} + + def __init__( + self, + id: str, + *, + type: IntegrationMappingPartnerItemSlackTypeField = IntegrationMappingPartnerItemSlackTypeField.CHANNEL, + slack_workspace_id: Optional[str] = None, + slack_org_id: Optional[str] = None, + **kwargs + ): + """ + :param id: ID of the mapped item (of type referenced in `type`). + :type id: str + :param type: Type of the mapped item referenced in `id`., defaults to IntegrationMappingPartnerItemSlackTypeField.CHANNEL + :type type: IntegrationMappingPartnerItemSlackTypeField, optional + :param slack_workspace_id: ID of the Slack workspace with which the item is associated. Use this parameter if Box for Slack is installed at a workspace level. Do not use `slack_org_id` at the same time., defaults to None + :type slack_workspace_id: Optional[str], optional + :param slack_org_id: ID of the Slack org with which the item is associated. Use this parameter if Box for Slack is installed at the org level. Do not use `slack_workspace_id` at the same time., defaults to None + :type slack_org_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.slack_workspace_id = slack_workspace_id + self.slack_org_id = slack_org_id diff --git a/box_sdk_gen/schemas/integration_mapping_partner_item_teams.py b/box_sdk_gen/schemas/integration_mapping_partner_item_teams.py new file mode 100644 index 000000000..4d5c0918a --- /dev/null +++ b/box_sdk_gen/schemas/integration_mapping_partner_item_teams.py @@ -0,0 +1,34 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class IntegrationMappingPartnerItemTeamsTypeField(str, Enum): + CHANNEL = 'channel' + TEAM = 'team' + + +class IntegrationMappingPartnerItemTeams(BaseObject): + _discriminator = 'type', {'channel', 'team'} + + def __init__( + self, + type: IntegrationMappingPartnerItemTeamsTypeField, + id: str, + tenant_id: str, + **kwargs + ): + """ + :param type: Type of the mapped item referenced in `id`. + :type type: IntegrationMappingPartnerItemTeamsTypeField + :param id: ID of the mapped item (of type referenced in `type`). + :type id: str + :param tenant_id: ID of the tenant that is registered with Microsoft Teams. + :type tenant_id: str + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.tenant_id = tenant_id diff --git a/box_sdk_gen/schemas/integration_mapping_partner_item_teams_create_request.py b/box_sdk_gen/schemas/integration_mapping_partner_item_teams_create_request.py new file mode 100644 index 000000000..f6f10fc43 --- /dev/null +++ b/box_sdk_gen/schemas/integration_mapping_partner_item_teams_create_request.py @@ -0,0 +1,38 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class IntegrationMappingPartnerItemTeamsCreateRequestTypeField(str, Enum): + CHANNEL = 'channel' + TEAM = 'team' + + +class IntegrationMappingPartnerItemTeamsCreateRequest(BaseObject): + _discriminator = 'type', {'channel', 'team'} + + def __init__( + self, + type: IntegrationMappingPartnerItemTeamsCreateRequestTypeField, + id: str, + tenant_id: str, + team_id: str, + **kwargs + ): + """ + :param type: Type of the mapped item referenced in `id`. + :type type: IntegrationMappingPartnerItemTeamsCreateRequestTypeField + :param id: ID of the mapped item (of type referenced in `type`). + :type id: str + :param tenant_id: ID of the tenant that is registered with Microsoft Teams. + :type tenant_id: str + :param team_id: ID of the team that is registered with Microsoft Teams. + :type team_id: str + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.tenant_id = tenant_id + self.team_id = team_id diff --git a/box_sdk_gen/schemas/integration_mapping_slack_create_request.py b/box_sdk_gen/schemas/integration_mapping_slack_create_request.py new file mode 100644 index 000000000..85586fe6b --- /dev/null +++ b/box_sdk_gen/schemas/integration_mapping_slack_create_request.py @@ -0,0 +1,32 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.integration_mapping_partner_item_slack import ( + IntegrationMappingPartnerItemSlack, +) + +from box_sdk_gen.schemas.integration_mapping_box_item_slack import ( + IntegrationMappingBoxItemSlack, +) + +from box_sdk_gen.schemas.integration_mapping_slack_options import ( + IntegrationMappingSlackOptions, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class IntegrationMappingSlackCreateRequest(BaseObject): + def __init__( + self, + partner_item: IntegrationMappingPartnerItemSlack, + box_item: IntegrationMappingBoxItemSlack, + *, + options: Optional[IntegrationMappingSlackOptions] = None, + **kwargs + ): + super().__init__(**kwargs) + self.partner_item = partner_item + self.box_item = box_item + self.options = options diff --git a/box_sdk_gen/schemas/integration_mapping_slack_options.py b/box_sdk_gen/schemas/integration_mapping_slack_options.py new file mode 100644 index 000000000..701d27a82 --- /dev/null +++ b/box_sdk_gen/schemas/integration_mapping_slack_options.py @@ -0,0 +1,21 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class IntegrationMappingSlackOptions(BaseObject): + def __init__( + self, *, is_access_management_disabled: Optional[bool] = None, **kwargs + ): + """ + :param is_access_management_disabled: Indicates whether or not channel member + access to the underlying box item + should be automatically managed. + Depending on type of channel, access is managed + through creating collaborations or shared links., defaults to None + :type is_access_management_disabled: Optional[bool], optional + """ + super().__init__(**kwargs) + self.is_access_management_disabled = is_access_management_disabled diff --git a/box_sdk_gen/schemas/integration_mapping_teams.py b/box_sdk_gen/schemas/integration_mapping_teams.py new file mode 100644 index 000000000..f967e4fd3 --- /dev/null +++ b/box_sdk_gen/schemas/integration_mapping_teams.py @@ -0,0 +1,69 @@ +from enum import Enum + +from typing import Optional + +from typing import Union + +from box_sdk_gen.schemas.integration_mapping_base import IntegrationMappingBaseTypeField + +from box_sdk_gen.schemas.integration_mapping_base import IntegrationMappingBase + +from box_sdk_gen.schemas.integration_mapping_partner_item_teams import ( + IntegrationMappingPartnerItemTeams, +) + +from box_sdk_gen.schemas.folder_reference import FolderReference + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class IntegrationMappingTeamsIntegrationTypeField(str, Enum): + TEAMS = 'teams' + + +class IntegrationMappingTeams(IntegrationMappingBase): + def __init__( + self, + partner_item: Union[IntegrationMappingPartnerItemTeams], + box_item: FolderReference, + id: str, + *, + integration_type: Optional[IntegrationMappingTeamsIntegrationTypeField] = None, + is_overridden_by_manual_mapping: Optional[bool] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + type: IntegrationMappingBaseTypeField = IntegrationMappingBaseTypeField.INTEGRATION_MAPPING, + **kwargs + ): + """ + :param partner_item: Mapped item object for Teams. + :type partner_item: Union[IntegrationMappingPartnerItemTeams] + :param id: A unique identifier of a folder mapping + (part of a composite key together + with `integration_type`). + :type id: str + :param integration_type: Identifies the Box partner app, + with which the mapping is associated. + Supports Slack and Teams. + (part of the composite key together with `id`)., defaults to None + :type integration_type: Optional[IntegrationMappingTeamsIntegrationTypeField], optional + :param is_overridden_by_manual_mapping: Identifies whether the mapping has + been manually set by the team owner from UI for channels + (as opposed to being automatically created)., defaults to None + :type is_overridden_by_manual_mapping: Optional[bool], optional + :param created_at: When the integration mapping object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the integration mapping object was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param type: Mapping type., defaults to IntegrationMappingBaseTypeField.INTEGRATION_MAPPING + :type type: IntegrationMappingBaseTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.partner_item = partner_item + self.box_item = box_item + self.integration_type = integration_type + self.is_overridden_by_manual_mapping = is_overridden_by_manual_mapping + self.created_at = created_at + self.modified_at = modified_at diff --git a/box_sdk_gen/schemas/integration_mapping_teams_create_request.py b/box_sdk_gen/schemas/integration_mapping_teams_create_request.py new file mode 100644 index 000000000..2f615a43d --- /dev/null +++ b/box_sdk_gen/schemas/integration_mapping_teams_create_request.py @@ -0,0 +1,21 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.integration_mapping_partner_item_teams_create_request import ( + IntegrationMappingPartnerItemTeamsCreateRequest, +) + +from box_sdk_gen.schemas.folder_reference import FolderReference + +from box_sdk_gen.box.errors import BoxSDKError + + +class IntegrationMappingTeamsCreateRequest(BaseObject): + def __init__( + self, + partner_item: IntegrationMappingPartnerItemTeamsCreateRequest, + box_item: FolderReference, + **kwargs + ): + super().__init__(**kwargs) + self.partner_item = partner_item + self.box_item = box_item diff --git a/box_sdk_gen/schemas/integration_mappings.py b/box_sdk_gen/schemas/integration_mappings.py new file mode 100644 index 000000000..638165d7e --- /dev/null +++ b/box_sdk_gen/schemas/integration_mappings.py @@ -0,0 +1,34 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.integration_mapping import IntegrationMapping + +from box_sdk_gen.box.errors import BoxSDKError + + +class IntegrationMappings(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + entries: Optional[List[IntegrationMapping]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param entries: A list of integration mappings., defaults to None + :type entries: Optional[List[IntegrationMapping]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/integration_mappings_teams.py b/box_sdk_gen/schemas/integration_mappings_teams.py new file mode 100644 index 000000000..f727ff6c2 --- /dev/null +++ b/box_sdk_gen/schemas/integration_mappings_teams.py @@ -0,0 +1,21 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.integration_mapping_teams import IntegrationMappingTeams + +from box_sdk_gen.box.errors import BoxSDKError + + +class IntegrationMappingsTeams(BaseObject): + def __init__( + self, *, entries: Optional[List[IntegrationMappingTeams]] = None, **kwargs + ): + """ + :param entries: A list of integration mappings., defaults to None + :type entries: Optional[List[IntegrationMappingTeams]], optional + """ + super().__init__(**kwargs) + self.entries = entries diff --git a/box_sdk_gen/schemas/invite.py b/box_sdk_gen/schemas/invite.py new file mode 100644 index 000000000..28487d988 --- /dev/null +++ b/box_sdk_gen/schemas/invite.py @@ -0,0 +1,85 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class InviteTypeField(str, Enum): + INVITE = 'invite' + + +class InviteInvitedToTypeField(str, Enum): + ENTERPRISE = 'enterprise' + + +class InviteInvitedToField(BaseObject): + _discriminator = 'type', {'enterprise'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[InviteInvitedToTypeField] = None, + name: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this enterprise., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `enterprise`., defaults to None + :type type: Optional[InviteInvitedToTypeField], optional + :param name: The name of the enterprise., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + + +class Invite(BaseObject): + _discriminator = 'type', {'invite'} + + def __init__( + self, + id: str, + *, + type: InviteTypeField = InviteTypeField.INVITE, + invited_to: Optional[InviteInvitedToField] = None, + actionable_by: Optional[UserMini] = None, + invited_by: Optional[UserMini] = None, + status: Optional[str] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param id: The unique identifier for this invite. + :type id: str + :param type: The value will always be `invite`., defaults to InviteTypeField.INVITE + :type type: InviteTypeField, optional + :param invited_to: A representation of a Box enterprise., defaults to None + :type invited_to: Optional[InviteInvitedToField], optional + :param status: The status of the invite., defaults to None + :type status: Optional[str], optional + :param created_at: When the invite was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the invite was modified., defaults to None + :type modified_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.invited_to = invited_to + self.actionable_by = actionable_by + self.invited_by = invited_by + self.status = status + self.created_at = created_at + self.modified_at = modified_at diff --git a/box_sdk_gen/schemas/items.py b/box_sdk_gen/schemas/items.py new file mode 100644 index 000000000..9dc24c0f8 --- /dev/null +++ b/box_sdk_gen/schemas/items.py @@ -0,0 +1,94 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Union + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + + +class ItemsOrderDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class ItemsOrderField(BaseObject): + def __init__( + self, + *, + by: Optional[str] = None, + direction: Optional[ItemsOrderDirectionField] = None, + **kwargs + ): + """ + :param by: The field to order by., defaults to None + :type by: Optional[str], optional + :param direction: The direction to order by, either ascending or descending., defaults to None + :type direction: Optional[ItemsOrderDirectionField], optional + """ + super().__init__(**kwargs) + self.by = by + self.direction = direction + + +class Items(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + total_count: Optional[int] = None, + offset: Optional[int] = None, + order: Optional[List[ItemsOrderField]] = None, + entries: Optional[List[Union[FileFull, FolderMini, WebLink]]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type total_count: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type offset: Optional[int], optional + :param order: The order by which items are returned. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type order: Optional[List[ItemsOrderField]], optional + :param entries: The items in this collection., defaults to None + :type entries: Optional[List[Union[FileFull, FolderMini, WebLink]]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.total_count = total_count + self.offset = offset + self.order = order + self.entries = entries diff --git a/box_sdk_gen/schemas/items_offset_paginated.py b/box_sdk_gen/schemas/items_offset_paginated.py new file mode 100644 index 000000000..a360480a6 --- /dev/null +++ b/box_sdk_gen/schemas/items_offset_paginated.py @@ -0,0 +1,86 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Union + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + + +class ItemsOffsetPaginatedOrderDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class ItemsOffsetPaginatedOrderField(BaseObject): + def __init__( + self, + *, + by: Optional[str] = None, + direction: Optional[ItemsOffsetPaginatedOrderDirectionField] = None, + **kwargs + ): + """ + :param by: The field to order by., defaults to None + :type by: Optional[str], optional + :param direction: The direction to order by, either ascending or descending., defaults to None + :type direction: Optional[ItemsOffsetPaginatedOrderDirectionField], optional + """ + super().__init__(**kwargs) + self.by = by + self.direction = direction + + +class ItemsOffsetPaginated(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + order: Optional[List[ItemsOffsetPaginatedOrderField]] = None, + entries: Optional[List[Union[FileFull, FolderMini, WebLink]]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type total_count: Optional[int], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type offset: Optional[int], optional + :param order: The order by which items are returned. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type order: Optional[List[ItemsOffsetPaginatedOrderField]], optional + :param entries: The items in this collection., defaults to None + :type entries: Optional[List[Union[FileFull, FolderMini, WebLink]]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.limit = limit + self.offset = offset + self.order = order + self.entries = entries diff --git a/box_sdk_gen/schemas/keyword_skill_card.py b/box_sdk_gen/schemas/keyword_skill_card.py new file mode 100644 index 000000000..ba6cf2cc7 --- /dev/null +++ b/box_sdk_gen/schemas/keyword_skill_card.py @@ -0,0 +1,139 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class KeywordSkillCardTypeField(str, Enum): + SKILL_CARD = 'skill_card' + + +class KeywordSkillCardSkillCardTypeField(str, Enum): + KEYWORD = 'keyword' + + +class KeywordSkillCardSkillCardTitleField(BaseObject): + def __init__(self, message: str, *, code: Optional[str] = None, **kwargs): + """ + :param message: The actual title to show in the UI. + :type message: str + :param code: An optional identifier for the title., defaults to None + :type code: Optional[str], optional + """ + super().__init__(**kwargs) + self.message = message + self.code = code + + +class KeywordSkillCardSkillTypeField(str, Enum): + SERVICE = 'service' + + +class KeywordSkillCardSkillField(BaseObject): + _discriminator = 'type', {'service'} + + def __init__( + self, + id: str, + *, + type: KeywordSkillCardSkillTypeField = KeywordSkillCardSkillTypeField.SERVICE, + **kwargs + ): + """ + :param id: A custom identifier that represent the service that + applied this metadata. + :type id: str + :param type: The value will always be `service`., defaults to KeywordSkillCardSkillTypeField.SERVICE + :type type: KeywordSkillCardSkillTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class KeywordSkillCardInvocationTypeField(str, Enum): + SKILL_INVOCATION = 'skill_invocation' + + +class KeywordSkillCardInvocationField(BaseObject): + _discriminator = 'type', {'skill_invocation'} + + def __init__( + self, + id: str, + *, + type: KeywordSkillCardInvocationTypeField = KeywordSkillCardInvocationTypeField.SKILL_INVOCATION, + **kwargs + ): + """ + :param id: A custom identifier that represent the instance of + the service that applied this metadata. For example, + if your `image-recognition-service` runs on multiple + nodes, this field can be used to identify the ID of + the node that was used to apply the metadata. + :type id: str + :param type: The value will always be `skill_invocation`., defaults to KeywordSkillCardInvocationTypeField.SKILL_INVOCATION + :type type: KeywordSkillCardInvocationTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class KeywordSkillCardEntriesField(BaseObject): + def __init__(self, *, text: Optional[str] = None, **kwargs): + """ + :param text: The text of the keyword., defaults to None + :type text: Optional[str], optional + """ + super().__init__(**kwargs) + self.text = text + + +class KeywordSkillCard(BaseObject): + _discriminator = 'skill_card_type', {'keyword'} + + def __init__( + self, + skill: KeywordSkillCardSkillField, + invocation: KeywordSkillCardInvocationField, + entries: List[KeywordSkillCardEntriesField], + *, + created_at: Optional[DateTime] = None, + type: KeywordSkillCardTypeField = KeywordSkillCardTypeField.SKILL_CARD, + skill_card_type: KeywordSkillCardSkillCardTypeField = KeywordSkillCardSkillCardTypeField.KEYWORD, + skill_card_title: Optional[KeywordSkillCardSkillCardTitleField] = None, + **kwargs + ): + """ + :param skill: The service that applied this metadata. + :type skill: KeywordSkillCardSkillField + :param invocation: The invocation of this service, used to track + which instance of a service applied the metadata. + :type invocation: KeywordSkillCardInvocationField + :param entries: An list of entries in the metadata card. + :type entries: List[KeywordSkillCardEntriesField] + :param created_at: The optional date and time this card was created at., defaults to None + :type created_at: Optional[DateTime], optional + :param type: The value will always be `skill_card`., defaults to KeywordSkillCardTypeField.SKILL_CARD + :type type: KeywordSkillCardTypeField, optional + :param skill_card_type: The value will always be `keyword`., defaults to KeywordSkillCardSkillCardTypeField.KEYWORD + :type skill_card_type: KeywordSkillCardSkillCardTypeField, optional + :param skill_card_title: The title of the card., defaults to None + :type skill_card_title: Optional[KeywordSkillCardSkillCardTitleField], optional + """ + super().__init__(**kwargs) + self.skill = skill + self.invocation = invocation + self.entries = entries + self.created_at = created_at + self.type = type + self.skill_card_type = skill_card_type + self.skill_card_title = skill_card_title diff --git a/box_sdk_gen/schemas/legal_hold_policies.py b/box_sdk_gen/schemas/legal_hold_policies.py new file mode 100644 index 000000000..52c42f9d1 --- /dev/null +++ b/box_sdk_gen/schemas/legal_hold_policies.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.legal_hold_policy import LegalHoldPolicy + +from box_sdk_gen.box.errors import BoxSDKError + + +class LegalHoldPolicies(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[LegalHoldPolicy]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of legal hold policies., defaults to None + :type entries: Optional[List[LegalHoldPolicy]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/legal_hold_policy.py b/box_sdk_gen/schemas/legal_hold_policy.py new file mode 100644 index 000000000..5a91fa884 --- /dev/null +++ b/box_sdk_gen/schemas/legal_hold_policy.py @@ -0,0 +1,122 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.legal_hold_policy_mini import LegalHoldPolicyMiniTypeField + +from box_sdk_gen.schemas.legal_hold_policy_mini import LegalHoldPolicyMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class LegalHoldPolicyStatusField(str, Enum): + ACTIVE = 'active' + APPLYING = 'applying' + RELEASING = 'releasing' + RELEASED = 'released' + + +class LegalHoldPolicyAssignmentCountsField(BaseObject): + def __init__( + self, + *, + user: Optional[int] = None, + folder: Optional[int] = None, + file: Optional[int] = None, + file_version: Optional[int] = None, + **kwargs + ): + """ + :param user: The number of users this policy is applied to., defaults to None + :type user: Optional[int], optional + :param folder: The number of folders this policy is applied to., defaults to None + :type folder: Optional[int], optional + :param file: The number of files this policy is applied to., defaults to None + :type file: Optional[int], optional + :param file_version: The number of file versions this policy is applied to., defaults to None + :type file_version: Optional[int], optional + """ + super().__init__(**kwargs) + self.user = user + self.folder = folder + self.file = file + self.file_version = file_version + + +class LegalHoldPolicy(LegalHoldPolicyMini): + def __init__( + self, + id: str, + *, + policy_name: Optional[str] = None, + description: Optional[str] = None, + status: Optional[LegalHoldPolicyStatusField] = None, + assignment_counts: Optional[LegalHoldPolicyAssignmentCountsField] = None, + created_by: Optional[UserMini] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + deleted_at: Optional[DateTime] = None, + filter_started_at: Optional[DateTime] = None, + filter_ended_at: Optional[DateTime] = None, + release_notes: Optional[str] = None, + type: LegalHoldPolicyMiniTypeField = LegalHoldPolicyMiniTypeField.LEGAL_HOLD_POLICY, + **kwargs + ): + """ + :param id: The unique identifier for this legal hold policy. + :type id: str + :param policy_name: Name of the legal hold policy., defaults to None + :type policy_name: Optional[str], optional + :param description: Description of the legal hold policy. Optional + property with a 500 character limit., defaults to None + :type description: Optional[str], optional + :param status: Possible values: + * 'active' - the policy is not in a transition state. + * 'applying' - that the policy is in the process of + being applied. + * 'releasing' - that the process is in the process + of being released. + * 'released' - the policy is no longer active., defaults to None + :type status: Optional[LegalHoldPolicyStatusField], optional + :param assignment_counts: Counts of assignments within this a legal hold policy by item type., defaults to None + :type assignment_counts: Optional[LegalHoldPolicyAssignmentCountsField], optional + :param created_at: When the legal hold policy object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the legal hold policy object was modified. + Does not update when assignments are added or removed., defaults to None + :type modified_at: Optional[DateTime], optional + :param deleted_at: When the policy release request was sent. (Because + it can take time for a policy to fully delete, this + isn't quite the same time that the policy is fully deleted). + + If `null`, the policy was not deleted., defaults to None + :type deleted_at: Optional[DateTime], optional + :param filter_started_at: User-specified, optional date filter applies to + Custodian assignments only., defaults to None + :type filter_started_at: Optional[DateTime], optional + :param filter_ended_at: User-specified, optional date filter applies to + Custodian assignments only., defaults to None + :type filter_ended_at: Optional[DateTime], optional + :param release_notes: Optional notes about why the policy was created., defaults to None + :type release_notes: Optional[str], optional + :param type: The value will always be `legal_hold_policy`., defaults to LegalHoldPolicyMiniTypeField.LEGAL_HOLD_POLICY + :type type: LegalHoldPolicyMiniTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.policy_name = policy_name + self.description = description + self.status = status + self.assignment_counts = assignment_counts + self.created_by = created_by + self.created_at = created_at + self.modified_at = modified_at + self.deleted_at = deleted_at + self.filter_started_at = filter_started_at + self.filter_ended_at = filter_ended_at + self.release_notes = release_notes diff --git a/box_sdk_gen/schemas/legal_hold_policy_assignment.py b/box_sdk_gen/schemas/legal_hold_policy_assignment.py new file mode 100644 index 000000000..ec38983ab --- /dev/null +++ b/box_sdk_gen/schemas/legal_hold_policy_assignment.py @@ -0,0 +1,61 @@ +from typing import Optional + +from typing import Union + +from box_sdk_gen.schemas.legal_hold_policy_assignment_base import ( + LegalHoldPolicyAssignmentBaseTypeField, +) + +from box_sdk_gen.schemas.legal_hold_policy_assignment_base import ( + LegalHoldPolicyAssignmentBase, +) + +from box_sdk_gen.schemas.legal_hold_policy_mini import LegalHoldPolicyMini + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class LegalHoldPolicyAssignment(LegalHoldPolicyAssignmentBase): + def __init__( + self, + *, + legal_hold_policy: Optional[LegalHoldPolicyMini] = None, + assigned_to: Optional[Union[File, Folder, WebLink]] = None, + assigned_by: Optional[UserMini] = None, + assigned_at: Optional[DateTime] = None, + deleted_at: Optional[DateTime] = None, + id: Optional[str] = None, + type: Optional[LegalHoldPolicyAssignmentBaseTypeField] = None, + **kwargs + ): + """ + :param assigned_at: When the legal hold policy assignment object was + created., defaults to None + :type assigned_at: Optional[DateTime], optional + :param deleted_at: When the assignment release request was sent. + (Because it can take time for an assignment to fully + delete, this isn't quite the same time that the + assignment is fully deleted). If null, Assignment + was not deleted., defaults to None + :type deleted_at: Optional[DateTime], optional + :param id: The unique identifier for this legal hold assignment., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `legal_hold_policy_assignment`., defaults to None + :type type: Optional[LegalHoldPolicyAssignmentBaseTypeField], optional + """ + super().__init__(id=id, type=type, **kwargs) + self.legal_hold_policy = legal_hold_policy + self.assigned_to = assigned_to + self.assigned_by = assigned_by + self.assigned_at = assigned_at + self.deleted_at = deleted_at diff --git a/box_sdk_gen/schemas/legal_hold_policy_assignment_base.py b/box_sdk_gen/schemas/legal_hold_policy_assignment_base.py new file mode 100644 index 000000000..2e1897da7 --- /dev/null +++ b/box_sdk_gen/schemas/legal_hold_policy_assignment_base.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class LegalHoldPolicyAssignmentBaseTypeField(str, Enum): + LEGAL_HOLD_POLICY_ASSIGNMENT = 'legal_hold_policy_assignment' + + +class LegalHoldPolicyAssignmentBase(BaseObject): + _discriminator = 'type', {'legal_hold_policy_assignment'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[LegalHoldPolicyAssignmentBaseTypeField] = None, + **kwargs + ): + """ + :param id: The unique identifier for this legal hold assignment., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `legal_hold_policy_assignment`., defaults to None + :type type: Optional[LegalHoldPolicyAssignmentBaseTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/legal_hold_policy_assignments.py b/box_sdk_gen/schemas/legal_hold_policy_assignments.py new file mode 100644 index 000000000..d66c7de7f --- /dev/null +++ b/box_sdk_gen/schemas/legal_hold_policy_assignments.py @@ -0,0 +1,39 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.legal_hold_policy_assignment import LegalHoldPolicyAssignment + +from box_sdk_gen.box.errors import BoxSDKError + + +class LegalHoldPolicyAssignments(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[LegalHoldPolicyAssignment]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of legal hold + policy assignments., defaults to None + :type entries: Optional[List[LegalHoldPolicyAssignment]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/legal_hold_policy_mini.py b/box_sdk_gen/schemas/legal_hold_policy_mini.py new file mode 100644 index 000000000..e2a82a406 --- /dev/null +++ b/box_sdk_gen/schemas/legal_hold_policy_mini.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class LegalHoldPolicyMiniTypeField(str, Enum): + LEGAL_HOLD_POLICY = 'legal_hold_policy' + + +class LegalHoldPolicyMini(BaseObject): + _discriminator = 'type', {'legal_hold_policy'} + + def __init__( + self, + id: str, + *, + type: LegalHoldPolicyMiniTypeField = LegalHoldPolicyMiniTypeField.LEGAL_HOLD_POLICY, + **kwargs + ): + """ + :param id: The unique identifier for this legal hold policy. + :type id: str + :param type: The value will always be `legal_hold_policy`., defaults to LegalHoldPolicyMiniTypeField.LEGAL_HOLD_POLICY + :type type: LegalHoldPolicyMiniTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/metadata.py b/box_sdk_gen/schemas/metadata.py new file mode 100644 index 000000000..1eee77e39 --- /dev/null +++ b/box_sdk_gen/schemas/metadata.py @@ -0,0 +1,36 @@ +from typing import Optional + +from box_sdk_gen.schemas.metadata_base import MetadataBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class Metadata(MetadataBase): + def __init__( + self, + *, + parent: Optional[str] = None, + template: Optional[str] = None, + scope: Optional[str] = None, + version: Optional[int] = None, + **kwargs + ): + """ + :param parent: The identifier of the item that this metadata instance + has been attached to. This combines the `type` and the `id` + of the parent in the form `{type}_{id}`., defaults to None + :type parent: Optional[str], optional + :param template: The name of the template., defaults to None + :type template: Optional[str], optional + :param scope: An ID for the scope in which this template + has been applied. This will be `enterprise_{enterprise_id}` for templates + defined for use in this enterprise, and `global` for general templates + that are available to all enterprises using Box., defaults to None + :type scope: Optional[str], optional + :param version: The version of the metadata instance. This version starts at 0 and + increases every time a user-defined property is modified., defaults to None + :type version: Optional[int], optional + """ + super().__init__( + parent=parent, template=template, scope=scope, version=version, **kwargs + ) diff --git a/box_sdk_gen/schemas/metadata_base.py b/box_sdk_gen/schemas/metadata_base.py new file mode 100644 index 000000000..f7173666a --- /dev/null +++ b/box_sdk_gen/schemas/metadata_base.py @@ -0,0 +1,55 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataBase(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'parent': '$parent', + 'template': '$template', + 'scope': '$scope', + 'version': '$version', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + '$parent': 'parent', + '$template': 'template', + '$scope': 'scope', + '$version': 'version', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + parent: Optional[str] = None, + template: Optional[str] = None, + scope: Optional[str] = None, + version: Optional[int] = None, + **kwargs + ): + """ + :param parent: The identifier of the item that this metadata instance + has been attached to. This combines the `type` and the `id` + of the parent in the form `{type}_{id}`., defaults to None + :type parent: Optional[str], optional + :param template: The name of the template., defaults to None + :type template: Optional[str], optional + :param scope: An ID for the scope in which this template + has been applied. This will be `enterprise_{enterprise_id}` for templates + defined for use in this enterprise, and `global` for general templates + that are available to all enterprises using Box., defaults to None + :type scope: Optional[str], optional + :param version: The version of the metadata instance. This version starts at 0 and + increases every time a user-defined property is modified., defaults to None + :type version: Optional[int], optional + """ + super().__init__(**kwargs) + self.parent = parent + self.template = template + self.scope = scope + self.version = version diff --git a/box_sdk_gen/schemas/metadata_cascade_policies.py b/box_sdk_gen/schemas/metadata_cascade_policies.py new file mode 100644 index 000000000..7c12ac0aa --- /dev/null +++ b/box_sdk_gen/schemas/metadata_cascade_policies.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.metadata_cascade_policy import MetadataCascadePolicy + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataCascadePolicies(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[MetadataCascadePolicy]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of metadata cascade policies., defaults to None + :type entries: Optional[List[MetadataCascadePolicy]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/metadata_cascade_policy.py b/box_sdk_gen/schemas/metadata_cascade_policy.py new file mode 100644 index 000000000..a3096866f --- /dev/null +++ b/box_sdk_gen/schemas/metadata_cascade_policy.py @@ -0,0 +1,126 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Dict + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataCascadePolicyTypeField(str, Enum): + METADATA_CASCADE_POLICY = 'metadata_cascade_policy' + + +class MetadataCascadePolicyOwnerEnterpriseTypeField(str, Enum): + ENTERPRISE = 'enterprise' + + +class MetadataCascadePolicyOwnerEnterpriseField(BaseObject): + _discriminator = 'type', {'enterprise'} + + def __init__( + self, + *, + type: Optional[MetadataCascadePolicyOwnerEnterpriseTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `enterprise`., defaults to None + :type type: Optional[MetadataCascadePolicyOwnerEnterpriseTypeField], optional + :param id: The ID of the enterprise that owns the policy., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class MetadataCascadePolicyParentTypeField(str, Enum): + FOLDER = 'folder' + + +class MetadataCascadePolicyParentField(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + *, + type: Optional[MetadataCascadePolicyParentTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `folder`., defaults to None + :type type: Optional[MetadataCascadePolicyParentTypeField], optional + :param id: The ID of the folder the policy is applied to., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class MetadataCascadePolicy(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'template_key': 'templateKey', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'templateKey': 'template_key', + **BaseObject._json_to_fields_mapping, + } + _discriminator = 'type', {'metadata_cascade_policy'} + + def __init__( + self, + id: str, + *, + type: MetadataCascadePolicyTypeField = MetadataCascadePolicyTypeField.METADATA_CASCADE_POLICY, + owner_enterprise: Optional[MetadataCascadePolicyOwnerEnterpriseField] = None, + parent: Optional[MetadataCascadePolicyParentField] = None, + scope: Optional[str] = None, + template_key: Optional[str] = None, + **kwargs + ): + """ + :param id: The ID of the metadata cascade policy object. + :type id: str + :param type: The value will always be `metadata_cascade_policy`., defaults to MetadataCascadePolicyTypeField.METADATA_CASCADE_POLICY + :type type: MetadataCascadePolicyTypeField, optional + :param owner_enterprise: The enterprise that owns this policy., defaults to None + :type owner_enterprise: Optional[MetadataCascadePolicyOwnerEnterpriseField], optional + :param parent: Represent the folder the policy is applied to., defaults to None + :type parent: Optional[MetadataCascadePolicyParentField], optional + :param scope: The scope of the metadata cascade policy can either be `global` or + `enterprise_*`. The `global` scope is used for policies that are + available to any Box enterprise. The `enterprise_*` scope represents + policies that have been created within a specific enterprise, where `*` + will be the ID of that enterprise., defaults to None + :type scope: Optional[str], optional + :param template_key: The key of the template that is cascaded down to the folder's + children. + + In many cases the template key is automatically derived + of its display name, for example `Contract Template` would + become `contractTemplate`. In some cases the creator of the + template will have provided its own template key. + + Please [list the templates for an enterprise][list], or + get all instances on a [file][file] or [folder][folder] + to inspect a template's key. + + [list]: e://get-metadata-templates-enterprise + [file]: e://get-files-id-metadata + [folder]: e://get-folders-id-metadata, defaults to None + :type template_key: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.owner_enterprise = owner_enterprise + self.parent = parent + self.scope = scope + self.template_key = template_key diff --git a/box_sdk_gen/schemas/metadata_error.py b/box_sdk_gen/schemas/metadata_error.py new file mode 100644 index 000000000..24d5ab170 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_error.py @@ -0,0 +1,29 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataError(BaseObject): + def __init__( + self, + *, + code: Optional[str] = None, + message: Optional[str] = None, + request_id: Optional[str] = None, + **kwargs + ): + """ + :param code: A Box-specific error code., defaults to None + :type code: Optional[str], optional + :param message: A short message describing the error., defaults to None + :type message: Optional[str], optional + :param request_id: A unique identifier for this response, which can be used + when contacting Box support., defaults to None + :type request_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.code = code + self.message = message + self.request_id = request_id diff --git a/box_sdk_gen/schemas/metadata_field_filter_date_range.py b/box_sdk_gen/schemas/metadata_field_filter_date_range.py new file mode 100644 index 000000000..8fd94463c --- /dev/null +++ b/box_sdk_gen/schemas/metadata_field_filter_date_range.py @@ -0,0 +1,28 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class MetadataFieldFilterDateRange(BaseObject): + def __init__( + self, *, lt: Optional[DateTime] = None, gt: Optional[DateTime] = None, **kwargs + ): + """ + :param lt: Specifies the (inclusive) upper bound for the metadata field + value. The value of a field must be lower than (`lt`) or + equal to this value for the search query to match this + template., defaults to None + :type lt: Optional[DateTime], optional + :param gt: Specifies the (inclusive) lower bound for the metadata field + value. The value of a field must be greater than (`gt`) or + equal to this value for the search query to match this + template., defaults to None + :type gt: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.lt = lt + self.gt = gt diff --git a/box_sdk_gen/schemas/metadata_field_filter_float_range.py b/box_sdk_gen/schemas/metadata_field_filter_float_range.py new file mode 100644 index 000000000..7e5d528c7 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_field_filter_float_range.py @@ -0,0 +1,26 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataFieldFilterFloatRange(BaseObject): + def __init__( + self, *, lt: Optional[float] = None, gt: Optional[float] = None, **kwargs + ): + """ + :param lt: Specifies the (inclusive) upper bound for the metadata field + value. The value of a field must be lower than (`lt`) or + equal to this value for the search query to match this + template., defaults to None + :type lt: Optional[float], optional + :param gt: Specifies the (inclusive) lower bound for the metadata field + value. The value of a field must be greater than (`gt`) or + equal to this value for the search query to match this + template., defaults to None + :type gt: Optional[float], optional + """ + super().__init__(**kwargs) + self.lt = lt + self.gt = gt diff --git a/box_sdk_gen/schemas/metadata_filter.py b/box_sdk_gen/schemas/metadata_filter.py new file mode 100644 index 000000000..3df54e073 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_filter.py @@ -0,0 +1,90 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from typing import Union + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.metadata_field_filter_float_range import ( + MetadataFieldFilterFloatRange, +) + +from box_sdk_gen.schemas.metadata_field_filter_date_range import ( + MetadataFieldFilterDateRange, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataFilterScopeField(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + ENTERPRISE__ENTERPRISE_ID_ = 'enterprise_{enterprise_id}' + + +class MetadataFilter(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'template_key': 'templateKey', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'templateKey': 'template_key', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + scope: Optional[MetadataFilterScopeField] = None, + template_key: Optional[str] = None, + filters: Optional[ + Dict[ + str, + Union[ + str, + float, + List[str], + MetadataFieldFilterFloatRange, + MetadataFieldFilterDateRange, + ], + ] + ] = None, + **kwargs + ): + """ + :param scope: Specifies the scope of the template to filter search results by. + + This will be `enterprise_{enterprise_id}` for templates defined + 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 used to filter search results. + + In many cases the template key is automatically derived + of its display name, for example `Contract Template` would + become `contractTemplate`. In some cases the creator of the + template will have provided its own template key. + + Please [list the templates for an enterprise][list], or + get all instances on a [file][file] or [folder][folder] + to inspect a template's key. + + [list]: e://get-metadata-templates-enterprise + [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 + self.template_key = template_key + self.filters = filters diff --git a/box_sdk_gen/schemas/metadata_full.py b/box_sdk_gen/schemas/metadata_full.py new file mode 100644 index 000000000..4e52a463c --- /dev/null +++ b/box_sdk_gen/schemas/metadata_full.py @@ -0,0 +1,76 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.schemas.metadata_base import MetadataBase + +from box_sdk_gen.schemas.metadata import Metadata + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataFull(Metadata): + _fields_to_json_mapping: Dict[str, str] = { + 'can_edit': '$canEdit', + 'id': '$id', + 'type': '$type', + 'type_version': '$typeVersion', + **Metadata._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + '$canEdit': 'can_edit', + '$id': 'id', + '$type': 'type', + '$typeVersion': 'type_version', + **Metadata._json_to_fields_mapping, + } + + def __init__( + self, + *, + can_edit: Optional[bool] = None, + id: Optional[str] = None, + type: Optional[str] = None, + type_version: Optional[int] = None, + parent: Optional[str] = None, + template: Optional[str] = None, + scope: Optional[str] = None, + version: Optional[int] = None, + **kwargs + ): + """ + :param can_edit: Whether the user can edit this metadata instance., defaults to None + :type can_edit: Optional[bool], optional + :param id: A UUID to identify the metadata instance., defaults to None + :type id: Optional[str], optional + :param type: A unique identifier for the "type" of this instance. This is an + internal system property and should not be used by a client + application., defaults to None + :type type: Optional[str], optional + :param type_version: The last-known version of the template of the object. This is an + internal system property and should not be used by a client + application., defaults to None + :type type_version: Optional[int], optional + :param parent: The identifier of the item that this metadata instance + has been attached to. This combines the `type` and the `id` + of the parent in the form `{type}_{id}`., defaults to None + :type parent: Optional[str], optional + :param template: The name of the template., defaults to None + :type template: Optional[str], optional + :param scope: An ID for the scope in which this template + has been applied. This will be `enterprise_{enterprise_id}` for templates + defined for use in this enterprise, and `global` for general templates + that are available to all enterprises using Box., defaults to None + :type scope: Optional[str], optional + :param version: The version of the metadata instance. This version starts at 0 and + increases every time a user-defined property is modified., defaults to None + :type version: Optional[int], optional + """ + super().__init__( + parent=parent, template=template, scope=scope, version=version, **kwargs + ) + self.can_edit = can_edit + self.id = id + self.type = type + self.type_version = type_version + self.extra_data = kwargs diff --git a/box_sdk_gen/schemas/metadata_instance_value.py b/box_sdk_gen/schemas/metadata_instance_value.py new file mode 100644 index 000000000..89af11f7b --- /dev/null +++ b/box_sdk_gen/schemas/metadata_instance_value.py @@ -0,0 +1,7 @@ +from typing import Union + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + +MetadataInstanceValue = Union[str, int, float, List[str]] diff --git a/box_sdk_gen/schemas/metadata_query.py b/box_sdk_gen/schemas/metadata_query.py new file mode 100644 index 000000000..e98c90b51 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_query.py @@ -0,0 +1,129 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Dict + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataQueryOrderByDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class MetadataQueryOrderByField(BaseObject): + def __init__( + self, + *, + field_key: Optional[str] = None, + direction: Optional[MetadataQueryOrderByDirectionField] = None, + **kwargs + ): + """ + :param field_key: The metadata template field to order by. + + The `field_key` represents the `key` value of a field from the + metadata template being searched for., defaults to None + :type field_key: Optional[str], optional + :param direction: The direction to order by, either ascending or descending. + + The `ordering` direction must be the same for each item in the + array., defaults to None + :type direction: Optional[MetadataQueryOrderByDirectionField], optional + """ + super().__init__(**kwargs) + self.field_key = field_key + self.direction = direction + + +class MetadataQuery(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'from_': 'from', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'from': 'from_', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + from_: str, + ancestor_folder_id: str, + *, + query: Optional[str] = None, + query_params: Optional[Dict] = None, + order_by: Optional[List[MetadataQueryOrderByField]] = None, + limit: Optional[int] = None, + marker: Optional[str] = None, + fields: Optional[List[str]] = None, + **kwargs + ): + """ + :param from_: Specifies the template used in the query. Must be in the form + `scope.templateKey`. Not all templates can be used in this field, + most notably the built-in, Box-provided classification templates + can not be used in a query. + :type from_: str + :param ancestor_folder_id: The ID of the folder that you are restricting the query to. A + value of zero will return results from all folders you have access + to. A non-zero value will only return results found in the folder + corresponding to the ID or in any of its subfolders. + :type ancestor_folder_id: str + :param query: The query to perform. A query is a logical expression that is very similar + to a SQL `SELECT` statement. Values in the search query can be turned into + parameters specified in the `query_param` arguments list to prevent having + to manually insert search values into the query string. + + For example, a value of `:amount` would represent the `amount` value in + `query_params` object., defaults to None + :type query: Optional[str], optional + :param query_params: Set of arguments corresponding to the parameters specified in the + `query`. The type of each parameter used in the `query_params` must match + the type of the corresponding metadata template field., defaults to None + :type query_params: Optional[Dict], optional + :param order_by: A list of template fields and directions to sort the metadata query + results by. + + The ordering `direction` must be the same for each item in the array., defaults to None + :type order_by: Optional[List[MetadataQueryOrderByField]], optional + :param limit: A value between 0 and 100 that indicates the maximum number of results + to return for a single request. This only specifies a maximum + boundary and will not guarantee the minimum number of results + returned., defaults to None + :type limit: Optional[int], optional + :param marker: Marker to use for requesting the next page., defaults to None + :type marker: Optional[str], optional + :param fields: By default, this endpoint returns only the most basic info about the items for + which the query matches. This attribute can be used to specify a list of + additional attributes to return for any item, including its metadata. + + This attribute takes a list of item fields, metadata template identifiers, + or metadata template field identifiers. + + For example: + + * `created_by` will add the details of the user who created the item to + the response. + * `metadata..` will return the mini-representation + of the metadata instance identified by the `scope` and `templateKey`. + * `metadata...` will return all the mini-representation + of the metadata instance identified by the `scope` and `templateKey` plus + the field specified by the `field` name. Multiple fields for the same + `scope` and `templateKey` can be defined., defaults to None + :type fields: Optional[List[str]], optional + """ + super().__init__(**kwargs) + self.from_ = from_ + self.ancestor_folder_id = ancestor_folder_id + self.query = query + self.query_params = query_params + self.order_by = order_by + self.limit = limit + self.marker = marker + self.fields = fields diff --git a/box_sdk_gen/schemas/metadata_query_index.py b/box_sdk_gen/schemas/metadata_query_index.py new file mode 100644 index 000000000..8eb1687ae --- /dev/null +++ b/box_sdk_gen/schemas/metadata_query_index.py @@ -0,0 +1,66 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataQueryIndexStatusField(str, Enum): + BUILDING = 'building' + ACTIVE = 'active' + DISABLED = 'disabled' + + +class MetadataQueryIndexFieldsSortDirectionField(str, Enum): + ASC = 'asc' + DESC = 'desc' + + +class MetadataQueryIndexFieldsField(BaseObject): + def __init__( + self, + *, + key: Optional[str] = None, + sort_direction: Optional[MetadataQueryIndexFieldsSortDirectionField] = None, + **kwargs + ): + """ + :param key: The metadata template field key., defaults to None + :type key: Optional[str], optional + :param sort_direction: The sort direction of the field., defaults to None + :type sort_direction: Optional[MetadataQueryIndexFieldsSortDirectionField], optional + """ + super().__init__(**kwargs) + self.key = key + self.sort_direction = sort_direction + + +class MetadataQueryIndex(BaseObject): + def __init__( + self, + type: str, + status: MetadataQueryIndexStatusField, + *, + id: Optional[str] = None, + fields: Optional[List[MetadataQueryIndexFieldsField]] = None, + **kwargs + ): + """ + :param type: Value is always `metadata_query_index`. + :type type: str + :param status: The status of the metadata query index. + :type status: MetadataQueryIndexStatusField + :param id: The ID of the metadata query index., defaults to None + :type id: Optional[str], optional + :param fields: A list of template fields which make up the index., defaults to None + :type fields: Optional[List[MetadataQueryIndexFieldsField]], optional + """ + super().__init__(**kwargs) + self.type = type + self.status = status + self.id = id + self.fields = fields diff --git a/box_sdk_gen/schemas/metadata_query_results.py b/box_sdk_gen/schemas/metadata_query_results.py new file mode 100644 index 000000000..381389e8b --- /dev/null +++ b/box_sdk_gen/schemas/metadata_query_results.py @@ -0,0 +1,43 @@ +from typing import Optional + +from typing import List + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataQueryResults(BaseObject): + def __init__( + self, + *, + entries: Optional[List[Union[FileFull, FolderFull]]] = None, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: The mini representation of the files and folders that match the search + terms. + + By default, this endpoint returns only the most basic info about the + items. To get additional fields for each item, including any of the + metadata, use the `fields` attribute in the query., defaults to None + :type entries: Optional[List[Union[FileFull, FolderFull]]], optional + :param limit: The limit that was used for this search. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/metadata_template.py b/box_sdk_gen/schemas/metadata_template.py new file mode 100644 index 000000000..2e3ae449c --- /dev/null +++ b/box_sdk_gen/schemas/metadata_template.py @@ -0,0 +1,175 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataTemplateTypeField(str, Enum): + METADATA_TEMPLATE = 'metadata_template' + + +class MetadataTemplateFieldsTypeField(str, Enum): + STRING = 'string' + FLOAT = 'float' + DATE = 'date' + ENUM = 'enum' + MULTISELECT = 'multiSelect' + INTEGER = 'integer' + + +class MetadataTemplateFieldsOptionsField(BaseObject): + def __init__(self, key: str, *, id: Optional[str] = None, **kwargs): + """ + :param key: The text value of the option. This represents both the display name of the + option and the internal key used when updating templates. + :type key: str + :param id: The internal unique identifier of the the option., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.key = key + self.id = id + + +class MetadataTemplateFieldsField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + **BaseObject._json_to_fields_mapping, + } + _discriminator = 'type', { + 'string', + 'float', + 'date', + 'enum', + 'multiSelect', + 'integer', + } + + def __init__( + self, + type: MetadataTemplateFieldsTypeField, + key: str, + display_name: str, + *, + description: Optional[str] = None, + hidden: Optional[bool] = None, + options: Optional[List[MetadataTemplateFieldsOptionsField]] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of field. The basic fields are a `string` field for text, a + `float` field for numbers, and a `date` fields to present the user with a + date-time picker. + + Additionally, metadata templates support an `enum` field for a basic list + of items, and ` multiSelect` field for a similar list of items where the + user can select more than one value. + + **Note**: The `integer` value is deprecated. + It is still present in the response, + but cannot be used in the POST request. + :type type: MetadataTemplateFieldsTypeField + :param key: A unique identifier for the field. The identifier must + be unique within the template to which it belongs. + :type key: str + :param display_name: The display name of the field as it is shown to the user in the web and + mobile apps. + :type display_name: str + :param description: A description of the field. This is not shown to the user., defaults to None + :type description: Optional[str], optional + :param hidden: Whether this field is hidden in the UI for the user and can only be set + through the API instead., defaults to None + :type hidden: Optional[bool], optional + :param options: A list of options for this field. This is used in combination + with the `enum` and `multiSelect` field types., defaults to None + :type options: Optional[List[MetadataTemplateFieldsOptionsField]], optional + :param id: The unique ID of the metadata template field., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.key = key + self.display_name = display_name + self.description = description + self.hidden = hidden + self.options = options + self.id = id + + +class MetadataTemplate(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'template_key': 'templateKey', + 'display_name': 'displayName', + 'copy_instance_on_item_copy': 'copyInstanceOnItemCopy', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'templateKey': 'template_key', + 'displayName': 'display_name', + 'copyInstanceOnItemCopy': 'copy_instance_on_item_copy', + **BaseObject._json_to_fields_mapping, + } + _discriminator = 'type', {'metadata_template'} + + def __init__( + self, + id: str, + *, + type: MetadataTemplateTypeField = MetadataTemplateTypeField.METADATA_TEMPLATE, + scope: Optional[str] = None, + template_key: Optional[str] = None, + display_name: Optional[str] = None, + hidden: Optional[bool] = None, + fields: Optional[List[MetadataTemplateFieldsField]] = None, + copy_instance_on_item_copy: Optional[bool] = None, + **kwargs + ): + """ + :param id: The ID of the metadata template. + :type id: str + :param type: The value will always be `metadata_template`., defaults to MetadataTemplateTypeField.METADATA_TEMPLATE + :type type: MetadataTemplateTypeField, optional + :param scope: The scope of the metadata template can either be `global` or + `enterprise_*`. The `global` scope is used for templates that are + available to any Box enterprise. The `enterprise_*` scope represents + templates that have been created within a specific enterprise, where `*` + will be the ID of that enterprise., defaults to None + :type scope: Optional[str], optional + :param template_key: A unique identifier for the template. This identifier is unique across + the `scope` of the enterprise to which the metadata template is being + applied, yet is not necessarily unique across different enterprises., defaults to None + :type template_key: Optional[str], optional + :param display_name: The display name of the template. This can be seen in the Box web app + and mobile apps., defaults to None + :type display_name: Optional[str], optional + :param hidden: Defines if this template is visible in the Box web app UI, or if + it is purely intended for usage through the API., defaults to None + :type hidden: Optional[bool], optional + :param fields: An ordered list of template fields which are part of the template. Each + field can be a regular text field, date field, number field, as well as a + single or multi-select list., defaults to None + :type fields: Optional[List[MetadataTemplateFieldsField]], optional + :param copy_instance_on_item_copy: Whether or not to include the metadata when a file or folder is copied., defaults to None + :type copy_instance_on_item_copy: Optional[bool], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.scope = scope + self.template_key = template_key + self.display_name = display_name + self.hidden = hidden + self.fields = fields + self.copy_instance_on_item_copy = copy_instance_on_item_copy diff --git a/box_sdk_gen/schemas/metadata_templates.py b/box_sdk_gen/schemas/metadata_templates.py new file mode 100644 index 000000000..f0f8b6fa7 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_templates.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.metadata_template import MetadataTemplate + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataTemplates(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[MetadataTemplate]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of metadata templates., defaults to None + :type entries: Optional[List[MetadataTemplate]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/metadatas.py b/box_sdk_gen/schemas/metadatas.py new file mode 100644 index 000000000..428f4cf6c --- /dev/null +++ b/box_sdk_gen/schemas/metadatas.py @@ -0,0 +1,28 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.metadata import Metadata + +from box_sdk_gen.box.errors import BoxSDKError + + +class Metadatas(BaseObject): + def __init__( + self, + *, + entries: Optional[List[Metadata]] = None, + limit: Optional[int] = None, + **kwargs + ): + """ + :param entries: A list of metadata instances, as applied to this file or folder., defaults to None + :type entries: Optional[List[Metadata]], optional + :param limit: The limit that was used for this page of results., defaults to None + :type limit: Optional[int], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit diff --git a/box_sdk_gen/schemas/o_auth_2_error.py b/box_sdk_gen/schemas/o_auth_2_error.py new file mode 100644 index 000000000..524fe4f20 --- /dev/null +++ b/box_sdk_gen/schemas/o_auth_2_error.py @@ -0,0 +1,24 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class OAuth2Error(BaseObject): + def __init__( + self, + *, + error: Optional[str] = None, + error_description: Optional[str] = None, + **kwargs + ): + """ + :param error: The type of the error returned., defaults to None + :type error: Optional[str], optional + :param error_description: The type of the error returned., defaults to None + :type error_description: Optional[str], optional + """ + super().__init__(**kwargs) + self.error = error + self.error_description = error_description diff --git a/box_sdk_gen/schemas/outcome.py b/box_sdk_gen/schemas/outcome.py new file mode 100644 index 000000000..e0060eb42 --- /dev/null +++ b/box_sdk_gen/schemas/outcome.py @@ -0,0 +1,36 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.collaborator_variable import CollaboratorVariable + +from box_sdk_gen.schemas.completion_rule_variable import CompletionRuleVariable + +from box_sdk_gen.schemas.role_variable import RoleVariable + +from box_sdk_gen.box.errors import BoxSDKError + + +class Outcome(BaseObject): + def __init__( + self, + id: str, + *, + collaborators: Optional[CollaboratorVariable] = None, + completion_rule: Optional[CompletionRuleVariable] = None, + file_collaborator_role: Optional[RoleVariable] = None, + task_collaborators: Optional[CollaboratorVariable] = None, + role: Optional[RoleVariable] = None, + **kwargs + ): + """ + :param id: ID of a specific outcome. + :type id: str + """ + super().__init__(**kwargs) + self.id = id + self.collaborators = collaborators + self.completion_rule = completion_rule + self.file_collaborator_role = file_collaborator_role + self.task_collaborators = task_collaborators + self.role = role diff --git a/box_sdk_gen/schemas/post_o_auth_2_revoke.py b/box_sdk_gen/schemas/post_o_auth_2_revoke.py new file mode 100644 index 000000000..d93eec3c4 --- /dev/null +++ b/box_sdk_gen/schemas/post_o_auth_2_revoke.py @@ -0,0 +1,30 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class PostOAuth2Revoke(BaseObject): + def __init__( + self, + *, + client_id: Optional[str] = None, + client_secret: Optional[str] = None, + token: Optional[str] = None, + **kwargs + ): + """ + :param client_id: The Client ID of the application requesting to revoke the + access token., defaults to None + :type client_id: Optional[str], optional + :param client_secret: The client secret of the application requesting to revoke + an access token., defaults to None + :type client_secret: Optional[str], optional + :param token: The access token to revoke., defaults to None + :type token: Optional[str], optional + """ + super().__init__(**kwargs) + self.client_id = client_id + self.client_secret = client_secret + self.token = token diff --git a/box_sdk_gen/schemas/post_o_auth_2_token.py b/box_sdk_gen/schemas/post_o_auth_2_token.py new file mode 100644 index 000000000..1d92c4c54 --- /dev/null +++ b/box_sdk_gen/schemas/post_o_auth_2_token.py @@ -0,0 +1,146 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class PostOAuth2TokenGrantTypeField(str, Enum): + AUTHORIZATION_CODE = 'authorization_code' + REFRESH_TOKEN = 'refresh_token' + CLIENT_CREDENTIALS = 'client_credentials' + URN_IETF_PARAMS_OAUTH_GRANT_TYPE_JWT_BEARER = ( + 'urn:ietf:params:oauth:grant-type:jwt-bearer' + ) + URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE = ( + 'urn:ietf:params:oauth:grant-type:token-exchange' + ) + + +class PostOAuth2TokenSubjectTokenTypeField(str, Enum): + URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ACCESS_TOKEN = ( + 'urn:ietf:params:oauth:token-type:access_token' + ) + + +class PostOAuth2TokenActorTokenTypeField(str, Enum): + URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ID_TOKEN = ( + 'urn:ietf:params:oauth:token-type:id_token' + ) + + +class PostOAuth2TokenBoxSubjectTypeField(str, Enum): + ENTERPRISE = 'enterprise' + USER = 'user' + + +class PostOAuth2Token(BaseObject): + def __init__( + self, + grant_type: PostOAuth2TokenGrantTypeField, + *, + client_id: Optional[str] = None, + client_secret: Optional[str] = None, + code: Optional[str] = None, + refresh_token: Optional[str] = None, + assertion: Optional[str] = None, + subject_token: Optional[str] = None, + subject_token_type: Optional[PostOAuth2TokenSubjectTokenTypeField] = None, + actor_token: Optional[str] = None, + actor_token_type: Optional[PostOAuth2TokenActorTokenTypeField] = None, + scope: Optional[str] = None, + resource: Optional[str] = None, + box_subject_type: Optional[PostOAuth2TokenBoxSubjectTypeField] = None, + box_subject_id: Optional[str] = None, + box_shared_link: Optional[str] = None, + **kwargs + ): + """ + :param grant_type: The type of request being made, either using a client-side obtained + authorization code, a refresh token, a JWT assertion, client credentials + grant or another access token for the purpose of downscoping a token. + :type grant_type: PostOAuth2TokenGrantTypeField + :param client_id: The Client ID of the application requesting an access token. + + Used in combination with `authorization_code`, `client_credentials`, or + `urn:ietf:params:oauth:grant-type:jwt-bearer` as the `grant_type`., defaults to None + :type client_id: Optional[str], optional + :param client_secret: The client secret of the application requesting an access token. + + Used in combination with `authorization_code`, `client_credentials`, or + `urn:ietf:params:oauth:grant-type:jwt-bearer` as the `grant_type`., defaults to None + :type client_secret: Optional[str], optional + :param code: The client-side authorization code passed to your application by + Box in the browser redirect after the user has successfully + granted your application permission to make API calls on their + behalf. + + Used in combination with `authorization_code` as the `grant_type`., defaults to None + :type code: Optional[str], optional + :param refresh_token: A refresh token used to get a new access token with. + + Used in combination with `refresh_token` as the `grant_type`., defaults to None + :type refresh_token: Optional[str], optional + :param assertion: A JWT assertion for which to request a new access token. + + Used in combination with `urn:ietf:params:oauth:grant-type:jwt-bearer` + as the `grant_type`., defaults to None + :type assertion: Optional[str], optional + :param subject_token: The token to exchange for a downscoped token. This can be a regular + access token, a JWT assertion, or an app token. + + Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` + as the `grant_type`., defaults to None + :type subject_token: Optional[str], optional + :param subject_token_type: The type of `subject_token` passed in. + + Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` + as the `grant_type`., defaults to None + :type subject_token_type: Optional[PostOAuth2TokenSubjectTokenTypeField], optional + :param actor_token: The token used to create an annotator token. + This is a JWT assertion. + + Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` + as the `grant_type`., defaults to None + :type actor_token: Optional[str], optional + :param actor_token_type: The type of `actor_token` passed in. + + Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` + as the `grant_type`., defaults to None + :type actor_token_type: Optional[PostOAuth2TokenActorTokenTypeField], optional + :param scope: The space-delimited list of scopes that you want apply to the + new access token. + + The `subject_token` will need to have all of these scopes or + the call will error with **401 Unauthorized**.., defaults to None + :type scope: Optional[str], optional + :param resource: Full URL for the file that the token should be generated for., defaults to None + :type resource: Optional[str], optional + :param box_subject_type: Used in combination with `client_credentials` as the `grant_type`., defaults to None + :type box_subject_type: Optional[PostOAuth2TokenBoxSubjectTypeField], optional + :param box_subject_id: Used in combination with `client_credentials` as the `grant_type`. + Value is determined by `box_subject_type`. If `user` use user ID and if + `enterprise` use enterprise ID., defaults to None + :type box_subject_id: Optional[str], optional + :param box_shared_link: Full URL of the shared link on the file or folder + that the token should be generated for., defaults to None + :type box_shared_link: Optional[str], optional + """ + super().__init__(**kwargs) + self.grant_type = grant_type + self.client_id = client_id + self.client_secret = client_secret + self.code = code + self.refresh_token = refresh_token + self.assertion = assertion + self.subject_token = subject_token + self.subject_token_type = subject_token_type + self.actor_token = actor_token + self.actor_token_type = actor_token_type + self.scope = scope + self.resource = resource + self.box_subject_type = box_subject_type + self.box_subject_id = box_subject_id + self.box_shared_link = box_shared_link diff --git a/box_sdk_gen/schemas/post_o_auth_2_token_refresh_access_token.py b/box_sdk_gen/schemas/post_o_auth_2_token_refresh_access_token.py new file mode 100644 index 000000000..a6eb8c5e3 --- /dev/null +++ b/box_sdk_gen/schemas/post_o_auth_2_token_refresh_access_token.py @@ -0,0 +1,36 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class PostOAuth2TokenRefreshAccessTokenGrantTypeField(str, Enum): + REFRESH_TOKEN = 'refresh_token' + + +class PostOAuth2TokenRefreshAccessToken(BaseObject): + def __init__( + self, + client_id: str, + client_secret: str, + refresh_token: str, + *, + grant_type: PostOAuth2TokenRefreshAccessTokenGrantTypeField = PostOAuth2TokenRefreshAccessTokenGrantTypeField.REFRESH_TOKEN, + **kwargs + ): + """ + :param client_id: The client ID of the application requesting to refresh the token. + :type client_id: str + :param client_secret: The client secret of the application requesting to refresh the token. + :type client_secret: str + :param refresh_token: The refresh token to refresh. + :type refresh_token: str + :param grant_type: The type of request being made, in this case a refresh request., defaults to PostOAuth2TokenRefreshAccessTokenGrantTypeField.REFRESH_TOKEN + :type grant_type: PostOAuth2TokenRefreshAccessTokenGrantTypeField, optional + """ + super().__init__(**kwargs) + self.client_id = client_id + self.client_secret = client_secret + self.refresh_token = refresh_token + self.grant_type = grant_type diff --git a/box_sdk_gen/schemas/realtime_server.py b/box_sdk_gen/schemas/realtime_server.py new file mode 100644 index 000000000..f8228268e --- /dev/null +++ b/box_sdk_gen/schemas/realtime_server.py @@ -0,0 +1,43 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class RealtimeServer(BaseObject): + def __init__( + self, + *, + type: Optional[str] = None, + url: Optional[str] = None, + ttl: Optional[str] = None, + max_retries: Optional[str] = None, + retry_timeout: Optional[int] = None, + **kwargs + ): + """ + :param type: The value will always be `realtime_server`., defaults to None + :type type: Optional[str], optional + :param url: The URL for the server., defaults to None + :type url: Optional[str], optional + :param ttl: The time in minutes for which this server is available., defaults to None + :type ttl: Optional[str], optional + :param max_retries: The maximum number of retries this server will + allow before a new long poll should be started by + getting a [new list of server](#options-events)., defaults to None + :type max_retries: Optional[str], optional + :param retry_timeout: The maximum number of seconds without a response + after which you should retry the long poll connection. + + This helps to overcome network issues where the long + poll looks to be working but no packages are coming + through., defaults to None + :type retry_timeout: Optional[int], optional + """ + super().__init__(**kwargs) + self.type = type + self.url = url + self.ttl = ttl + self.max_retries = max_retries + self.retry_timeout = retry_timeout diff --git a/box_sdk_gen/schemas/realtime_servers.py b/box_sdk_gen/schemas/realtime_servers.py new file mode 100644 index 000000000..933fb5c52 --- /dev/null +++ b/box_sdk_gen/schemas/realtime_servers.py @@ -0,0 +1,28 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.realtime_server import RealtimeServer + +from box_sdk_gen.box.errors import BoxSDKError + + +class RealtimeServers(BaseObject): + def __init__( + self, + *, + chunk_size: Optional[int] = None, + entries: Optional[List[RealtimeServer]] = None, + **kwargs + ): + """ + :param chunk_size: The number of items in this response., defaults to None + :type chunk_size: Optional[int], optional + :param entries: A list of real-time servers., defaults to None + :type entries: Optional[List[RealtimeServer]], optional + """ + super().__init__(**kwargs) + self.chunk_size = chunk_size + self.entries = entries diff --git a/box_sdk_gen/schemas/recent_item.py b/box_sdk_gen/schemas/recent_item.py new file mode 100644 index 000000000..837de2aa6 --- /dev/null +++ b/box_sdk_gen/schemas/recent_item.py @@ -0,0 +1,56 @@ +from enum import Enum + +from typing import Optional + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class RecentItemInteractionTypeField(str, Enum): + ITEM_PREVIEW = 'item_preview' + ITEM_UPLOAD = 'item_upload' + ITEM_COMMENT = 'item_comment' + ITEM_OPEN = 'item_open' + ITEM_MODIFY = 'item_modify' + + +class RecentItem(BaseObject): + def __init__( + self, + *, + type: Optional[str] = None, + item: Optional[Union[FileFull, FolderFull, WebLink]] = None, + interaction_type: Optional[RecentItemInteractionTypeField] = None, + interacted_at: Optional[DateTime] = None, + interaction_shared_link: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `recent_item`., defaults to None + :type type: Optional[str], optional + :param interaction_type: The most recent type of access the user performed on + the item., defaults to None + :type interaction_type: Optional[RecentItemInteractionTypeField], optional + :param interacted_at: The time of the most recent interaction., defaults to None + :type interacted_at: Optional[DateTime], optional + :param interaction_shared_link: If the item was accessed through a shared link it will appear here, + otherwise this will be null., defaults to None + :type interaction_shared_link: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.item = item + self.interaction_type = interaction_type + self.interacted_at = interacted_at + self.interaction_shared_link = interaction_shared_link diff --git a/box_sdk_gen/schemas/recent_items.py b/box_sdk_gen/schemas/recent_items.py new file mode 100644 index 000000000..8498a8fb9 --- /dev/null +++ b/box_sdk_gen/schemas/recent_items.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.recent_item import RecentItem + +from box_sdk_gen.box.errors import BoxSDKError + + +class RecentItems(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[RecentItem]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of recent items., defaults to None + :type entries: Optional[List[RecentItem]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/retention_policies.py b/box_sdk_gen/schemas/retention_policies.py new file mode 100644 index 000000000..3a365edc3 --- /dev/null +++ b/box_sdk_gen/schemas/retention_policies.py @@ -0,0 +1,34 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.retention_policy import RetentionPolicy + +from box_sdk_gen.box.errors import BoxSDKError + + +class RetentionPolicies(BaseObject): + def __init__( + self, + *, + entries: Optional[List[RetentionPolicy]] = None, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: A list in which each entry represents a retention policy object., defaults to None + :type entries: Optional[List[RetentionPolicy]], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/retention_policy.py b/box_sdk_gen/schemas/retention_policy.py new file mode 100644 index 000000000..91f884090 --- /dev/null +++ b/box_sdk_gen/schemas/retention_policy.py @@ -0,0 +1,173 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.retention_policy_base import RetentionPolicyBaseTypeField + +from box_sdk_gen.schemas.retention_policy_base import RetentionPolicyBase + +from box_sdk_gen.schemas.retention_policy_mini import ( + RetentionPolicyMiniDispositionActionField, +) + +from box_sdk_gen.schemas.retention_policy_mini import RetentionPolicyMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class RetentionPolicyPolicyTypeField(str, Enum): + FINITE = 'finite' + INDEFINITE = 'indefinite' + + +class RetentionPolicyRetentionTypeField(str, Enum): + MODIFIABLE = 'modifiable' + NON_MODIFIABLE = 'non_modifiable' + + +class RetentionPolicyStatusField(str, Enum): + ACTIVE = 'active' + RETIRED = 'retired' + + +class RetentionPolicyAssignmentCountsField(BaseObject): + def __init__( + self, + *, + enterprise: Optional[int] = None, + folder: Optional[int] = None, + metadata_template: Optional[int] = None, + **kwargs + ): + """ + :param enterprise: The number of enterprise assignments this policy has. The maximum value is 1., defaults to None + :type enterprise: Optional[int], optional + :param folder: The number of folder assignments this policy has., defaults to None + :type folder: Optional[int], optional + :param metadata_template: The number of metadata template assignments this policy has., defaults to None + :type metadata_template: Optional[int], optional + """ + super().__init__(**kwargs) + self.enterprise = enterprise + self.folder = folder + self.metadata_template = metadata_template + + +class RetentionPolicy(RetentionPolicyMini): + def __init__( + self, + id: str, + *, + description: Optional[str] = None, + policy_type: Optional[RetentionPolicyPolicyTypeField] = None, + retention_type: Optional[RetentionPolicyRetentionTypeField] = None, + status: Optional[RetentionPolicyStatusField] = None, + created_by: Optional[UserMini] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + can_owner_extend_retention: Optional[bool] = None, + are_owners_notified: Optional[bool] = None, + custom_notification_recipients: Optional[List[UserMini]] = None, + assignment_counts: Optional[RetentionPolicyAssignmentCountsField] = None, + policy_name: Optional[str] = None, + retention_length: Optional[str] = None, + disposition_action: Optional[RetentionPolicyMiniDispositionActionField] = None, + type: RetentionPolicyBaseTypeField = RetentionPolicyBaseTypeField.RETENTION_POLICY, + **kwargs + ): + """ + :param id: The unique identifier that represents a retention policy. + :type id: str + :param description: The additional text description of the retention policy., defaults to None + :type description: Optional[str], optional + :param policy_type: The type of the retention policy. A retention + policy type can either be `finite`, where a + specific amount of time to retain the content is known + upfront, or `indefinite`, where the amount of time + to retain the content is still unknown., defaults to None + :type policy_type: Optional[RetentionPolicyPolicyTypeField], optional + :param retention_type: Specifies the retention type: + + * `modifiable`: You can modify the retention policy. For example, + you can add or remove folders, shorten or lengthen + the policy duration, or delete the assignment. + Use this type if your retention policy + is not related to any regulatory purposes. + + * `non-modifiable`: You can modify the retention policy + only in a limited way: add a folder, lengthen the duration, + retire the policy, change the disposition action + or notification settings. You cannot perform other actions, + such as deleting the assignment or shortening the + policy duration. Use this type to ensure + compliance with regulatory retention policies., defaults to None + :type retention_type: Optional[RetentionPolicyRetentionTypeField], optional + :param status: The status of the retention policy. The status of + a policy will be `active`, unless explicitly retired by an + administrator, in which case the status will be `retired`. + Once a policy has been retired, it cannot become + active again., defaults to None + :type status: Optional[RetentionPolicyStatusField], optional + :param created_at: When the retention policy object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the retention policy object was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param can_owner_extend_retention: Determines if the owner of items under the policy + can extend the retention when the original + retention duration is about to end., defaults to None + :type can_owner_extend_retention: Optional[bool], optional + :param are_owners_notified: Determines if owners and co-owners of items + under the policy are notified when + the retention duration is about to end., defaults to None + :type are_owners_notified: Optional[bool], optional + :param custom_notification_recipients: A list of users notified when the retention policy duration is about to end., defaults to None + :type custom_notification_recipients: Optional[List[UserMini]], optional + :param assignment_counts: Counts the retention policy assignments for each item type., defaults to None + :type assignment_counts: Optional[RetentionPolicyAssignmentCountsField], optional + :param policy_name: The name given to the retention policy., defaults to None + :type policy_name: Optional[str], optional + :param retention_length: The length of the retention policy. This value + specifies the duration in days that the retention + policy will be active for after being assigned to + content. If the policy has a `policy_type` of + `indefinite`, the `retention_length` will also be + `indefinite`., defaults to None + :type retention_length: Optional[str], optional + :param disposition_action: The disposition action of the retention policy. + This action can be `permanently_delete`, which + will cause the content retained by the policy + to be permanently deleted, or `remove_retention`, + which will lift the retention policy from the content, + allowing it to be deleted by users, + once the retention policy has expired., defaults to None + :type disposition_action: Optional[RetentionPolicyMiniDispositionActionField], optional + :param type: The value will always be `retention_policy`., defaults to RetentionPolicyBaseTypeField.RETENTION_POLICY + :type type: RetentionPolicyBaseTypeField, optional + """ + super().__init__( + id=id, + policy_name=policy_name, + retention_length=retention_length, + disposition_action=disposition_action, + type=type, + **kwargs + ) + self.description = description + self.policy_type = policy_type + self.retention_type = retention_type + self.status = status + self.created_by = created_by + self.created_at = created_at + self.modified_at = modified_at + self.can_owner_extend_retention = can_owner_extend_retention + self.are_owners_notified = are_owners_notified + self.custom_notification_recipients = custom_notification_recipients + self.assignment_counts = assignment_counts diff --git a/box_sdk_gen/schemas/retention_policy_assignment.py b/box_sdk_gen/schemas/retention_policy_assignment.py new file mode 100644 index 000000000..a1e492256 --- /dev/null +++ b/box_sdk_gen/schemas/retention_policy_assignment.py @@ -0,0 +1,113 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.retention_policy_mini import RetentionPolicyMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class RetentionPolicyAssignmentTypeField(str, Enum): + RETENTION_POLICY_ASSIGNMENT = 'retention_policy_assignment' + + +class RetentionPolicyAssignmentAssignedToTypeField(str, Enum): + FOLDER = 'folder' + ENTERPRISE = 'enterprise' + METADATA_TEMPLATE = 'metadata_template' + + +class RetentionPolicyAssignmentAssignedToField(BaseObject): + _discriminator = 'type', {'folder', 'enterprise', 'metadata_template'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[RetentionPolicyAssignmentAssignedToTypeField] = None, + **kwargs + ): + """ + :param id: The ID of the folder, enterprise, or metadata template + the policy is assigned to. + Set to null or omit when type is set to enterprise., defaults to None + :type id: Optional[str], optional + :param type: The type of resource the policy is assigned to., defaults to None + :type type: Optional[RetentionPolicyAssignmentAssignedToTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class RetentionPolicyAssignmentFilterFieldsField(BaseObject): + def __init__( + self, *, field: Optional[str] = None, value: Optional[str] = None, **kwargs + ): + """ + :param field: The metadata attribute key id., defaults to None + :type field: Optional[str], optional + :param value: The metadata attribute field id. For value, only + enum and multiselect types are supported., defaults to None + :type value: Optional[str], optional + """ + super().__init__(**kwargs) + self.field = field + self.value = value + + +class RetentionPolicyAssignment(BaseObject): + _discriminator = 'type', {'retention_policy_assignment'} + + def __init__( + self, + id: str, + *, + type: RetentionPolicyAssignmentTypeField = RetentionPolicyAssignmentTypeField.RETENTION_POLICY_ASSIGNMENT, + retention_policy: Optional[RetentionPolicyMini] = None, + assigned_to: Optional[RetentionPolicyAssignmentAssignedToField] = None, + filter_fields: Optional[ + List[RetentionPolicyAssignmentFilterFieldsField] + ] = None, + assigned_by: Optional[UserMini] = None, + assigned_at: Optional[DateTime] = None, + start_date_field: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for a retention policy assignment. + :type id: str + :param type: The value will always be `retention_policy_assignment`., defaults to RetentionPolicyAssignmentTypeField.RETENTION_POLICY_ASSIGNMENT + :type type: RetentionPolicyAssignmentTypeField, optional + :param assigned_to: The `type` and `id` of the content that is under + retention. The `type` can either be `folder` + `enterprise`, or `metadata_template`., defaults to None + :type assigned_to: Optional[RetentionPolicyAssignmentAssignedToField], optional + :param filter_fields: An array of field objects. Values are only returned if the `assigned_to` + type is `metadata_template`. Otherwise, the array is blank., defaults to None + :type filter_fields: Optional[List[RetentionPolicyAssignmentFilterFieldsField]], optional + :param assigned_at: When the retention policy assignment object was + created., defaults to None + :type assigned_at: Optional[DateTime], optional + :param start_date_field: The date the retention policy assignment begins. + If the `assigned_to` type is `metadata_template`, + this field can be a date field's metadata attribute key id., defaults to None + :type start_date_field: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.retention_policy = retention_policy + self.assigned_to = assigned_to + self.filter_fields = filter_fields + self.assigned_by = assigned_by + self.assigned_at = assigned_at + self.start_date_field = start_date_field diff --git a/box_sdk_gen/schemas/retention_policy_assignment_base.py b/box_sdk_gen/schemas/retention_policy_assignment_base.py new file mode 100644 index 000000000..35a04809a --- /dev/null +++ b/box_sdk_gen/schemas/retention_policy_assignment_base.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class RetentionPolicyAssignmentBaseTypeField(str, Enum): + RETENTION_POLICY_ASSIGNMENT = 'retention_policy_assignment' + + +class RetentionPolicyAssignmentBase(BaseObject): + _discriminator = 'type', {'retention_policy_assignment'} + + def __init__( + self, + id: str, + *, + type: RetentionPolicyAssignmentBaseTypeField = RetentionPolicyAssignmentBaseTypeField.RETENTION_POLICY_ASSIGNMENT, + **kwargs + ): + """ + :param id: The unique identifier that represents a file version. + :type id: str + :param type: The value will always be `retention_policy_assignment`., defaults to RetentionPolicyAssignmentBaseTypeField.RETENTION_POLICY_ASSIGNMENT + :type type: RetentionPolicyAssignmentBaseTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/retention_policy_assignments.py b/box_sdk_gen/schemas/retention_policy_assignments.py new file mode 100644 index 000000000..5429d0cfc --- /dev/null +++ b/box_sdk_gen/schemas/retention_policy_assignments.py @@ -0,0 +1,34 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.retention_policy_assignment import RetentionPolicyAssignment + +from box_sdk_gen.box.errors import BoxSDKError + + +class RetentionPolicyAssignments(BaseObject): + def __init__( + self, + *, + entries: Optional[List[RetentionPolicyAssignment]] = None, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: A list of retention policy assignments., defaults to None + :type entries: Optional[List[RetentionPolicyAssignment]], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/retention_policy_base.py b/box_sdk_gen/schemas/retention_policy_base.py new file mode 100644 index 000000000..550261c73 --- /dev/null +++ b/box_sdk_gen/schemas/retention_policy_base.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class RetentionPolicyBaseTypeField(str, Enum): + RETENTION_POLICY = 'retention_policy' + + +class RetentionPolicyBase(BaseObject): + _discriminator = 'type', {'retention_policy'} + + def __init__( + self, + id: str, + *, + type: RetentionPolicyBaseTypeField = RetentionPolicyBaseTypeField.RETENTION_POLICY, + **kwargs + ): + """ + :param id: The unique identifier that represents a retention policy. + :type id: str + :param type: The value will always be `retention_policy`., defaults to RetentionPolicyBaseTypeField.RETENTION_POLICY + :type type: RetentionPolicyBaseTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/retention_policy_mini.py b/box_sdk_gen/schemas/retention_policy_mini.py new file mode 100644 index 000000000..ff8ad0b20 --- /dev/null +++ b/box_sdk_gen/schemas/retention_policy_mini.py @@ -0,0 +1,54 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.retention_policy_base import RetentionPolicyBaseTypeField + +from box_sdk_gen.schemas.retention_policy_base import RetentionPolicyBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class RetentionPolicyMiniDispositionActionField(str, Enum): + PERMANENTLY_DELETE = 'permanently_delete' + REMOVE_RETENTION = 'remove_retention' + + +class RetentionPolicyMini(RetentionPolicyBase): + def __init__( + self, + id: str, + *, + policy_name: Optional[str] = None, + retention_length: Optional[str] = None, + disposition_action: Optional[RetentionPolicyMiniDispositionActionField] = None, + type: RetentionPolicyBaseTypeField = RetentionPolicyBaseTypeField.RETENTION_POLICY, + **kwargs + ): + """ + :param id: The unique identifier that represents a retention policy. + :type id: str + :param policy_name: The name given to the retention policy., defaults to None + :type policy_name: Optional[str], optional + :param retention_length: The length of the retention policy. This value + specifies the duration in days that the retention + policy will be active for after being assigned to + content. If the policy has a `policy_type` of + `indefinite`, the `retention_length` will also be + `indefinite`., defaults to None + :type retention_length: Optional[str], optional + :param disposition_action: The disposition action of the retention policy. + This action can be `permanently_delete`, which + will cause the content retained by the policy + to be permanently deleted, or `remove_retention`, + which will lift the retention policy from the content, + allowing it to be deleted by users, + once the retention policy has expired., defaults to None + :type disposition_action: Optional[RetentionPolicyMiniDispositionActionField], optional + :param type: The value will always be `retention_policy`., defaults to RetentionPolicyBaseTypeField.RETENTION_POLICY + :type type: RetentionPolicyBaseTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.policy_name = policy_name + self.retention_length = retention_length + self.disposition_action = disposition_action diff --git a/box_sdk_gen/schemas/role_variable.py b/box_sdk_gen/schemas/role_variable.py new file mode 100644 index 000000000..9e35facdf --- /dev/null +++ b/box_sdk_gen/schemas/role_variable.py @@ -0,0 +1,47 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class RoleVariableTypeField(str, Enum): + VARIABLE = 'variable' + + +class RoleVariableVariableTypeField(str, Enum): + COLLABORATOR_ROLE = 'collaborator_role' + + +class RoleVariableVariableValueField(str, Enum): + EDITOR = 'editor' + VIEWER = 'viewer' + PREVIEWER = 'previewer' + UPLOADER = 'uploader' + PREVIEWER_UPLOADER = 'previewer uploader' + VIEWER_UPLOADER = 'viewer uploader' + CO_OWNER = 'co-owner' + + +class RoleVariable(BaseObject): + _discriminator = 'type', {'variable'} + + def __init__( + self, + variable_value: RoleVariableVariableValueField, + *, + type: RoleVariableTypeField = RoleVariableTypeField.VARIABLE, + variable_type: RoleVariableVariableTypeField = RoleVariableVariableTypeField.COLLABORATOR_ROLE, + **kwargs + ): + """ + :param type: Role object type., defaults to RoleVariableTypeField.VARIABLE + :type type: RoleVariableTypeField, optional + :param variable_type: The variable type used + by the object., defaults to RoleVariableVariableTypeField.COLLABORATOR_ROLE + :type variable_type: RoleVariableVariableTypeField, optional + """ + super().__init__(**kwargs) + self.variable_value = variable_value + self.type = type + self.variable_type = variable_type diff --git a/box_sdk_gen/schemas/search_result_with_shared_link.py b/box_sdk_gen/schemas/search_result_with_shared_link.py new file mode 100644 index 000000000..e7eaf52e5 --- /dev/null +++ b/box_sdk_gen/schemas/search_result_with_shared_link.py @@ -0,0 +1,37 @@ +from typing import Optional + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + + +class SearchResultWithSharedLink(BaseObject): + def __init__( + self, + *, + accessible_via_shared_link: Optional[str] = None, + item: Optional[Union[FileFull, FolderFull, WebLink]] = None, + type: Optional[str] = None, + **kwargs + ): + """ + :param accessible_via_shared_link: The optional shared link through which the user has access to this + item. This value is only returned for items for which the user has + recently accessed the file through a shared link. For all other + items this value will return `null`., defaults to None + :type accessible_via_shared_link: Optional[str], optional + :param type: The result type. The value is always `search_result`., defaults to None + :type type: Optional[str], optional + """ + super().__init__(**kwargs) + self.accessible_via_shared_link = accessible_via_shared_link + self.item = item + self.type = type diff --git a/box_sdk_gen/schemas/search_results.py b/box_sdk_gen/schemas/search_results.py new file mode 100644 index 000000000..aa30ca8bd --- /dev/null +++ b/box_sdk_gen/schemas/search_results.py @@ -0,0 +1,59 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + + +class SearchResultsTypeField(str, Enum): + SEARCH_RESULTS_ITEMS = 'search_results_items' + + +class SearchResults(BaseObject): + _discriminator = 'type', {'search_results_items'} + + def __init__( + self, + *, + total_count: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + type: SearchResultsTypeField = SearchResultsTypeField.SEARCH_RESULTS_ITEMS, + entries: Optional[List[Union[FileFull, FolderFull, WebLink]]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the search results. + The total number of entries in the collection may be less than + `total_count`., defaults to None + :type total_count: Optional[int], optional + :param limit: The limit that was used for this search. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed., defaults to None + :type limit: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter used., defaults to None + :type offset: Optional[int], optional + :param type: Specifies the response as search result items without shared links., defaults to SearchResultsTypeField.SEARCH_RESULTS_ITEMS + :type type: SearchResultsTypeField, optional + :param entries: The search results for the query provided., defaults to None + :type entries: Optional[List[Union[FileFull, FolderFull, WebLink]]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.limit = limit + self.offset = offset + self.type = type + self.entries = entries diff --git a/box_sdk_gen/schemas/search_results_with_shared_links.py b/box_sdk_gen/schemas/search_results_with_shared_links.py new file mode 100644 index 000000000..cdf023cba --- /dev/null +++ b/box_sdk_gen/schemas/search_results_with_shared_links.py @@ -0,0 +1,57 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.search_result_with_shared_link import ( + SearchResultWithSharedLink, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class SearchResultsWithSharedLinksTypeField(str, Enum): + SEARCH_RESULTS_WITH_SHARED_LINKS = 'search_results_with_shared_links' + + +class SearchResultsWithSharedLinks(BaseObject): + _discriminator = 'type', {'search_results_with_shared_links'} + + def __init__( + self, + *, + total_count: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + type: SearchResultsWithSharedLinksTypeField = SearchResultsWithSharedLinksTypeField.SEARCH_RESULTS_WITH_SHARED_LINKS, + entries: Optional[List[SearchResultWithSharedLink]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the search results. + The total number of entries in the collection may be less than + `total_count`., defaults to None + :type total_count: Optional[int], optional + :param limit: The limit that was used for this search. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed., defaults to None + :type limit: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter used., defaults to None + :type offset: Optional[int], optional + :param type: Specifies the response as search result items with shared links., defaults to SearchResultsWithSharedLinksTypeField.SEARCH_RESULTS_WITH_SHARED_LINKS + :type type: SearchResultsWithSharedLinksTypeField, optional + :param entries: The search results for the query provided, including the + additional information about any shared links through + which the item has been shared with the user., defaults to None + :type entries: Optional[List[SearchResultWithSharedLink]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.limit = limit + self.offset = offset + self.type = type + self.entries = entries diff --git a/box_sdk_gen/schemas/session_termination_message.py b/box_sdk_gen/schemas/session_termination_message.py new file mode 100644 index 000000000..2afe2acdb --- /dev/null +++ b/box_sdk_gen/schemas/session_termination_message.py @@ -0,0 +1,15 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SessionTerminationMessage(BaseObject): + def __init__(self, *, message: Optional[str] = None, **kwargs): + """ + :param message: The unique identifier for the termination job status., defaults to None + :type message: Optional[str], optional + """ + super().__init__(**kwargs) + self.message = message diff --git a/box_sdk_gen/schemas/shield_information_barrier.py b/box_sdk_gen/schemas/shield_information_barrier.py new file mode 100644 index 000000000..efe6b2212 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier.py @@ -0,0 +1,77 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.enterprise_base import EnterpriseBase + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class ShieldInformationBarrierTypeField(str, Enum): + SHIELD_INFORMATION_BARRIER = 'shield_information_barrier' + + +class ShieldInformationBarrierStatusField(str, Enum): + DRAFT = 'draft' + PENDING = 'pending' + DISABLED = 'disabled' + ENABLED = 'enabled' + INVALID = 'invalid' + + +class ShieldInformationBarrier(BaseObject): + _discriminator = 'type', {'shield_information_barrier'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ShieldInformationBarrierTypeField] = None, + enterprise: Optional[EnterpriseBase] = None, + status: Optional[ShieldInformationBarrierStatusField] = None, + created_at: Optional[DateTime] = None, + created_by: Optional[UserBase] = None, + updated_at: Optional[DateTime] = None, + updated_by: Optional[UserBase] = None, + enabled_at: Optional[DateTime] = None, + enabled_by: Optional[UserBase] = None, + **kwargs + ): + """ + :param id: The unique identifier for the shield information barrier., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information barrier., defaults to None + :type type: Optional[ShieldInformationBarrierTypeField], optional + :param enterprise: The `type` and `id` of enterprise this barrier is under., defaults to None + :type enterprise: Optional[EnterpriseBase], optional + :param status: Status of the shield information barrier., defaults to None + :type status: Optional[ShieldInformationBarrierStatusField], optional + :param created_at: ISO date time string when this + shield information barrier object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param created_by: The user who created this shield information barrier., defaults to None + :type created_by: Optional[UserBase], optional + :param updated_at: ISO date time string when this shield information barrier was updated., defaults to None + :type updated_at: Optional[DateTime], optional + :param updated_by: The user that updated this shield information barrier., defaults to None + :type updated_by: Optional[UserBase], optional + :param enabled_at: ISO date time string when this shield information barrier was enabled., defaults to None + :type enabled_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.enterprise = enterprise + self.status = status + self.created_at = created_at + self.created_by = created_by + self.updated_at = updated_at + self.updated_by = updated_by + self.enabled_at = enabled_at + self.enabled_by = enabled_by diff --git a/box_sdk_gen/schemas/shield_information_barrier_base.py b/box_sdk_gen/schemas/shield_information_barrier_base.py new file mode 100644 index 000000000..2181be859 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_base.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierBaseTypeField(str, Enum): + SHIELD_INFORMATION_BARRIER = 'shield_information_barrier' + + +class ShieldInformationBarrierBase(BaseObject): + _discriminator = 'type', {'shield_information_barrier'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ShieldInformationBarrierBaseTypeField] = None, + **kwargs + ): + """ + :param id: The unique identifier for the shield information barrier., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information barrier., defaults to None + :type type: Optional[ShieldInformationBarrierBaseTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/shield_information_barrier_reference.py b/box_sdk_gen/schemas/shield_information_barrier_reference.py new file mode 100644 index 000000000..c2f4debff --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_reference.py @@ -0,0 +1,20 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierReference(BaseObject): + def __init__( + self, + *, + shield_information_barrier: Optional[ShieldInformationBarrierBase] = None, + **kwargs + ): + super().__init__(**kwargs) + self.shield_information_barrier = shield_information_barrier diff --git a/box_sdk_gen/schemas/shield_information_barrier_report.py b/box_sdk_gen/schemas/shield_information_barrier_report.py new file mode 100644 index 000000000..4701838e4 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_report.py @@ -0,0 +1,69 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.shield_information_barrier_report_base import ( + ShieldInformationBarrierReportBaseTypeField, +) + +from box_sdk_gen.schemas.shield_information_barrier_report_base import ( + ShieldInformationBarrierReportBase, +) + +from box_sdk_gen.schemas.shield_information_barrier_reference import ( + ShieldInformationBarrierReference, +) + +from box_sdk_gen.schemas.shield_information_barrier_report_details import ( + ShieldInformationBarrierReportDetails, +) + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class ShieldInformationBarrierReportStatusField(str, Enum): + PENDING = 'pending' + ERROR = 'error' + DONE = 'done' + CANCELLED = 'cancelled' + + +class ShieldInformationBarrierReport(ShieldInformationBarrierReportBase): + def __init__( + self, + *, + shield_information_barrier: Optional[ShieldInformationBarrierReference] = None, + status: Optional[ShieldInformationBarrierReportStatusField] = None, + details: Optional[ShieldInformationBarrierReportDetails] = None, + created_at: Optional[DateTime] = None, + created_by: Optional[UserBase] = None, + updated_at: Optional[DateTime] = None, + id: Optional[str] = None, + type: Optional[ShieldInformationBarrierReportBaseTypeField] = None, + **kwargs + ): + """ + :param status: Status of the shield information report., defaults to None + :type status: Optional[ShieldInformationBarrierReportStatusField], optional + :param created_at: ISO date time string when this + shield information barrier report object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param updated_at: ISO date time string when this + shield information barrier report was updated., defaults to None + :type updated_at: Optional[DateTime], optional + :param id: The unique identifier for the shield information barrier report., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information barrier report., defaults to None + :type type: Optional[ShieldInformationBarrierReportBaseTypeField], optional + """ + super().__init__(id=id, type=type, **kwargs) + self.shield_information_barrier = shield_information_barrier + self.status = status + self.details = details + self.created_at = created_at + self.created_by = created_by + self.updated_at = updated_at diff --git a/box_sdk_gen/schemas/shield_information_barrier_report_base.py b/box_sdk_gen/schemas/shield_information_barrier_report_base.py new file mode 100644 index 000000000..d23f05aad --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_report_base.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierReportBaseTypeField(str, Enum): + SHIELD_INFORMATION_BARRIER_REPORT = 'shield_information_barrier_report' + + +class ShieldInformationBarrierReportBase(BaseObject): + _discriminator = 'type', {'shield_information_barrier_report'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ShieldInformationBarrierReportBaseTypeField] = None, + **kwargs + ): + """ + :param id: The unique identifier for the shield information barrier report., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information barrier report., defaults to None + :type type: Optional[ShieldInformationBarrierReportBaseTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/shield_information_barrier_report_details.py b/box_sdk_gen/schemas/shield_information_barrier_report_details.py new file mode 100644 index 000000000..e26e8e7cf --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_report_details.py @@ -0,0 +1,26 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierReportDetailsDetailsField(BaseObject): + def __init__(self, *, folder_id: Optional[str] = None, **kwargs): + """ + :param folder_id: Folder ID for locating this report., defaults to None + :type folder_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.folder_id = folder_id + + +class ShieldInformationBarrierReportDetails(BaseObject): + def __init__( + self, + *, + details: Optional[ShieldInformationBarrierReportDetailsDetailsField] = None, + **kwargs + ): + super().__init__(**kwargs) + self.details = details diff --git a/box_sdk_gen/schemas/shield_information_barrier_reports.py b/box_sdk_gen/schemas/shield_information_barrier_reports.py new file mode 100644 index 000000000..76c81ab9a --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_reports.py @@ -0,0 +1,37 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.shield_information_barrier_report import ( + ShieldInformationBarrierReport, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierReports(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + entries: Optional[List[ShieldInformationBarrierReport]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param entries: A list of shield information + barrier reports., defaults to None + :type entries: Optional[List[ShieldInformationBarrierReport]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment.py b/box_sdk_gen/schemas/shield_information_barrier_segment.py new file mode 100644 index 000000000..9bd253a10 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_segment.py @@ -0,0 +1,64 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class ShieldInformationBarrierSegmentTypeField(str, Enum): + SHIELD_INFORMATION_BARRIER_SEGMENT = 'shield_information_barrier_segment' + + +class ShieldInformationBarrierSegment(BaseObject): + _discriminator = 'type', {'shield_information_barrier_segment'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ShieldInformationBarrierSegmentTypeField] = None, + shield_information_barrier: Optional[ShieldInformationBarrierBase] = None, + name: Optional[str] = None, + description: Optional[str] = None, + created_at: Optional[DateTime] = None, + created_by: Optional[UserBase] = None, + updated_at: Optional[DateTime] = None, + updated_by: Optional[UserBase] = None, + **kwargs + ): + """ + :param id: The unique identifier for the shield information barrier segment., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information barrier segment., defaults to None + :type type: Optional[ShieldInformationBarrierSegmentTypeField], optional + :param name: Name of the shield information barrier segment., defaults to None + :type name: Optional[str], optional + :param description: Description of the shield information barrier segment., defaults to None + :type description: Optional[str], optional + :param created_at: ISO date time string when this shield information + barrier object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param updated_at: ISO date time string when this + shield information barrier segment was updated., defaults to None + :type updated_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.shield_information_barrier = shield_information_barrier + self.name = name + self.description = description + self.created_at = created_at + self.created_by = created_by + self.updated_at = updated_at + self.updated_by = updated_by diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_member.py b/box_sdk_gen/schemas/shield_information_barrier_segment_member.py new file mode 100644 index 000000000..0a037bc8c --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_member.py @@ -0,0 +1,101 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.shield_information_barrier_segment_member_base import ( + ShieldInformationBarrierSegmentMemberBaseTypeField, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_member_base import ( + ShieldInformationBarrierSegmentMemberBase, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_member_mini import ( + ShieldInformationBarrierSegmentMemberMini, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class ShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentTypeField( + str, Enum +): + SHIELD_INFORMATION_BARRIER_SEGMENT = 'shield_information_barrier_segment' + + +class ShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentField( + BaseObject +): + _discriminator = 'type', {'shield_information_barrier_segment'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ + ShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentTypeField + ] = None, + **kwargs + ): + """ + :param id: The ID reference of the requesting + shield information barrier segment., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information barrier segment., defaults to None + :type type: Optional[ShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class ShieldInformationBarrierSegmentMember(ShieldInformationBarrierSegmentMemberMini): + def __init__( + self, + *, + shield_information_barrier: Optional[ShieldInformationBarrierBase] = None, + shield_information_barrier_segment: Optional[ + ShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentField + ] = None, + created_at: Optional[DateTime] = None, + created_by: Optional[UserBase] = None, + updated_at: Optional[DateTime] = None, + updated_by: Optional[UserBase] = None, + user: Optional[UserBase] = None, + id: Optional[str] = None, + type: Optional[ShieldInformationBarrierSegmentMemberBaseTypeField] = None, + **kwargs + ): + """ + :param shield_information_barrier_segment: The `type` and `id` of the requested + shield information barrier segment., defaults to None + :type shield_information_barrier_segment: Optional[ShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentField], optional + :param created_at: ISO date time string when this shield + information barrier object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param updated_at: ISO date time string when this + shield information barrier segment Member was updated., defaults to None + :type updated_at: Optional[DateTime], optional + :param id: The unique identifier for the + shield information barrier segment member., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information barrier segment member., defaults to None + :type type: Optional[ShieldInformationBarrierSegmentMemberBaseTypeField], optional + """ + super().__init__(user=user, id=id, type=type, **kwargs) + self.shield_information_barrier = shield_information_barrier + self.shield_information_barrier_segment = shield_information_barrier_segment + self.created_at = created_at + self.created_by = created_by + self.updated_at = updated_at + self.updated_by = updated_by diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_member_base.py b/box_sdk_gen/schemas/shield_information_barrier_segment_member_base.py new file mode 100644 index 000000000..28c9158a2 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_member_base.py @@ -0,0 +1,35 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierSegmentMemberBaseTypeField(str, Enum): + SHIELD_INFORMATION_BARRIER_SEGMENT_MEMBER = ( + 'shield_information_barrier_segment_member' + ) + + +class ShieldInformationBarrierSegmentMemberBase(BaseObject): + _discriminator = 'type', {'shield_information_barrier_segment_member'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ShieldInformationBarrierSegmentMemberBaseTypeField] = None, + **kwargs + ): + """ + :param id: The unique identifier for the + shield information barrier segment member., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information barrier segment member., defaults to None + :type type: Optional[ShieldInformationBarrierSegmentMemberBaseTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_member_mini.py b/box_sdk_gen/schemas/shield_information_barrier_segment_member_mini.py new file mode 100644 index 000000000..ef416af8a --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_member_mini.py @@ -0,0 +1,35 @@ +from typing import Optional + +from box_sdk_gen.schemas.shield_information_barrier_segment_member_base import ( + ShieldInformationBarrierSegmentMemberBaseTypeField, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_member_base import ( + ShieldInformationBarrierSegmentMemberBase, +) + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierSegmentMemberMini( + ShieldInformationBarrierSegmentMemberBase +): + def __init__( + self, + *, + user: Optional[UserBase] = None, + id: Optional[str] = None, + type: Optional[ShieldInformationBarrierSegmentMemberBaseTypeField] = None, + **kwargs + ): + """ + :param id: The unique identifier for the + shield information barrier segment member., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information barrier segment member., defaults to None + :type type: Optional[ShieldInformationBarrierSegmentMemberBaseTypeField], optional + """ + super().__init__(id=id, type=type, **kwargs) + self.user = user diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_members.py b/box_sdk_gen/schemas/shield_information_barrier_segment_members.py new file mode 100644 index 000000000..86cd4dca0 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_members.py @@ -0,0 +1,37 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.shield_information_barrier_segment_member import ( + ShieldInformationBarrierSegmentMember, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierSegmentMembers(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + entries: Optional[List[ShieldInformationBarrierSegmentMember]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param entries: A list of shield information + barrier segment members., defaults to None + :type entries: Optional[List[ShieldInformationBarrierSegmentMember]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_restriction.py b/box_sdk_gen/schemas/shield_information_barrier_segment_restriction.py new file mode 100644 index 000000000..2715bcb33 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_restriction.py @@ -0,0 +1,83 @@ +from typing import Optional + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction_base import ( + ShieldInformationBarrierSegmentRestrictionBaseTypeField, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction_base import ( + ShieldInformationBarrierSegmentRestrictionBase, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction_mini import ( + ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentField, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction_mini import ( + ShieldInformationBarrierSegmentRestrictionMiniRestrictedSegmentField, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction_mini import ( + ShieldInformationBarrierSegmentRestrictionMini, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class ShieldInformationBarrierSegmentRestriction( + ShieldInformationBarrierSegmentRestrictionMini +): + def __init__( + self, + shield_information_barrier_segment: ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentField, + restricted_segment: ShieldInformationBarrierSegmentRestrictionMiniRestrictedSegmentField, + *, + shield_information_barrier: Optional[ShieldInformationBarrierBase] = None, + created_at: Optional[DateTime] = None, + created_by: Optional[UserBase] = None, + updated_at: Optional[DateTime] = None, + updated_by: Optional[UserBase] = None, + type: Optional[ShieldInformationBarrierSegmentRestrictionBaseTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param shield_information_barrier_segment: The `type` and `id` of the + requested shield information barrier segment. + :type shield_information_barrier_segment: ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentField + :param restricted_segment: The `type` and `id` of the + restricted shield information barrier segment. + :type restricted_segment: ShieldInformationBarrierSegmentRestrictionMiniRestrictedSegmentField + :param created_at: ISO date time string when this + shield information barrier + Segment Restriction object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param updated_at: ISO date time string when this + shield information barrier segment + Restriction was updated., defaults to None + :type updated_at: Optional[DateTime], optional + :param type: Shield information barrier segment restriction., defaults to None + :type type: Optional[ShieldInformationBarrierSegmentRestrictionBaseTypeField], optional + :param id: The unique identifier for the + shield information barrier segment restriction., defaults to None + :type id: Optional[str], optional + """ + super().__init__( + shield_information_barrier_segment=shield_information_barrier_segment, + restricted_segment=restricted_segment, + type=type, + id=id, + **kwargs + ) + self.shield_information_barrier = shield_information_barrier + self.created_at = created_at + self.created_by = created_by + self.updated_at = updated_at + self.updated_by = updated_by diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_restriction_base.py b/box_sdk_gen/schemas/shield_information_barrier_segment_restriction_base.py new file mode 100644 index 000000000..49dbccf56 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_restriction_base.py @@ -0,0 +1,35 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierSegmentRestrictionBaseTypeField(str, Enum): + SHIELD_INFORMATION_BARRIER_SEGMENT_RESTRICTION = ( + 'shield_information_barrier_segment_restriction' + ) + + +class ShieldInformationBarrierSegmentRestrictionBase(BaseObject): + _discriminator = 'type', {'shield_information_barrier_segment_restriction'} + + def __init__( + self, + *, + type: Optional[ShieldInformationBarrierSegmentRestrictionBaseTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: Shield information barrier segment restriction., defaults to None + :type type: Optional[ShieldInformationBarrierSegmentRestrictionBaseTypeField], optional + :param id: The unique identifier for the + shield information barrier segment restriction., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_restriction_mini.py b/box_sdk_gen/schemas/shield_information_barrier_segment_restriction_mini.py new file mode 100644 index 000000000..a84864fe2 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_restriction_mini.py @@ -0,0 +1,107 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction_base import ( + ShieldInformationBarrierSegmentRestrictionBaseTypeField, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction_base import ( + ShieldInformationBarrierSegmentRestrictionBase, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentTypeField( + str, Enum +): + SHIELD_INFORMATION_BARRIER_SEGMENT = 'shield_information_barrier_segment' + + +class ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentField( + BaseObject +): + _discriminator = 'type', {'shield_information_barrier_segment'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ + ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentTypeField + ] = None, + **kwargs + ): + """ + :param id: The ID reference of the + requesting shield information barrier segment., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information barrier segment., defaults to None + :type type: Optional[ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class ShieldInformationBarrierSegmentRestrictionMiniRestrictedSegmentTypeField( + str, Enum +): + SHIELD_INFORMATION_BARRIER_SEGMENT = 'shield_information_barrier_segment' + + +class ShieldInformationBarrierSegmentRestrictionMiniRestrictedSegmentField(BaseObject): + _discriminator = 'type', {'shield_information_barrier_segment'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ + ShieldInformationBarrierSegmentRestrictionMiniRestrictedSegmentTypeField + ] = None, + **kwargs + ): + """ + :param id: The ID reference of the + restricted shield information barrier segment., defaults to None + :type id: Optional[str], optional + :param type: The type of the shield information segment., defaults to None + :type type: Optional[ShieldInformationBarrierSegmentRestrictionMiniRestrictedSegmentTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class ShieldInformationBarrierSegmentRestrictionMini( + ShieldInformationBarrierSegmentRestrictionBase +): + def __init__( + self, + shield_information_barrier_segment: ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentField, + restricted_segment: ShieldInformationBarrierSegmentRestrictionMiniRestrictedSegmentField, + *, + type: Optional[ShieldInformationBarrierSegmentRestrictionBaseTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param shield_information_barrier_segment: The `type` and `id` of the + requested shield information barrier segment. + :type shield_information_barrier_segment: ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentField + :param restricted_segment: The `type` and `id` of the + restricted shield information barrier segment. + :type restricted_segment: ShieldInformationBarrierSegmentRestrictionMiniRestrictedSegmentField + :param type: Shield information barrier segment restriction., defaults to None + :type type: Optional[ShieldInformationBarrierSegmentRestrictionBaseTypeField], optional + :param id: The unique identifier for the + shield information barrier segment restriction., defaults to None + :type id: Optional[str], optional + """ + super().__init__(type=type, id=id, **kwargs) + self.shield_information_barrier_segment = shield_information_barrier_segment + self.restricted_segment = restricted_segment diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_restrictions.py b/box_sdk_gen/schemas/shield_information_barrier_segment_restrictions.py new file mode 100644 index 000000000..7f8e625c7 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_restrictions.py @@ -0,0 +1,37 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction import ( + ShieldInformationBarrierSegmentRestriction, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierSegmentRestrictions(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + entries: Optional[List[ShieldInformationBarrierSegmentRestriction]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param entries: A list of shield information barrier + segment restriction objects., defaults to None + :type entries: Optional[List[ShieldInformationBarrierSegmentRestriction]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/shield_information_barrier_segments.py b/box_sdk_gen/schemas/shield_information_barrier_segments.py new file mode 100644 index 000000000..8a4ab20d6 --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barrier_segments.py @@ -0,0 +1,37 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.shield_information_barrier_segment import ( + ShieldInformationBarrierSegment, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarrierSegments(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + entries: Optional[List[ShieldInformationBarrierSegment]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param entries: A list of shield information barrier + segments., defaults to None + :type entries: Optional[List[ShieldInformationBarrierSegment]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/shield_information_barriers.py b/box_sdk_gen/schemas/shield_information_barriers.py new file mode 100644 index 000000000..475d83d5b --- /dev/null +++ b/box_sdk_gen/schemas/shield_information_barriers.py @@ -0,0 +1,34 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.shield_information_barrier import ShieldInformationBarrier + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldInformationBarriers(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + entries: Optional[List[ShieldInformationBarrier]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param entries: A list of shield information barrier objects., defaults to None + :type entries: Optional[List[ShieldInformationBarrier]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/sign_request.py b/box_sdk_gen/schemas/sign_request.py new file mode 100644 index 000000000..16d904d1c --- /dev/null +++ b/box_sdk_gen/schemas/sign_request.py @@ -0,0 +1,187 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.sign_request_prefill_tag import SignRequestPrefillTag + +from box_sdk_gen.schemas.sign_request_base import SignRequestBase + +from box_sdk_gen.schemas.file_base import FileBase + +from box_sdk_gen.schemas.sign_request_signer import SignRequestSigner + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class SignRequestTypeField(str, Enum): + SIGN_REQUEST = 'sign-request' + + +class SignRequestStatusField(str, Enum): + CONVERTING = 'converting' + CREATED = 'created' + SENT = 'sent' + VIEWED = 'viewed' + SIGNED = 'signed' + CANCELLED = 'cancelled' + DECLINED = 'declined' + ERROR_CONVERTING = 'error_converting' + ERROR_SENDING = 'error_sending' + EXPIRED = 'expired' + FINALIZING = 'finalizing' + ERROR_FINALIZING = 'error_finalizing' + + +class SignRequestSignFilesField(BaseObject): + def __init__( + self, + *, + files: Optional[List[FileMini]] = None, + is_ready_for_download: Optional[bool] = None, + **kwargs + ): + """ + :param is_ready_for_download: Indicates whether the `sign_files` documents are processing + and the PDFs may be out of date. A change to any document + requires processing on all `sign_files`. We + recommended waiting until processing is finished + (and this value is true) before downloading the PDFs., defaults to None + :type is_ready_for_download: Optional[bool], optional + """ + super().__init__(**kwargs) + self.files = files + self.is_ready_for_download = is_ready_for_download + + +class SignRequest(SignRequestBase): + def __init__( + self, + *, + type: Optional[SignRequestTypeField] = None, + source_files: Optional[List[FileBase]] = None, + signers: Optional[List[SignRequestSigner]] = None, + signature_color: Optional[str] = None, + id: Optional[str] = None, + prepare_url: Optional[str] = None, + signing_log: Optional[FileMini] = None, + status: Optional[SignRequestStatusField] = None, + sign_files: Optional[SignRequestSignFilesField] = None, + auto_expire_at: Optional[DateTime] = None, + parent_folder: Optional[FolderMini] = None, + collaborator_level: Optional[str] = None, + sender_email: Optional[str] = None, + sender_id: Optional[int] = None, + is_document_preparation_needed: Optional[bool] = None, + redirect_url: Optional[str] = None, + declined_redirect_url: Optional[str] = None, + are_text_signatures_enabled: Optional[bool] = None, + email_subject: Optional[str] = None, + email_message: Optional[str] = None, + are_reminders_enabled: Optional[bool] = None, + name: Optional[str] = None, + prefill_tags: Optional[List[SignRequestPrefillTag]] = None, + days_valid: Optional[int] = None, + external_id: Optional[str] = None, + template_id: Optional[str] = None, + external_system_name: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `sign-request`., defaults to None + :type type: Optional[SignRequestTypeField], optional + :param source_files: List of files to create a signing document from. This is currently limited to ten files. Only the ID and type fields are required for each file., defaults to None + :type source_files: Optional[List[FileBase]], optional + :param signers: Array of signers for the signature request., defaults to None + :type signers: Optional[List[SignRequestSigner]], optional + :param signature_color: Force a specific color for the signature (blue, black, or red)., defaults to None + :type signature_color: Optional[str], optional + :param id: Box Sign request ID., defaults to None + :type id: Optional[str], optional + :param prepare_url: This URL is returned if `is_document_preparation_needed` is + set to `true` in the request. The parameter is used to prepare + the signature request + using the UI. The signature request is not + sent until the preparation + phase is complete., defaults to None + :type prepare_url: Optional[str], optional + :param status: Describes the status of the signature request., defaults to None + :type status: Optional[SignRequestStatusField], optional + :param sign_files: List of files that will be signed, which are copies of the original + source files. A new version of these files are created as signers sign + and can be downloaded at any point in the signing process., defaults to None + :type sign_files: Optional[SignRequestSignFilesField], optional + :param auto_expire_at: Uses `days_valid` to calculate the date and time, in GMT, the sign request will expire if unsigned., defaults to None + :type auto_expire_at: Optional[DateTime], optional + :param collaborator_level: The collaborator level of the user to the sign request. Values can include "owner", "editor", and "viewer"., defaults to None + :type collaborator_level: Optional[str], optional + :param sender_email: The email address of the sender of the sign request., defaults to None + :type sender_email: Optional[str], optional + :param sender_id: The user ID of the sender of the sign request., defaults to None + :type sender_id: Optional[int], optional + :param is_document_preparation_needed: Indicates if the sender should receive a `prepare_url` in the response to complete document preparation using the UI., defaults to None + :type is_document_preparation_needed: Optional[bool], optional + :param redirect_url: When specified, the signature request will be redirected to this url when a document is signed., defaults to None + :type redirect_url: Optional[str], optional + :param declined_redirect_url: The uri that a signer will be redirected to after declining to sign a document., defaults to None + :type declined_redirect_url: Optional[str], optional + :param are_text_signatures_enabled: Disables the usage of signatures generated by typing (text)., defaults to None + :type are_text_signatures_enabled: Optional[bool], optional + :param email_subject: Subject of sign request email. This is cleaned by sign request. If this field is not passed, a default subject will be used., defaults to None + :type email_subject: Optional[str], optional + :param email_message: Message to include in sign request email. The field is cleaned through sanitization of specific characters. However, some html tags are allowed. Links included in the message are also converted to hyperlinks in the email. The message may contain the following html tags including `a`, `abbr`, `acronym`, `b`, `blockquote`, `code`, `em`, `i`, `ul`, `li`, `ol`, and `strong`. Be aware that when the text to html ratio is too high, the email may end up in spam filters. Custom styles on these tags are not allowed. If this field is not passed, a default message will be used., defaults to None + :type email_message: Optional[str], optional + :param are_reminders_enabled: Reminds signers to sign a document on day 3, 8, 13 and 18. Reminders are only sent to outstanding signers., defaults to None + :type are_reminders_enabled: Optional[bool], optional + :param name: Name of the signature request., defaults to None + :type name: Optional[str], optional + :param prefill_tags: When a document contains sign-related tags in the content, you can prefill them using this `prefill_tags` by referencing the 'id' of the tag as the `external_id` field of the prefill tag., defaults to None + :type prefill_tags: Optional[List[SignRequestPrefillTag]], optional + :param days_valid: Set the number of days after which the created signature request will automatically expire if not completed. By default, we do not apply any expiration date on signature requests, and the signature request does not expire., defaults to None + :type days_valid: Optional[int], optional + :param external_id: This can be used to reference an ID in an external system that the sign request is related to., defaults to None + :type external_id: Optional[str], optional + :param template_id: When a signature request is created from a template this field will indicate the id of that template., defaults to None + :type template_id: Optional[str], optional + :param external_system_name: Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`., defaults to None + :type external_system_name: Optional[str], optional + """ + super().__init__( + is_document_preparation_needed=is_document_preparation_needed, + redirect_url=redirect_url, + declined_redirect_url=declined_redirect_url, + are_text_signatures_enabled=are_text_signatures_enabled, + email_subject=email_subject, + email_message=email_message, + are_reminders_enabled=are_reminders_enabled, + name=name, + prefill_tags=prefill_tags, + days_valid=days_valid, + external_id=external_id, + template_id=template_id, + external_system_name=external_system_name, + **kwargs + ) + self.type = type + self.source_files = source_files + self.signers = signers + self.signature_color = signature_color + self.id = id + self.prepare_url = prepare_url + self.signing_log = signing_log + self.status = status + self.sign_files = sign_files + self.auto_expire_at = auto_expire_at + self.parent_folder = parent_folder + self.collaborator_level = collaborator_level + self.sender_email = sender_email + self.sender_id = sender_id diff --git a/box_sdk_gen/schemas/sign_request_base.py b/box_sdk_gen/schemas/sign_request_base.py new file mode 100644 index 000000000..181c97249 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_base.py @@ -0,0 +1,72 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.sign_request_prefill_tag import SignRequestPrefillTag + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestBase(BaseObject): + def __init__( + self, + *, + is_document_preparation_needed: Optional[bool] = None, + redirect_url: Optional[str] = None, + declined_redirect_url: Optional[str] = None, + are_text_signatures_enabled: Optional[bool] = None, + email_subject: Optional[str] = None, + email_message: Optional[str] = None, + are_reminders_enabled: Optional[bool] = None, + name: Optional[str] = None, + prefill_tags: Optional[List[SignRequestPrefillTag]] = None, + days_valid: Optional[int] = None, + external_id: Optional[str] = None, + template_id: Optional[str] = None, + external_system_name: Optional[str] = None, + **kwargs + ): + """ + :param is_document_preparation_needed: Indicates if the sender should receive a `prepare_url` in the response to complete document preparation using the UI., defaults to None + :type is_document_preparation_needed: Optional[bool], optional + :param redirect_url: When specified, the signature request will be redirected to this url when a document is signed., defaults to None + :type redirect_url: Optional[str], optional + :param declined_redirect_url: The uri that a signer will be redirected to after declining to sign a document., defaults to None + :type declined_redirect_url: Optional[str], optional + :param are_text_signatures_enabled: Disables the usage of signatures generated by typing (text)., defaults to None + :type are_text_signatures_enabled: Optional[bool], optional + :param email_subject: Subject of sign request email. This is cleaned by sign request. If this field is not passed, a default subject will be used., defaults to None + :type email_subject: Optional[str], optional + :param email_message: Message to include in sign request email. The field is cleaned through sanitization of specific characters. However, some html tags are allowed. Links included in the message are also converted to hyperlinks in the email. The message may contain the following html tags including `a`, `abbr`, `acronym`, `b`, `blockquote`, `code`, `em`, `i`, `ul`, `li`, `ol`, and `strong`. Be aware that when the text to html ratio is too high, the email may end up in spam filters. Custom styles on these tags are not allowed. If this field is not passed, a default message will be used., defaults to None + :type email_message: Optional[str], optional + :param are_reminders_enabled: Reminds signers to sign a document on day 3, 8, 13 and 18. Reminders are only sent to outstanding signers., defaults to None + :type are_reminders_enabled: Optional[bool], optional + :param name: Name of the signature request., defaults to None + :type name: Optional[str], optional + :param prefill_tags: When a document contains sign-related tags in the content, you can prefill them using this `prefill_tags` by referencing the 'id' of the tag as the `external_id` field of the prefill tag., defaults to None + :type prefill_tags: Optional[List[SignRequestPrefillTag]], optional + :param days_valid: Set the number of days after which the created signature request will automatically expire if not completed. By default, we do not apply any expiration date on signature requests, and the signature request does not expire., defaults to None + :type days_valid: Optional[int], optional + :param external_id: This can be used to reference an ID in an external system that the sign request is related to., defaults to None + :type external_id: Optional[str], optional + :param template_id: When a signature request is created from a template this field will indicate the id of that template., defaults to None + :type template_id: Optional[str], optional + :param external_system_name: Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`., defaults to None + :type external_system_name: Optional[str], optional + """ + super().__init__(**kwargs) + self.is_document_preparation_needed = is_document_preparation_needed + self.redirect_url = redirect_url + self.declined_redirect_url = declined_redirect_url + self.are_text_signatures_enabled = are_text_signatures_enabled + self.email_subject = email_subject + self.email_message = email_message + self.are_reminders_enabled = are_reminders_enabled + self.name = name + self.prefill_tags = prefill_tags + self.days_valid = days_valid + self.external_id = external_id + self.template_id = template_id + self.external_system_name = external_system_name diff --git a/box_sdk_gen/schemas/sign_request_create_request.py b/box_sdk_gen/schemas/sign_request_create_request.py new file mode 100644 index 000000000..8d18ce00b --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_create_request.py @@ -0,0 +1,109 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.sign_request_prefill_tag import SignRequestPrefillTag + +from box_sdk_gen.schemas.sign_request_base import SignRequestBase + +from box_sdk_gen.schemas.file_base import FileBase + +from box_sdk_gen.schemas.sign_request_create_signer import SignRequestCreateSigner + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestCreateRequestSignatureColorField(str, Enum): + BLUE = 'blue' + BLACK = 'black' + RED = 'red' + + +class SignRequestCreateRequest(SignRequestBase): + def __init__( + self, + signers: List[SignRequestCreateSigner], + *, + source_files: Optional[List[FileBase]] = None, + signature_color: Optional[SignRequestCreateRequestSignatureColorField] = None, + parent_folder: Optional[FolderMini] = None, + is_document_preparation_needed: Optional[bool] = None, + redirect_url: Optional[str] = None, + declined_redirect_url: Optional[str] = None, + are_text_signatures_enabled: Optional[bool] = None, + email_subject: Optional[str] = None, + email_message: Optional[str] = None, + are_reminders_enabled: Optional[bool] = None, + name: Optional[str] = None, + prefill_tags: Optional[List[SignRequestPrefillTag]] = None, + days_valid: Optional[int] = None, + external_id: Optional[str] = None, + template_id: Optional[str] = None, + external_system_name: Optional[str] = None, + **kwargs + ): + """ + :param signers: Array of signers for the signature request. 35 is the + max number of signers permitted. + + **Note**: It may happen that some signers belong to conflicting [segments](r://shield-information-barrier-segment-member) (user groups). + This means that due to the security policies, users are assigned to segments to prevent exchanges or communication that could lead to ethical conflicts. + In such a case, an attempt to send the sign request will result in an error. + + Read more about [segments and ethical walls](https://support.box.com/hc/en-us/articles/9920431507603-Understanding-Information-Barriers#h_01GFVJEHQA06N7XEZ4GCZ9GFAQ). + :type signers: List[SignRequestCreateSigner] + :param source_files: List of files to create a signing document from. This is currently limited to ten files. Only the ID and type fields are required for each file., defaults to None + :type source_files: Optional[List[FileBase]], optional + :param signature_color: Force a specific color for the signature (blue, black, or red)., defaults to None + :type signature_color: Optional[SignRequestCreateRequestSignatureColorField], optional + :param is_document_preparation_needed: Indicates if the sender should receive a `prepare_url` in the response to complete document preparation using the UI., defaults to None + :type is_document_preparation_needed: Optional[bool], optional + :param redirect_url: When specified, the signature request will be redirected to this url when a document is signed., defaults to None + :type redirect_url: Optional[str], optional + :param declined_redirect_url: The uri that a signer will be redirected to after declining to sign a document., defaults to None + :type declined_redirect_url: Optional[str], optional + :param are_text_signatures_enabled: Disables the usage of signatures generated by typing (text)., defaults to None + :type are_text_signatures_enabled: Optional[bool], optional + :param email_subject: Subject of sign request email. This is cleaned by sign request. If this field is not passed, a default subject will be used., defaults to None + :type email_subject: Optional[str], optional + :param email_message: Message to include in sign request email. The field is cleaned through sanitization of specific characters. However, some html tags are allowed. Links included in the message are also converted to hyperlinks in the email. The message may contain the following html tags including `a`, `abbr`, `acronym`, `b`, `blockquote`, `code`, `em`, `i`, `ul`, `li`, `ol`, and `strong`. Be aware that when the text to html ratio is too high, the email may end up in spam filters. Custom styles on these tags are not allowed. If this field is not passed, a default message will be used., defaults to None + :type email_message: Optional[str], optional + :param are_reminders_enabled: Reminds signers to sign a document on day 3, 8, 13 and 18. Reminders are only sent to outstanding signers., defaults to None + :type are_reminders_enabled: Optional[bool], optional + :param name: Name of the signature request., defaults to None + :type name: Optional[str], optional + :param prefill_tags: When a document contains sign-related tags in the content, you can prefill them using this `prefill_tags` by referencing the 'id' of the tag as the `external_id` field of the prefill tag., defaults to None + :type prefill_tags: Optional[List[SignRequestPrefillTag]], optional + :param days_valid: Set the number of days after which the created signature request will automatically expire if not completed. By default, we do not apply any expiration date on signature requests, and the signature request does not expire., defaults to None + :type days_valid: Optional[int], optional + :param external_id: This can be used to reference an ID in an external system that the sign request is related to., defaults to None + :type external_id: Optional[str], optional + :param template_id: When a signature request is created from a template this field will indicate the id of that template., defaults to None + :type template_id: Optional[str], optional + :param external_system_name: Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`., defaults to None + :type external_system_name: Optional[str], optional + """ + super().__init__( + is_document_preparation_needed=is_document_preparation_needed, + redirect_url=redirect_url, + declined_redirect_url=declined_redirect_url, + are_text_signatures_enabled=are_text_signatures_enabled, + email_subject=email_subject, + email_message=email_message, + are_reminders_enabled=are_reminders_enabled, + name=name, + prefill_tags=prefill_tags, + days_valid=days_valid, + external_id=external_id, + template_id=template_id, + external_system_name=external_system_name, + **kwargs + ) + self.signers = signers + self.source_files = source_files + self.signature_color = signature_color + self.parent_folder = parent_folder diff --git a/box_sdk_gen/schemas/sign_request_create_signer.py b/box_sdk_gen/schemas/sign_request_create_signer.py new file mode 100644 index 000000000..ed62e5cb1 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_create_signer.py @@ -0,0 +1,98 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestCreateSignerRoleField(str, Enum): + SIGNER = 'signer' + APPROVER = 'approver' + FINAL_COPY_READER = 'final_copy_reader' + + +class SignRequestCreateSigner(BaseObject): + def __init__( + self, + *, + email: Optional[str] = None, + role: Optional[SignRequestCreateSignerRoleField] = None, + is_in_person: Optional[bool] = None, + order: Optional[int] = None, + embed_url_external_user_id: Optional[str] = None, + redirect_url: Optional[str] = None, + declined_redirect_url: Optional[str] = None, + login_required: Optional[bool] = None, + verification_phone_number: Optional[str] = None, + password: Optional[str] = None, + signer_group_id: Optional[str] = None, + suppress_notifications: Optional[bool] = None, + **kwargs + ): + """ + :param email: Email address of the signer. + The email address of the signer is required when making signature requests, except when using templates that are configured to include emails., defaults to None + :type email: Optional[str], optional + :param role: Defines the role of the signer in the signature request. A `signer` + must sign the document and an `approver` must approve the document. A + `final_copy_reader` only receives the final signed document and signing + log., defaults to None + :type role: Optional[SignRequestCreateSignerRoleField], optional + :param is_in_person: Used in combination with an embed URL for a sender. After the + sender signs, they are redirected to the next `in_person` signer., defaults to None + :type is_in_person: Optional[bool], optional + :param order: Order of the signer., defaults to None + :type order: Optional[int], optional + :param embed_url_external_user_id: User ID for the signer in an external application responsible + for authentication when accessing the embed URL., defaults to None + :type embed_url_external_user_id: Optional[str], optional + :param redirect_url: The URL that a signer will be redirected + to after signing a document. Defining this URL + overrides default or global redirect URL + settings for a specific signer. + If no declined redirect URL is specified, + this URL will be used for decline actions as well., defaults to None + :type redirect_url: Optional[str], optional + :param declined_redirect_url: The URL that a signer will be redirect + to after declining to sign a document. + Defining this URL overrides default or global + declined redirect URL settings for a specific signer., defaults to None + :type declined_redirect_url: Optional[str], optional + :param login_required: If set to true, the signer will need to log in to a Box account + before signing the request. If the signer does not have + an existing account, they will have the option to create + a free Box account., defaults to None + :type login_required: Optional[bool], optional + :param verification_phone_number: If set, this phone number will be used to verify the signer + via two-factor authentication before they are able to sign the document. + Cannot be selected in combination with `login_required`., defaults to None + :type verification_phone_number: Optional[str], optional + :param password: If set, the signer is required to enter the password before they are able + to sign a document. This field is write only., defaults to None + :type password: Optional[str], optional + :param signer_group_id: If set, signers who have the same value will be assigned to the same input and to the same signer group. + A signer group is not a Box Group. It is an entity that belongs to a Sign Request and can only be + used/accessed within this Sign Request. A signer group is expected to have more than one signer. + If the provided value is only used for one signer, this value will be ignored and request will be handled + as it was intended for an individual signer. The value provided can be any string and only used to + determine which signers belongs to same group. A successful response will provide a generated UUID value + instead for signers in the same signer group., defaults to None + :type signer_group_id: Optional[str], optional + :param suppress_notifications: If true, no emails about the sign request will be sent., defaults to None + :type suppress_notifications: Optional[bool], optional + """ + super().__init__(**kwargs) + self.email = email + self.role = role + self.is_in_person = is_in_person + self.order = order + self.embed_url_external_user_id = embed_url_external_user_id + self.redirect_url = redirect_url + self.declined_redirect_url = declined_redirect_url + self.login_required = login_required + self.verification_phone_number = verification_phone_number + self.password = password + self.signer_group_id = signer_group_id + self.suppress_notifications = suppress_notifications diff --git a/box_sdk_gen/schemas/sign_request_prefill_tag.py b/box_sdk_gen/schemas/sign_request_prefill_tag.py new file mode 100644 index 000000000..88eb1a90c --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_prefill_tag.py @@ -0,0 +1,34 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import Date + + +class SignRequestPrefillTag(BaseObject): + def __init__( + self, + *, + document_tag_id: Optional[str] = None, + text_value: Optional[str] = None, + checkbox_value: Optional[bool] = None, + date_value: Optional[Date] = None, + **kwargs + ): + """ + :param document_tag_id: This references the ID of a specific tag contained in a file of the signature request., defaults to None + :type document_tag_id: Optional[str], optional + :param text_value: Text prefill value., defaults to None + :type text_value: Optional[str], optional + :param checkbox_value: Checkbox prefill value., defaults to None + :type checkbox_value: Optional[bool], optional + :param date_value: Date prefill value., defaults to None + :type date_value: Optional[Date], optional + """ + super().__init__(**kwargs) + self.document_tag_id = document_tag_id + self.text_value = text_value + self.checkbox_value = checkbox_value + self.date_value = date_value diff --git a/box_sdk_gen/schemas/sign_request_signer.py b/box_sdk_gen/schemas/sign_request_signer.py new file mode 100644 index 000000000..e9575f17e --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer.py @@ -0,0 +1,159 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.sign_request_create_signer import ( + SignRequestCreateSignerRoleField, +) + +from box_sdk_gen.schemas.sign_request_create_signer import SignRequestCreateSigner + +from box_sdk_gen.schemas.sign_request_signer_input import SignRequestSignerInput + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class SignRequestSignerSignerDecisionTypeField(str, Enum): + SIGNED = 'signed' + DECLINED = 'declined' + + +class SignRequestSignerSignerDecisionField(BaseObject): + _discriminator = 'type', {'signed', 'declined'} + + def __init__( + self, + *, + type: Optional[SignRequestSignerSignerDecisionTypeField] = None, + finalized_at: Optional[DateTime] = None, + additional_info: Optional[str] = None, + **kwargs + ): + """ + :param type: Type of decision made by the signer., defaults to None + :type type: Optional[SignRequestSignerSignerDecisionTypeField], optional + :param finalized_at: Date and Time that the decision was made., defaults to None + :type finalized_at: Optional[DateTime], optional + :param additional_info: Additional info about the decision, such as the decline reason from the signer., defaults to None + :type additional_info: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.finalized_at = finalized_at + self.additional_info = additional_info + + +class SignRequestSigner(SignRequestCreateSigner): + def __init__( + self, + *, + has_viewed_document: Optional[bool] = None, + signer_decision: Optional[SignRequestSignerSignerDecisionField] = None, + inputs: Optional[List[SignRequestSignerInput]] = None, + embed_url: Optional[str] = None, + iframeable_embed_url: Optional[str] = None, + email: Optional[str] = None, + role: Optional[SignRequestCreateSignerRoleField] = None, + is_in_person: Optional[bool] = None, + order: Optional[int] = None, + embed_url_external_user_id: Optional[str] = None, + redirect_url: Optional[str] = None, + declined_redirect_url: Optional[str] = None, + login_required: Optional[bool] = None, + verification_phone_number: Optional[str] = None, + password: Optional[str] = None, + signer_group_id: Optional[str] = None, + suppress_notifications: Optional[bool] = None, + **kwargs + ): + """ + :param has_viewed_document: Set to `true` if the signer views the document., defaults to None + :type has_viewed_document: Optional[bool], optional + :param signer_decision: Final decision made by the signer., defaults to None + :type signer_decision: Optional[SignRequestSignerSignerDecisionField], optional + :param embed_url: URL to direct a signer to for signing., defaults to None + :type embed_url: Optional[str], optional + :param iframeable_embed_url: This URL is specifically designed for + signing documents within an HTML `iframe` tag. + It will be returned in the response + only if the `embed_url_external_user_id` + parameter was passed in the + `create Box Sign request` call., defaults to None + :type iframeable_embed_url: Optional[str], optional + :param email: Email address of the signer. + The email address of the signer is required when making signature requests, except when using templates that are configured to include emails., defaults to None + :type email: Optional[str], optional + :param role: Defines the role of the signer in the signature request. A `signer` + must sign the document and an `approver` must approve the document. A + `final_copy_reader` only receives the final signed document and signing + log., defaults to None + :type role: Optional[SignRequestCreateSignerRoleField], optional + :param is_in_person: Used in combination with an embed URL for a sender. After the + sender signs, they are redirected to the next `in_person` signer., defaults to None + :type is_in_person: Optional[bool], optional + :param order: Order of the signer., defaults to None + :type order: Optional[int], optional + :param embed_url_external_user_id: User ID for the signer in an external application responsible + for authentication when accessing the embed URL., defaults to None + :type embed_url_external_user_id: Optional[str], optional + :param redirect_url: The URL that a signer will be redirected + to after signing a document. Defining this URL + overrides default or global redirect URL + settings for a specific signer. + If no declined redirect URL is specified, + this URL will be used for decline actions as well., defaults to None + :type redirect_url: Optional[str], optional + :param declined_redirect_url: The URL that a signer will be redirect + to after declining to sign a document. + Defining this URL overrides default or global + declined redirect URL settings for a specific signer., defaults to None + :type declined_redirect_url: Optional[str], optional + :param login_required: If set to true, the signer will need to log in to a Box account + before signing the request. If the signer does not have + an existing account, they will have the option to create + a free Box account., defaults to None + :type login_required: Optional[bool], optional + :param verification_phone_number: If set, this phone number will be used to verify the signer + via two-factor authentication before they are able to sign the document. + Cannot be selected in combination with `login_required`., defaults to None + :type verification_phone_number: Optional[str], optional + :param password: If set, the signer is required to enter the password before they are able + to sign a document. This field is write only., defaults to None + :type password: Optional[str], optional + :param signer_group_id: If set, signers who have the same value will be assigned to the same input and to the same signer group. + A signer group is not a Box Group. It is an entity that belongs to a Sign Request and can only be + used/accessed within this Sign Request. A signer group is expected to have more than one signer. + If the provided value is only used for one signer, this value will be ignored and request will be handled + as it was intended for an individual signer. The value provided can be any string and only used to + determine which signers belongs to same group. A successful response will provide a generated UUID value + instead for signers in the same signer group., defaults to None + :type signer_group_id: Optional[str], optional + :param suppress_notifications: If true, no emails about the sign request will be sent., defaults to None + :type suppress_notifications: Optional[bool], optional + """ + super().__init__( + email=email, + role=role, + is_in_person=is_in_person, + order=order, + embed_url_external_user_id=embed_url_external_user_id, + redirect_url=redirect_url, + declined_redirect_url=declined_redirect_url, + login_required=login_required, + verification_phone_number=verification_phone_number, + password=password, + signer_group_id=signer_group_id, + suppress_notifications=suppress_notifications, + **kwargs + ) + self.has_viewed_document = has_viewed_document + self.signer_decision = signer_decision + self.inputs = inputs + self.embed_url = embed_url + self.iframeable_embed_url = iframeable_embed_url diff --git a/box_sdk_gen/schemas/sign_request_signer_input.py b/box_sdk_gen/schemas/sign_request_signer_input.py new file mode 100644 index 000000000..20067e3f3 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input.py @@ -0,0 +1,81 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.utils import Date + +from box_sdk_gen.schemas.sign_request_prefill_tag import SignRequestPrefillTag + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputTypeField(str, Enum): + SIGNATURE = 'signature' + DATE = 'date' + TEXT = 'text' + CHECKBOX = 'checkbox' + RADIO = 'radio' + DROPDOWN = 'dropdown' + + +class SignRequestSignerInputContentTypeField(str, Enum): + SIGNATURE = 'signature' + INITIAL = 'initial' + STAMP = 'stamp' + DATE = 'date' + CHECKBOX = 'checkbox' + TEXT = 'text' + FULL_NAME = 'full_name' + FIRST_NAME = 'first_name' + LAST_NAME = 'last_name' + COMPANY = 'company' + TITLE = 'title' + EMAIL = 'email' + ATTACHMENT = 'attachment' + RADIO = 'radio' + DROPDOWN = 'dropdown' + + +class SignRequestSignerInput(SignRequestPrefillTag): + def __init__( + self, + page_index: int, + *, + type: Optional[SignRequestSignerInputTypeField] = None, + content_type: Optional[SignRequestSignerInputContentTypeField] = None, + read_only: Optional[bool] = None, + document_tag_id: Optional[str] = None, + text_value: Optional[str] = None, + checkbox_value: Optional[bool] = None, + date_value: Optional[Date] = None, + **kwargs + ): + """ + :param page_index: Index of page that the input is on. + :type page_index: int + :param type: Type of input., defaults to None + :type type: Optional[SignRequestSignerInputTypeField], optional + :param content_type: Content type of input., defaults to None + :type content_type: Optional[SignRequestSignerInputContentTypeField], optional + :param read_only: Whether this input was defined as read-only(immutable by signers) or not., defaults to None + :type read_only: Optional[bool], optional + :param document_tag_id: This references the ID of a specific tag contained in a file of the signature request., defaults to None + :type document_tag_id: Optional[str], optional + :param text_value: Text prefill value., defaults to None + :type text_value: Optional[str], optional + :param checkbox_value: Checkbox prefill value., defaults to None + :type checkbox_value: Optional[bool], optional + :param date_value: Date prefill value., defaults to None + :type date_value: Optional[Date], optional + """ + super().__init__( + document_tag_id=document_tag_id, + text_value=text_value, + checkbox_value=checkbox_value, + date_value=date_value, + **kwargs + ) + self.page_index = page_index + self.type = type + self.content_type = content_type + self.read_only = read_only diff --git a/box_sdk_gen/schemas/sign_requests.py b/box_sdk_gen/schemas/sign_requests.py new file mode 100644 index 000000000..71c88ebf2 --- /dev/null +++ b/box_sdk_gen/schemas/sign_requests.py @@ -0,0 +1,34 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.sign_request import SignRequest + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequests(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + entries: Optional[List[SignRequest]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param entries: A list of Box Sign requests., defaults to None + :type entries: Optional[List[SignRequest]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/sign_template.py b/box_sdk_gen/schemas/sign_template.py new file mode 100644 index 000000000..08f7ca684 --- /dev/null +++ b/box_sdk_gen/schemas/sign_template.py @@ -0,0 +1,251 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.schemas.template_signer import TemplateSigner + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignTemplateTypeField(str, Enum): + SIGN_TEMPLATE = 'sign-template' + + +class SignTemplateAdditionalInfoNonEditableField(str, Enum): + EMAIL_SUBJECT = 'email_subject' + EMAIL_MESSAGE = 'email_message' + NAME = 'name' + DAYS_VALID = 'days_valid' + SIGNERS = 'signers' + SOURCE_FILES = 'source_files' + + +class SignTemplateAdditionalInfoRequiredSignersField(str, Enum): + EMAIL = 'email' + + +class SignTemplateAdditionalInfoRequiredField(BaseObject): + def __init__( + self, + *, + signers: Optional[ + List[List[SignTemplateAdditionalInfoRequiredSignersField]] + ] = None, + **kwargs + ): + """ + :param signers: Required signer fields., defaults to None + :type signers: Optional[List[List[SignTemplateAdditionalInfoRequiredSignersField]]], optional + """ + super().__init__(**kwargs) + self.signers = signers + + +class SignTemplateAdditionalInfoField(BaseObject): + def __init__( + self, + *, + non_editable: Optional[List[SignTemplateAdditionalInfoNonEditableField]] = None, + required: Optional[SignTemplateAdditionalInfoRequiredField] = None, + **kwargs + ): + """ + :param non_editable: Non editable fields., defaults to None + :type non_editable: Optional[List[SignTemplateAdditionalInfoNonEditableField]], optional + :param required: Required fields., defaults to None + :type required: Optional[SignTemplateAdditionalInfoRequiredField], optional + """ + super().__init__(**kwargs) + self.non_editable = non_editable + self.required = required + + +class SignTemplateReadySignLinkField(BaseObject): + def __init__( + self, + *, + url: Optional[str] = None, + name: Optional[str] = None, + instructions: Optional[str] = None, + folder_id: Optional[str] = None, + is_notification_disabled: Optional[bool] = None, + is_active: Optional[bool] = None, + **kwargs + ): + """ + :param url: The URL that can be sent to signers., defaults to None + :type url: Optional[str], optional + :param name: Request name., defaults to None + :type name: Optional[str], optional + :param instructions: Extra instructions for all signers., defaults to None + :type instructions: Optional[str], optional + :param folder_id: The destination folder to place final, + signed document and signing + log. Only `ID` and `type` fields are required. + The root folder, + folder ID `0`, cannot be used., defaults to None + :type folder_id: Optional[str], optional + :param is_notification_disabled: Whether to disable notifications when + a signer has signed., defaults to None + :type is_notification_disabled: Optional[bool], optional + :param is_active: Whether the ready sign link is enabled or not., defaults to None + :type is_active: Optional[bool], optional + """ + super().__init__(**kwargs) + self.url = url + self.name = name + self.instructions = instructions + self.folder_id = folder_id + self.is_notification_disabled = is_notification_disabled + self.is_active = is_active + + +class SignTemplateCustomBrandingField(BaseObject): + def __init__( + self, + *, + company_name: Optional[str] = None, + logo_uri: Optional[str] = None, + branding_color: Optional[str] = None, + email_footer_text: Optional[str] = None, + **kwargs + ): + """ + :param company_name: Name of the company., defaults to None + :type company_name: Optional[str], optional + :param logo_uri: Custom branding logo URI in the form of a base64 image., defaults to None + :type logo_uri: Optional[str], optional + :param branding_color: Custom branding color in hex., defaults to None + :type branding_color: Optional[str], optional + :param email_footer_text: Content of the email footer., defaults to None + :type email_footer_text: Optional[str], optional + """ + super().__init__(**kwargs) + self.company_name = company_name + self.logo_uri = logo_uri + self.branding_color = branding_color + self.email_footer_text = email_footer_text + + +class SignTemplate(BaseObject): + _discriminator = 'type', {'sign-template'} + + def __init__( + self, + *, + type: Optional[SignTemplateTypeField] = None, + id: Optional[str] = None, + name: Optional[str] = None, + email_subject: Optional[str] = None, + email_message: Optional[str] = None, + days_valid: Optional[int] = None, + parent_folder: Optional[FolderMini] = None, + source_files: Optional[List[FileMini]] = None, + are_fields_locked: Optional[bool] = None, + are_options_locked: Optional[bool] = None, + are_recipients_locked: Optional[bool] = None, + are_email_settings_locked: Optional[bool] = None, + are_files_locked: Optional[bool] = None, + signers: Optional[List[TemplateSigner]] = None, + additional_info: Optional[SignTemplateAdditionalInfoField] = None, + ready_sign_link: Optional[SignTemplateReadySignLinkField] = None, + custom_branding: Optional[SignTemplateCustomBrandingField] = None, + **kwargs + ): + """ + :param type: The value will always be `sign-template`., defaults to None + :type type: Optional[SignTemplateTypeField], optional + :param id: Template identifier., defaults to None + :type id: Optional[str], optional + :param name: The name of the template., defaults to None + :type name: Optional[str], optional + :param email_subject: Subject of signature request email. This is cleaned by sign + request. If this field is not passed, a default subject will be used., defaults to None + :type email_subject: Optional[str], optional + :param email_message: Message to include in signature request email. The field + is cleaned through sanitization of specific characters. However, + some html tags are allowed. Links included in the + message are also converted to hyperlinks in the email. The + message may contain the following html tags including `a`, `abbr`, + `acronym`, `b`, `blockquote`, `code`, `em`, `i`, `ul`, `li`, `ol`, and + `strong`. Be aware that when the text + to html ratio is too high, the email + may end up in spam filters. Custom styles on + these tags are not allowed. + If this field is not passed, a default message will be used., defaults to None + :type email_message: Optional[str], optional + :param days_valid: Set the number of days after which the + created signature request will automatically + expire if not completed. By default, we do + not apply any expiration date on signature + requests, and the signature request does not expire., defaults to None + :type days_valid: Optional[int], optional + :param source_files: List of files to create a signing document from. + Only the ID and type fields are required + for each file., defaults to None + :type source_files: Optional[List[FileMini]], optional + :param are_fields_locked: Indicates if the template input + fields are editable or not., defaults to None + :type are_fields_locked: Optional[bool], optional + :param are_options_locked: Indicates if the template document options + are editable or not, + for example renaming the document., defaults to None + :type are_options_locked: Optional[bool], optional + :param are_recipients_locked: Indicates if the template signers are editable or not., defaults to None + :type are_recipients_locked: Optional[bool], optional + :param are_email_settings_locked: Indicates if the template email settings are editable or not., defaults to None + :type are_email_settings_locked: Optional[bool], optional + :param are_files_locked: Indicates if the template files are editable or not. + This includes deleting or renaming template files., defaults to None + :type are_files_locked: Optional[bool], optional + :param signers: Array of signers for the template. + + **Note**: It may happen that some signers specified in the template belong to conflicting [segments](r://shield-information-barrier-segment-member) (user groups). + This means that due to the security policies, users are assigned to segments to prevent exchanges or communication that could lead to ethical conflicts. + In such a case, an attempt to send a sign request based on a template that lists signers in conflicting segments will result in an error. + + Read more about [segments and ethical walls](https://support.box.com/hc/en-us/articles/9920431507603-Understanding-Information-Barriers#h_01GFVJEHQA06N7XEZ4GCZ9GFAQ)., defaults to None + :type signers: Optional[List[TemplateSigner]], optional + :param additional_info: Additional information on which fields are + required and which fields are not editable., defaults to None + :type additional_info: Optional[SignTemplateAdditionalInfoField], optional + :param ready_sign_link: Box's ready-sign link feature enables you to create a + link to a signature request that + you've created from a template. Use this link + when you want to post a signature request + on a public form — such as an email, social media post, + or web page — without knowing who the signers will be. + Note: The ready-sign link feature is + limited to Enterprise Plus customers and not + available to Box Verified Enterprises., defaults to None + :type ready_sign_link: Optional[SignTemplateReadySignLinkField], optional + :param custom_branding: Custom branding applied to notifications + and signature requests., defaults to None + :type custom_branding: Optional[SignTemplateCustomBrandingField], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.name = name + self.email_subject = email_subject + self.email_message = email_message + self.days_valid = days_valid + self.parent_folder = parent_folder + self.source_files = source_files + self.are_fields_locked = are_fields_locked + self.are_options_locked = are_options_locked + self.are_recipients_locked = are_recipients_locked + self.are_email_settings_locked = are_email_settings_locked + self.are_files_locked = are_files_locked + self.signers = signers + self.additional_info = additional_info + self.ready_sign_link = ready_sign_link + self.custom_branding = custom_branding diff --git a/box_sdk_gen/schemas/sign_templates.py b/box_sdk_gen/schemas/sign_templates.py new file mode 100644 index 000000000..00f7f744e --- /dev/null +++ b/box_sdk_gen/schemas/sign_templates.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.sign_template import SignTemplate + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignTemplates(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[SignTemplate]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of templates., defaults to None + :type entries: Optional[List[SignTemplate]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/skill_cards_metadata.py b/box_sdk_gen/schemas/skill_cards_metadata.py new file mode 100644 index 000000000..0c2135962 --- /dev/null +++ b/box_sdk_gen/schemas/skill_cards_metadata.py @@ -0,0 +1,102 @@ +from typing import Optional + +from typing import List + +from typing import Union + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCard + +from box_sdk_gen.schemas.timeline_skill_card import TimelineSkillCard + +from box_sdk_gen.schemas.transcript_skill_card import TranscriptSkillCard + +from box_sdk_gen.schemas.status_skill_card import StatusSkillCard + +from box_sdk_gen.box.errors import BoxSDKError + + +class SkillCardsMetadata(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'can_edit': '$canEdit', + 'id': '$id', + 'parent': '$parent', + 'scope': '$scope', + 'template': '$template', + 'type': '$type', + 'type_version': '$typeVersion', + 'version': '$version', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + '$canEdit': 'can_edit', + '$id': 'id', + '$parent': 'parent', + '$scope': 'scope', + '$template': 'template', + '$type': 'type', + '$typeVersion': 'type_version', + '$version': 'version', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + can_edit: Optional[bool] = None, + id: Optional[str] = None, + parent: Optional[str] = None, + scope: Optional[str] = None, + template: Optional[str] = None, + type: Optional[str] = None, + type_version: Optional[int] = None, + version: Optional[int] = None, + cards: Optional[ + List[ + Union[ + KeywordSkillCard, + TimelineSkillCard, + TranscriptSkillCard, + StatusSkillCard, + ] + ] + ] = None, + **kwargs + ): + """ + :param can_edit: Whether the user can edit this metadata., defaults to None + :type can_edit: Optional[bool], optional + :param id: A UUID to identify the metadata object., defaults to None + :type id: Optional[str], optional + :param parent: An ID for the parent folder., defaults to None + :type parent: Optional[str], optional + :param scope: An ID for the scope in which this template + has been applied., defaults to None + :type scope: Optional[str], optional + :param template: The name of the template., defaults to None + :type template: Optional[str], optional + :param type: A unique identifier for the "type" of this instance. This is an internal + system property and should not be used by a client application., defaults to None + :type type: Optional[str], optional + :param type_version: The last-known version of the template of the object. This is an internal + system property and should not be used by a client application., defaults to None + :type type_version: Optional[int], optional + :param version: The version of the metadata object. Starts at 0 and increases every time + a user-defined property is modified., defaults to None + :type version: Optional[int], optional + :param cards: A list of Box Skill cards that have been applied to this file., defaults to None + :type cards: Optional[List[Union[KeywordSkillCard, TimelineSkillCard, TranscriptSkillCard, StatusSkillCard]]], optional + """ + super().__init__(**kwargs) + self.can_edit = can_edit + self.id = id + self.parent = parent + self.scope = scope + self.template = template + self.type = type + self.type_version = type_version + self.version = version + self.cards = cards diff --git a/box_sdk_gen/schemas/skill_invocation.py b/box_sdk_gen/schemas/skill_invocation.py new file mode 100644 index 000000000..29c9f0e32 --- /dev/null +++ b/box_sdk_gen/schemas/skill_invocation.py @@ -0,0 +1,257 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Union + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.schemas.event import Event + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class SkillInvocationTypeField(str, Enum): + SKILL_INVOCATION = 'skill_invocation' + + +class SkillInvocationSkillTypeField(str, Enum): + SKILL = 'skill' + + +class SkillInvocationSkillField(BaseObject): + _discriminator = 'type', {'skill'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[SkillInvocationSkillTypeField] = None, + name: Optional[str] = None, + api_key: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this skill., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `skill`., defaults to None + :type type: Optional[SkillInvocationSkillTypeField], optional + :param name: The name of the skill., defaults to None + :type name: Optional[str], optional + :param api_key: The client ID of the application., defaults to None + :type api_key: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + self.api_key = api_key + + +class SkillInvocationTokenReadTokenTypeField(str, Enum): + BEARER = 'bearer' + + +class SkillInvocationTokenReadField(BaseObject): + def __init__( + self, + *, + access_token: Optional[str] = None, + expires_in: Optional[int] = None, + token_type: Optional[SkillInvocationTokenReadTokenTypeField] = None, + restricted_to: Optional[str] = None, + **kwargs + ): + """ + :param access_token: The requested access token., defaults to None + :type access_token: Optional[str], optional + :param expires_in: The time in seconds by which this token will expire., defaults to None + :type expires_in: Optional[int], optional + :param token_type: The type of access token returned., defaults to None + :type token_type: Optional[SkillInvocationTokenReadTokenTypeField], optional + :param restricted_to: The permissions that this access token permits, + providing a list of resources (files, folders, etc) + and the scopes permitted for each of those resources., defaults to None + :type restricted_to: Optional[str], optional + """ + super().__init__(**kwargs) + self.access_token = access_token + self.expires_in = expires_in + self.token_type = token_type + self.restricted_to = restricted_to + + +class SkillInvocationTokenWriteTokenTypeField(str, Enum): + BEARER = 'bearer' + + +class SkillInvocationTokenWriteField(BaseObject): + def __init__( + self, + *, + access_token: Optional[str] = None, + expires_in: Optional[int] = None, + token_type: Optional[SkillInvocationTokenWriteTokenTypeField] = None, + restricted_to: Optional[str] = None, + **kwargs + ): + """ + :param access_token: The requested access token., defaults to None + :type access_token: Optional[str], optional + :param expires_in: The time in seconds by which this token will expire., defaults to None + :type expires_in: Optional[int], optional + :param token_type: The type of access token returned., defaults to None + :type token_type: Optional[SkillInvocationTokenWriteTokenTypeField], optional + :param restricted_to: The permissions that this access token permits, + providing a list of resources (files, folders, etc) + and the scopes permitted for each of those resources., defaults to None + :type restricted_to: Optional[str], optional + """ + super().__init__(**kwargs) + self.access_token = access_token + self.expires_in = expires_in + self.token_type = token_type + self.restricted_to = restricted_to + + +class SkillInvocationTokenField(BaseObject): + def __init__( + self, + *, + read: Optional[SkillInvocationTokenReadField] = None, + write: Optional[SkillInvocationTokenWriteField] = None, + **kwargs + ): + """ + :param read: The basics of an access token., defaults to None + :type read: Optional[SkillInvocationTokenReadField], optional + :param write: The basics of an access token., defaults to None + :type write: Optional[SkillInvocationTokenWriteField], optional + """ + super().__init__(**kwargs) + self.read = read + self.write = write + + +class SkillInvocationStatusStateField(str, Enum): + INVOKED = 'invoked' + PROCESSING = 'processing' + SUCCESS = 'success' + TRANSIENT_FAILURE = 'transient_failure' + PERMANENT_FAILURE = 'permanent_failure' + + +class SkillInvocationStatusField(BaseObject): + def __init__( + self, + *, + state: Optional[SkillInvocationStatusStateField] = None, + message: Optional[str] = None, + error_code: Optional[str] = None, + additional_info: Optional[str] = None, + **kwargs + ): + """ + :param state: The state of this event. + + * `invoked` - Triggered the skill with event details to start + applying skill on the file. + * `processing` - Currently processing. + * `success` - Completed processing with a success. + * `transient_failure` - Encountered an issue which can be + retried. + * `permanent_failure` - Encountered a permanent issue and + retry would not help., defaults to None + :type state: Optional[SkillInvocationStatusStateField], optional + :param message: Status information., defaults to None + :type message: Optional[str], optional + :param error_code: Error code information, if error occurred., defaults to None + :type error_code: Optional[str], optional + :param additional_info: Additional status information., defaults to None + :type additional_info: Optional[str], optional + """ + super().__init__(**kwargs) + self.state = state + self.message = message + self.error_code = error_code + self.additional_info = additional_info + + +class SkillInvocationEnterpriseTypeField(str, Enum): + ENTERPRISE = 'enterprise' + + +class SkillInvocationEnterpriseField(BaseObject): + _discriminator = 'type', {'enterprise'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[SkillInvocationEnterpriseTypeField] = None, + name: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this enterprise., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `enterprise`., defaults to None + :type type: Optional[SkillInvocationEnterpriseTypeField], optional + :param name: The name of the enterprise., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + + +class SkillInvocation(BaseObject): + _discriminator = 'type', {'skill_invocation'} + + def __init__( + self, + *, + type: Optional[SkillInvocationTypeField] = None, + id: Optional[str] = None, + skill: Optional[SkillInvocationSkillField] = None, + token: Optional[SkillInvocationTokenField] = None, + status: Optional[SkillInvocationStatusField] = None, + created_at: Optional[DateTime] = None, + trigger: Optional[str] = None, + enterprise: Optional[SkillInvocationEnterpriseField] = None, + source: Optional[Union[File, Folder]] = None, + event: Optional[Event] = None, + **kwargs + ): + """ + :param type: The value will always be `skill_invocation`., defaults to None + :type type: Optional[SkillInvocationTypeField], optional + :param id: Unique identifier for the invocation request., defaults to None + :type id: Optional[str], optional + :param token: The read-only and read-write access tokens for this item., defaults to None + :type token: Optional[SkillInvocationTokenField], optional + :param status: The details status of this event., defaults to None + :type status: Optional[SkillInvocationStatusField], optional + :param created_at: The time this invocation was created., defaults to None + :type created_at: Optional[DateTime], optional + :param trigger: Action that triggered the invocation., defaults to None + :type trigger: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.skill = skill + self.token = token + self.status = status + self.created_at = created_at + self.trigger = trigger + self.enterprise = enterprise + self.source = source + self.event = event diff --git a/box_sdk_gen/schemas/status_skill_card.py b/box_sdk_gen/schemas/status_skill_card.py new file mode 100644 index 000000000..8ae1d2bb3 --- /dev/null +++ b/box_sdk_gen/schemas/status_skill_card.py @@ -0,0 +1,158 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class StatusSkillCardTypeField(str, Enum): + SKILL_CARD = 'skill_card' + + +class StatusSkillCardSkillCardTypeField(str, Enum): + STATUS = 'status' + + +class StatusSkillCardSkillCardTitleField(BaseObject): + def __init__(self, message: str, *, code: Optional[str] = None, **kwargs): + """ + :param message: The actual title to show in the UI. + :type message: str + :param code: An optional identifier for the title., defaults to None + :type code: Optional[str], optional + """ + super().__init__(**kwargs) + self.message = message + self.code = code + + +class StatusSkillCardStatusCodeField(str, Enum): + INVOKED = 'invoked' + PROCESSING = 'processing' + SUCCESS = 'success' + TRANSIENT_FAILURE = 'transient_failure' + PERMANENT_FAILURE = 'permanent_failure' + + +class StatusSkillCardStatusField(BaseObject): + def __init__( + self, + code: StatusSkillCardStatusCodeField, + *, + message: Optional[str] = None, + **kwargs + ): + """ + :param code: A code for the status of this Skill invocation. By + default each of these will have their own accompanied + messages. These can be adjusted by setting the `message` + value on this object. + :type code: StatusSkillCardStatusCodeField + :param message: A custom message that can be provided with this status. + This will be shown in the web app to the end user., defaults to None + :type message: Optional[str], optional + """ + super().__init__(**kwargs) + self.code = code + self.message = message + + +class StatusSkillCardSkillTypeField(str, Enum): + SERVICE = 'service' + + +class StatusSkillCardSkillField(BaseObject): + _discriminator = 'type', {'service'} + + def __init__( + self, + id: str, + *, + type: StatusSkillCardSkillTypeField = StatusSkillCardSkillTypeField.SERVICE, + **kwargs + ): + """ + :param id: A custom identifier that represent the service that + applied this metadata. + :type id: str + :param type: The value will always be `service`., defaults to StatusSkillCardSkillTypeField.SERVICE + :type type: StatusSkillCardSkillTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class StatusSkillCardInvocationTypeField(str, Enum): + SKILL_INVOCATION = 'skill_invocation' + + +class StatusSkillCardInvocationField(BaseObject): + _discriminator = 'type', {'skill_invocation'} + + def __init__( + self, + id: str, + *, + type: StatusSkillCardInvocationTypeField = StatusSkillCardInvocationTypeField.SKILL_INVOCATION, + **kwargs + ): + """ + :param id: A custom identifier that represent the instance of + the service that applied this metadata. For example, + if your `image-recognition-service` runs on multiple + nodes, this field can be used to identify the ID of + the node that was used to apply the metadata. + :type id: str + :param type: The value will always be `skill_invocation`., defaults to StatusSkillCardInvocationTypeField.SKILL_INVOCATION + :type type: StatusSkillCardInvocationTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class StatusSkillCard(BaseObject): + _discriminator = 'skill_card_type', {'status'} + + def __init__( + self, + status: StatusSkillCardStatusField, + skill: StatusSkillCardSkillField, + invocation: StatusSkillCardInvocationField, + *, + created_at: Optional[DateTime] = None, + type: StatusSkillCardTypeField = StatusSkillCardTypeField.SKILL_CARD, + skill_card_type: StatusSkillCardSkillCardTypeField = StatusSkillCardSkillCardTypeField.STATUS, + skill_card_title: Optional[StatusSkillCardSkillCardTitleField] = None, + **kwargs + ): + """ + :param status: Sets the status of the skill. This can be used to show a message to the user while the Skill is processing the data, or if it was not able to process the file. + :type status: StatusSkillCardStatusField + :param skill: The service that applied this metadata. + :type skill: StatusSkillCardSkillField + :param invocation: The invocation of this service, used to track + which instance of a service applied the metadata. + :type invocation: StatusSkillCardInvocationField + :param created_at: The optional date and time this card was created at., defaults to None + :type created_at: Optional[DateTime], optional + :param type: The value will always be `skill_card`., defaults to StatusSkillCardTypeField.SKILL_CARD + :type type: StatusSkillCardTypeField, optional + :param skill_card_type: The value will always be `status`., defaults to StatusSkillCardSkillCardTypeField.STATUS + :type skill_card_type: StatusSkillCardSkillCardTypeField, optional + :param skill_card_title: The title of the card., defaults to None + :type skill_card_title: Optional[StatusSkillCardSkillCardTitleField], optional + """ + super().__init__(**kwargs) + self.status = status + self.skill = skill + self.invocation = invocation + self.created_at = created_at + self.type = type + self.skill_card_type = skill_card_type + self.skill_card_title = skill_card_title diff --git a/box_sdk_gen/schemas/storage_policies.py b/box_sdk_gen/schemas/storage_policies.py new file mode 100644 index 000000000..232f48d88 --- /dev/null +++ b/box_sdk_gen/schemas/storage_policies.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.storage_policy import StoragePolicy + +from box_sdk_gen.box.errors import BoxSDKError + + +class StoragePolicies(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[StoragePolicy]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of storage policies., defaults to None + :type entries: Optional[List[StoragePolicy]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/storage_policy.py b/box_sdk_gen/schemas/storage_policy.py new file mode 100644 index 000000000..eb5e1cf38 --- /dev/null +++ b/box_sdk_gen/schemas/storage_policy.py @@ -0,0 +1,28 @@ +from typing import Optional + +from box_sdk_gen.schemas.storage_policy_mini import StoragePolicyMiniTypeField + +from box_sdk_gen.schemas.storage_policy_mini import StoragePolicyMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class StoragePolicy(StoragePolicyMini): + def __init__( + self, + id: str, + *, + name: Optional[str] = None, + type: StoragePolicyMiniTypeField = StoragePolicyMiniTypeField.STORAGE_POLICY, + **kwargs + ): + """ + :param id: The unique identifier for this storage policy. + :type id: str + :param name: A descriptive name of the region., defaults to None + :type name: Optional[str], optional + :param type: The value will always be `storage_policy`., defaults to StoragePolicyMiniTypeField.STORAGE_POLICY + :type type: StoragePolicyMiniTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.name = name diff --git a/box_sdk_gen/schemas/storage_policy_assignment.py b/box_sdk_gen/schemas/storage_policy_assignment.py new file mode 100644 index 000000000..5d53c429b --- /dev/null +++ b/box_sdk_gen/schemas/storage_policy_assignment.py @@ -0,0 +1,53 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.storage_policy_mini import StoragePolicyMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class StoragePolicyAssignmentTypeField(str, Enum): + STORAGE_POLICY_ASSIGNMENT = 'storage_policy_assignment' + + +class StoragePolicyAssignmentAssignedToField(BaseObject): + def __init__( + self, *, id: Optional[str] = None, type: Optional[str] = None, **kwargs + ): + """ + :param id: The unique identifier for this object., defaults to None + :type id: Optional[str], optional + :param type: The type for this object., defaults to None + :type type: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class StoragePolicyAssignment(BaseObject): + _discriminator = 'type', {'storage_policy_assignment'} + + def __init__( + self, + id: str, + *, + type: StoragePolicyAssignmentTypeField = StoragePolicyAssignmentTypeField.STORAGE_POLICY_ASSIGNMENT, + storage_policy: Optional[StoragePolicyMini] = None, + assigned_to: Optional[StoragePolicyAssignmentAssignedToField] = None, + **kwargs + ): + """ + :param id: The unique identifier for a storage policy assignment. + :type id: str + :param type: The value will always be `storage_policy_assignment`., defaults to StoragePolicyAssignmentTypeField.STORAGE_POLICY_ASSIGNMENT + :type type: StoragePolicyAssignmentTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.storage_policy = storage_policy + self.assigned_to = assigned_to diff --git a/box_sdk_gen/schemas/storage_policy_assignments.py b/box_sdk_gen/schemas/storage_policy_assignments.py new file mode 100644 index 000000000..6e409766f --- /dev/null +++ b/box_sdk_gen/schemas/storage_policy_assignments.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.storage_policy_assignment import StoragePolicyAssignment + +from box_sdk_gen.box.errors import BoxSDKError + + +class StoragePolicyAssignments(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[StoragePolicyAssignment]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of storage policy assignments., defaults to None + :type entries: Optional[List[StoragePolicyAssignment]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/storage_policy_mini.py b/box_sdk_gen/schemas/storage_policy_mini.py new file mode 100644 index 000000000..430dc8ba9 --- /dev/null +++ b/box_sdk_gen/schemas/storage_policy_mini.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class StoragePolicyMiniTypeField(str, Enum): + STORAGE_POLICY = 'storage_policy' + + +class StoragePolicyMini(BaseObject): + _discriminator = 'type', {'storage_policy'} + + def __init__( + self, + id: str, + *, + type: StoragePolicyMiniTypeField = StoragePolicyMiniTypeField.STORAGE_POLICY, + **kwargs + ): + """ + :param id: The unique identifier for this storage policy. + :type id: str + :param type: The value will always be `storage_policy`., defaults to StoragePolicyMiniTypeField.STORAGE_POLICY + :type type: StoragePolicyMiniTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/task.py b/box_sdk_gen/schemas/task.py new file mode 100644 index 000000000..053942b06 --- /dev/null +++ b/box_sdk_gen/schemas/task.py @@ -0,0 +1,87 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.schemas.task_assignments import TaskAssignments + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TaskTypeField(str, Enum): + TASK = 'task' + + +class TaskActionField(str, Enum): + REVIEW = 'review' + COMPLETE = 'complete' + + +class TaskCompletionRuleField(str, Enum): + ALL_ASSIGNEES = 'all_assignees' + ANY_ASSIGNEE = 'any_assignee' + + +class Task(BaseObject): + _discriminator = 'type', {'task'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[TaskTypeField] = None, + item: Optional[FileMini] = None, + due_at: Optional[DateTime] = None, + action: Optional[TaskActionField] = None, + message: Optional[str] = None, + task_assignment_collection: Optional[TaskAssignments] = None, + is_completed: Optional[bool] = None, + created_by: Optional[UserMini] = None, + created_at: Optional[DateTime] = None, + completion_rule: Optional[TaskCompletionRuleField] = None, + **kwargs + ): + """ + :param id: The unique identifier for this task., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `task`., defaults to None + :type type: Optional[TaskTypeField], optional + :param due_at: When the task is due., defaults to None + :type due_at: Optional[DateTime], optional + :param action: The type of task the task assignee will be prompted to + perform., defaults to None + :type action: Optional[TaskActionField], optional + :param message: A message that will be included with the task., defaults to None + :type message: Optional[str], optional + :param is_completed: Whether the task has been completed., defaults to None + :type is_completed: Optional[bool], optional + :param created_at: When the task object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param completion_rule: Defines which assignees need to complete this task before the task + is considered completed. + + * `all_assignees` requires all assignees to review or + approve the the task in order for it to be considered completed. + * `any_assignee` accepts any one assignee to review or + approve the the task in order for it to be considered completed., defaults to None + :type completion_rule: Optional[TaskCompletionRuleField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.item = item + self.due_at = due_at + self.action = action + self.message = message + self.task_assignment_collection = task_assignment_collection + self.is_completed = is_completed + self.created_by = created_by + self.created_at = created_at + self.completion_rule = completion_rule diff --git a/box_sdk_gen/schemas/task_assignment.py b/box_sdk_gen/schemas/task_assignment.py new file mode 100644 index 000000000..6784ec12e --- /dev/null +++ b/box_sdk_gen/schemas/task_assignment.py @@ -0,0 +1,76 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TaskAssignmentTypeField(str, Enum): + TASK_ASSIGNMENT = 'task_assignment' + + +class TaskAssignmentResolutionStateField(str, Enum): + COMPLETED = 'completed' + INCOMPLETE = 'incomplete' + APPROVED = 'approved' + REJECTED = 'rejected' + + +class TaskAssignment(BaseObject): + _discriminator = 'type', {'task_assignment'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[TaskAssignmentTypeField] = None, + item: Optional[FileMini] = None, + assigned_to: Optional[UserMini] = None, + message: Optional[str] = None, + completed_at: Optional[DateTime] = None, + assigned_at: Optional[DateTime] = None, + reminded_at: Optional[DateTime] = None, + resolution_state: Optional[TaskAssignmentResolutionStateField] = None, + assigned_by: Optional[UserMini] = None, + **kwargs + ): + """ + :param id: The unique identifier for this task assignment., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `task_assignment`., defaults to None + :type type: Optional[TaskAssignmentTypeField], optional + :param message: A message that will is included with the task + assignment. This is visible to the assigned user in the web and mobile + UI., defaults to None + :type message: Optional[str], optional + :param completed_at: The date at which this task assignment was + completed. This will be `null` if the task is not completed yet., defaults to None + :type completed_at: Optional[DateTime], optional + :param assigned_at: The date at which this task was assigned to the user., defaults to None + :type assigned_at: Optional[DateTime], optional + :param reminded_at: The date at which the assigned user was reminded of this task + assignment., defaults to None + :type reminded_at: Optional[DateTime], optional + :param resolution_state: The current state of the assignment. The available states depend on + the `action` value of the task object., defaults to None + :type resolution_state: Optional[TaskAssignmentResolutionStateField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.item = item + self.assigned_to = assigned_to + self.message = message + self.completed_at = completed_at + self.assigned_at = assigned_at + self.reminded_at = reminded_at + self.resolution_state = resolution_state + self.assigned_by = assigned_by diff --git a/box_sdk_gen/schemas/task_assignments.py b/box_sdk_gen/schemas/task_assignments.py new file mode 100644 index 000000000..66c3937f5 --- /dev/null +++ b/box_sdk_gen/schemas/task_assignments.py @@ -0,0 +1,28 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.task_assignment import TaskAssignment + +from box_sdk_gen.box.errors import BoxSDKError + + +class TaskAssignments(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + entries: Optional[List[TaskAssignment]] = None, + **kwargs + ): + """ + :param total_count: The total number of items in this collection., defaults to None + :type total_count: Optional[int], optional + :param entries: A list of task assignments., defaults to None + :type entries: Optional[List[TaskAssignment]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries diff --git a/box_sdk_gen/schemas/tasks.py b/box_sdk_gen/schemas/tasks.py new file mode 100644 index 000000000..0fd54f38a --- /dev/null +++ b/box_sdk_gen/schemas/tasks.py @@ -0,0 +1,30 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.task import Task + +from box_sdk_gen.box.errors import BoxSDKError + + +class Tasks(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + entries: Optional[List[Task]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`., defaults to None + :type total_count: Optional[int], optional + :param entries: A list of tasks., defaults to None + :type entries: Optional[List[Task]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries diff --git a/box_sdk_gen/schemas/template_signer.py b/box_sdk_gen/schemas/template_signer.py new file mode 100644 index 000000000..be1404779 --- /dev/null +++ b/box_sdk_gen/schemas/template_signer.py @@ -0,0 +1,80 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.template_signer_input import TemplateSignerInput + +from box_sdk_gen.box.errors import BoxSDKError + + +class TemplateSignerRoleField(str, Enum): + SIGNER = 'signer' + APPROVER = 'approver' + FINAL_COPY_READER = 'final_copy_reader' + + +class TemplateSigner(BaseObject): + def __init__( + self, + *, + inputs: Optional[List[TemplateSignerInput]] = None, + email: Optional[str] = None, + role: Optional[TemplateSignerRoleField] = None, + is_in_person: Optional[bool] = None, + order: Optional[int] = None, + signer_group_id: Optional[str] = None, + label: Optional[str] = None, + public_id: Optional[str] = None, + is_password_required: Optional[bool] = None, + is_phone_number_required: Optional[bool] = None, + login_required: Optional[bool] = None, + **kwargs + ): + """ + :param email: Email address of the signer., defaults to None + :type email: Optional[str], optional + :param role: Defines the role of the signer in the signature request. A role of + `signer` needs to sign the document, a role `approver` + approves the document and + a `final_copy_reader` role only + receives the final signed document and signing log., defaults to None + :type role: Optional[TemplateSignerRoleField], optional + :param is_in_person: Used in combination with an embed URL for a sender. + After the sender signs, they will be + redirected to the next `in_person` signer., defaults to None + :type is_in_person: Optional[bool], optional + :param order: Order of the signer., defaults to None + :type order: Optional[int], optional + :param signer_group_id: If provided, this value points signers that are assigned the same inputs and belongs to same signer group. + A signer group is not a Box Group. It is an entity that belongs to the template itself and can only be used + within Box Sign requests created from it., defaults to None + :type signer_group_id: Optional[str], optional + :param label: A placeholder label for the signer set by the template creator to differentiate between signers., defaults to None + :type label: Optional[str], optional + :param public_id: An identifier for the signer. This can be used to identify a signer within the template., defaults to None + :type public_id: Optional[str], optional + :param is_password_required: If true for signers with a defined email, the password provided when the template was created is used by default. + If true for signers without a specified / defined email, the creator needs to provide a password when using the template., defaults to None + :type is_password_required: Optional[bool], optional + :param is_phone_number_required: If true for signers with a defined email, the phone number provided when the template was created is used by default. + If true for signers without a specified / defined email, the template creator needs to provide a phone number when creating a request., defaults to None + :type is_phone_number_required: Optional[bool], optional + :param login_required: If true, the signer is required to login to access the document., defaults to None + :type login_required: Optional[bool], optional + """ + super().__init__(**kwargs) + self.inputs = inputs + self.email = email + self.role = role + self.is_in_person = is_in_person + self.order = order + self.signer_group_id = signer_group_id + self.label = label + self.public_id = public_id + self.is_password_required = is_password_required + self.is_phone_number_required = is_phone_number_required + self.login_required = login_required diff --git a/box_sdk_gen/schemas/template_signer_input.py b/box_sdk_gen/schemas/template_signer_input.py new file mode 100644 index 000000000..32702404a --- /dev/null +++ b/box_sdk_gen/schemas/template_signer_input.py @@ -0,0 +1,147 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.internal.utils import Date + +from box_sdk_gen.schemas.sign_request_prefill_tag import SignRequestPrefillTag + +from box_sdk_gen.box.errors import BoxSDKError + + +class TemplateSignerInputTypeField(str, Enum): + SIGNATURE = 'signature' + DATE = 'date' + TEXT = 'text' + CHECKBOX = 'checkbox' + ATTACHMENT = 'attachment' + RADIO = 'radio' + DROPDOWN = 'dropdown' + + +class TemplateSignerInputContentTypeField(str, Enum): + SIGNATURE = 'signature' + INITIAL = 'initial' + STAMP = 'stamp' + DATE = 'date' + CHECKBOX = 'checkbox' + TEXT = 'text' + FULL_NAME = 'full_name' + FIRST_NAME = 'first_name' + LAST_NAME = 'last_name' + COMPANY = 'company' + TITLE = 'title' + EMAIL = 'email' + ATTACHMENT = 'attachment' + RADIO = 'radio' + DROPDOWN = 'dropdown' + + +class TemplateSignerInputCoordinatesField(BaseObject): + def __init__( + self, *, x: Optional[float] = None, y: Optional[float] = None, **kwargs + ): + """ + :param x: Relative x coordinate to the page the input is on, ranging from 0 to 1., defaults to None + :type x: Optional[float], optional + :param y: Relative y coordinate to the page the input is on, ranging from 0 to 1., defaults to None + :type y: Optional[float], optional + """ + super().__init__(**kwargs) + self.x = x + self.y = y + + +class TemplateSignerInputDimensionsField(BaseObject): + def __init__( + self, *, width: Optional[float] = None, height: Optional[float] = None, **kwargs + ): + """ + :param width: Relative width to the page the input is on, ranging from 0 to 1., defaults to None + :type width: Optional[float], optional + :param height: Relative height to the page the input is on, ranging from 0 to 1., defaults to None + :type height: Optional[float], optional + """ + super().__init__(**kwargs) + self.width = width + self.height = height + + +class TemplateSignerInput(SignRequestPrefillTag): + def __init__( + self, + page_index: int, + *, + type: Optional[TemplateSignerInputTypeField] = None, + content_type: Optional[TemplateSignerInputContentTypeField] = None, + is_required: Optional[bool] = None, + document_id: Optional[str] = None, + dropdown_choices: Optional[List[str]] = None, + group_id: Optional[str] = None, + coordinates: Optional[TemplateSignerInputCoordinatesField] = None, + dimensions: Optional[TemplateSignerInputDimensionsField] = None, + label: Optional[str] = None, + read_only: Optional[bool] = None, + document_tag_id: Optional[str] = None, + text_value: Optional[str] = None, + checkbox_value: Optional[bool] = None, + date_value: Optional[Date] = None, + **kwargs + ): + """ + :param page_index: Index of page that the input is on. + :type page_index: int + :param type: Type of input., defaults to None + :type type: Optional[TemplateSignerInputTypeField], optional + :param content_type: Content type of input., defaults to None + :type content_type: Optional[TemplateSignerInputContentTypeField], optional + :param is_required: Whether or not the input is required., defaults to None + :type is_required: Optional[bool], optional + :param document_id: Document identifier., defaults to None + :type document_id: Optional[str], optional + :param dropdown_choices: When the input is of the type `dropdown` this + values will be filled with all the + dropdown options., defaults to None + :type dropdown_choices: Optional[List[str]], optional + :param group_id: When the input is of type `radio` they can be + grouped to gather with this identifier., defaults to None + :type group_id: Optional[str], optional + :param coordinates: Where the input is located on a page., defaults to None + :type coordinates: Optional[TemplateSignerInputCoordinatesField], optional + :param dimensions: The size of the input., defaults to None + :type dimensions: Optional[TemplateSignerInputDimensionsField], optional + :param label: The label field is used especially for text, attachment, radio, and checkbox type inputs., defaults to None + :type label: Optional[str], optional + :param read_only: Whether this input was defined as read-only(immutable by signers) or not., defaults to None + :type read_only: Optional[bool], optional + :param document_tag_id: This references the ID of a specific tag contained in a file of the signature request., defaults to None + :type document_tag_id: Optional[str], optional + :param text_value: Text prefill value., defaults to None + :type text_value: Optional[str], optional + :param checkbox_value: Checkbox prefill value., defaults to None + :type checkbox_value: Optional[bool], optional + :param date_value: Date prefill value., defaults to None + :type date_value: Optional[Date], optional + """ + super().__init__( + document_tag_id=document_tag_id, + text_value=text_value, + checkbox_value=checkbox_value, + date_value=date_value, + **kwargs + ) + self.page_index = page_index + self.type = type + self.content_type = content_type + self.is_required = is_required + self.document_id = document_id + self.dropdown_choices = dropdown_choices + self.group_id = group_id + self.coordinates = coordinates + self.dimensions = dimensions + self.label = label + self.read_only = read_only diff --git a/box_sdk_gen/schemas/terms_of_service.py b/box_sdk_gen/schemas/terms_of_service.py new file mode 100644 index 000000000..f36ac992c --- /dev/null +++ b/box_sdk_gen/schemas/terms_of_service.py @@ -0,0 +1,92 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.terms_of_service_base import TermsOfServiceBaseTypeField + +from box_sdk_gen.schemas.terms_of_service_base import TermsOfServiceBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TermsOfServiceStatusField(str, Enum): + ENABLED = 'enabled' + DISABLED = 'disabled' + + +class TermsOfServiceEnterpriseTypeField(str, Enum): + ENTERPRISE = 'enterprise' + + +class TermsOfServiceEnterpriseField(BaseObject): + _discriminator = 'type', {'enterprise'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[TermsOfServiceEnterpriseTypeField] = None, + name: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this enterprise., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `enterprise`., defaults to None + :type type: Optional[TermsOfServiceEnterpriseTypeField], optional + :param name: The name of the enterprise., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + + +class TermsOfServiceTosTypeField(str, Enum): + MANAGED = 'managed' + EXTERNAL = 'external' + + +class TermsOfService(TermsOfServiceBase): + def __init__( + self, + id: str, + *, + status: Optional[TermsOfServiceStatusField] = None, + enterprise: Optional[TermsOfServiceEnterpriseField] = None, + tos_type: Optional[TermsOfServiceTosTypeField] = None, + text: Optional[str] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + type: TermsOfServiceBaseTypeField = TermsOfServiceBaseTypeField.TERMS_OF_SERVICE, + **kwargs + ): + """ + :param id: The unique identifier for this terms of service. + :type id: str + :param status: Whether these terms are enabled or not., defaults to None + :type status: Optional[TermsOfServiceStatusField], optional + :param tos_type: Whether to apply these terms to managed users or external users., defaults to None + :type tos_type: Optional[TermsOfServiceTosTypeField], optional + :param text: The text for your terms and conditions. This text could be + empty if the `status` is set to `disabled`., defaults to None + :type text: Optional[str], optional + :param created_at: When the legal item was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the legal item was modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param type: The value will always be `terms_of_service`., defaults to TermsOfServiceBaseTypeField.TERMS_OF_SERVICE + :type type: TermsOfServiceBaseTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.status = status + self.enterprise = enterprise + self.tos_type = tos_type + self.text = text + self.created_at = created_at + self.modified_at = modified_at diff --git a/box_sdk_gen/schemas/terms_of_service_base.py b/box_sdk_gen/schemas/terms_of_service_base.py new file mode 100644 index 000000000..d2cb2b8f3 --- /dev/null +++ b/box_sdk_gen/schemas/terms_of_service_base.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class TermsOfServiceBaseTypeField(str, Enum): + TERMS_OF_SERVICE = 'terms_of_service' + + +class TermsOfServiceBase(BaseObject): + _discriminator = 'type', {'terms_of_service'} + + def __init__( + self, + id: str, + *, + type: TermsOfServiceBaseTypeField = TermsOfServiceBaseTypeField.TERMS_OF_SERVICE, + **kwargs + ): + """ + :param id: The unique identifier for this terms of service. + :type id: str + :param type: The value will always be `terms_of_service`., defaults to TermsOfServiceBaseTypeField.TERMS_OF_SERVICE + :type type: TermsOfServiceBaseTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/terms_of_service_user_status.py b/box_sdk_gen/schemas/terms_of_service_user_status.py new file mode 100644 index 000000000..d9a8dc3ed --- /dev/null +++ b/box_sdk_gen/schemas/terms_of_service_user_status.py @@ -0,0 +1,54 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.terms_of_service_base import TermsOfServiceBase + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TermsOfServiceUserStatusTypeField(str, Enum): + TERMS_OF_SERVICE_USER_STATUS = 'terms_of_service_user_status' + + +class TermsOfServiceUserStatus(BaseObject): + _discriminator = 'type', {'terms_of_service_user_status'} + + def __init__( + self, + id: str, + *, + type: TermsOfServiceUserStatusTypeField = TermsOfServiceUserStatusTypeField.TERMS_OF_SERVICE_USER_STATUS, + tos: Optional[TermsOfServiceBase] = None, + user: Optional[UserMini] = None, + is_accepted: Optional[bool] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param id: The unique identifier for this terms of service user status. + :type id: str + :param type: The value will always be `terms_of_service_user_status`., defaults to TermsOfServiceUserStatusTypeField.TERMS_OF_SERVICE_USER_STATUS + :type type: TermsOfServiceUserStatusTypeField, optional + :param is_accepted: If the user has accepted the terms of services., defaults to None + :type is_accepted: Optional[bool], optional + :param created_at: When the legal item was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the legal item was modified., defaults to None + :type modified_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.tos = tos + self.user = user + self.is_accepted = is_accepted + self.created_at = created_at + self.modified_at = modified_at diff --git a/box_sdk_gen/schemas/terms_of_service_user_statuses.py b/box_sdk_gen/schemas/terms_of_service_user_statuses.py new file mode 100644 index 000000000..5438133b1 --- /dev/null +++ b/box_sdk_gen/schemas/terms_of_service_user_statuses.py @@ -0,0 +1,28 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.terms_of_service_user_status import TermsOfServiceUserStatus + +from box_sdk_gen.box.errors import BoxSDKError + + +class TermsOfServiceUserStatuses(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + entries: Optional[List[TermsOfServiceUserStatus]] = None, + **kwargs + ): + """ + :param total_count: The total number of objects., defaults to None + :type total_count: Optional[int], optional + :param entries: A list of terms of service user statuses., defaults to None + :type entries: Optional[List[TermsOfServiceUserStatus]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries diff --git a/box_sdk_gen/schemas/terms_of_services.py b/box_sdk_gen/schemas/terms_of_services.py new file mode 100644 index 000000000..880a1958d --- /dev/null +++ b/box_sdk_gen/schemas/terms_of_services.py @@ -0,0 +1,28 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.terms_of_service import TermsOfService + +from box_sdk_gen.box.errors import BoxSDKError + + +class TermsOfServices(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + entries: Optional[List[TermsOfService]] = None, + **kwargs + ): + """ + :param total_count: The total number of objects., defaults to None + :type total_count: Optional[int], optional + :param entries: A list of terms of service objects., defaults to None + :type entries: Optional[List[TermsOfService]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries diff --git a/box_sdk_gen/schemas/timeline_skill_card.py b/box_sdk_gen/schemas/timeline_skill_card.py new file mode 100644 index 000000000..a08774061 --- /dev/null +++ b/box_sdk_gen/schemas/timeline_skill_card.py @@ -0,0 +1,182 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TimelineSkillCardTypeField(str, Enum): + SKILL_CARD = 'skill_card' + + +class TimelineSkillCardSkillCardTypeField(str, Enum): + TIMELINE = 'timeline' + + +class TimelineSkillCardSkillCardTitleField(BaseObject): + def __init__(self, message: str, *, code: Optional[str] = None, **kwargs): + """ + :param message: The actual title to show in the UI. + :type message: str + :param code: An optional identifier for the title., defaults to None + :type code: Optional[str], optional + """ + super().__init__(**kwargs) + self.message = message + self.code = code + + +class TimelineSkillCardSkillTypeField(str, Enum): + SERVICE = 'service' + + +class TimelineSkillCardSkillField(BaseObject): + _discriminator = 'type', {'service'} + + def __init__( + self, + id: str, + *, + type: TimelineSkillCardSkillTypeField = TimelineSkillCardSkillTypeField.SERVICE, + **kwargs + ): + """ + :param id: A custom identifier that represent the service that + applied this metadata. + :type id: str + :param type: The value will always be `service`., defaults to TimelineSkillCardSkillTypeField.SERVICE + :type type: TimelineSkillCardSkillTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class TimelineSkillCardInvocationTypeField(str, Enum): + SKILL_INVOCATION = 'skill_invocation' + + +class TimelineSkillCardInvocationField(BaseObject): + _discriminator = 'type', {'skill_invocation'} + + def __init__( + self, + id: str, + *, + type: TimelineSkillCardInvocationTypeField = TimelineSkillCardInvocationTypeField.SKILL_INVOCATION, + **kwargs + ): + """ + :param id: A custom identifier that represent the instance of + the service that applied this metadata. For example, + if your `image-recognition-service` runs on multiple + nodes, this field can be used to identify the ID of + the node that was used to apply the metadata. + :type id: str + :param type: The value will always be `skill_invocation`., defaults to TimelineSkillCardInvocationTypeField.SKILL_INVOCATION + :type type: TimelineSkillCardInvocationTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class TimelineSkillCardEntriesAppearsField(BaseObject): + def __init__( + self, *, start: Optional[int] = None, end: Optional[int] = None, **kwargs + ): + """ + :param start: The time in seconds when an + entry should start appearing on a timeline., defaults to None + :type start: Optional[int], optional + :param end: The time in seconds when an + entry should stop appearing on a timeline., defaults to None + :type end: Optional[int], optional + """ + super().__init__(**kwargs) + self.start = start + self.end = end + + +class TimelineSkillCardEntriesField(BaseObject): + def __init__( + self, + *, + text: Optional[str] = None, + appears: Optional[List[TimelineSkillCardEntriesAppearsField]] = None, + image_url: Optional[str] = None, + **kwargs + ): + """ + :param text: The text of the entry. This would be the display + name for an item being placed on the timeline, for example the name + of the person who was detected in a video., defaults to None + :type text: Optional[str], optional + :param appears: Defines a list of timestamps for when this item should appear on the + timeline., defaults to None + :type appears: Optional[List[TimelineSkillCardEntriesAppearsField]], optional + :param image_url: The image to show on a for an entry that appears + on a timeline. This image URL is required for every entry. + + The image will be shown in a + list of items (for example faces), and clicking + the image will show the user where that entry + appears during the duration of this entry., defaults to None + :type image_url: Optional[str], optional + """ + super().__init__(**kwargs) + self.text = text + self.appears = appears + self.image_url = image_url + + +class TimelineSkillCard(BaseObject): + _discriminator = 'skill_card_type', {'timeline'} + + def __init__( + self, + skill: TimelineSkillCardSkillField, + invocation: TimelineSkillCardInvocationField, + entries: List[TimelineSkillCardEntriesField], + *, + created_at: Optional[DateTime] = None, + type: TimelineSkillCardTypeField = TimelineSkillCardTypeField.SKILL_CARD, + skill_card_type: TimelineSkillCardSkillCardTypeField = TimelineSkillCardSkillCardTypeField.TIMELINE, + skill_card_title: Optional[TimelineSkillCardSkillCardTitleField] = None, + duration: Optional[int] = None, + **kwargs + ): + """ + :param skill: The service that applied this metadata. + :type skill: TimelineSkillCardSkillField + :param invocation: The invocation of this service, used to track + which instance of a service applied the metadata. + :type invocation: TimelineSkillCardInvocationField + :param entries: A list of entries on the timeline. + :type entries: List[TimelineSkillCardEntriesField] + :param created_at: The optional date and time this card was created at., defaults to None + :type created_at: Optional[DateTime], optional + :param type: The value will always be `skill_card`., defaults to TimelineSkillCardTypeField.SKILL_CARD + :type type: TimelineSkillCardTypeField, optional + :param skill_card_type: The value will always be `timeline`., defaults to TimelineSkillCardSkillCardTypeField.TIMELINE + :type skill_card_type: TimelineSkillCardSkillCardTypeField, optional + :param skill_card_title: The title of the card., defaults to None + :type skill_card_title: Optional[TimelineSkillCardSkillCardTitleField], optional + :param duration: An total duration in seconds of the timeline., defaults to None + :type duration: Optional[int], optional + """ + super().__init__(**kwargs) + self.skill = skill + self.invocation = invocation + self.entries = entries + self.created_at = created_at + self.type = type + self.skill_card_type = skill_card_type + self.skill_card_title = skill_card_title + self.duration = duration diff --git a/box_sdk_gen/schemas/tracking_code.py b/box_sdk_gen/schemas/tracking_code.py new file mode 100644 index 000000000..0cc016209 --- /dev/null +++ b/box_sdk_gen/schemas/tracking_code.py @@ -0,0 +1,37 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class TrackingCodeTypeField(str, Enum): + TRACKING_CODE = 'tracking_code' + + +class TrackingCode(BaseObject): + _discriminator = 'type', {'tracking_code'} + + def __init__( + self, + *, + type: Optional[TrackingCodeTypeField] = None, + name: Optional[str] = None, + value: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `tracking_code`., defaults to None + :type type: Optional[TrackingCodeTypeField], optional + :param name: The name of the tracking code, which must be preconfigured in + the Admin Console., defaults to None + :type name: Optional[str], optional + :param value: The value of the tracking code., defaults to None + :type value: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.name = name + self.value = value diff --git a/box_sdk_gen/schemas/transcript_skill_card.py b/box_sdk_gen/schemas/transcript_skill_card.py new file mode 100644 index 000000000..43c8742e5 --- /dev/null +++ b/box_sdk_gen/schemas/transcript_skill_card.py @@ -0,0 +1,169 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TranscriptSkillCardTypeField(str, Enum): + SKILL_CARD = 'skill_card' + + +class TranscriptSkillCardSkillCardTypeField(str, Enum): + TRANSCRIPT = 'transcript' + + +class TranscriptSkillCardSkillCardTitleField(BaseObject): + def __init__(self, message: str, *, code: Optional[str] = None, **kwargs): + """ + :param message: The actual title to show in the UI. + :type message: str + :param code: An optional identifier for the title., defaults to None + :type code: Optional[str], optional + """ + super().__init__(**kwargs) + self.message = message + self.code = code + + +class TranscriptSkillCardSkillTypeField(str, Enum): + SERVICE = 'service' + + +class TranscriptSkillCardSkillField(BaseObject): + _discriminator = 'type', {'service'} + + def __init__( + self, + id: str, + *, + type: TranscriptSkillCardSkillTypeField = TranscriptSkillCardSkillTypeField.SERVICE, + **kwargs + ): + """ + :param id: A custom identifier that represent the service that + applied this metadata. + :type id: str + :param type: The value will always be `service`., defaults to TranscriptSkillCardSkillTypeField.SERVICE + :type type: TranscriptSkillCardSkillTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class TranscriptSkillCardInvocationTypeField(str, Enum): + SKILL_INVOCATION = 'skill_invocation' + + +class TranscriptSkillCardInvocationField(BaseObject): + _discriminator = 'type', {'skill_invocation'} + + def __init__( + self, + id: str, + *, + type: TranscriptSkillCardInvocationTypeField = TranscriptSkillCardInvocationTypeField.SKILL_INVOCATION, + **kwargs + ): + """ + :param id: A custom identifier that represent the instance of + the service that applied this metadata. For example, + if your `image-recognition-service` runs on multiple + nodes, this field can be used to identify the ID of + the node that was used to apply the metadata. + :type id: str + :param type: The value will always be `skill_invocation`., defaults to TranscriptSkillCardInvocationTypeField.SKILL_INVOCATION + :type type: TranscriptSkillCardInvocationTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class TranscriptSkillCardEntriesAppearsField(BaseObject): + def __init__(self, *, start: Optional[int] = None, **kwargs): + """ + :param start: The time in seconds when an + entry should start appearing on a timeline., defaults to None + :type start: Optional[int], optional + """ + super().__init__(**kwargs) + self.start = start + + +class TranscriptSkillCardEntriesField(BaseObject): + def __init__( + self, + *, + text: Optional[str] = None, + appears: Optional[List[TranscriptSkillCardEntriesAppearsField]] = None, + **kwargs + ): + """ + :param text: The text of the entry. This would be the transcribed text assigned + to the entry on the timeline., defaults to None + :type text: Optional[str], optional + :param appears: Defines when a transcribed bit of text appears. This only includes a + start time and no end time., defaults to None + :type appears: Optional[List[TranscriptSkillCardEntriesAppearsField]], optional + """ + super().__init__(**kwargs) + self.text = text + self.appears = appears + + +class TranscriptSkillCard(BaseObject): + _discriminator = 'skill_card_type', {'transcript'} + + def __init__( + self, + skill: TranscriptSkillCardSkillField, + invocation: TranscriptSkillCardInvocationField, + entries: List[TranscriptSkillCardEntriesField], + *, + created_at: Optional[DateTime] = None, + type: TranscriptSkillCardTypeField = TranscriptSkillCardTypeField.SKILL_CARD, + skill_card_type: TranscriptSkillCardSkillCardTypeField = TranscriptSkillCardSkillCardTypeField.TRANSCRIPT, + skill_card_title: Optional[TranscriptSkillCardSkillCardTitleField] = None, + duration: Optional[int] = None, + **kwargs + ): + """ + :param skill: The service that applied this metadata. + :type skill: TranscriptSkillCardSkillField + :param invocation: The invocation of this service, used to track + which instance of a service applied the metadata. + :type invocation: TranscriptSkillCardInvocationField + :param entries: An list of entries for the card. This represents the individual entries of + the transcription. + :type entries: List[TranscriptSkillCardEntriesField] + :param created_at: The optional date and time this card was created at., defaults to None + :type created_at: Optional[DateTime], optional + :param type: The value will always be `skill_card`., defaults to TranscriptSkillCardTypeField.SKILL_CARD + :type type: TranscriptSkillCardTypeField, optional + :param skill_card_type: The value will always be `transcript`., defaults to TranscriptSkillCardSkillCardTypeField.TRANSCRIPT + :type skill_card_type: TranscriptSkillCardSkillCardTypeField, optional + :param skill_card_title: The title of the card., defaults to None + :type skill_card_title: Optional[TranscriptSkillCardSkillCardTitleField], optional + :param duration: An optional total duration in seconds. + + Used with a `skill_card_type` of `transcript` or + `timeline`., defaults to None + :type duration: Optional[int], optional + """ + super().__init__(**kwargs) + self.skill = skill + self.invocation = invocation + self.entries = entries + self.created_at = created_at + self.type = type + self.skill_card_type = skill_card_type + self.skill_card_title = skill_card_title + self.duration = duration diff --git a/box_sdk_gen/schemas/trash_file.py b/box_sdk_gen/schemas/trash_file.py new file mode 100644 index 000000000..37374c80d --- /dev/null +++ b/box_sdk_gen/schemas/trash_file.py @@ -0,0 +1,198 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Dict + +from box_sdk_gen.schemas.file_version_mini import FileVersionMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TrashFileTypeField(str, Enum): + FILE = 'file' + + +class TrashFilePathCollectionEntriesTypeField(str, Enum): + FOLDER = 'folder' + + +class TrashFilePathCollectionEntriesField(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + *, + type: Optional[TrashFilePathCollectionEntriesTypeField] = None, + id: Optional[str] = None, + sequence_id: Optional[str] = None, + etag: Optional[str] = None, + name: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `folder`., defaults to None + :type type: Optional[TrashFilePathCollectionEntriesTypeField], optional + :param id: The unique identifier that represent a folder., defaults to None + :type id: Optional[str], optional + :param sequence_id: This field is null for the Trash folder., defaults to None + :type sequence_id: Optional[str], optional + :param etag: This field is null for the Trash folder., defaults to None + :type etag: Optional[str], optional + :param name: The name of the Trash folder., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.sequence_id = sequence_id + self.etag = etag + self.name = name + + +class TrashFilePathCollectionField(BaseObject): + def __init__( + self, + total_count: int, + entries: List[TrashFilePathCollectionEntriesField], + **kwargs + ): + """ + :param total_count: The number of folders in this list. + :type total_count: int + :param entries: Array of folders for this item's path collection. + :type entries: List[TrashFilePathCollectionEntriesField] + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries + + +class TrashFileItemStatusField(str, Enum): + ACTIVE = 'active' + TRASHED = 'trashed' + DELETED = 'deleted' + + +class TrashFile(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'sha_1': 'sha1', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'sha1': 'sha_1', + **BaseObject._json_to_fields_mapping, + } + _discriminator = 'type', {'file'} + + def __init__( + self, + id: str, + sequence_id: str, + sha_1: str, + description: str, + size: int, + path_collection: TrashFilePathCollectionField, + created_at: DateTime, + modified_at: DateTime, + modified_by: UserMini, + owned_by: UserMini, + item_status: TrashFileItemStatusField, + *, + etag: Optional[str] = None, + type: TrashFileTypeField = TrashFileTypeField.FILE, + name: Optional[str] = None, + file_version: Optional[FileVersionMini] = None, + trashed_at: Optional[DateTime] = None, + purged_at: Optional[DateTime] = None, + content_created_at: Optional[DateTime] = None, + content_modified_at: Optional[DateTime] = None, + created_by: Optional[UserMini] = None, + shared_link: Optional[str] = None, + parent: Optional[FolderMini] = None, + **kwargs + ): + """ + :param id: The unique identifier that represent a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + :type id: str + :param sha_1: The SHA1 hash of the file. This can be used to compare the contents + of a file on Box with a local file. + :type sha_1: str + :param description: The optional description of this file. + :type description: str + :param size: The file size in bytes. Be careful parsing this integer as it can + get very large and cause an integer overflow. + :type size: int + :param created_at: The date and time when the file was created on Box. + :type created_at: DateTime + :param modified_at: The date and time when the file was last updated on Box. + :type modified_at: DateTime + :param item_status: Defines if this item has been deleted or not. + + * `active` when the item has is not in the trash + * `trashed` when the item has been moved to the trash but not deleted + * `deleted` when the item has been permanently deleted. + :type item_status: TrashFileItemStatusField + :param etag: The HTTP `etag` of this file. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the file if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `file`., defaults to TrashFileTypeField.FILE + :type type: TrashFileTypeField, optional + :param name: The name of the file., defaults to None + :type name: Optional[str], optional + :param trashed_at: The time at which this file was put in the trash., defaults to None + :type trashed_at: Optional[DateTime], optional + :param purged_at: The time at which this file is expected to be purged + from the trash., defaults to None + :type purged_at: Optional[DateTime], optional + :param content_created_at: The date and time at which this file was originally + created, which might be before it was uploaded to Box., defaults to None + :type content_created_at: Optional[DateTime], optional + :param content_modified_at: The date and time at which this file was last updated, + which might be before it was uploaded to Box., defaults to None + :type content_modified_at: Optional[DateTime], optional + :param shared_link: The shared link for this file. This will + be `null` if a file has been trashed, since the link will no longer + be active., defaults to None + :type shared_link: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.sequence_id = sequence_id + self.sha_1 = sha_1 + self.description = description + self.size = size + self.path_collection = path_collection + self.created_at = created_at + self.modified_at = modified_at + self.modified_by = modified_by + self.owned_by = owned_by + self.item_status = item_status + self.etag = etag + self.type = type + self.name = name + self.file_version = file_version + self.trashed_at = trashed_at + self.purged_at = purged_at + self.content_created_at = content_created_at + self.content_modified_at = content_modified_at + self.created_by = created_by + self.shared_link = shared_link + self.parent = parent diff --git a/box_sdk_gen/schemas/trash_file_restored.py b/box_sdk_gen/schemas/trash_file_restored.py new file mode 100644 index 000000000..65051dfa5 --- /dev/null +++ b/box_sdk_gen/schemas/trash_file_restored.py @@ -0,0 +1,157 @@ +from enum import Enum + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.schemas.file_version_mini import FileVersionMini + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TrashFileRestoredTypeField(str, Enum): + FILE = 'file' + + +class TrashFileRestoredPathCollectionField(BaseObject): + def __init__(self, total_count: int, entries: List[FolderMini], **kwargs): + """ + :param total_count: The number of folders in this list. + :type total_count: int + :param entries: The parent folders for this item. + :type entries: List[FolderMini] + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries + + +class TrashFileRestoredItemStatusField(str, Enum): + ACTIVE = 'active' + TRASHED = 'trashed' + DELETED = 'deleted' + + +class TrashFileRestored(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'sha_1': 'sha1', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'sha1': 'sha_1', + **BaseObject._json_to_fields_mapping, + } + _discriminator = 'type', {'file'} + + def __init__( + self, + id: str, + sequence_id: str, + sha_1: str, + description: str, + size: int, + path_collection: TrashFileRestoredPathCollectionField, + created_at: DateTime, + modified_at: DateTime, + modified_by: UserMini, + owned_by: UserMini, + item_status: TrashFileRestoredItemStatusField, + *, + etag: Optional[str] = None, + type: TrashFileRestoredTypeField = TrashFileRestoredTypeField.FILE, + name: Optional[str] = None, + file_version: Optional[FileVersionMini] = None, + trashed_at: Optional[str] = None, + purged_at: Optional[str] = None, + content_created_at: Optional[DateTime] = None, + content_modified_at: Optional[DateTime] = None, + created_by: Optional[UserMini] = None, + shared_link: Optional[str] = None, + parent: Optional[FolderMini] = None, + **kwargs + ): + """ + :param id: The unique identifier that represent a file. + + The ID for any file can be determined + by visiting a file in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/files/123` + the `file_id` is `123`. + :type id: str + :param sha_1: The SHA1 hash of the file. This can be used to compare the contents + of a file on Box with a local file. + :type sha_1: str + :param description: The optional description of this file. + :type description: str + :param size: The file size in bytes. Be careful parsing this integer as it can + get very large and cause an integer overflow. + :type size: int + :param created_at: The date and time when the file was created on Box. + :type created_at: DateTime + :param modified_at: The date and time when the file was last updated on Box. + :type modified_at: DateTime + :param item_status: Defines if this item has been deleted or not. + + * `active` when the item has is not in the trash + * `trashed` when the item has been moved to the trash but not deleted + * `deleted` when the item has been permanently deleted. + :type item_status: TrashFileRestoredItemStatusField + :param etag: The HTTP `etag` of this file. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the file if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `file`., defaults to TrashFileRestoredTypeField.FILE + :type type: TrashFileRestoredTypeField, optional + :param name: The name of the file., defaults to None + :type name: Optional[str], optional + :param trashed_at: The time at which this file was put in the + trash - becomes `null` after restore., defaults to None + :type trashed_at: Optional[str], optional + :param purged_at: The time at which this file is expected to be purged + from the trash - becomes `null` after restore., defaults to None + :type purged_at: Optional[str], optional + :param content_created_at: The date and time at which this file was originally + created, which might be before it was uploaded to Box., defaults to None + :type content_created_at: Optional[DateTime], optional + :param content_modified_at: The date and time at which this file was last updated, + which might be before it was uploaded to Box., defaults to None + :type content_modified_at: Optional[DateTime], optional + :param shared_link: The shared link for this file. This will + be `null` if a file had been trashed, even though the original shared + link does become active again., defaults to None + :type shared_link: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.sequence_id = sequence_id + self.sha_1 = sha_1 + self.description = description + self.size = size + self.path_collection = path_collection + self.created_at = created_at + self.modified_at = modified_at + self.modified_by = modified_by + self.owned_by = owned_by + self.item_status = item_status + self.etag = etag + self.type = type + self.name = name + self.file_version = file_version + self.trashed_at = trashed_at + self.purged_at = purged_at + self.content_created_at = content_created_at + self.content_modified_at = content_modified_at + self.created_by = created_by + self.shared_link = shared_link + self.parent = parent diff --git a/box_sdk_gen/schemas/trash_folder.py b/box_sdk_gen/schemas/trash_folder.py new file mode 100644 index 000000000..cffb89cd1 --- /dev/null +++ b/box_sdk_gen/schemas/trash_folder.py @@ -0,0 +1,188 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TrashFolderTypeField(str, Enum): + FOLDER = 'folder' + + +class TrashFolderPathCollectionEntriesTypeField(str, Enum): + FOLDER = 'folder' + + +class TrashFolderPathCollectionEntriesField(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + *, + type: Optional[TrashFolderPathCollectionEntriesTypeField] = None, + id: Optional[str] = None, + sequence_id: Optional[str] = None, + etag: Optional[str] = None, + name: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `folder`., defaults to None + :type type: Optional[TrashFolderPathCollectionEntriesTypeField], optional + :param id: The unique identifier that represent a folder., defaults to None + :type id: Optional[str], optional + :param sequence_id: This field is null for the Trash folder., defaults to None + :type sequence_id: Optional[str], optional + :param etag: This field is null for the Trash folder., defaults to None + :type etag: Optional[str], optional + :param name: The name of the Trash folder., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.sequence_id = sequence_id + self.etag = etag + self.name = name + + +class TrashFolderPathCollectionField(BaseObject): + def __init__( + self, + total_count: int, + entries: List[TrashFolderPathCollectionEntriesField], + **kwargs + ): + """ + :param total_count: The number of folders in this list. + :type total_count: int + :param entries: Array of folders for this item's path collection. + :type entries: List[TrashFolderPathCollectionEntriesField] + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries + + +class TrashFolderItemStatusField(str, Enum): + ACTIVE = 'active' + TRASHED = 'trashed' + DELETED = 'deleted' + + +class TrashFolder(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + id: str, + name: str, + description: str, + size: int, + path_collection: TrashFolderPathCollectionField, + created_by: UserMini, + modified_by: UserMini, + owned_by: UserMini, + item_status: TrashFolderItemStatusField, + *, + etag: Optional[str] = None, + type: TrashFolderTypeField = TrashFolderTypeField.FOLDER, + sequence_id: Optional[str] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + trashed_at: Optional[DateTime] = None, + purged_at: Optional[DateTime] = None, + content_created_at: Optional[DateTime] = None, + content_modified_at: Optional[DateTime] = None, + shared_link: Optional[str] = None, + folder_upload_email: Optional[str] = None, + parent: Optional[FolderMini] = None, + **kwargs + ): + """ + :param id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting a folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folders/123` + the `folder_id` is `123`. + :type id: str + :param name: The name of the folder. + :type name: str + :param size: The folder size in bytes. + + Be careful parsing this integer as its + value can get very large. + :type size: int + :param item_status: Defines if this item has been deleted or not. + + * `active` when the item has is not in the trash + * `trashed` when the item has been moved to the trash but not deleted + * `deleted` when the item has been permanently deleted. + :type item_status: TrashFolderItemStatusField + :param etag: The HTTP `etag` of this folder. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the folder if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `folder`., defaults to TrashFolderTypeField.FOLDER + :type type: TrashFolderTypeField, optional + :param created_at: The date and time when the folder was created. This value may + be `null` for some folders such as the root folder or the trash + folder., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The date and time when the folder was last updated. This value may + be `null` for some folders such as the root folder or the trash + folder., defaults to None + :type modified_at: Optional[DateTime], optional + :param trashed_at: The time at which this folder was put in the trash., defaults to None + :type trashed_at: Optional[DateTime], optional + :param purged_at: The time at which this folder is expected to be purged + from the trash., defaults to None + :type purged_at: Optional[DateTime], optional + :param content_created_at: The date and time at which this folder was originally + created., defaults to None + :type content_created_at: Optional[DateTime], optional + :param content_modified_at: The date and time at which this folder was last updated., defaults to None + :type content_modified_at: Optional[DateTime], optional + :param shared_link: The shared link for this folder. This will + be `null` if a folder has been trashed, since the link will no longer + be active., defaults to None + :type shared_link: Optional[str], optional + :param folder_upload_email: The folder upload email for this folder. This will + be `null` if a folder has been trashed, since the upload will no longer + work., defaults to None + :type folder_upload_email: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.name = name + self.description = description + self.size = size + self.path_collection = path_collection + self.created_by = created_by + self.modified_by = modified_by + self.owned_by = owned_by + self.item_status = item_status + self.etag = etag + self.type = type + self.sequence_id = sequence_id + self.created_at = created_at + self.modified_at = modified_at + self.trashed_at = trashed_at + self.purged_at = purged_at + self.content_created_at = content_created_at + self.content_modified_at = content_modified_at + self.shared_link = shared_link + self.folder_upload_email = folder_upload_email + self.parent = parent diff --git a/box_sdk_gen/schemas/trash_folder_restored.py b/box_sdk_gen/schemas/trash_folder_restored.py new file mode 100644 index 000000000..5fe75104a --- /dev/null +++ b/box_sdk_gen/schemas/trash_folder_restored.py @@ -0,0 +1,147 @@ +from enum import Enum + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TrashFolderRestoredTypeField(str, Enum): + FOLDER = 'folder' + + +class TrashFolderRestoredPathCollectionField(BaseObject): + def __init__(self, total_count: int, entries: List[FolderMini], **kwargs): + """ + :param total_count: The number of folders in this list. + :type total_count: int + :param entries: The parent folders for this item. + :type entries: List[FolderMini] + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries + + +class TrashFolderRestoredItemStatusField(str, Enum): + ACTIVE = 'active' + TRASHED = 'trashed' + DELETED = 'deleted' + + +class TrashFolderRestored(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + *, + id: Optional[str] = None, + etag: Optional[str] = None, + type: Optional[TrashFolderRestoredTypeField] = None, + sequence_id: Optional[str] = None, + name: Optional[str] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + description: Optional[str] = None, + size: Optional[int] = None, + path_collection: Optional[TrashFolderRestoredPathCollectionField] = None, + created_by: Optional[UserMini] = None, + modified_by: Optional[UserMini] = None, + trashed_at: Optional[str] = None, + purged_at: Optional[str] = None, + content_created_at: Optional[DateTime] = None, + content_modified_at: Optional[DateTime] = None, + owned_by: Optional[UserMini] = None, + shared_link: Optional[str] = None, + folder_upload_email: Optional[str] = None, + parent: Optional[FolderMini] = None, + item_status: Optional[TrashFolderRestoredItemStatusField] = None, + **kwargs + ): + """ + :param id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting a folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folders/123` + the `folder_id` is `123`., defaults to None + :type id: Optional[str], optional + :param etag: The HTTP `etag` of this folder. This can be used within some API + endpoints in the `If-Match` and `If-None-Match` headers to only + perform changes on the folder if (no) changes have happened., defaults to None + :type etag: Optional[str], optional + :param type: The value will always be `folder`., defaults to None + :type type: Optional[TrashFolderRestoredTypeField], optional + :param name: The name of the folder., defaults to None + :type name: Optional[str], optional + :param created_at: The date and time when the folder was created. This value may + be `null` for some folders such as the root folder or the trash + folder., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The date and time when the folder was last updated. This value may + be `null` for some folders such as the root folder or the trash + folder., defaults to None + :type modified_at: Optional[DateTime], optional + :param size: The folder size in bytes. + + Be careful parsing this integer as its + value can get very large., defaults to None + :type size: Optional[int], optional + :param trashed_at: The time at which this folder was put in the + trash - becomes `null` after restore., defaults to None + :type trashed_at: Optional[str], optional + :param purged_at: The time at which this folder is expected to be purged + from the trash - becomes `null` after restore., defaults to None + :type purged_at: Optional[str], optional + :param content_created_at: The date and time at which this folder was originally + created., defaults to None + :type content_created_at: Optional[DateTime], optional + :param content_modified_at: The date and time at which this folder was last updated., defaults to None + :type content_modified_at: Optional[DateTime], optional + :param shared_link: The shared link for this file. This will + be `null` if a folder had been trashed, even though the original shared + link does become active again., defaults to None + :type shared_link: Optional[str], optional + :param folder_upload_email: The folder upload email for this folder. This will + be `null` if a folder has been trashed, even though the original upload + email does become active again., defaults to None + :type folder_upload_email: Optional[str], optional + :param item_status: Defines if this item has been deleted or not. + + * `active` when the item has is not in the trash, + * `trashed` when the item has been moved to the trash but not deleted, + * `deleted` when the item has been permanently deleted., defaults to None + :type item_status: Optional[TrashFolderRestoredItemStatusField], optional + """ + super().__init__(**kwargs) + self.id = id + self.etag = etag + self.type = type + self.sequence_id = sequence_id + self.name = name + self.created_at = created_at + self.modified_at = modified_at + self.description = description + self.size = size + self.path_collection = path_collection + self.created_by = created_by + self.modified_by = modified_by + self.trashed_at = trashed_at + self.purged_at = purged_at + self.content_created_at = content_created_at + self.content_modified_at = content_modified_at + self.owned_by = owned_by + self.shared_link = shared_link + self.folder_upload_email = folder_upload_email + self.parent = parent + self.item_status = item_status diff --git a/box_sdk_gen/schemas/trash_web_link.py b/box_sdk_gen/schemas/trash_web_link.py new file mode 100644 index 000000000..5e807b2b7 --- /dev/null +++ b/box_sdk_gen/schemas/trash_web_link.py @@ -0,0 +1,160 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TrashWebLinkTypeField(str, Enum): + WEB_LINK = 'web_link' + + +class TrashWebLinkPathCollectionEntriesTypeField(str, Enum): + FOLDER = 'folder' + + +class TrashWebLinkPathCollectionEntriesField(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + *, + type: Optional[TrashWebLinkPathCollectionEntriesTypeField] = None, + id: Optional[str] = None, + sequence_id: Optional[str] = None, + etag: Optional[str] = None, + name: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `folder`., defaults to None + :type type: Optional[TrashWebLinkPathCollectionEntriesTypeField], optional + :param id: The unique identifier that represent a folder., defaults to None + :type id: Optional[str], optional + :param sequence_id: This field is null for the Trash folder., defaults to None + :type sequence_id: Optional[str], optional + :param etag: This field is null for the Trash folder., defaults to None + :type etag: Optional[str], optional + :param name: The name of the Trash folder., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.sequence_id = sequence_id + self.etag = etag + self.name = name + + +class TrashWebLinkPathCollectionField(BaseObject): + def __init__( + self, + total_count: int, + entries: List[TrashWebLinkPathCollectionEntriesField], + **kwargs + ): + """ + :param total_count: The number of folders in this list. + :type total_count: int + :param entries: Array of folders for this item's path collection. + :type entries: List[TrashWebLinkPathCollectionEntriesField] + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries + + +class TrashWebLinkItemStatusField(str, Enum): + ACTIVE = 'active' + TRASHED = 'trashed' + DELETED = 'deleted' + + +class TrashWebLink(BaseObject): + _discriminator = 'type', {'web_link'} + + def __init__( + self, + *, + type: Optional[TrashWebLinkTypeField] = None, + id: Optional[str] = None, + sequence_id: Optional[str] = None, + etag: Optional[str] = None, + name: Optional[str] = None, + url: Optional[str] = None, + parent: Optional[FolderMini] = None, + description: Optional[str] = None, + path_collection: Optional[TrashWebLinkPathCollectionField] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + trashed_at: Optional[DateTime] = None, + purged_at: Optional[DateTime] = None, + created_by: Optional[UserMini] = None, + modified_by: Optional[UserMini] = None, + owned_by: Optional[UserMini] = None, + shared_link: Optional[str] = None, + item_status: Optional[TrashWebLinkItemStatusField] = None, + **kwargs + ): + """ + :param type: The value will always be `web_link`., defaults to None + :type type: Optional[TrashWebLinkTypeField], optional + :param id: The unique identifier for this web link., defaults to None + :type id: Optional[str], optional + :param etag: The entity tag of this web link. Used with `If-Match` + headers., defaults to None + :type etag: Optional[str], optional + :param name: The name of the web link., defaults to None + :type name: Optional[str], optional + :param url: The URL this web link points to., defaults to None + :type url: Optional[str], optional + :param description: The description accompanying the web link. This is + visible within the Box web application., defaults to None + :type description: Optional[str], optional + :param created_at: When this file was created on Box’s servers., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When this file was last updated on the Box + servers., defaults to None + :type modified_at: Optional[DateTime], optional + :param trashed_at: When this file was last moved to the trash., defaults to None + :type trashed_at: Optional[DateTime], optional + :param purged_at: When this file will be permanently deleted., defaults to None + :type purged_at: Optional[DateTime], optional + :param shared_link: The shared link for this bookmark. This will + be `null` if a bookmark has been trashed, since the link will no longer + be active., defaults to None + :type shared_link: Optional[str], optional + :param item_status: Whether this item is deleted or not. Values include `active`, + `trashed` if the file has been moved to the trash, and `deleted` if + the file has been permanently deleted., defaults to None + :type item_status: Optional[TrashWebLinkItemStatusField], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.sequence_id = sequence_id + self.etag = etag + self.name = name + self.url = url + self.parent = parent + self.description = description + self.path_collection = path_collection + self.created_at = created_at + self.modified_at = modified_at + self.trashed_at = trashed_at + self.purged_at = purged_at + self.created_by = created_by + self.modified_by = modified_by + self.owned_by = owned_by + self.shared_link = shared_link + self.item_status = item_status diff --git a/box_sdk_gen/schemas/trash_web_link_restored.py b/box_sdk_gen/schemas/trash_web_link_restored.py new file mode 100644 index 000000000..e2f033968 --- /dev/null +++ b/box_sdk_gen/schemas/trash_web_link_restored.py @@ -0,0 +1,120 @@ +from enum import Enum + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class TrashWebLinkRestoredTypeField(str, Enum): + WEB_LINK = 'web_link' + + +class TrashWebLinkRestoredPathCollectionField(BaseObject): + def __init__(self, total_count: int, entries: List[FolderMini], **kwargs): + """ + :param total_count: The number of folders in this list. + :type total_count: int + :param entries: The parent folders for this item. + :type entries: List[FolderMini] + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries + + +class TrashWebLinkRestoredItemStatusField(str, Enum): + ACTIVE = 'active' + TRASHED = 'trashed' + DELETED = 'deleted' + + +class TrashWebLinkRestored(BaseObject): + _discriminator = 'type', {'web_link'} + + def __init__( + self, + sequence_id: str, + path_collection: TrashWebLinkRestoredPathCollectionField, + *, + type: Optional[TrashWebLinkRestoredTypeField] = None, + id: Optional[str] = None, + etag: Optional[str] = None, + name: Optional[str] = None, + url: Optional[str] = None, + parent: Optional[FolderMini] = None, + description: Optional[str] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + trashed_at: Optional[str] = None, + purged_at: Optional[str] = None, + created_by: Optional[UserMini] = None, + modified_by: Optional[UserMini] = None, + owned_by: Optional[UserMini] = None, + shared_link: Optional[str] = None, + item_status: Optional[TrashWebLinkRestoredItemStatusField] = None, + **kwargs + ): + """ + :param type: The value will always be `web_link`., defaults to None + :type type: Optional[TrashWebLinkRestoredTypeField], optional + :param id: The unique identifier for this web link., defaults to None + :type id: Optional[str], optional + :param etag: The entity tag of this web link. Used with `If-Match` + headers., defaults to None + :type etag: Optional[str], optional + :param name: The name of the web link., defaults to None + :type name: Optional[str], optional + :param url: The URL this web link points to., defaults to None + :type url: Optional[str], optional + :param description: The description accompanying the web link. This is + visible within the Box web application., defaults to None + :type description: Optional[str], optional + :param created_at: When this file was created on Box’s servers., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When this file was last updated on the Box + servers., defaults to None + :type modified_at: Optional[DateTime], optional + :param trashed_at: The time at which this bookmark was put in the + trash - becomes `null` after restore., defaults to None + :type trashed_at: Optional[str], optional + :param purged_at: The time at which this bookmark will be permanently + deleted - becomes `null` after restore., defaults to None + :type purged_at: Optional[str], optional + :param shared_link: The shared link for this bookmark. This will + be `null` if a bookmark had been trashed, even though the original shared + link does become active again., defaults to None + :type shared_link: Optional[str], optional + :param item_status: Whether this item is deleted or not. Values include `active`, + `trashed` if the file has been moved to the trash, and `deleted` if + the file has been permanently deleted., defaults to None + :type item_status: Optional[TrashWebLinkRestoredItemStatusField], optional + """ + super().__init__(**kwargs) + self.sequence_id = sequence_id + self.path_collection = path_collection + self.type = type + self.id = id + self.etag = etag + self.name = name + self.url = url + self.parent = parent + self.description = description + self.created_at = created_at + self.modified_at = modified_at + self.trashed_at = trashed_at + self.purged_at = purged_at + self.created_by = created_by + self.modified_by = modified_by + self.owned_by = owned_by + self.shared_link = shared_link + self.item_status = item_status diff --git a/box_sdk_gen/schemas/upload_part.py b/box_sdk_gen/schemas/upload_part.py new file mode 100644 index 000000000..b9bef8b1d --- /dev/null +++ b/box_sdk_gen/schemas/upload_part.py @@ -0,0 +1,42 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.schemas.upload_part_mini import UploadPartMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class UploadPart(UploadPartMini): + _fields_to_json_mapping: Dict[str, str] = { + 'sha_1': 'sha1', + **UploadPartMini._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'sha1': 'sha_1', + **UploadPartMini._json_to_fields_mapping, + } + + def __init__( + self, + *, + sha_1: Optional[str] = None, + part_id: Optional[str] = None, + offset: Optional[int] = None, + size: Optional[int] = None, + **kwargs + ): + """ + :param sha_1: The SHA1 hash of the chunk., defaults to None + :type sha_1: Optional[str], optional + :param part_id: The unique ID of the chunk., defaults to None + :type part_id: Optional[str], optional + :param offset: The offset of the chunk within the file + in bytes. The lower bound of the position + of the chunk within the file., defaults to None + :type offset: Optional[int], optional + :param size: The size of the chunk in bytes., defaults to None + :type size: Optional[int], optional + """ + super().__init__(part_id=part_id, offset=offset, size=size, **kwargs) + self.sha_1 = sha_1 diff --git a/box_sdk_gen/schemas/upload_part_mini.py b/box_sdk_gen/schemas/upload_part_mini.py new file mode 100644 index 000000000..70c511ca4 --- /dev/null +++ b/box_sdk_gen/schemas/upload_part_mini.py @@ -0,0 +1,30 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UploadPartMini(BaseObject): + def __init__( + self, + *, + part_id: Optional[str] = None, + offset: Optional[int] = None, + size: Optional[int] = None, + **kwargs + ): + """ + :param part_id: The unique ID of the chunk., defaults to None + :type part_id: Optional[str], optional + :param offset: The offset of the chunk within the file + in bytes. The lower bound of the position + of the chunk within the file., defaults to None + :type offset: Optional[int], optional + :param size: The size of the chunk in bytes., defaults to None + :type size: Optional[int], optional + """ + super().__init__(**kwargs) + self.part_id = part_id + self.offset = offset + self.size = size diff --git a/box_sdk_gen/schemas/upload_parts.py b/box_sdk_gen/schemas/upload_parts.py new file mode 100644 index 000000000..34c7e69a6 --- /dev/null +++ b/box_sdk_gen/schemas/upload_parts.py @@ -0,0 +1,81 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.upload_part import UploadPart + +from box_sdk_gen.box.errors import BoxSDKError + + +class UploadPartsOrderDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class UploadPartsOrderField(BaseObject): + def __init__( + self, + *, + by: Optional[str] = None, + direction: Optional[UploadPartsOrderDirectionField] = None, + **kwargs + ): + """ + :param by: The field to order by., defaults to None + :type by: Optional[str], optional + :param direction: The direction to order by, either ascending or descending., defaults to None + :type direction: Optional[UploadPartsOrderDirectionField], optional + """ + super().__init__(**kwargs) + self.by = by + self.direction = direction + + +class UploadParts(BaseObject): + def __init__( + self, + *, + total_count: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, + order: Optional[List[UploadPartsOrderField]] = None, + entries: Optional[List[UploadPart]] = None, + **kwargs + ): + """ + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type total_count: Optional[int], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type offset: Optional[int], optional + :param order: The order by which items are returned. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type order: Optional[List[UploadPartsOrderField]], optional + :param entries: A list of uploaded chunks for an upload + session., defaults to None + :type entries: Optional[List[UploadPart]], optional + """ + super().__init__(**kwargs) + self.total_count = total_count + self.limit = limit + self.offset = offset + self.order = order + self.entries = entries diff --git a/box_sdk_gen/schemas/upload_session.py b/box_sdk_gen/schemas/upload_session.py new file mode 100644 index 000000000..be0806203 --- /dev/null +++ b/box_sdk_gen/schemas/upload_session.py @@ -0,0 +1,95 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class UploadSessionTypeField(str, Enum): + UPLOAD_SESSION = 'upload_session' + + +class UploadSessionSessionEndpointsField(BaseObject): + def __init__( + self, + *, + upload_part: Optional[str] = None, + commit: Optional[str] = None, + abort: Optional[str] = None, + list_parts: Optional[str] = None, + status: Optional[str] = None, + log_event: Optional[str] = None, + **kwargs + ): + """ + :param upload_part: The URL to upload parts to., defaults to None + :type upload_part: Optional[str], optional + :param commit: The URL used to commit the file., defaults to None + :type commit: Optional[str], optional + :param abort: The URL for used to abort the session., defaults to None + :type abort: Optional[str], optional + :param list_parts: The URL users to list all parts., defaults to None + :type list_parts: Optional[str], optional + :param status: The URL used to get the status of the upload., defaults to None + :type status: Optional[str], optional + :param log_event: The URL used to get the upload log from., defaults to None + :type log_event: Optional[str], optional + """ + super().__init__(**kwargs) + self.upload_part = upload_part + self.commit = commit + self.abort = abort + self.list_parts = list_parts + self.status = status + self.log_event = log_event + + +class UploadSession(BaseObject): + _discriminator = 'type', {'upload_session'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[UploadSessionTypeField] = None, + session_expires_at: Optional[DateTime] = None, + part_size: Optional[int] = None, + total_parts: Optional[int] = None, + num_parts_processed: Optional[int] = None, + session_endpoints: Optional[UploadSessionSessionEndpointsField] = None, + **kwargs + ): + """ + :param id: The unique identifier for this session., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `upload_session`., defaults to None + :type type: Optional[UploadSessionTypeField], optional + :param session_expires_at: The date and time when this session expires., defaults to None + :type session_expires_at: Optional[DateTime], optional + :param part_size: The size in bytes that must be used for all parts of of the + upload. + + Only the last part is allowed to be of a smaller size., defaults to None + :type part_size: Optional[int], optional + :param total_parts: The total number of parts expected in this upload session, + as determined by the file size and part size., defaults to None + :type total_parts: Optional[int], optional + :param num_parts_processed: The number of parts that have been uploaded and processed + by the server. This starts at `0`. + + When committing a file files, inspecting this property can + provide insight if all parts have been uploaded correctly., defaults to None + :type num_parts_processed: Optional[int], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.session_expires_at = session_expires_at + self.part_size = part_size + self.total_parts = total_parts + self.num_parts_processed = num_parts_processed + self.session_endpoints = session_endpoints diff --git a/box_sdk_gen/schemas/upload_url.py b/box_sdk_gen/schemas/upload_url.py new file mode 100644 index 000000000..16ebe3b82 --- /dev/null +++ b/box_sdk_gen/schemas/upload_url.py @@ -0,0 +1,25 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UploadUrl(BaseObject): + def __init__( + self, + *, + upload_url: Optional[str] = None, + upload_token: Optional[str] = None, + **kwargs + ): + """ + :param upload_url: A URL for an upload session that can be used to upload + the file., defaults to None + :type upload_url: Optional[str], optional + :param upload_token: An optional access token to use to upload the file., defaults to None + :type upload_token: Optional[str], optional + """ + super().__init__(**kwargs) + self.upload_url = upload_url + self.upload_token = upload_token diff --git a/box_sdk_gen/schemas/uploaded_part.py b/box_sdk_gen/schemas/uploaded_part.py new file mode 100644 index 000000000..e8d1fa273 --- /dev/null +++ b/box_sdk_gen/schemas/uploaded_part.py @@ -0,0 +1,13 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.upload_part import UploadPart + +from box_sdk_gen.box.errors import BoxSDKError + + +class UploadedPart(BaseObject): + def __init__(self, *, part: Optional[UploadPart] = None, **kwargs): + super().__init__(**kwargs) + self.part = part diff --git a/box_sdk_gen/schemas/user.py b/box_sdk_gen/schemas/user.py new file mode 100644 index 000000000..58eb548fc --- /dev/null +++ b/box_sdk_gen/schemas/user.py @@ -0,0 +1,120 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.user_base import UserBaseTypeField + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class UserStatusField(str, Enum): + ACTIVE = 'active' + INACTIVE = 'inactive' + CANNOT_DELETE_EDIT = 'cannot_delete_edit' + CANNOT_DELETE_EDIT_UPLOAD = 'cannot_delete_edit_upload' + + +class UserNotificationEmailField(BaseObject): + def __init__( + self, + *, + email: Optional[str] = None, + is_confirmed: Optional[bool] = None, + **kwargs + ): + """ + :param email: The email address to send the notifications to., defaults to None + :type email: Optional[str], optional + :param is_confirmed: Specifies if this email address has been confirmed., defaults to None + :type is_confirmed: Optional[bool], optional + """ + super().__init__(**kwargs) + self.email = email + self.is_confirmed = is_confirmed + + +class User(UserMini): + def __init__( + self, + id: str, + *, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + language: Optional[str] = None, + timezone: Optional[str] = None, + space_amount: Optional[int] = None, + space_used: Optional[int] = None, + max_upload_size: Optional[int] = None, + status: Optional[UserStatusField] = None, + job_title: Optional[str] = None, + phone: Optional[str] = None, + address: Optional[str] = None, + avatar_url: Optional[str] = None, + notification_email: Optional[UserNotificationEmailField] = None, + name: Optional[str] = None, + login: Optional[str] = None, + type: UserBaseTypeField = UserBaseTypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param created_at: When the user object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the user object was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param language: The language of the user, formatted in modified version of the + [ISO 639-1](/guides/api-calls/language-codes) format., defaults to None + :type language: Optional[str], optional + :param timezone: The user's timezone., defaults to None + :type timezone: Optional[str], optional + :param space_amount: The user’s total available space amount in bytes., defaults to None + :type space_amount: Optional[int], optional + :param space_used: The amount of space in use by the user., defaults to None + :type space_used: Optional[int], optional + :param max_upload_size: The maximum individual file size in bytes the user can have., defaults to None + :type max_upload_size: Optional[int], optional + :param status: The user's account status., defaults to None + :type status: Optional[UserStatusField], optional + :param job_title: The user’s job title., defaults to None + :type job_title: Optional[str], optional + :param phone: The user’s phone number., defaults to None + :type phone: Optional[str], optional + :param address: The user’s address., defaults to None + :type address: Optional[str], optional + :param avatar_url: URL of the user’s avatar image., defaults to None + :type avatar_url: Optional[str], optional + :param notification_email: An alternate notification email address to which email + notifications are sent. When it's confirmed, this will be + the email address to which notifications are sent instead of + to the primary email address., defaults to None + :type notification_email: Optional[UserNotificationEmailField], optional + :param name: The display name of this user., defaults to None + :type name: Optional[str], optional + :param login: The primary email address of this user., defaults to None + :type login: Optional[str], optional + :param type: The value will always be `user`., defaults to UserBaseTypeField.USER + :type type: UserBaseTypeField, optional + """ + super().__init__(id=id, name=name, login=login, type=type, **kwargs) + self.created_at = created_at + self.modified_at = modified_at + self.language = language + self.timezone = timezone + self.space_amount = space_amount + self.space_used = space_used + self.max_upload_size = max_upload_size + self.status = status + self.job_title = job_title + self.phone = phone + self.address = address + self.avatar_url = avatar_url + self.notification_email = notification_email diff --git a/box_sdk_gen/schemas/user_avatar.py b/box_sdk_gen/schemas/user_avatar.py new file mode 100644 index 000000000..c13545ec1 --- /dev/null +++ b/box_sdk_gen/schemas/user_avatar.py @@ -0,0 +1,38 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserAvatarPicUrlsField(BaseObject): + def __init__( + self, + *, + small: Optional[str] = None, + large: Optional[str] = None, + preview: Optional[str] = None, + **kwargs + ): + """ + :param small: The location of a small-sized avatar., defaults to None + :type small: Optional[str], optional + :param large: The location of a large-sized avatar., defaults to None + :type large: Optional[str], optional + :param preview: The location of the avatar preview., defaults to None + :type preview: Optional[str], optional + """ + super().__init__(**kwargs) + self.small = small + self.large = large + self.preview = preview + + +class UserAvatar(BaseObject): + def __init__(self, *, pic_urls: Optional[UserAvatarPicUrlsField] = None, **kwargs): + """ + :param pic_urls: Represents an object with user avatar URLs., defaults to None + :type pic_urls: Optional[UserAvatarPicUrlsField], optional + """ + super().__init__(**kwargs) + self.pic_urls = pic_urls diff --git a/box_sdk_gen/schemas/user_base.py b/box_sdk_gen/schemas/user_base.py new file mode 100644 index 000000000..a4aefecb8 --- /dev/null +++ b/box_sdk_gen/schemas/user_base.py @@ -0,0 +1,26 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserBaseTypeField(str, Enum): + USER = 'user' + + +class UserBase(BaseObject): + _discriminator = 'type', {'user'} + + def __init__( + self, id: str, *, type: UserBaseTypeField = UserBaseTypeField.USER, **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param type: The value will always be `user`., defaults to UserBaseTypeField.USER + :type type: UserBaseTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/user_collaborations.py b/box_sdk_gen/schemas/user_collaborations.py new file mode 100644 index 000000000..848ce5974 --- /dev/null +++ b/box_sdk_gen/schemas/user_collaborations.py @@ -0,0 +1,36 @@ +from typing import Optional + +from box_sdk_gen.schemas.user_base import UserBaseTypeField + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserCollaborations(UserBase): + def __init__( + self, + id: str, + *, + name: Optional[str] = None, + login: Optional[str] = None, + is_active: Optional[bool] = None, + type: UserBaseTypeField = UserBaseTypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param name: The display name of this user. If the collaboration status is `pending`, an empty string is returned., defaults to None + :type name: Optional[str], optional + :param login: The primary email address of this user. If the collaboration status is `pending`, an empty string is returned., defaults to None + :type login: Optional[str], optional + :param is_active: If set to `false`, the user is either deactivated or deleted., defaults to None + :type is_active: Optional[bool], optional + :param type: The value will always be `user`., defaults to UserBaseTypeField.USER + :type type: UserBaseTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.name = name + self.login = login + self.is_active = is_active diff --git a/box_sdk_gen/schemas/user_full.py b/box_sdk_gen/schemas/user_full.py new file mode 100644 index 000000000..b35ae441f --- /dev/null +++ b/box_sdk_gen/schemas/user_full.py @@ -0,0 +1,199 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.user_base import UserBaseTypeField + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.user import UserStatusField + +from box_sdk_gen.schemas.user import UserNotificationEmailField + +from box_sdk_gen.schemas.user import User + +from box_sdk_gen.schemas.tracking_code import TrackingCode + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserFullRoleField(str, Enum): + ADMIN = 'admin' + COADMIN = 'coadmin' + USER = 'user' + + +class UserFullEnterpriseTypeField(str, Enum): + ENTERPRISE = 'enterprise' + + +class UserFullEnterpriseField(BaseObject): + _discriminator = 'type', {'enterprise'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[UserFullEnterpriseTypeField] = None, + name: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this enterprise., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `enterprise`., defaults to None + :type type: Optional[UserFullEnterpriseTypeField], optional + :param name: The name of the enterprise., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + + +class UserFull(User): + def __init__( + self, + id: str, + *, + role: Optional[UserFullRoleField] = None, + tracking_codes: Optional[List[TrackingCode]] = None, + can_see_managed_users: Optional[bool] = None, + is_sync_enabled: Optional[bool] = None, + is_external_collab_restricted: Optional[bool] = None, + is_exempt_from_device_limits: Optional[bool] = None, + is_exempt_from_login_verification: Optional[bool] = None, + enterprise: Optional[UserFullEnterpriseField] = None, + my_tags: Optional[List[str]] = None, + hostname: Optional[str] = None, + is_platform_access_only: Optional[bool] = None, + external_app_user_id: Optional[str] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + language: Optional[str] = None, + timezone: Optional[str] = None, + space_amount: Optional[int] = None, + space_used: Optional[int] = None, + max_upload_size: Optional[int] = None, + status: Optional[UserStatusField] = None, + job_title: Optional[str] = None, + phone: Optional[str] = None, + address: Optional[str] = None, + avatar_url: Optional[str] = None, + notification_email: Optional[UserNotificationEmailField] = None, + name: Optional[str] = None, + login: Optional[str] = None, + type: UserBaseTypeField = UserBaseTypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param role: The user’s enterprise role., defaults to None + :type role: Optional[UserFullRoleField], optional + :param tracking_codes: Tracking codes allow an admin to generate reports from the + admin console and assign an attribute to a specific group + of users. This setting must be enabled for an enterprise + before it can be used., defaults to None + :type tracking_codes: Optional[List[TrackingCode]], optional + :param can_see_managed_users: Whether the user can see other enterprise users in their contact list., defaults to None + :type can_see_managed_users: Optional[bool], optional + :param is_sync_enabled: Whether the user can use Box Sync., defaults to None + :type is_sync_enabled: Optional[bool], optional + :param is_external_collab_restricted: Whether the user is allowed to collaborate with users outside their + enterprise., defaults to None + :type is_external_collab_restricted: Optional[bool], optional + :param is_exempt_from_device_limits: Whether to exempt the user from Enterprise device limits., defaults to None + :type is_exempt_from_device_limits: Optional[bool], optional + :param is_exempt_from_login_verification: Whether the user must use two-factor authentication., defaults to None + :type is_exempt_from_login_verification: Optional[bool], optional + :param my_tags: Tags for all files and folders owned by the user. Values returned + will only contain tags that were set by the requester., defaults to None + :type my_tags: Optional[List[str]], optional + :param hostname: The root (protocol, subdomain, domain) of any links that need to be + generated for the user., defaults to None + :type hostname: Optional[str], optional + :param is_platform_access_only: Whether the user is an App User., defaults to None + :type is_platform_access_only: Optional[bool], optional + :param external_app_user_id: An external identifier for an app user, which can be used to look up + the user. This can be used to tie user IDs from external identity + providers to Box users., defaults to None + :type external_app_user_id: Optional[str], optional + :param created_at: When the user object was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When the user object was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + :param language: The language of the user, formatted in modified version of the + [ISO 639-1](/guides/api-calls/language-codes) format., defaults to None + :type language: Optional[str], optional + :param timezone: The user's timezone., defaults to None + :type timezone: Optional[str], optional + :param space_amount: The user’s total available space amount in bytes., defaults to None + :type space_amount: Optional[int], optional + :param space_used: The amount of space in use by the user., defaults to None + :type space_used: Optional[int], optional + :param max_upload_size: The maximum individual file size in bytes the user can have., defaults to None + :type max_upload_size: Optional[int], optional + :param status: The user's account status., defaults to None + :type status: Optional[UserStatusField], optional + :param job_title: The user’s job title., defaults to None + :type job_title: Optional[str], optional + :param phone: The user’s phone number., defaults to None + :type phone: Optional[str], optional + :param address: The user’s address., defaults to None + :type address: Optional[str], optional + :param avatar_url: URL of the user’s avatar image., defaults to None + :type avatar_url: Optional[str], optional + :param notification_email: An alternate notification email address to which email + notifications are sent. When it's confirmed, this will be + the email address to which notifications are sent instead of + to the primary email address., defaults to None + :type notification_email: Optional[UserNotificationEmailField], optional + :param name: The display name of this user., defaults to None + :type name: Optional[str], optional + :param login: The primary email address of this user., defaults to None + :type login: Optional[str], optional + :param type: The value will always be `user`., defaults to UserBaseTypeField.USER + :type type: UserBaseTypeField, optional + """ + super().__init__( + id=id, + created_at=created_at, + modified_at=modified_at, + language=language, + timezone=timezone, + space_amount=space_amount, + space_used=space_used, + max_upload_size=max_upload_size, + status=status, + job_title=job_title, + phone=phone, + address=address, + avatar_url=avatar_url, + notification_email=notification_email, + name=name, + login=login, + type=type, + **kwargs + ) + self.role = role + self.tracking_codes = tracking_codes + self.can_see_managed_users = can_see_managed_users + self.is_sync_enabled = is_sync_enabled + self.is_external_collab_restricted = is_external_collab_restricted + self.is_exempt_from_device_limits = is_exempt_from_device_limits + self.is_exempt_from_login_verification = is_exempt_from_login_verification + self.enterprise = enterprise + self.my_tags = my_tags + self.hostname = hostname + self.is_platform_access_only = is_platform_access_only + self.external_app_user_id = external_app_user_id diff --git a/box_sdk_gen/schemas/user_integration_mappings.py b/box_sdk_gen/schemas/user_integration_mappings.py new file mode 100644 index 000000000..8e14d80f5 --- /dev/null +++ b/box_sdk_gen/schemas/user_integration_mappings.py @@ -0,0 +1,32 @@ +from typing import Optional + +from box_sdk_gen.schemas.user_base import UserBaseTypeField + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserIntegrationMappings(UserBase): + def __init__( + self, + id: str, + *, + name: Optional[str] = None, + login: Optional[str] = None, + type: UserBaseTypeField = UserBaseTypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param name: The display name of this user., defaults to None + :type name: Optional[str], optional + :param login: The primary email address of this user., defaults to None + :type login: Optional[str], optional + :param type: The value will always be `user`., defaults to UserBaseTypeField.USER + :type type: UserBaseTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.name = name + self.login = login diff --git a/box_sdk_gen/schemas/user_mini.py b/box_sdk_gen/schemas/user_mini.py new file mode 100644 index 000000000..e53d185b7 --- /dev/null +++ b/box_sdk_gen/schemas/user_mini.py @@ -0,0 +1,32 @@ +from typing import Optional + +from box_sdk_gen.schemas.user_base import UserBaseTypeField + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserMini(UserBase): + def __init__( + self, + id: str, + *, + name: Optional[str] = None, + login: Optional[str] = None, + type: UserBaseTypeField = UserBaseTypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param name: The display name of this user., defaults to None + :type name: Optional[str], optional + :param login: The primary email address of this user., defaults to None + :type login: Optional[str], optional + :param type: The value will always be `user`., defaults to UserBaseTypeField.USER + :type type: UserBaseTypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.name = name + self.login = login diff --git a/box_sdk_gen/schemas/users.py b/box_sdk_gen/schemas/users.py new file mode 100644 index 000000000..81c8d6d82 --- /dev/null +++ b/box_sdk_gen/schemas/users.py @@ -0,0 +1,88 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.box.errors import BoxSDKError + + +class UsersOrderDirectionField(str, Enum): + ASC = 'ASC' + DESC = 'DESC' + + +class UsersOrderField(BaseObject): + def __init__( + self, + *, + by: Optional[str] = None, + direction: Optional[UsersOrderDirectionField] = None, + **kwargs + ): + """ + :param by: The field to order by., defaults to None + :type by: Optional[str], optional + :param direction: The direction to order by, either ascending or descending., defaults to None + :type direction: Optional[UsersOrderDirectionField], optional + """ + super().__init__(**kwargs) + self.by = by + self.direction = direction + + +class Users(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + total_count: Optional[int] = None, + offset: Optional[int] = None, + order: Optional[List[UsersOrderField]] = None, + entries: Optional[List[UserFull]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param total_count: One greater than the offset of the last entry in the entire collection. + The total number of entries in the collection may be less than + `total_count`. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type total_count: Optional[int], optional + :param offset: The 0-based offset of the first entry in this set. This will be the same + as the `offset` query parameter. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type offset: Optional[int], optional + :param order: The order by which items are returned. + + This field is only returned for calls that use offset-based pagination. + For marker-based paginated APIs, this field will be omitted., defaults to None + :type order: Optional[List[UsersOrderField]], optional + :param entries: A list of users., defaults to None + :type entries: Optional[List[UserFull]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.total_count = total_count + self.offset = offset + self.order = order + self.entries = entries diff --git a/box_sdk_gen/schemas/v2025_r0/__init__.py b/box_sdk_gen/schemas/v2025_r0/__init__.py new file mode 100644 index 000000000..cd0c28ba6 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/__init__.py @@ -0,0 +1,119 @@ +from box_sdk_gen.schemas.v2025_r0.archive_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.archives_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_batch_base_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_document_generation_data_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_base_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_tag_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_tags_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_tags_processing_message_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_reference_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_create_request_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_base_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_templates_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.file_version_base_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_jobs_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_batch_create_request_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.folder_reference_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.group_base_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.group_mini_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_base_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_action_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_create_request_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_update_request_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_copy_request_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_create_request_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_item_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_items_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_update_request_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_list_mini_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_lists_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_country_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_domain_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_email_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_integration_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_ip_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_request_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_lists_update_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_lists_create_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shield_list_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.terms_of_service_base_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.user_base_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.user_mini_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hubs_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_user_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_access_grantee_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_collaborations_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_full_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.doc_gen_jobs_full_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.weblink_reference_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_item_reference_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_item_operation_result_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_items_manage_response_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_item_operation_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_items_manage_request_v2025_r0 import * diff --git a/box_sdk_gen/schemas/v2025_r0/archive_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/archive_v2025_r0.py new file mode 100644 index 000000000..4c65dafba --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/archive_v2025_r0.py @@ -0,0 +1,43 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ArchiveV2025R0TypeField(str, Enum): + ARCHIVE = 'archive' + + +class ArchiveV2025R0(BaseObject): + _discriminator = 'type', {'archive'} + + def __init__( + self, + id: str, + name: str, + size: int, + *, + type: ArchiveV2025R0TypeField = ArchiveV2025R0TypeField.ARCHIVE, + **kwargs + ): + r""" + :param id: The unique identifier that represents an archive. + :type id: str + :param name: The name of the archive. + + The following restrictions to the archive name apply: names containing + non-printable ASCII characters, forward and backward slashes + (`/`, `\`), names with trailing spaces, and names `.` and `..` are + not allowed. + :type name: str + :param size: The size of the archive in bytes. + :type size: int + :param type: The value will always be `archive`., defaults to ArchiveV2025R0TypeField.ARCHIVE + :type type: ArchiveV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.name = name + self.size = size + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/archives_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/archives_v2025_r0.py new file mode 100644 index 000000000..95c1b67ab --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/archives_v2025_r0.py @@ -0,0 +1,34 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.archive_v2025_r0 import ArchiveV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class ArchivesV2025R0(BaseObject): + def __init__( + self, + *, + entries: Optional[List[ArchiveV2025R0]] = None, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: A list in which each entry represents an archive object., defaults to None + :type entries: Optional[List[ArchiveV2025R0]], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/v2025_r0/client_error_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/client_error_v2025_r0.py new file mode 100644 index 000000000..f081211dd --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/client_error_v2025_r0.py @@ -0,0 +1,77 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ClientErrorV2025R0TypeField(str, Enum): + ERROR = 'error' + + +class ClientErrorV2025R0CodeField(str, Enum): + CREATED = 'created' + ACCEPTED = 'accepted' + NO_CONTENT = 'no_content' + REDIRECT = 'redirect' + NOT_MODIFIED = 'not_modified' + BAD_REQUEST = 'bad_request' + UNAUTHORIZED = 'unauthorized' + FORBIDDEN = 'forbidden' + NOT_FOUND = 'not_found' + METHOD_NOT_ALLOWED = 'method_not_allowed' + CONFLICT = 'conflict' + PRECONDITION_FAILED = 'precondition_failed' + TOO_MANY_REQUESTS = 'too_many_requests' + INTERNAL_SERVER_ERROR = 'internal_server_error' + UNAVAILABLE = 'unavailable' + ITEM_NAME_INVALID = 'item_name_invalid' + INSUFFICIENT_SCOPE = 'insufficient_scope' + + +class ClientErrorV2025R0(BaseObject): + _discriminator = 'type', {'error'} + + def __init__( + self, + *, + type: Optional[ClientErrorV2025R0TypeField] = None, + status: Optional[int] = None, + code: Optional[ClientErrorV2025R0CodeField] = None, + message: Optional[str] = None, + context_info: Optional[Dict] = None, + help_url: Optional[str] = None, + request_id: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `error`., defaults to None + :type type: Optional[ClientErrorV2025R0TypeField], optional + :param status: The HTTP status of the response., defaults to None + :type status: Optional[int], optional + :param code: A Box-specific error code., defaults to None + :type code: Optional[ClientErrorV2025R0CodeField], optional + :param message: A short message describing the error., defaults to None + :type message: Optional[str], optional + :param context_info: A free-form object that contains additional context + about the error. The possible fields are defined on + a per-endpoint basis. `message` is only one example., defaults to None + :type context_info: Optional[Dict], optional + :param help_url: A URL that links to more information about why this error occurred., defaults to None + :type help_url: Optional[str], optional + :param request_id: A unique identifier for this response, which can be used + when contacting Box support., defaults to None + :type request_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.status = status + self.code = code + self.message = message + self.context_info = context_info + self.help_url = help_url + self.request_id = request_id diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_batch_base_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_batch_base_v2025_r0.py new file mode 100644 index 000000000..ff82b370a --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_batch_base_v2025_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenBatchBaseV2025R0TypeField(str, Enum): + DOCGEN_BATCH = 'docgen_batch' + + +class DocGenBatchBaseV2025R0(BaseObject): + _discriminator = 'type', {'docgen_batch'} + + def __init__( + self, + id: str, + *, + type: DocGenBatchBaseV2025R0TypeField = DocGenBatchBaseV2025R0TypeField.DOCGEN_BATCH, + **kwargs + ): + """ + :param id: The unique identifier that represents a Box Doc Gen batch. + :type id: str + :param type: The value will always be `docgen_batch`., defaults to DocGenBatchBaseV2025R0TypeField.DOCGEN_BATCH + :type type: DocGenBatchBaseV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_batch_create_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_batch_create_request_v2025_r0.py new file mode 100644 index 000000000..391291724 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_batch_create_request_v2025_r0.py @@ -0,0 +1,71 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.schemas.v2025_r0.file_version_base_v2025_r0 import ( + FileVersionBaseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_document_generation_data_v2025_r0 import ( + DocGenDocumentGenerationDataV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenBatchCreateRequestV2025R0DestinationFolderTypeField(str, Enum): + FOLDER = 'folder' + + +class DocGenBatchCreateRequestV2025R0DestinationFolderField(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + id: str, + *, + type: DocGenBatchCreateRequestV2025R0DestinationFolderTypeField = DocGenBatchCreateRequestV2025R0DestinationFolderTypeField.FOLDER, + **kwargs + ): + """ + :param id: ID of the folder. + :type id: str + :param type: The value will always be `folder`., defaults to DocGenBatchCreateRequestV2025R0DestinationFolderTypeField.FOLDER + :type type: DocGenBatchCreateRequestV2025R0DestinationFolderTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class DocGenBatchCreateRequestV2025R0(BaseObject): + def __init__( + self, + file: FileReferenceV2025R0, + input_source: str, + destination_folder: DocGenBatchCreateRequestV2025R0DestinationFolderField, + output_type: str, + document_generation_data: List[DocGenDocumentGenerationDataV2025R0], + *, + file_version: Optional[FileVersionBaseV2025R0] = None, + **kwargs + ): + """ + :param input_source: Source of input. The value has to be `api` for all the API-based document generation requests. + :type input_source: str + :param output_type: Type of the output file. + :type output_type: str + """ + super().__init__(**kwargs) + self.file = file + self.input_source = input_source + self.destination_folder = destination_folder + self.output_type = output_type + self.document_generation_data = document_generation_data + self.file_version = file_version diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_document_generation_data_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_document_generation_data_v2025_r0.py new file mode 100644 index 000000000..b7a70a2a2 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_document_generation_data_v2025_r0.py @@ -0,0 +1,16 @@ +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenDocumentGenerationDataV2025R0(BaseObject): + def __init__(self, generated_file_name: str, user_input: Dict, **kwargs): + """ + :param generated_file_name: File name of the output file. + :type generated_file_name: str + """ + super().__init__(**kwargs) + self.generated_file_name = generated_file_name + self.user_input = user_input diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_base_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_base_v2025_r0.py new file mode 100644 index 000000000..041ca4c0f --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_base_v2025_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenJobBaseV2025R0TypeField(str, Enum): + DOCGEN_JOB = 'docgen_job' + + +class DocGenJobBaseV2025R0(BaseObject): + _discriminator = 'type', {'docgen_job'} + + def __init__( + self, + id: str, + *, + type: DocGenJobBaseV2025R0TypeField = DocGenJobBaseV2025R0TypeField.DOCGEN_JOB, + **kwargs + ): + """ + :param id: The unique identifier that represent a Box Doc Gen job. + :type id: str + :param type: The value will always be `docgen_job`., defaults to DocGenJobBaseV2025R0TypeField.DOCGEN_JOB + :type type: DocGenJobBaseV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py new file mode 100644 index 000000000..9debfb02f --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py @@ -0,0 +1,82 @@ +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_base_v2025_r0 import ( + DocGenJobBaseV2025R0TypeField, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_base_v2025_r0 import DocGenJobBaseV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_batch_base_v2025_r0 import ( + DocGenBatchBaseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.schemas.v2025_r0.file_version_base_v2025_r0 import ( + FileVersionBaseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_v2025_r0 import ( + DocGenJobV2025R0StatusField, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_v2025_r0 import DocGenJobV2025R0 + +from box_sdk_gen.schemas.v2025_r0.user_base_v2025_r0 import UserBaseV2025R0 + +from box_sdk_gen.schemas.v2025_r0.enterprise_reference_v2025_r0 import ( + EnterpriseReferenceV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenJobFullV2025R0(DocGenJobV2025R0): + def __init__( + self, + created_by: UserBaseV2025R0, + enterprise: EnterpriseReferenceV2025R0, + source: str, + batch: DocGenBatchBaseV2025R0, + template_file: FileReferenceV2025R0, + template_file_version: FileVersionBaseV2025R0, + status: DocGenJobV2025R0StatusField, + output_type: str, + id: str, + *, + created_at: Optional[str] = None, + output_file: Optional[Optional[FileReferenceV2025R0]] = None, + output_file_version: Optional[Optional[FileVersionBaseV2025R0]] = None, + type: DocGenJobBaseV2025R0TypeField = DocGenJobBaseV2025R0TypeField.DOCGEN_JOB, + **kwargs + ): + """ + :param source: Source of the request. + :type source: str + :param status: Status of the job. + :type status: DocGenJobV2025R0StatusField + :param output_type: Type of the generated file. + :type output_type: str + :param id: The unique identifier that represent a Box Doc Gen job. + :type id: str + :param created_at: Time of job creation., defaults to None + :type created_at: Optional[str], optional + :param type: The value will always be `docgen_job`., defaults to DocGenJobBaseV2025R0TypeField.DOCGEN_JOB + :type type: DocGenJobBaseV2025R0TypeField, optional + """ + super().__init__( + batch=batch, + template_file=template_file, + template_file_version=template_file_version, + status=status, + output_type=output_type, + id=id, + output_file=output_file, + output_file_version=output_file_version, + type=type, + **kwargs + ) + self.created_by = created_by + self.enterprise = enterprise + self.source = source + self.created_at = created_at diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py new file mode 100644 index 000000000..43e37b347 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py @@ -0,0 +1,64 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_base_v2025_r0 import ( + DocGenJobBaseV2025R0TypeField, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_base_v2025_r0 import DocGenJobBaseV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_batch_base_v2025_r0 import ( + DocGenBatchBaseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.schemas.v2025_r0.file_version_base_v2025_r0 import ( + FileVersionBaseV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenJobV2025R0StatusField(str, Enum): + SUBMITTED = 'submitted' + COMPLETED = 'completed' + FAILED = 'failed' + COMPLETED_WITH_ERROR = 'completed_with_error' + PENDING = 'pending' + + +class DocGenJobV2025R0(DocGenJobBaseV2025R0): + def __init__( + self, + batch: DocGenBatchBaseV2025R0, + template_file: FileReferenceV2025R0, + template_file_version: FileVersionBaseV2025R0, + status: DocGenJobV2025R0StatusField, + output_type: str, + id: str, + *, + output_file: Optional[Optional[FileReferenceV2025R0]] = None, + output_file_version: Optional[Optional[FileVersionBaseV2025R0]] = None, + type: DocGenJobBaseV2025R0TypeField = DocGenJobBaseV2025R0TypeField.DOCGEN_JOB, + **kwargs + ): + """ + :param status: Status of the job. + :type status: DocGenJobV2025R0StatusField + :param output_type: Type of the generated file. + :type output_type: str + :param id: The unique identifier that represent a Box Doc Gen job. + :type id: str + :param type: The value will always be `docgen_job`., defaults to DocGenJobBaseV2025R0TypeField.DOCGEN_JOB + :type type: DocGenJobBaseV2025R0TypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.batch = batch + self.template_file = template_file + self.template_file_version = template_file_version + self.status = status + self.output_type = output_type + self.output_file = output_file + self.output_file_version = output_file_version diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_jobs_full_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_jobs_full_v2025_r0.py new file mode 100644 index 000000000..029beff64 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_jobs_full_v2025_r0.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_full_v2025_r0 import DocGenJobFullV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenJobsFullV2025R0(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[DocGenJobFullV2025R0]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: List of jobs., defaults to None + :type entries: Optional[List[DocGenJobFullV2025R0]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_jobs_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_jobs_v2025_r0.py new file mode 100644 index 000000000..d5c8d66f9 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_jobs_v2025_r0.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_v2025_r0 import DocGenJobV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenJobsV2025R0(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[DocGenJobV2025R0]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: List of jobs., defaults to None + :type entries: Optional[List[DocGenJobV2025R0]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_tag_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_tag_v2025_r0.py new file mode 100644 index 000000000..7b33566ad --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_tag_v2025_r0.py @@ -0,0 +1,38 @@ +from enum import Enum + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenTagV2025R0TagTypeField(str, Enum): + TEXT = 'text' + ARITHMETIC = 'arithmetic' + CONDITIONAL = 'conditional' + FOR_LOOP = 'for-loop' + TABLE_LOOP = 'table-loop' + IMAGE = 'image' + + +class DocGenTagV2025R0(BaseObject): + def __init__( + self, + tag_content: str, + tag_type: DocGenTagV2025R0TagTypeField, + json_paths: List[str], + **kwargs + ): + """ + :param tag_content: The content of the tag. + :type tag_content: str + :param tag_type: Type of the tag. + :type tag_type: DocGenTagV2025R0TagTypeField + :param json_paths: List of the paths. + :type json_paths: List[str] + """ + super().__init__(**kwargs) + self.tag_content = tag_content + self.tag_type = tag_type + self.json_paths = json_paths diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_tags_processing_message_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_tags_processing_message_v2025_r0.py new file mode 100644 index 000000000..00001b17c --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_tags_processing_message_v2025_r0.py @@ -0,0 +1,13 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenTagsProcessingMessageV2025R0(BaseObject): + def __init__(self, message: str, **kwargs): + """ + :param message: A message informing the user that document tags are still being processed. + :type message: str + """ + super().__init__(**kwargs) + self.message = message diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_tags_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_tags_v2025_r0.py new file mode 100644 index 000000000..b51284117 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_tags_v2025_r0.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.doc_gen_tag_v2025_r0 import DocGenTagV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenTagsV2025R0(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[DocGenTagV2025R0]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: List of tags., defaults to None + :type entries: Optional[List[DocGenTagV2025R0]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_template_base_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_template_base_v2025_r0.py new file mode 100644 index 000000000..aff11476f --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_template_base_v2025_r0.py @@ -0,0 +1,13 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenTemplateBaseV2025R0(BaseObject): + def __init__(self, *, file: Optional[FileReferenceV2025R0] = None, **kwargs): + super().__init__(**kwargs) + self.file = file diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_template_create_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_template_create_request_v2025_r0.py new file mode 100644 index 000000000..9927af5fd --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_template_create_request_v2025_r0.py @@ -0,0 +1,11 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenTemplateCreateRequestV2025R0(BaseObject): + def __init__(self, file: FileReferenceV2025R0, **kwargs): + super().__init__(**kwargs) + self.file = file diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_template_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_template_v2025_r0.py new file mode 100644 index 000000000..2d4debdbd --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_template_v2025_r0.py @@ -0,0 +1,25 @@ +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_base_v2025_r0 import ( + DocGenTemplateBaseV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenTemplateV2025R0(DocGenTemplateBaseV2025R0): + def __init__( + self, + *, + file_name: Optional[str] = None, + file: Optional[FileReferenceV2025R0] = None, + **kwargs + ): + """ + :param file_name: The name of the template., defaults to None + :type file_name: Optional[str], optional + """ + super().__init__(file=file, **kwargs) + self.file_name = file_name diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_templates_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_templates_v2025_r0.py new file mode 100644 index 000000000..0bef7118b --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_templates_v2025_r0.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_v2025_r0 import DocGenTemplateV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class DocGenTemplatesV2025R0(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[DocGenTemplateV2025R0]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of templates., defaults to None + :type entries: Optional[List[DocGenTemplateV2025R0]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_reference_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_reference_v2025_r0.py new file mode 100644 index 000000000..8a55a9250 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_reference_v2025_r0.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseReferenceV2025R0TypeField(str, Enum): + ENTERPRISE = 'enterprise' + + +class EnterpriseReferenceV2025R0(BaseObject): + _discriminator = 'type', {'enterprise'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[EnterpriseReferenceV2025R0TypeField] = None, + **kwargs + ): + """ + :param id: The unique identifier for this enterprise., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `enterprise`., defaults to None + :type type: Optional[EnterpriseReferenceV2025R0TypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/file_reference_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/file_reference_v2025_r0.py new file mode 100644 index 000000000..66216b28e --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/file_reference_v2025_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileReferenceV2025R0TypeField(str, Enum): + FILE = 'file' + + +class FileReferenceV2025R0(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + id: str, + *, + type: FileReferenceV2025R0TypeField = FileReferenceV2025R0TypeField.FILE, + **kwargs + ): + """ + :param id: ID of the object. + :type id: str + :param type: The value will always be `file`., defaults to FileReferenceV2025R0TypeField.FILE + :type type: FileReferenceV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/file_version_base_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/file_version_base_v2025_r0.py new file mode 100644 index 000000000..fb4cd1842 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/file_version_base_v2025_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class FileVersionBaseV2025R0TypeField(str, Enum): + FILE_VERSION = 'file_version' + + +class FileVersionBaseV2025R0(BaseObject): + _discriminator = 'type', {'file_version'} + + def __init__( + self, + id: str, + *, + type: FileVersionBaseV2025R0TypeField = FileVersionBaseV2025R0TypeField.FILE_VERSION, + **kwargs + ): + """ + :param id: The unique identifier that represent a file version. + :type id: str + :param type: The value will always be `file_version`., defaults to FileVersionBaseV2025R0TypeField.FILE_VERSION + :type type: FileVersionBaseV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/folder_reference_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/folder_reference_v2025_r0.py new file mode 100644 index 000000000..c45fccac3 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/folder_reference_v2025_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class FolderReferenceV2025R0TypeField(str, Enum): + FOLDER = 'folder' + + +class FolderReferenceV2025R0(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + id: str, + *, + type: FolderReferenceV2025R0TypeField = FolderReferenceV2025R0TypeField.FOLDER, + **kwargs + ): + """ + :param id: ID of the folder. + :type id: str + :param type: The value will always be `folder`., defaults to FolderReferenceV2025R0TypeField.FOLDER + :type type: FolderReferenceV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/group_base_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/group_base_v2025_r0.py new file mode 100644 index 000000000..b14762bf1 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/group_base_v2025_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class GroupBaseV2025R0TypeField(str, Enum): + GROUP = 'group' + + +class GroupBaseV2025R0(BaseObject): + _discriminator = 'type', {'group'} + + def __init__( + self, + id: str, + *, + type: GroupBaseV2025R0TypeField = GroupBaseV2025R0TypeField.GROUP, + **kwargs + ): + """ + :param id: The unique identifier for this object. + :type id: str + :param type: The value will always be `group`., defaults to GroupBaseV2025R0TypeField.GROUP + :type type: GroupBaseV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/group_mini_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/group_mini_v2025_r0.py new file mode 100644 index 000000000..965e5e513 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/group_mini_v2025_r0.py @@ -0,0 +1,39 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.group_base_v2025_r0 import GroupBaseV2025R0TypeField + +from box_sdk_gen.schemas.v2025_r0.group_base_v2025_r0 import GroupBaseV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class GroupMiniV2025R0GroupTypeField(str, Enum): + MANAGED_GROUP = 'managed_group' + ALL_USERS_GROUP = 'all_users_group' + + +class GroupMiniV2025R0(GroupBaseV2025R0): + def __init__( + self, + id: str, + *, + name: Optional[str] = None, + group_type: Optional[GroupMiniV2025R0GroupTypeField] = None, + type: GroupBaseV2025R0TypeField = GroupBaseV2025R0TypeField.GROUP, + **kwargs + ): + """ + :param id: The unique identifier for this object. + :type id: str + :param name: The name of the group., defaults to None + :type name: Optional[str], optional + :param group_type: The type of the group., defaults to None + :type group_type: Optional[GroupMiniV2025R0GroupTypeField], optional + :param type: The value will always be `group`., defaults to GroupBaseV2025R0TypeField.GROUP + :type type: GroupBaseV2025R0TypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.name = name + self.group_type = group_type diff --git a/box_sdk_gen/schemas/v2025_r0/hub_access_grantee_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_access_grantee_v2025_r0.py new file mode 100644 index 000000000..ccabcea22 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_access_grantee_v2025_r0.py @@ -0,0 +1,11 @@ +from typing import Union + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_user_v2025_r0 import ( + HubCollaborationUserV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.group_mini_v2025_r0 import GroupMiniV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + +HubAccessGranteeV2025R0 = Union[HubCollaborationUserV2025R0, GroupMiniV2025R0] diff --git a/box_sdk_gen/schemas/v2025_r0/hub_action_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_action_v2025_r0.py new file mode 100644 index 000000000..7e81ca860 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_action_v2025_r0.py @@ -0,0 +1,8 @@ +from enum import Enum + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubActionV2025R0(str, Enum): + ADD = 'add' + REMOVE = 'remove' diff --git a/box_sdk_gen/schemas/v2025_r0/hub_base_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_base_v2025_r0.py new file mode 100644 index 000000000..78212df31 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_base_v2025_r0.py @@ -0,0 +1,36 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubBaseV2025R0TypeField(str, Enum): + HUBS = 'hubs' + + +class HubBaseV2025R0(BaseObject): + _discriminator = 'type', {'hubs'} + + def __init__( + self, + id: str, + *, + type: HubBaseV2025R0TypeField = HubBaseV2025R0TypeField.HUBS, + **kwargs + ): + """ + :param id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting a hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + :type id: str + :param type: The value will always be `hubs`., defaults to HubBaseV2025R0TypeField.HUBS + :type type: HubBaseV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_create_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_create_request_v2025_r0.py new file mode 100644 index 000000000..cb926aa1f --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_create_request_v2025_r0.py @@ -0,0 +1,84 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import Optional + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubCollaborationCreateRequestV2025R0HubTypeField(str, Enum): + HUBS = 'hubs' + + +class HubCollaborationCreateRequestV2025R0HubField(BaseObject): + _discriminator = 'type', {'hubs'} + + def __init__( + self, + id: str, + *, + type: HubCollaborationCreateRequestV2025R0HubTypeField = HubCollaborationCreateRequestV2025R0HubTypeField.HUBS, + **kwargs + ): + """ + :param id: ID of the object. + :type id: str + :param type: The value will always be `hubs`., defaults to HubCollaborationCreateRequestV2025R0HubTypeField.HUBS + :type type: HubCollaborationCreateRequestV2025R0HubTypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class HubCollaborationCreateRequestV2025R0AccessibleByField(BaseObject): + def __init__( + self, + type: str, + *, + id: Optional[str] = None, + login: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of collaborator to invite. + Possible values are `user` or `group`. + :type type: str + :param id: The ID of the user or group. + + Alternatively, use `login` to specify a user by email + address., defaults to None + :type id: Optional[str], optional + :param login: The email address of the user who gets access to the item. + + Alternatively, use `id` to specify a user by user ID., defaults to None + :type login: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + self.login = login + + +class HubCollaborationCreateRequestV2025R0(BaseObject): + def __init__( + self, + hub: HubCollaborationCreateRequestV2025R0HubField, + accessible_by: HubCollaborationCreateRequestV2025R0AccessibleByField, + role: str, + **kwargs + ): + """ + :param hub: Hubs reference. + :type hub: HubCollaborationCreateRequestV2025R0HubField + :param accessible_by: The user or group who gets access to the item. + :type accessible_by: HubCollaborationCreateRequestV2025R0AccessibleByField + :param role: The level of access granted to hub. + Possible values are `editor`, `viewer`, and `co-owner`. + :type role: str + """ + super().__init__(**kwargs) + self.hub = hub + self.accessible_by = accessible_by + self.role = role diff --git a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_update_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_update_request_v2025_r0.py new file mode 100644 index 000000000..28990c279 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_update_request_v2025_r0.py @@ -0,0 +1,16 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubCollaborationUpdateRequestV2025R0(BaseObject): + def __init__(self, *, role: Optional[str] = None, **kwargs): + """ + :param role: The level of access granted to hub. + Possible values are `editor`, `viewer`, and `co-owner`., defaults to None + :type role: Optional[str], optional + """ + super().__init__(**kwargs) + self.role = role diff --git a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_user_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_user_v2025_r0.py new file mode 100644 index 000000000..cff4046ce --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_user_v2025_r0.py @@ -0,0 +1,32 @@ +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.user_base_v2025_r0 import UserBaseV2025R0TypeField + +from box_sdk_gen.schemas.v2025_r0.user_base_v2025_r0 import UserBaseV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubCollaborationUserV2025R0(UserBaseV2025R0): + def __init__( + self, + id: str, + *, + name: Optional[str] = None, + login: Optional[str] = None, + type: UserBaseV2025R0TypeField = UserBaseV2025R0TypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param name: The display name of this user. If the collaboration status is `pending`, an empty string is returned., defaults to None + :type name: Optional[str], optional + :param login: The primary email address of this user. If the collaboration status is `pending`, an empty string is returned., defaults to None + :type login: Optional[str], optional + :param type: The value will always be `user`., defaults to UserBaseV2025R0TypeField.USER + :type type: UserBaseV2025R0TypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.name = name + self.login = login diff --git a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_v2025_r0.py new file mode 100644 index 000000000..aa88f752f --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_v2025_r0.py @@ -0,0 +1,173 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_user_v2025_r0 import ( + HubCollaborationUserV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.group_mini_v2025_r0 import GroupMiniV2025R0 + +from box_sdk_gen.schemas.v2025_r0.hub_base_v2025_r0 import HubBaseV2025R0 + +from box_sdk_gen.schemas.v2025_r0.hub_access_grantee_v2025_r0 import ( + HubAccessGranteeV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.terms_of_service_base_v2025_r0 import ( + TermsOfServiceBaseV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubCollaborationV2025R0TypeField(str, Enum): + HUB_COLLABORATION = 'hub_collaboration' + + +class HubCollaborationV2025R0StatusField(str, Enum): + ACCEPTED = 'accepted' + PENDING = 'pending' + REJECTED = 'rejected' + + +class HubCollaborationV2025R0AcceptanceRequirementsStatusTermsOfServiceRequirementField( + BaseObject +): + def __init__( + self, + *, + is_accepted: Optional[bool] = None, + terms_of_service: Optional[TermsOfServiceBaseV2025R0] = None, + **kwargs + ): + """ + :param is_accepted: Whether or not the terms of service have been accepted. The + field is `null` when there is no terms of service required., defaults to None + :type is_accepted: Optional[bool], optional + """ + super().__init__(**kwargs) + self.is_accepted = is_accepted + self.terms_of_service = terms_of_service + + +class HubCollaborationV2025R0AcceptanceRequirementsStatusStrongPasswordRequirementField( + BaseObject +): + def __init__( + self, + *, + enterprise_has_strong_password_required_for_external_users: Optional[ + bool + ] = None, + user_has_strong_password: Optional[bool] = None, + **kwargs + ): + """ + :param enterprise_has_strong_password_required_for_external_users: Whether or not the enterprise that owns the content requires + a strong password to collaborate on the content, or enforces + an exposed password detection for the external collaborators., defaults to None + :type enterprise_has_strong_password_required_for_external_users: Optional[bool], optional + :param user_has_strong_password: Whether or not the user has a strong and not exposed password set + for their account. The field is `null` when a strong password is + not required., defaults to None + :type user_has_strong_password: Optional[bool], optional + """ + super().__init__(**kwargs) + self.enterprise_has_strong_password_required_for_external_users = ( + enterprise_has_strong_password_required_for_external_users + ) + self.user_has_strong_password = user_has_strong_password + + +class HubCollaborationV2025R0AcceptanceRequirementsStatusTwoFactorAuthenticationRequirementField( + BaseObject +): + def __init__( + self, + *, + enterprise_has_two_factor_auth_enabled: Optional[bool] = None, + user_has_two_factor_authentication_enabled: Optional[bool] = None, + **kwargs + ): + """ + :param enterprise_has_two_factor_auth_enabled: Whether or not the enterprise that owns the content requires + two-factor authentication to be enabled in order to + collaborate on the content., defaults to None + :type enterprise_has_two_factor_auth_enabled: Optional[bool], optional + :param user_has_two_factor_authentication_enabled: Whether or not the user has two-factor authentication + enabled. The field is `null` when two-factor + authentication is not required., defaults to None + :type user_has_two_factor_authentication_enabled: Optional[bool], optional + """ + super().__init__(**kwargs) + self.enterprise_has_two_factor_auth_enabled = ( + enterprise_has_two_factor_auth_enabled + ) + self.user_has_two_factor_authentication_enabled = ( + user_has_two_factor_authentication_enabled + ) + + +class HubCollaborationV2025R0AcceptanceRequirementsStatusField(BaseObject): + def __init__( + self, + *, + terms_of_service_requirement: Optional[ + HubCollaborationV2025R0AcceptanceRequirementsStatusTermsOfServiceRequirementField + ] = None, + strong_password_requirement: Optional[ + HubCollaborationV2025R0AcceptanceRequirementsStatusStrongPasswordRequirementField + ] = None, + two_factor_authentication_requirement: Optional[ + HubCollaborationV2025R0AcceptanceRequirementsStatusTwoFactorAuthenticationRequirementField + ] = None, + **kwargs + ): + super().__init__(**kwargs) + self.terms_of_service_requirement = terms_of_service_requirement + self.strong_password_requirement = strong_password_requirement + self.two_factor_authentication_requirement = ( + two_factor_authentication_requirement + ) + + +class HubCollaborationV2025R0(BaseObject): + _discriminator = 'type', {'hub_collaboration'} + + def __init__( + self, + id: str, + *, + type: HubCollaborationV2025R0TypeField = HubCollaborationV2025R0TypeField.HUB_COLLABORATION, + hub: Optional[HubBaseV2025R0] = None, + accessible_by: Optional[HubAccessGranteeV2025R0] = None, + role: Optional[str] = None, + status: Optional[HubCollaborationV2025R0StatusField] = None, + acceptance_requirements_status: Optional[ + HubCollaborationV2025R0AcceptanceRequirementsStatusField + ] = None, + **kwargs + ): + """ + :param id: The unique identifier for this collaboration. + :type id: str + :param type: The value will always be `hub_collaboration`., defaults to HubCollaborationV2025R0TypeField.HUB_COLLABORATION + :type type: HubCollaborationV2025R0TypeField, optional + :param role: The level of access granted to hub. + Possible values are `editor`, `viewer`, and `co-owner`., defaults to None + :type role: Optional[str], optional + :param status: The status of the collaboration invitation. If the status + is `pending`, `login` and `name` return an empty string., defaults to None + :type status: Optional[HubCollaborationV2025R0StatusField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.hub = hub + self.accessible_by = accessible_by + self.role = role + self.status = status + self.acceptance_requirements_status = acceptance_requirements_status diff --git a/box_sdk_gen/schemas/v2025_r0/hub_collaborations_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_collaborations_v2025_r0.py new file mode 100644 index 000000000..b63fd5e66 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_collaborations_v2025_r0.py @@ -0,0 +1,36 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_v2025_r0 import ( + HubCollaborationV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubCollaborationsV2025R0(BaseObject): + def __init__( + self, + *, + entries: Optional[List[HubCollaborationV2025R0]] = None, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: A list of hub collaborations., defaults to None + :type entries: Optional[List[HubCollaborationV2025R0]], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py new file mode 100644 index 000000000..31da19359 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py @@ -0,0 +1,24 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubCopyRequestV2025R0(BaseObject): + def __init__( + self, + *, + title: Optional[str] = None, + description: Optional[str] = None, + **kwargs + ): + """ + :param title: Title of the Hub. It cannot be empty and should be less than 50 characters., defaults to None + :type title: Optional[str], optional + :param description: Description of the Hub., defaults to None + :type description: Optional[str], optional + """ + super().__init__(**kwargs) + self.title = title + self.description = description diff --git a/box_sdk_gen/schemas/v2025_r0/hub_create_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_create_request_v2025_r0.py new file mode 100644 index 000000000..16b9cd32e --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_create_request_v2025_r0.py @@ -0,0 +1,18 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubCreateRequestV2025R0(BaseObject): + def __init__(self, title: str, *, description: Optional[str] = None, **kwargs): + """ + :param title: Title of the Hub. It cannot be empty and should be less than 50 characters. + :type title: str + :param description: Description of the Hub., defaults to None + :type description: Optional[str], optional + """ + super().__init__(**kwargs) + self.title = title + self.description = description diff --git a/box_sdk_gen/schemas/v2025_r0/hub_item_operation_result_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_result_v2025_r0.py new file mode 100644 index 000000000..f1423d2c2 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_result_v2025_r0.py @@ -0,0 +1,44 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.schemas.v2025_r0.folder_reference_v2025_r0 import ( + FolderReferenceV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.weblink_reference_v2025_r0 import ( + WeblinkReferenceV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_item_reference_v2025_r0 import ( + HubItemReferenceV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubItemOperationResultV2025R0(BaseObject): + def __init__( + self, + *, + action: Optional[str] = None, + item: Optional[HubItemReferenceV2025R0] = None, + status: Optional[int] = None, + error: Optional[str] = None, + **kwargs + ): + """ + :param action: The action performed on the item., defaults to None + :type action: Optional[str], optional + :param status: The HTTP status code of the operation., defaults to None + :type status: Optional[int], optional + :param error: Error message if the operation failed., defaults to None + :type error: Optional[str], optional + """ + super().__init__(**kwargs) + self.action = action + self.item = item + self.status = status + self.error = error diff --git a/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py new file mode 100644 index 000000000..3584f0d20 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py @@ -0,0 +1,42 @@ +from enum import Enum + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.schemas.v2025_r0.folder_reference_v2025_r0 import ( + FolderReferenceV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.weblink_reference_v2025_r0 import ( + WeblinkReferenceV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubItemOperationV2025R0ActionField(str, Enum): + ADD = 'add' + REMOVE = 'remove' + + +class HubItemOperationV2025R0(BaseObject): + def __init__( + self, + action: HubItemOperationV2025R0ActionField, + item: Union[ + FileReferenceV2025R0, FolderReferenceV2025R0, WeblinkReferenceV2025R0 + ], + **kwargs + ): + """ + :param action: The action to perform on a Hub item. + :type action: HubItemOperationV2025R0ActionField + :param item: Reference to an item that can be added to a Hub. + :type item: Union[FileReferenceV2025R0, FolderReferenceV2025R0, WeblinkReferenceV2025R0] + """ + super().__init__(**kwargs) + self.action = action + self.item = item diff --git a/box_sdk_gen/schemas/v2025_r0/hub_item_reference_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_item_reference_v2025_r0.py new file mode 100644 index 000000000..545cb7079 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_item_reference_v2025_r0.py @@ -0,0 +1,17 @@ +from typing import Union + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.schemas.v2025_r0.folder_reference_v2025_r0 import ( + FolderReferenceV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.weblink_reference_v2025_r0 import ( + WeblinkReferenceV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +HubItemReferenceV2025R0 = Union[ + FileReferenceV2025R0, FolderReferenceV2025R0, WeblinkReferenceV2025R0 +] diff --git a/box_sdk_gen/schemas/v2025_r0/hub_item_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_item_v2025_r0.py new file mode 100644 index 000000000..f2fae55a6 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_item_v2025_r0.py @@ -0,0 +1,29 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubItemV2025R0TypeField(str, Enum): + FILE = 'file' + FOLDER = 'folder' + WEB_LINK = 'web_link' + + +class HubItemV2025R0(BaseObject): + _discriminator = 'type', {'file', 'folder', 'web_link'} + + def __init__(self, id: str, type: HubItemV2025R0TypeField, name: str, **kwargs): + """ + :param id: The unique identifier for this item. + :type id: str + :param type: The type of the item. + :type type: HubItemV2025R0TypeField + :param name: The name of the item. + :type name: str + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name diff --git a/box_sdk_gen/schemas/v2025_r0/hub_items_manage_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_items_manage_request_v2025_r0.py new file mode 100644 index 000000000..563d2502d --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_items_manage_request_v2025_r0.py @@ -0,0 +1,23 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.hub_item_operation_v2025_r0 import ( + HubItemOperationV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubItemsManageRequestV2025R0(BaseObject): + def __init__( + self, *, operations: Optional[List[HubItemOperationV2025R0]] = None, **kwargs + ): + """ + :param operations: List of operations to perform on Hub items., defaults to None + :type operations: Optional[List[HubItemOperationV2025R0]], optional + """ + super().__init__(**kwargs) + self.operations = operations diff --git a/box_sdk_gen/schemas/v2025_r0/hub_items_manage_response_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_items_manage_response_v2025_r0.py new file mode 100644 index 000000000..36747ceaf --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_items_manage_response_v2025_r0.py @@ -0,0 +1,19 @@ +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.hub_item_operation_result_v2025_r0 import ( + HubItemOperationResultV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubItemsManageResponseV2025R0(BaseObject): + def __init__(self, operations: List[HubItemOperationResultV2025R0], **kwargs): + """ + :param operations: List of operations performed on Hub items. + :type operations: List[HubItemOperationResultV2025R0] + """ + super().__init__(**kwargs) + self.operations = operations diff --git a/box_sdk_gen/schemas/v2025_r0/hub_items_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_items_v2025_r0.py new file mode 100644 index 000000000..652d1a114 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_items_v2025_r0.py @@ -0,0 +1,34 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.hub_item_v2025_r0 import HubItemV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubItemsV2025R0(BaseObject): + def __init__( + self, + *, + entries: Optional[List[HubItemV2025R0]] = None, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: A list of Hub items., defaults to None + :type entries: Optional[List[HubItemV2025R0]], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py new file mode 100644 index 000000000..dd75180d6 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py @@ -0,0 +1,42 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubUpdateRequestV2025R0(BaseObject): + def __init__( + self, + *, + title: Optional[str] = None, + description: Optional[str] = None, + is_ai_enabled: Optional[bool] = None, + is_collaboration_restricted_to_enterprise: Optional[bool] = None, + can_non_owners_invite: Optional[bool] = None, + can_shared_link_be_created: Optional[bool] = None, + **kwargs + ): + """ + :param title: Title of the Hub. It cannot be empty and should be less than 50 characters., defaults to None + :type title: Optional[str], optional + :param description: Description of the Hub., defaults to None + :type description: Optional[str], optional + :param is_ai_enabled: Indicates if AI features are enabled for the Hub., defaults to None + :type is_ai_enabled: Optional[bool], optional + :param is_collaboration_restricted_to_enterprise: Indicates if collaboration is restricted to the enterprise., defaults to None + :type is_collaboration_restricted_to_enterprise: Optional[bool], optional + :param can_non_owners_invite: Indicates if non-owners can invite others to the Hub., defaults to None + :type can_non_owners_invite: Optional[bool], optional + :param can_shared_link_be_created: Indicates if a shared link can be created for the Hub., defaults to None + :type can_shared_link_be_created: Optional[bool], optional + """ + super().__init__(**kwargs) + self.title = title + self.description = description + self.is_ai_enabled = is_ai_enabled + self.is_collaboration_restricted_to_enterprise = ( + is_collaboration_restricted_to_enterprise + ) + self.can_non_owners_invite = can_non_owners_invite + self.can_shared_link_be_created = can_shared_link_be_created diff --git a/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py new file mode 100644 index 000000000..efe6f8ef1 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py @@ -0,0 +1,78 @@ +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.hub_base_v2025_r0 import HubBaseV2025R0TypeField + +from box_sdk_gen.schemas.v2025_r0.hub_base_v2025_r0 import HubBaseV2025R0 + +from box_sdk_gen.schemas.v2025_r0.user_mini_v2025_r0 import UserMiniV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class HubV2025R0(HubBaseV2025R0): + def __init__( + self, + id: str, + *, + title: Optional[str] = None, + description: Optional[str] = None, + created_at: Optional[DateTime] = None, + updated_at: Optional[DateTime] = None, + created_by: Optional[UserMiniV2025R0] = None, + updated_by: Optional[UserMiniV2025R0] = None, + view_count: Optional[int] = None, + is_ai_enabled: Optional[bool] = None, + is_collaboration_restricted_to_enterprise: Optional[bool] = None, + can_non_owners_invite: Optional[bool] = None, + can_shared_link_be_created: Optional[bool] = None, + type: HubBaseV2025R0TypeField = HubBaseV2025R0TypeField.HUBS, + **kwargs + ): + """ + :param id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting a hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + :type id: str + :param title: The title given to the hub., defaults to None + :type title: Optional[str], optional + :param description: The description of the hub. First 200 characters are returned., defaults to None + :type description: Optional[str], optional + :param created_at: The date and time when the folder was created. This value may + be `null` for some folders such as the root folder or the trash + folder., defaults to None + :type created_at: Optional[DateTime], optional + :param updated_at: The date and time when the hub was last updated., defaults to None + :type updated_at: Optional[DateTime], optional + :param view_count: The number of views for the hub., defaults to None + :type view_count: Optional[int], optional + :param is_ai_enabled: Indicates if AI features are enabled for the hub., defaults to None + :type is_ai_enabled: Optional[bool], optional + :param is_collaboration_restricted_to_enterprise: Indicates if collaboration is restricted to the enterprise., defaults to None + :type is_collaboration_restricted_to_enterprise: Optional[bool], optional + :param can_non_owners_invite: Indicates if non-owners can invite others to the hub., defaults to None + :type can_non_owners_invite: Optional[bool], optional + :param can_shared_link_be_created: Indicates if a shared link can be created for the hub., defaults to None + :type can_shared_link_be_created: Optional[bool], optional + :param type: The value will always be `hubs`., defaults to HubBaseV2025R0TypeField.HUBS + :type type: HubBaseV2025R0TypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.title = title + self.description = description + self.created_at = created_at + self.updated_at = updated_at + self.created_by = created_by + self.updated_by = updated_by + self.view_count = view_count + self.is_ai_enabled = is_ai_enabled + self.is_collaboration_restricted_to_enterprise = ( + is_collaboration_restricted_to_enterprise + ) + self.can_non_owners_invite = can_non_owners_invite + self.can_shared_link_be_created = can_shared_link_be_created diff --git a/box_sdk_gen/schemas/v2025_r0/hubs_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hubs_v2025_r0.py new file mode 100644 index 000000000..47c8cc9f1 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hubs_v2025_r0.py @@ -0,0 +1,34 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.hub_v2025_r0 import HubV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubsV2025R0(BaseObject): + def __init__( + self, + *, + entries: Optional[List[HubV2025R0]] = None, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: A list of hubs., defaults to None + :type entries: Optional[List[HubV2025R0]], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/v2025_r0/shield_list_content_country_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_list_content_country_v2025_r0.py new file mode 100644 index 000000000..c1df40f1e --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_list_content_country_v2025_r0.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldListContentCountryV2025R0TypeField(str, Enum): + COUNTRY = 'country' + + +class ShieldListContentCountryV2025R0(BaseObject): + _discriminator = 'type', {'country'} + + def __init__( + self, + country_codes: List[str], + *, + type: ShieldListContentCountryV2025R0TypeField = ShieldListContentCountryV2025R0TypeField.COUNTRY, + **kwargs + ): + """ + :param country_codes: List of country codes values. + :type country_codes: List[str] + :param type: The type of content in the shield list., defaults to ShieldListContentCountryV2025R0TypeField.COUNTRY + :type type: ShieldListContentCountryV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.country_codes = country_codes + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/shield_list_content_domain_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_list_content_domain_v2025_r0.py new file mode 100644 index 000000000..6a0c0dae8 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_list_content_domain_v2025_r0.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldListContentDomainV2025R0TypeField(str, Enum): + DOMAIN = 'domain' + + +class ShieldListContentDomainV2025R0(BaseObject): + _discriminator = 'type', {'domain'} + + def __init__( + self, + domains: List[str], + *, + type: ShieldListContentDomainV2025R0TypeField = ShieldListContentDomainV2025R0TypeField.DOMAIN, + **kwargs + ): + """ + :param domains: List of domain. + :type domains: List[str] + :param type: The type of content in the shield list., defaults to ShieldListContentDomainV2025R0TypeField.DOMAIN + :type type: ShieldListContentDomainV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.domains = domains + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/shield_list_content_email_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_list_content_email_v2025_r0.py new file mode 100644 index 000000000..6883277bd --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_list_content_email_v2025_r0.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldListContentEmailV2025R0TypeField(str, Enum): + EMAIL = 'email' + + +class ShieldListContentEmailV2025R0(BaseObject): + _discriminator = 'type', {'email'} + + def __init__( + self, + email_addresses: List[str], + *, + type: ShieldListContentEmailV2025R0TypeField = ShieldListContentEmailV2025R0TypeField.EMAIL, + **kwargs + ): + """ + :param email_addresses: List of emails. + :type email_addresses: List[str] + :param type: The type of content in the shield list., defaults to ShieldListContentEmailV2025R0TypeField.EMAIL + :type type: ShieldListContentEmailV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.email_addresses = email_addresses + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/shield_list_content_integration_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_list_content_integration_v2025_r0.py new file mode 100644 index 000000000..a1a9f327b --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_list_content_integration_v2025_r0.py @@ -0,0 +1,44 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldListContentIntegrationV2025R0TypeField(str, Enum): + INTEGRATION = 'integration' + + +class ShieldListContentIntegrationV2025R0IntegrationsField(BaseObject): + def __init__(self, *, id: Optional[str] = None, **kwargs): + """ + :param id: The ID of the integration., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + + +class ShieldListContentIntegrationV2025R0(BaseObject): + _discriminator = 'type', {'integration'} + + def __init__( + self, + integrations: List[ShieldListContentIntegrationV2025R0IntegrationsField], + *, + type: ShieldListContentIntegrationV2025R0TypeField = ShieldListContentIntegrationV2025R0TypeField.INTEGRATION, + **kwargs + ): + """ + :param integrations: List of integration. + :type integrations: List[ShieldListContentIntegrationV2025R0IntegrationsField] + :param type: The type of content in the shield list., defaults to ShieldListContentIntegrationV2025R0TypeField.INTEGRATION + :type type: ShieldListContentIntegrationV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.integrations = integrations + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/shield_list_content_ip_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_list_content_ip_v2025_r0.py new file mode 100644 index 000000000..fdaef0ecf --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_list_content_ip_v2025_r0.py @@ -0,0 +1,32 @@ +from enum import Enum + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldListContentIpV2025R0TypeField(str, Enum): + IP = 'ip' + + +class ShieldListContentIpV2025R0(BaseObject): + _discriminator = 'type', {'ip'} + + def __init__( + self, + ip_addresses: List[str], + *, + type: ShieldListContentIpV2025R0TypeField = ShieldListContentIpV2025R0TypeField.IP, + **kwargs + ): + """ + :param ip_addresses: List of ips and cidrs. + :type ip_addresses: List[str] + :param type: The type of content in the shield list., defaults to ShieldListContentIpV2025R0TypeField.IP + :type type: ShieldListContentIpV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.ip_addresses = ip_addresses + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/shield_list_content_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_list_content_request_v2025_r0.py new file mode 100644 index 000000000..266a96244 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_list_content_request_v2025_r0.py @@ -0,0 +1,26 @@ +from typing import Union + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_country_v2025_r0 import ( + ShieldListContentCountryV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_domain_v2025_r0 import ( + ShieldListContentDomainV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_email_v2025_r0 import ( + ShieldListContentEmailV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_ip_v2025_r0 import ( + ShieldListContentIpV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +ShieldListContentRequestV2025R0 = Union[ + ShieldListContentCountryV2025R0, + ShieldListContentDomainV2025R0, + ShieldListContentEmailV2025R0, + ShieldListContentIpV2025R0, +] diff --git a/box_sdk_gen/schemas/v2025_r0/shield_list_content_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_list_content_v2025_r0.py new file mode 100644 index 000000000..9c5adacf8 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_list_content_v2025_r0.py @@ -0,0 +1,31 @@ +from typing import Union + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_country_v2025_r0 import ( + ShieldListContentCountryV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_domain_v2025_r0 import ( + ShieldListContentDomainV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_email_v2025_r0 import ( + ShieldListContentEmailV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_ip_v2025_r0 import ( + ShieldListContentIpV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_integration_v2025_r0 import ( + ShieldListContentIntegrationV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +ShieldListContentV2025R0 = Union[ + ShieldListContentCountryV2025R0, + ShieldListContentDomainV2025R0, + ShieldListContentEmailV2025R0, + ShieldListContentIpV2025R0, + ShieldListContentIntegrationV2025R0, +] diff --git a/box_sdk_gen/schemas/v2025_r0/shield_list_mini_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_list_mini_v2025_r0.py new file mode 100644 index 000000000..1bd5f412d --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_list_mini_v2025_r0.py @@ -0,0 +1,50 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldListMiniV2025R0TypeField(str, Enum): + SHIELD_LIST = 'shield_list' + + +class ShieldListMiniV2025R0ContentField(BaseObject): + def __init__(self, *, type: Optional[str] = None, **kwargs): + """ + :param type: The type of content in the shield list., defaults to None + :type type: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + + +class ShieldListMiniV2025R0(BaseObject): + _discriminator = 'type', {'shield_list'} + + def __init__( + self, + id: str, + name: str, + content: ShieldListMiniV2025R0ContentField, + *, + type: ShieldListMiniV2025R0TypeField = ShieldListMiniV2025R0TypeField.SHIELD_LIST, + **kwargs + ): + """ + :param id: Unique global identifier for this list. + :type id: str + :param name: Name of Shield List. + :type name: str + :param type: The type of object., defaults to ShieldListMiniV2025R0TypeField.SHIELD_LIST + :type type: ShieldListMiniV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.name = name + self.content = content + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/shield_list_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_list_v2025_r0.py new file mode 100644 index 000000000..a7981b729 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_list_v2025_r0.py @@ -0,0 +1,76 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_country_v2025_r0 import ( + ShieldListContentCountryV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_domain_v2025_r0 import ( + ShieldListContentDomainV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_email_v2025_r0 import ( + ShieldListContentEmailV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_ip_v2025_r0 import ( + ShieldListContentIpV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_integration_v2025_r0 import ( + ShieldListContentIntegrationV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_reference_v2025_r0 import ( + EnterpriseReferenceV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_v2025_r0 import ( + ShieldListContentV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class ShieldListV2025R0(BaseObject): + def __init__( + self, + id: str, + type: str, + name: str, + enterprise: EnterpriseReferenceV2025R0, + created_at: DateTime, + updated_at: DateTime, + content: ShieldListContentV2025R0, + *, + description: Optional[str] = None, + **kwargs + ): + """ + :param id: Unique identifier for the shield list. + :type id: str + :param type: Type of the object. + :type type: str + :param name: Name of the shield list. + :type name: str + :param created_at: ISO date time string when this shield list object was created. + :type created_at: DateTime + :param updated_at: ISO date time string when this shield list object was updated. + :type updated_at: DateTime + :param description: Description of Shield List., defaults to None + :type description: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + self.enterprise = enterprise + self.created_at = created_at + self.updated_at = updated_at + self.content = content + self.description = description diff --git a/box_sdk_gen/schemas/v2025_r0/shield_lists_create_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_lists_create_v2025_r0.py new file mode 100644 index 000000000..c582f4c8e --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_lists_create_v2025_r0.py @@ -0,0 +1,48 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_country_v2025_r0 import ( + ShieldListContentCountryV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_domain_v2025_r0 import ( + ShieldListContentDomainV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_email_v2025_r0 import ( + ShieldListContentEmailV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_ip_v2025_r0 import ( + ShieldListContentIpV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_request_v2025_r0 import ( + ShieldListContentRequestV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldListsCreateV2025R0(BaseObject): + def __init__( + self, + name: str, + content: ShieldListContentRequestV2025R0, + *, + description: Optional[str] = None, + **kwargs + ): + """ + :param name: The name of the shield list. + :type name: str + :param description: Optional description of Shield List., defaults to None + :type description: Optional[str], optional + """ + super().__init__(**kwargs) + self.name = name + self.content = content + self.description = description diff --git a/box_sdk_gen/schemas/v2025_r0/shield_lists_update_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_lists_update_v2025_r0.py new file mode 100644 index 000000000..6a85584a9 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_lists_update_v2025_r0.py @@ -0,0 +1,48 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_country_v2025_r0 import ( + ShieldListContentCountryV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_domain_v2025_r0 import ( + ShieldListContentDomainV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_email_v2025_r0 import ( + ShieldListContentEmailV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_ip_v2025_r0 import ( + ShieldListContentIpV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_request_v2025_r0 import ( + ShieldListContentRequestV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldListsUpdateV2025R0(BaseObject): + def __init__( + self, + name: str, + content: ShieldListContentRequestV2025R0, + *, + description: Optional[str] = None, + **kwargs + ): + """ + :param name: The name of the shield list. + :type name: str + :param description: Optional description of Shield List., defaults to None + :type description: Optional[str], optional + """ + super().__init__(**kwargs) + self.name = name + self.content = content + self.description = description diff --git a/box_sdk_gen/schemas/v2025_r0/shield_lists_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_lists_v2025_r0.py new file mode 100644 index 000000000..1b2beceeb --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_lists_v2025_r0.py @@ -0,0 +1,21 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.shield_list_mini_v2025_r0 import ShieldListMiniV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class ShieldListsV2025R0(BaseObject): + def __init__( + self, *, entries: Optional[List[ShieldListMiniV2025R0]] = None, **kwargs + ): + """ + :param entries: A list of shield list objects., defaults to None + :type entries: Optional[List[ShieldListMiniV2025R0]], optional + """ + super().__init__(**kwargs) + self.entries = entries diff --git a/box_sdk_gen/schemas/v2025_r0/terms_of_service_base_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/terms_of_service_base_v2025_r0.py new file mode 100644 index 000000000..0d7c7ae0b --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/terms_of_service_base_v2025_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class TermsOfServiceBaseV2025R0TypeField(str, Enum): + TERMS_OF_SERVICE = 'terms_of_service' + + +class TermsOfServiceBaseV2025R0(BaseObject): + _discriminator = 'type', {'terms_of_service'} + + def __init__( + self, + id: str, + *, + type: TermsOfServiceBaseV2025R0TypeField = TermsOfServiceBaseV2025R0TypeField.TERMS_OF_SERVICE, + **kwargs + ): + """ + :param id: The unique identifier for this terms of service. + :type id: str + :param type: The value will always be `terms_of_service`., defaults to TermsOfServiceBaseV2025R0TypeField.TERMS_OF_SERVICE + :type type: TermsOfServiceBaseV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/user_base_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/user_base_v2025_r0.py new file mode 100644 index 000000000..f5910d63f --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/user_base_v2025_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserBaseV2025R0TypeField(str, Enum): + USER = 'user' + + +class UserBaseV2025R0(BaseObject): + _discriminator = 'type', {'user'} + + def __init__( + self, + id: str, + *, + type: UserBaseV2025R0TypeField = UserBaseV2025R0TypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param type: The value will always be `user`., defaults to UserBaseV2025R0TypeField.USER + :type type: UserBaseV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/user_mini_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/user_mini_v2025_r0.py new file mode 100644 index 000000000..f9ef21883 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/user_mini_v2025_r0.py @@ -0,0 +1,32 @@ +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.user_base_v2025_r0 import UserBaseV2025R0TypeField + +from box_sdk_gen.schemas.v2025_r0.user_base_v2025_r0 import UserBaseV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserMiniV2025R0(UserBaseV2025R0): + def __init__( + self, + id: str, + *, + name: Optional[str] = None, + login: Optional[str] = None, + type: UserBaseV2025R0TypeField = UserBaseV2025R0TypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param name: The display name of this user., defaults to None + :type name: Optional[str], optional + :param login: The primary email address of this user., defaults to None + :type login: Optional[str], optional + :param type: The value will always be `user`., defaults to UserBaseV2025R0TypeField.USER + :type type: UserBaseV2025R0TypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.name = name + self.login = login diff --git a/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py new file mode 100644 index 000000000..1e767a598 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class WeblinkReferenceV2025R0TypeField(str, Enum): + WEBLINK = 'weblink' + + +class WeblinkReferenceV2025R0(BaseObject): + _discriminator = 'type', {'weblink'} + + def __init__( + self, + id: str, + *, + type: WeblinkReferenceV2025R0TypeField = WeblinkReferenceV2025R0TypeField.WEBLINK, + **kwargs + ): + """ + :param id: ID of the weblink. + :type id: str + :param type: The value will always be `weblink`., defaults to WeblinkReferenceV2025R0TypeField.WEBLINK + :type type: WeblinkReferenceV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/watermark.py b/box_sdk_gen/schemas/watermark.py new file mode 100644 index 000000000..eb5464cdd --- /dev/null +++ b/box_sdk_gen/schemas/watermark.py @@ -0,0 +1,34 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class WatermarkWatermarkField(BaseObject): + def __init__( + self, + *, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param created_at: When this watermark was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When this task was modified., defaults to None + :type modified_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.created_at = created_at + self.modified_at = modified_at + + +class Watermark(BaseObject): + def __init__( + self, *, watermark: Optional[WatermarkWatermarkField] = None, **kwargs + ): + super().__init__(**kwargs) + self.watermark = watermark diff --git a/box_sdk_gen/schemas/web_link.py b/box_sdk_gen/schemas/web_link.py new file mode 100644 index 000000000..c85512f47 --- /dev/null +++ b/box_sdk_gen/schemas/web_link.py @@ -0,0 +1,248 @@ +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.web_link_base import WebLinkBaseTypeField + +from box_sdk_gen.schemas.web_link_base import WebLinkBase + +from box_sdk_gen.schemas.web_link_mini import WebLinkMini + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class WebLinkPathCollectionField(BaseObject): + def __init__(self, total_count: int, entries: List[FolderMini], **kwargs): + """ + :param total_count: The number of folders in this list. + :type total_count: int + :param entries: The parent folders for this item. + :type entries: List[FolderMini] + """ + super().__init__(**kwargs) + self.total_count = total_count + self.entries = entries + + +class WebLinkSharedLinkAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class WebLinkSharedLinkEffectiveAccessField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + +class WebLinkSharedLinkEffectivePermissionField(str, Enum): + CAN_EDIT = 'can_edit' + CAN_DOWNLOAD = 'can_download' + CAN_PREVIEW = 'can_preview' + NO_ACCESS = 'no_access' + + +class WebLinkSharedLinkPermissionsField(BaseObject): + def __init__(self, can_download: bool, can_preview: bool, can_edit: bool, **kwargs): + """ + :param can_download: Defines if the shared link allows for the item to be downloaded. For + shared links on folders, this also applies to any items in the folder. + + This value can be set to `true` when the effective access level is + set to `open` or `company`, not `collaborators`. + :type can_download: bool + :param can_preview: Defines if the shared link allows for the item to be previewed. + + This value is always `true`. For shared links on folders this also + applies to any items in the folder. + :type can_preview: bool + :param can_edit: Defines if the shared link allows for the item to be edited. + + This value can only be `true` if `can_download` is also `true` and if + the item has a type of `file`. + :type can_edit: bool + """ + super().__init__(**kwargs) + self.can_download = can_download + self.can_preview = can_preview + self.can_edit = can_edit + + +class WebLinkSharedLinkField(BaseObject): + def __init__( + self, + url: str, + effective_access: WebLinkSharedLinkEffectiveAccessField, + effective_permission: WebLinkSharedLinkEffectivePermissionField, + is_password_enabled: bool, + download_count: int, + preview_count: int, + *, + download_url: Optional[str] = None, + vanity_url: Optional[str] = None, + vanity_name: Optional[str] = None, + access: Optional[WebLinkSharedLinkAccessField] = None, + unshared_at: Optional[DateTime] = None, + permissions: Optional[WebLinkSharedLinkPermissionsField] = None, + **kwargs + ): + """ + :param url: The URL that can be used to access the item on Box. + + This URL will display the item in Box's preview UI where the file + can be downloaded if allowed. + + This URL will continue to work even when a custom `vanity_url` + has been set for this shared link. + :type url: str + :param effective_access: The effective access level for the shared link. This can be a more + restrictive access level than the value in the `access` field when the + enterprise settings restrict the allowed access levels. + :type effective_access: WebLinkSharedLinkEffectiveAccessField + :param effective_permission: The effective permissions for this shared link. + These result in the more restrictive combination of + the share link permissions and the item permissions set + by the administrator, the owner, and any ancestor item + such as a folder. + :type effective_permission: WebLinkSharedLinkEffectivePermissionField + :param is_password_enabled: Defines if the shared link requires a password to access the item. + :type is_password_enabled: bool + :param download_count: The number of times this item has been downloaded. + :type download_count: int + :param preview_count: The number of times this item has been previewed. + :type preview_count: int + :param download_url: A URL that can be used to download the file. This URL can be used in + a browser to download the file. This URL includes the file + extension so that the file will be saved with the right file type. + + This property will be `null` for folders., defaults to None + :type download_url: Optional[str], optional + :param vanity_url: The "Custom URL" that can also be used to preview the item on Box. Custom + URLs can only be created or modified in the Box Web application., defaults to None + :type vanity_url: Optional[str], optional + :param vanity_name: The custom name of a shared link, as used in the `vanity_url` field., defaults to None + :type vanity_name: Optional[str], optional + :param access: The access level for this shared link. + + * `open` - provides access to this item to anyone with this link + * `company` - only provides access to this item to people the same company + * `collaborators` - only provides access to this item to people who are + collaborators on this item + + If this field is omitted when creating the shared link, the access level + will be set to the default access level specified by the enterprise admin., defaults to None + :type access: Optional[WebLinkSharedLinkAccessField], optional + :param unshared_at: The date and time when this link will be unshared. This field can only be + set by users with paid accounts., defaults to None + :type unshared_at: Optional[DateTime], optional + :param permissions: Defines if this link allows a user to preview, edit, and download an item. + These permissions refer to the shared link only and + do not supersede permissions applied to the item itself., defaults to None + :type permissions: Optional[WebLinkSharedLinkPermissionsField], optional + """ + super().__init__(**kwargs) + self.url = url + self.effective_access = effective_access + self.effective_permission = effective_permission + self.is_password_enabled = is_password_enabled + self.download_count = download_count + self.preview_count = preview_count + self.download_url = download_url + self.vanity_url = vanity_url + self.vanity_name = vanity_name + self.access = access + self.unshared_at = unshared_at + self.permissions = permissions + + +class WebLinkItemStatusField(str, Enum): + ACTIVE = 'active' + TRASHED = 'trashed' + DELETED = 'deleted' + + +class WebLink(WebLinkMini): + def __init__( + self, + id: str, + *, + parent: Optional[FolderMini] = None, + description: Optional[str] = None, + path_collection: Optional[WebLinkPathCollectionField] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + trashed_at: Optional[DateTime] = None, + purged_at: Optional[DateTime] = None, + created_by: Optional[UserMini] = None, + modified_by: Optional[UserMini] = None, + owned_by: Optional[UserMini] = None, + shared_link: Optional[WebLinkSharedLinkField] = None, + item_status: Optional[WebLinkItemStatusField] = None, + url: Optional[str] = None, + sequence_id: Optional[str] = None, + name: Optional[str] = None, + type: WebLinkBaseTypeField = WebLinkBaseTypeField.WEB_LINK, + etag: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this web link. + :type id: str + :param description: The description accompanying the web link. This is + visible within the Box web application., defaults to None + :type description: Optional[str], optional + :param created_at: When this file was created on Box’s servers., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: When this file was last updated on the Box + servers., defaults to None + :type modified_at: Optional[DateTime], optional + :param trashed_at: When this file was moved to the trash., defaults to None + :type trashed_at: Optional[DateTime], optional + :param purged_at: When this file will be permanently deleted., defaults to None + :type purged_at: Optional[DateTime], optional + :param item_status: Whether this item is deleted or not. Values include `active`, + `trashed` if the file has been moved to the trash, and `deleted` if + the file has been permanently deleted., defaults to None + :type item_status: Optional[WebLinkItemStatusField], optional + :param url: The URL this web link points to., defaults to None + :type url: Optional[str], optional + :param name: The name of the web link., defaults to None + :type name: Optional[str], optional + :param type: The value will always be `web_link`., defaults to WebLinkBaseTypeField.WEB_LINK + :type type: WebLinkBaseTypeField, optional + :param etag: The entity tag of this web link. Used with `If-Match` + headers., defaults to None + :type etag: Optional[str], optional + """ + super().__init__( + id=id, + url=url, + sequence_id=sequence_id, + name=name, + type=type, + etag=etag, + **kwargs + ) + self.parent = parent + self.description = description + self.path_collection = path_collection + self.created_at = created_at + self.modified_at = modified_at + self.trashed_at = trashed_at + self.purged_at = purged_at + self.created_by = created_by + self.modified_by = modified_by + self.owned_by = owned_by + self.shared_link = shared_link + self.item_status = item_status diff --git a/box_sdk_gen/schemas/web_link_base.py b/box_sdk_gen/schemas/web_link_base.py new file mode 100644 index 000000000..5047cf051 --- /dev/null +++ b/box_sdk_gen/schemas/web_link_base.py @@ -0,0 +1,37 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class WebLinkBaseTypeField(str, Enum): + WEB_LINK = 'web_link' + + +class WebLinkBase(BaseObject): + _discriminator = 'type', {'web_link'} + + def __init__( + self, + id: str, + *, + type: WebLinkBaseTypeField = WebLinkBaseTypeField.WEB_LINK, + etag: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this web link. + :type id: str + :param type: The value will always be `web_link`., defaults to WebLinkBaseTypeField.WEB_LINK + :type type: WebLinkBaseTypeField, optional + :param etag: The entity tag of this web link. Used with `If-Match` + headers., defaults to None + :type etag: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.etag = etag diff --git a/box_sdk_gen/schemas/web_link_mini.py b/box_sdk_gen/schemas/web_link_mini.py new file mode 100644 index 000000000..8beb65852 --- /dev/null +++ b/box_sdk_gen/schemas/web_link_mini.py @@ -0,0 +1,38 @@ +from typing import Optional + +from box_sdk_gen.schemas.web_link_base import WebLinkBaseTypeField + +from box_sdk_gen.schemas.web_link_base import WebLinkBase + +from box_sdk_gen.box.errors import BoxSDKError + + +class WebLinkMini(WebLinkBase): + def __init__( + self, + id: str, + *, + url: Optional[str] = None, + sequence_id: Optional[str] = None, + name: Optional[str] = None, + type: WebLinkBaseTypeField = WebLinkBaseTypeField.WEB_LINK, + etag: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this web link. + :type id: str + :param url: The URL this web link points to., defaults to None + :type url: Optional[str], optional + :param name: The name of the web link., defaults to None + :type name: Optional[str], optional + :param type: The value will always be `web_link`., defaults to WebLinkBaseTypeField.WEB_LINK + :type type: WebLinkBaseTypeField, optional + :param etag: The entity tag of this web link. Used with `If-Match` + headers., defaults to None + :type etag: Optional[str], optional + """ + super().__init__(id=id, type=type, etag=etag, **kwargs) + self.url = url + self.sequence_id = sequence_id + self.name = name diff --git a/box_sdk_gen/schemas/webhook.py b/box_sdk_gen/schemas/webhook.py new file mode 100644 index 000000000..3cbf167f6 --- /dev/null +++ b/box_sdk_gen/schemas/webhook.py @@ -0,0 +1,96 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.webhook_mini import WebhookMiniTypeField + +from box_sdk_gen.schemas.webhook_mini import WebhookMiniTargetField + +from box_sdk_gen.schemas.webhook_mini import WebhookMini + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class WebhookTriggersField(str, Enum): + FILE_UPLOADED = 'FILE.UPLOADED' + FILE_PREVIEWED = 'FILE.PREVIEWED' + FILE_DOWNLOADED = 'FILE.DOWNLOADED' + FILE_TRASHED = 'FILE.TRASHED' + FILE_DELETED = 'FILE.DELETED' + FILE_RESTORED = 'FILE.RESTORED' + FILE_COPIED = 'FILE.COPIED' + FILE_MOVED = 'FILE.MOVED' + FILE_LOCKED = 'FILE.LOCKED' + FILE_UNLOCKED = 'FILE.UNLOCKED' + FILE_RENAMED = 'FILE.RENAMED' + COMMENT_CREATED = 'COMMENT.CREATED' + COMMENT_UPDATED = 'COMMENT.UPDATED' + COMMENT_DELETED = 'COMMENT.DELETED' + TASK_ASSIGNMENT_CREATED = 'TASK_ASSIGNMENT.CREATED' + TASK_ASSIGNMENT_UPDATED = 'TASK_ASSIGNMENT.UPDATED' + METADATA_INSTANCE_CREATED = 'METADATA_INSTANCE.CREATED' + METADATA_INSTANCE_UPDATED = 'METADATA_INSTANCE.UPDATED' + METADATA_INSTANCE_DELETED = 'METADATA_INSTANCE.DELETED' + FOLDER_CREATED = 'FOLDER.CREATED' + FOLDER_RENAMED = 'FOLDER.RENAMED' + FOLDER_DOWNLOADED = 'FOLDER.DOWNLOADED' + FOLDER_RESTORED = 'FOLDER.RESTORED' + FOLDER_DELETED = 'FOLDER.DELETED' + FOLDER_COPIED = 'FOLDER.COPIED' + FOLDER_MOVED = 'FOLDER.MOVED' + FOLDER_TRASHED = 'FOLDER.TRASHED' + WEBHOOK_DELETED = 'WEBHOOK.DELETED' + COLLABORATION_CREATED = 'COLLABORATION.CREATED' + COLLABORATION_ACCEPTED = 'COLLABORATION.ACCEPTED' + COLLABORATION_REJECTED = 'COLLABORATION.REJECTED' + COLLABORATION_REMOVED = 'COLLABORATION.REMOVED' + COLLABORATION_UPDATED = 'COLLABORATION.UPDATED' + SHARED_LINK_DELETED = 'SHARED_LINK.DELETED' + SHARED_LINK_CREATED = 'SHARED_LINK.CREATED' + SHARED_LINK_UPDATED = 'SHARED_LINK.UPDATED' + SIGN_REQUEST_COMPLETED = 'SIGN_REQUEST.COMPLETED' + SIGN_REQUEST_DECLINED = 'SIGN_REQUEST.DECLINED' + SIGN_REQUEST_EXPIRED = 'SIGN_REQUEST.EXPIRED' + SIGN_REQUEST_SIGNER_EMAIL_BOUNCED = 'SIGN_REQUEST.SIGNER_EMAIL_BOUNCED' + + +class Webhook(WebhookMini): + def __init__( + self, + *, + created_by: Optional[UserMini] = None, + created_at: Optional[DateTime] = None, + address: Optional[str] = None, + triggers: Optional[List[WebhookTriggersField]] = None, + id: Optional[str] = None, + type: Optional[WebhookMiniTypeField] = None, + target: Optional[WebhookMiniTargetField] = None, + **kwargs + ): + """ + :param created_at: A timestamp identifying the time that + the webhook was created., defaults to None + :type created_at: Optional[DateTime], optional + :param address: The URL that is notified by this webhook., defaults to None + :type address: Optional[str], optional + :param triggers: An array of event names that this webhook is + to be triggered for., defaults to None + :type triggers: Optional[List[WebhookTriggersField]], optional + :param id: The unique identifier for this webhook., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `webhook`., defaults to None + :type type: Optional[WebhookMiniTypeField], optional + :param target: The item that will trigger the webhook., defaults to None + :type target: Optional[WebhookMiniTargetField], optional + """ + super().__init__(id=id, type=type, target=target, **kwargs) + self.created_by = created_by + self.created_at = created_at + self.address = address + self.triggers = triggers diff --git a/box_sdk_gen/schemas/webhook_invocation.py b/box_sdk_gen/schemas/webhook_invocation.py new file mode 100644 index 000000000..86ba116e8 --- /dev/null +++ b/box_sdk_gen/schemas/webhook_invocation.py @@ -0,0 +1,100 @@ +from enum import Enum + +from typing import Optional + +from typing import Union + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.webhook import Webhook + +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class WebhookInvocationTypeField(str, Enum): + WEBHOOK_EVENT = 'webhook_event' + + +class WebhookInvocationTriggerField(str, Enum): + FILE_UPLOADED = 'FILE.UPLOADED' + FILE_PREVIEWED = 'FILE.PREVIEWED' + FILE_DOWNLOADED = 'FILE.DOWNLOADED' + FILE_TRASHED = 'FILE.TRASHED' + FILE_DELETED = 'FILE.DELETED' + FILE_RESTORED = 'FILE.RESTORED' + FILE_COPIED = 'FILE.COPIED' + FILE_MOVED = 'FILE.MOVED' + FILE_LOCKED = 'FILE.LOCKED' + FILE_UNLOCKED = 'FILE.UNLOCKED' + FILE_RENAMED = 'FILE.RENAMED' + COMMENT_CREATED = 'COMMENT.CREATED' + COMMENT_UPDATED = 'COMMENT.UPDATED' + COMMENT_DELETED = 'COMMENT.DELETED' + TASK_ASSIGNMENT_CREATED = 'TASK_ASSIGNMENT.CREATED' + TASK_ASSIGNMENT_UPDATED = 'TASK_ASSIGNMENT.UPDATED' + METADATA_INSTANCE_CREATED = 'METADATA_INSTANCE.CREATED' + METADATA_INSTANCE_UPDATED = 'METADATA_INSTANCE.UPDATED' + METADATA_INSTANCE_DELETED = 'METADATA_INSTANCE.DELETED' + FOLDER_CREATED = 'FOLDER.CREATED' + FOLDER_RENAMED = 'FOLDER.RENAMED' + FOLDER_DOWNLOADED = 'FOLDER.DOWNLOADED' + FOLDER_RESTORED = 'FOLDER.RESTORED' + FOLDER_DELETED = 'FOLDER.DELETED' + FOLDER_COPIED = 'FOLDER.COPIED' + FOLDER_MOVED = 'FOLDER.MOVED' + FOLDER_TRASHED = 'FOLDER.TRASHED' + WEBHOOK_DELETED = 'WEBHOOK.DELETED' + COLLABORATION_CREATED = 'COLLABORATION.CREATED' + COLLABORATION_ACCEPTED = 'COLLABORATION.ACCEPTED' + COLLABORATION_REJECTED = 'COLLABORATION.REJECTED' + COLLABORATION_REMOVED = 'COLLABORATION.REMOVED' + COLLABORATION_UPDATED = 'COLLABORATION.UPDATED' + SHARED_LINK_DELETED = 'SHARED_LINK.DELETED' + SHARED_LINK_CREATED = 'SHARED_LINK.CREATED' + SHARED_LINK_UPDATED = 'SHARED_LINK.UPDATED' + SIGN_REQUEST_COMPLETED = 'SIGN_REQUEST.COMPLETED' + SIGN_REQUEST_DECLINED = 'SIGN_REQUEST.DECLINED' + SIGN_REQUEST_EXPIRED = 'SIGN_REQUEST.EXPIRED' + SIGN_REQUEST_SIGNER_EMAIL_BOUNCED = 'SIGN_REQUEST.SIGNER_EMAIL_BOUNCED' + + +class WebhookInvocation(BaseObject): + _discriminator = 'type', {'webhook_event'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[WebhookInvocationTypeField] = None, + webhook: Optional[Webhook] = None, + created_by: Optional[UserMini] = None, + created_at: Optional[DateTime] = None, + trigger: Optional[WebhookInvocationTriggerField] = None, + source: Optional[Union[File, Folder]] = None, + **kwargs + ): + """ + :param id: The unique identifier for this webhook invocation., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `webhook_event`., defaults to None + :type type: Optional[WebhookInvocationTypeField], optional + :param created_at: A timestamp identifying the time that + the webhook event was triggered., defaults to None + :type created_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.webhook = webhook + self.created_by = created_by + self.created_at = created_at + self.trigger = trigger + self.source = source diff --git a/box_sdk_gen/schemas/webhook_mini.py b/box_sdk_gen/schemas/webhook_mini.py new file mode 100644 index 000000000..b9d33c3d8 --- /dev/null +++ b/box_sdk_gen/schemas/webhook_mini.py @@ -0,0 +1,62 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class WebhookMiniTypeField(str, Enum): + WEBHOOK = 'webhook' + + +class WebhookMiniTargetTypeField(str, Enum): + FILE = 'file' + FOLDER = 'folder' + + +class WebhookMiniTargetField(BaseObject): + _discriminator = 'type', {'file', 'folder'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[WebhookMiniTargetTypeField] = None, + **kwargs + ): + """ + :param id: The ID of the item to trigger a webhook., defaults to None + :type id: Optional[str], optional + :param type: The type of item to trigger a webhook., defaults to None + :type type: Optional[WebhookMiniTargetTypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + +class WebhookMini(BaseObject): + _discriminator = 'type', {'webhook'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[WebhookMiniTypeField] = None, + target: Optional[WebhookMiniTargetField] = None, + **kwargs + ): + """ + :param id: The unique identifier for this webhook., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `webhook`., defaults to None + :type type: Optional[WebhookMiniTypeField], optional + :param target: The item that will trigger the webhook., defaults to None + :type target: Optional[WebhookMiniTargetField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.target = target diff --git a/box_sdk_gen/schemas/webhooks.py b/box_sdk_gen/schemas/webhooks.py new file mode 100644 index 000000000..779086e51 --- /dev/null +++ b/box_sdk_gen/schemas/webhooks.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.webhook_mini import WebhookMini + +from box_sdk_gen.box.errors import BoxSDKError + + +class Webhooks(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[WebhookMini]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of webhooks., defaults to None + :type entries: Optional[List[WebhookMini]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/workflow.py b/box_sdk_gen/schemas/workflow.py new file mode 100644 index 000000000..2d5ecc394 --- /dev/null +++ b/box_sdk_gen/schemas/workflow.py @@ -0,0 +1,295 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.workflow_mini import WorkflowMiniTypeField + +from box_sdk_gen.schemas.workflow_mini import WorkflowMini + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class WorkflowFlowsTypeField(str, Enum): + FLOW = 'flow' + + +class WorkflowFlowsTriggerTypeField(str, Enum): + TRIGGER = 'trigger' + + +class WorkflowFlowsTriggerTriggerTypeField(str, Enum): + WORKFLOW_MANUAL_START = 'WORKFLOW_MANUAL_START' + + +class WorkflowFlowsTriggerScopeTypeField(str, Enum): + TRIGGER_SCOPE = 'trigger_scope' + + +class WorkflowFlowsTriggerScopeObjectTypeField(str, Enum): + FOLDER = 'folder' + + +class WorkflowFlowsTriggerScopeObjectField(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + *, + type: Optional[WorkflowFlowsTriggerScopeObjectTypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of the object., defaults to None + :type type: Optional[WorkflowFlowsTriggerScopeObjectTypeField], optional + :param id: The id of the object., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class WorkflowFlowsTriggerScopeField(BaseObject): + _discriminator = 'type', {'trigger_scope'} + + def __init__( + self, + *, + type: Optional[WorkflowFlowsTriggerScopeTypeField] = None, + ref: Optional[str] = None, + object: Optional[WorkflowFlowsTriggerScopeObjectField] = None, + **kwargs + ): + """ + :param type: The trigger scope's resource type., defaults to None + :type type: Optional[WorkflowFlowsTriggerScopeTypeField], optional + :param ref: Indicates the path of the condition value to check., defaults to None + :type ref: Optional[str], optional + :param object: The object the `ref` points to., defaults to None + :type object: Optional[WorkflowFlowsTriggerScopeObjectField], optional + """ + super().__init__(**kwargs) + self.type = type + self.ref = ref + self.object = object + + +class WorkflowFlowsTriggerField(BaseObject): + _discriminator = 'type', {'trigger'} + + def __init__( + self, + *, + type: Optional[WorkflowFlowsTriggerTypeField] = None, + trigger_type: Optional[WorkflowFlowsTriggerTriggerTypeField] = None, + scope: Optional[List[WorkflowFlowsTriggerScopeField]] = None, + **kwargs + ): + """ + :param type: The trigger's resource type., defaults to None + :type type: Optional[WorkflowFlowsTriggerTypeField], optional + :param trigger_type: The type of trigger selected for this flow., defaults to None + :type trigger_type: Optional[WorkflowFlowsTriggerTriggerTypeField], optional + :param scope: List of trigger scopes., defaults to None + :type scope: Optional[List[WorkflowFlowsTriggerScopeField]], optional + """ + super().__init__(**kwargs) + self.type = type + self.trigger_type = trigger_type + self.scope = scope + + +class WorkflowFlowsOutcomesTypeField(str, Enum): + OUTCOME = 'outcome' + + +class WorkflowFlowsOutcomesActionTypeField(str, Enum): + ADD_METADATA = 'add_metadata' + ASSIGN_TASK = 'assign_task' + COPY_FILE = 'copy_file' + COPY_FOLDER = 'copy_folder' + CREATE_FOLDER = 'create_folder' + DELETE_FILE = 'delete_file' + DELETE_FOLDER = 'delete_folder' + LOCK_FILE = 'lock_file' + MOVE_FILE = 'move_file' + MOVE_FOLDER = 'move_folder' + REMOVE_WATERMARK_FILE = 'remove_watermark_file' + RENAME_FOLDER = 'rename_folder' + RESTORE_FOLDER = 'restore_folder' + SHARE_FILE = 'share_file' + SHARE_FOLDER = 'share_folder' + UNLOCK_FILE = 'unlock_file' + UPLOAD_FILE = 'upload_file' + WAIT_FOR_TASK = 'wait_for_task' + WATERMARK_FILE = 'watermark_file' + GO_BACK_TO_STEP = 'go_back_to_step' + APPLY_FILE_CLASSIFICATION = 'apply_file_classification' + APPLY_FOLDER_CLASSIFICATION = 'apply_folder_classification' + SEND_NOTIFICATION = 'send_notification' + + +class WorkflowFlowsOutcomesIfRejectedTypeField(str, Enum): + OUTCOME = 'outcome' + + +class WorkflowFlowsOutcomesIfRejectedActionTypeField(str, Enum): + ADD_METADATA = 'add_metadata' + ASSIGN_TASK = 'assign_task' + COPY_FILE = 'copy_file' + COPY_FOLDER = 'copy_folder' + CREATE_FOLDER = 'create_folder' + DELETE_FILE = 'delete_file' + DELETE_FOLDER = 'delete_folder' + LOCK_FILE = 'lock_file' + MOVE_FILE = 'move_file' + MOVE_FOLDER = 'move_folder' + REMOVE_WATERMARK_FILE = 'remove_watermark_file' + RENAME_FOLDER = 'rename_folder' + RESTORE_FOLDER = 'restore_folder' + SHARE_FILE = 'share_file' + SHARE_FOLDER = 'share_folder' + UNLOCK_FILE = 'unlock_file' + UPLOAD_FILE = 'upload_file' + WAIT_FOR_TASK = 'wait_for_task' + WATERMARK_FILE = 'watermark_file' + GO_BACK_TO_STEP = 'go_back_to_step' + APPLY_FILE_CLASSIFICATION = 'apply_file_classification' + APPLY_FOLDER_CLASSIFICATION = 'apply_folder_classification' + SEND_NOTIFICATION = 'send_notification' + + +class WorkflowFlowsOutcomesIfRejectedField(BaseObject): + _discriminator = 'type', {'outcome'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[WorkflowFlowsOutcomesIfRejectedTypeField] = None, + name: Optional[str] = None, + action_type: Optional[WorkflowFlowsOutcomesIfRejectedActionTypeField] = None, + **kwargs + ): + """ + :param id: The identifier of the outcome., defaults to None + :type id: Optional[str], optional + :param type: The outcomes resource type., defaults to None + :type type: Optional[WorkflowFlowsOutcomesIfRejectedTypeField], optional + :param name: The name of the outcome., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + self.action_type = action_type + + +class WorkflowFlowsOutcomesField(BaseObject): + _discriminator = 'type', {'outcome'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[WorkflowFlowsOutcomesTypeField] = None, + name: Optional[str] = None, + action_type: Optional[WorkflowFlowsOutcomesActionTypeField] = None, + if_rejected: Optional[List[WorkflowFlowsOutcomesIfRejectedField]] = None, + **kwargs + ): + """ + :param id: The identifier of the outcome., defaults to None + :type id: Optional[str], optional + :param type: The outcomes resource type., defaults to None + :type type: Optional[WorkflowFlowsOutcomesTypeField], optional + :param name: The name of the outcome., defaults to None + :type name: Optional[str], optional + :param if_rejected: If `action_type` is `assign_task` and the task is rejected, returns a + list of outcomes to complete., defaults to None + :type if_rejected: Optional[List[WorkflowFlowsOutcomesIfRejectedField]], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + self.action_type = action_type + self.if_rejected = if_rejected + + +class WorkflowFlowsField(BaseObject): + _discriminator = 'type', {'flow'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[WorkflowFlowsTypeField] = None, + trigger: Optional[WorkflowFlowsTriggerField] = None, + outcomes: Optional[List[WorkflowFlowsOutcomesField]] = None, + created_at: Optional[DateTime] = None, + created_by: Optional[UserBase] = None, + **kwargs + ): + """ + :param id: The identifier of the flow., defaults to None + :type id: Optional[str], optional + :param type: The flow's resource type., defaults to None + :type type: Optional[WorkflowFlowsTypeField], optional + :param created_at: When this flow was created., defaults to None + :type created_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.trigger = trigger + self.outcomes = outcomes + self.created_at = created_at + self.created_by = created_by + + +class Workflow(WorkflowMini): + def __init__( + self, + *, + flows: Optional[List[WorkflowFlowsField]] = None, + id: Optional[str] = None, + type: Optional[WorkflowMiniTypeField] = None, + name: Optional[str] = None, + description: Optional[str] = None, + is_enabled: Optional[bool] = None, + **kwargs + ): + """ + :param flows: A list of flows assigned to a workflow., defaults to None + :type flows: Optional[List[WorkflowFlowsField]], optional + :param id: The unique identifier for the workflow., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `workflow`., defaults to None + :type type: Optional[WorkflowMiniTypeField], optional + :param name: The name of the workflow., defaults to None + :type name: Optional[str], optional + :param description: The description for a workflow., defaults to None + :type description: Optional[str], optional + :param is_enabled: Specifies if this workflow is enabled., defaults to None + :type is_enabled: Optional[bool], optional + """ + super().__init__( + id=id, + type=type, + name=name, + description=description, + is_enabled=is_enabled, + **kwargs + ) + self.flows = flows diff --git a/box_sdk_gen/schemas/workflow_full.py b/box_sdk_gen/schemas/workflow_full.py new file mode 100644 index 000000000..38a783c23 --- /dev/null +++ b/box_sdk_gen/schemas/workflow_full.py @@ -0,0 +1,66 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.workflow_mini import WorkflowMiniTypeField + +from box_sdk_gen.schemas.workflow_mini import WorkflowMini + +from box_sdk_gen.schemas.workflow import WorkflowFlowsField + +from box_sdk_gen.schemas.workflow import Workflow + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class WorkflowFull(Workflow): + def __init__( + self, + *, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + created_by: Optional[UserBase] = None, + modified_by: Optional[UserBase] = None, + flows: Optional[List[WorkflowFlowsField]] = None, + id: Optional[str] = None, + type: Optional[WorkflowMiniTypeField] = None, + name: Optional[str] = None, + description: Optional[str] = None, + is_enabled: Optional[bool] = None, + **kwargs + ): + """ + :param created_at: The date and time when the workflow was created on Box., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The date and time when the workflow was last updated on Box., defaults to None + :type modified_at: Optional[DateTime], optional + :param flows: A list of flows assigned to a workflow., defaults to None + :type flows: Optional[List[WorkflowFlowsField]], optional + :param id: The unique identifier for the workflow., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `workflow`., defaults to None + :type type: Optional[WorkflowMiniTypeField], optional + :param name: The name of the workflow., defaults to None + :type name: Optional[str], optional + :param description: The description for a workflow., defaults to None + :type description: Optional[str], optional + :param is_enabled: Specifies if this workflow is enabled., defaults to None + :type is_enabled: Optional[bool], optional + """ + super().__init__( + flows=flows, + id=id, + type=type, + name=name, + description=description, + is_enabled=is_enabled, + **kwargs + ) + self.created_at = created_at + self.modified_at = modified_at + self.created_by = created_by + self.modified_by = modified_by diff --git a/box_sdk_gen/schemas/workflow_mini.py b/box_sdk_gen/schemas/workflow_mini.py new file mode 100644 index 000000000..7b5f9e386 --- /dev/null +++ b/box_sdk_gen/schemas/workflow_mini.py @@ -0,0 +1,44 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class WorkflowMiniTypeField(str, Enum): + WORKFLOW = 'workflow' + + +class WorkflowMini(BaseObject): + _discriminator = 'type', {'workflow'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[WorkflowMiniTypeField] = None, + name: Optional[str] = None, + description: Optional[str] = None, + is_enabled: Optional[bool] = None, + **kwargs + ): + """ + :param id: The unique identifier for the workflow., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `workflow`., defaults to None + :type type: Optional[WorkflowMiniTypeField], optional + :param name: The name of the workflow., defaults to None + :type name: Optional[str], optional + :param description: The description for a workflow., defaults to None + :type description: Optional[str], optional + :param is_enabled: Specifies if this workflow is enabled., defaults to None + :type is_enabled: Optional[bool], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name + self.description = description + self.is_enabled = is_enabled diff --git a/box_sdk_gen/schemas/workflows.py b/box_sdk_gen/schemas/workflows.py new file mode 100644 index 000000000..0fdb1abad --- /dev/null +++ b/box_sdk_gen/schemas/workflows.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.workflow import Workflow + +from box_sdk_gen.box.errors import BoxSDKError + + +class Workflows(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[Workflow]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of workflows., defaults to None + :type entries: Optional[List[Workflow]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/zip_download.py b/box_sdk_gen/schemas/zip_download.py new file mode 100644 index 000000000..37d5c22bd --- /dev/null +++ b/box_sdk_gen/schemas/zip_download.py @@ -0,0 +1,103 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class ZipDownloadNameConflictsTypeField(str, Enum): + FILE = 'file' + FOLDER = 'folder' + + +class ZipDownloadNameConflictsField(BaseObject): + _discriminator = 'type', {'file', 'folder'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ZipDownloadNameConflictsTypeField] = None, + original_name: Optional[str] = None, + download_name: Optional[str] = None, + **kwargs + ): + """ + :param id: The identifier of the item., defaults to None + :type id: Optional[str], optional + :param type: The type of this item., defaults to None + :type type: Optional[ZipDownloadNameConflictsTypeField], optional + :param original_name: Box Developer Documentation., defaults to None + :type original_name: Optional[str], optional + :param download_name: The new name of this item as it will appear in the + downloaded `zip` archive., defaults to None + :type download_name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.original_name = original_name + self.download_name = download_name + + +class ZipDownload(BaseObject): + def __init__( + self, + *, + download_url: Optional[str] = None, + status_url: Optional[str] = None, + expires_at: Optional[DateTime] = None, + name_conflicts: Optional[List[List[ZipDownloadNameConflictsField]]] = None, + **kwargs + ): + """ + :param download_url: The URL that can be used to download the `zip` archive. A `Get` request to + this URL will start streaming the items requested. By default, this URL + is only valid for a few seconds, until the `expires_at` time, unless a + download is started after which it is valid for the duration of the + download. + + It is important to note that the domain and path of this URL might change + between API calls, and therefore it's important to use this URL as-is., defaults to None + :type download_url: Optional[str], optional + :param status_url: The URL that can be used to get the status of the `zip` archive being + downloaded. A `Get` request to this URL will return the number of files + in the archive as well as the number of items already downloaded or + skipped. By default, this URL is only valid for a few seconds, until the + `expires_at` time, unless a download is started after which the URL is + valid for 12 hours from the start of the download. + + It is important to note that the domain and path of this URL might change + between API calls, and therefore it's important to use this URL as-is., defaults to None + :type status_url: Optional[str], optional + :param expires_at: The time and date when this archive will expire. After this time the + `status_url` and `download_url` will return an error. + + By default, these URLs are only valid for a few seconds, unless a download + is started after which the `download_url` is valid for the duration of the + download, and the `status_url` is valid for 12 hours from the start of the + download., defaults to None + :type expires_at: Optional[DateTime], optional + :param name_conflicts: A list of conflicts that occurred when trying to create the archive. This + would occur when multiple items have been requested with the + same name. + + To solve these conflicts, the API will automatically rename an item + and return a mapping between the original item's name and its new + name. + + For every conflict, both files will be renamed and therefore this list + will always be a multiple of 2., defaults to None + :type name_conflicts: Optional[List[List[ZipDownloadNameConflictsField]]], optional + """ + super().__init__(**kwargs) + self.download_url = download_url + self.status_url = status_url + self.expires_at = expires_at + self.name_conflicts = name_conflicts diff --git a/box_sdk_gen/schemas/zip_download_request.py b/box_sdk_gen/schemas/zip_download_request.py new file mode 100644 index 000000000..45185b6f5 --- /dev/null +++ b/box_sdk_gen/schemas/zip_download_request.py @@ -0,0 +1,51 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from typing import Optional + +from box_sdk_gen.box.errors import BoxSDKError + + +class ZipDownloadRequestItemsTypeField(str, Enum): + FILE = 'file' + FOLDER = 'folder' + + +class ZipDownloadRequestItemsField(BaseObject): + _discriminator = 'type', {'file', 'folder'} + + def __init__(self, type: ZipDownloadRequestItemsTypeField, id: str, **kwargs): + """ + :param type: The type of the item to add to the archive. + :type type: ZipDownloadRequestItemsTypeField + :param id: The identifier of the item to add to the archive. When this item is + a folder then this can not be the root folder with ID `0`. + :type id: str + """ + super().__init__(**kwargs) + self.type = type + self.id = id + + +class ZipDownloadRequest(BaseObject): + def __init__( + self, + items: List[ZipDownloadRequestItemsField], + *, + download_file_name: Optional[str] = None, + **kwargs + ): + """ + :param items: A list of items to add to the `zip` archive. These can + be folders or files. + :type items: List[ZipDownloadRequestItemsField] + :param download_file_name: The optional name of the `zip` archive. This name will be appended by the + `.zip` file extension, for example `January Financials.zip`., defaults to None + :type download_file_name: Optional[str], optional + """ + super().__init__(**kwargs) + self.items = items + self.download_file_name = download_file_name diff --git a/box_sdk_gen/schemas/zip_download_status.py b/box_sdk_gen/schemas/zip_download_status.py new file mode 100644 index 000000000..2923e3a0a --- /dev/null +++ b/box_sdk_gen/schemas/zip_download_status.py @@ -0,0 +1,50 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ZipDownloadStatusStateField(str, Enum): + IN_PROGRESS = 'in_progress' + FAILED = 'failed' + SUCCEEDED = 'succeeded' + + +class ZipDownloadStatus(BaseObject): + def __init__( + self, + *, + total_file_count: Optional[int] = None, + downloaded_file_count: Optional[int] = None, + skipped_file_count: Optional[int] = None, + skipped_folder_count: Optional[int] = None, + state: Optional[ZipDownloadStatusStateField] = None, + **kwargs + ): + """ + :param total_file_count: The total number of files in the archive., defaults to None + :type total_file_count: Optional[int], optional + :param downloaded_file_count: The number of files that have already been downloaded., defaults to None + :type downloaded_file_count: Optional[int], optional + :param skipped_file_count: The number of files that have been skipped as they could not be + downloaded. In many cases this is due to permission issues that have + surfaced between the creation of the request for the archive and the + archive being downloaded., defaults to None + :type skipped_file_count: Optional[int], optional + :param skipped_folder_count: The number of folders that have been skipped as they could not be + downloaded. In many cases this is due to permission issues that have + surfaced between the creation of the request for the archive and the + archive being downloaded., defaults to None + :type skipped_folder_count: Optional[int], optional + :param state: The state of the archive being downloaded., defaults to None + :type state: Optional[ZipDownloadStatusStateField], optional + """ + super().__init__(**kwargs) + self.total_file_count = total_file_count + self.downloaded_file_count = downloaded_file_count + self.skipped_file_count = skipped_file_count + self.skipped_folder_count = skipped_folder_count + self.state = state diff --git a/box_sdk_gen/serialization/__init__.py b/box_sdk_gen/serialization/__init__.py new file mode 100644 index 000000000..c4b7d4cac --- /dev/null +++ b/box_sdk_gen/serialization/__init__.py @@ -0,0 +1 @@ +from box_sdk_gen.serialization.json import * diff --git a/box_sdk_gen/serialization/json.py b/box_sdk_gen/serialization/json.py new file mode 100644 index 000000000..7d7b580c0 --- /dev/null +++ b/box_sdk_gen/serialization/json.py @@ -0,0 +1,66 @@ +import json +from typing import Dict, get_origin, Union, Type +from urllib.parse import urlencode + +from ..internal.base_object import BaseObject + +SerializedData = Dict + + +def json_to_serialized_data(data: str) -> SerializedData: + return json.loads(data) + + +def sd_to_json(data: SerializedData) -> str: + return json.dumps(data) + + +def sd_to_url_params(data: SerializedData) -> str: + return urlencode(data) + + +def get_sd_value_by_key(data: SerializedData, key: str): + return data.get(key) + + +def serialize(obj: Union[BaseObject, dict, list]) -> SerializedData: + if isinstance(obj, dict): + obj = BaseObject(**obj).to_dict() + if isinstance(obj, BaseObject): + obj = obj.to_dict() + if isinstance(obj, list): + obj = [ + element.to_dict() if isinstance(element, BaseObject) else element + for element in obj + ] + return obj + + +def deserialize(value: SerializedData, type: Type[BaseObject]): + if get_origin(type) == Union: + type = BaseObject._deserialize_union('', value, type) + obj = type.from_dict(value) + obj._raw_data = value + return obj + + +def sanitized_value() -> str: + return '---[redacted]---' + + +def sanitize_serialized_data( + sd: SerializedData, keys_to_sanitize: Dict[str, str] +) -> SerializedData: + if not isinstance(sd, Dict): + return sd + sanitized_dictionary = {} + for key, value in sd.items(): + if key.lower() in keys_to_sanitize and isinstance(value, str): + sanitized_dictionary[key] = sanitized_value() + elif isinstance(value, Dict): + sanitized_dictionary[key] = sanitize_serialized_data( + value, keys_to_sanitize + ) + else: + sanitized_dictionary[key] = value + return sanitized_dictionary diff --git a/boxsdk/__init__.py b/boxsdk/__init__.py deleted file mode 100644 index 88e1d64fb..000000000 --- a/boxsdk/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .auth import JWTAuth, OAuth2, CCGAuth -from .client import * # pylint:disable=wildcard-import,redefined-builtin -from .exception import * # pylint:disable=wildcard-import -from .object import * # pylint:disable=wildcard-import,redefined-builtin -from .util.log import setup_logging -from .version import __version__ diff --git a/boxsdk/auth/__init__.py b/boxsdk/auth/__init__.py deleted file mode 100644 index 4ff79476d..000000000 --- a/boxsdk/auth/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from .cooperatively_managed_oauth2 import CooperativelyManagedOAuth2 -from .developer_token_auth import DeveloperTokenAuth -try: - from .jwt_auth import JWTAuth -except ImportError: - JWTAuth = None # If extras[jwt] are not installed, JWTAuth won't be available. -from .ccg_auth import CCGAuth -from .oauth2 import OAuth2 -try: - from .redis_managed_oauth2 import RedisManagedOAuth2 -except ImportError: - RedisManagedOAuth2 = None # If extras[redis] are not installed, RedisManagedOAuth2 won't be available. -try: - from .redis_managed_jwt_auth import RedisManagedJWTAuth -except ImportError: - RedisManagedJWTAuth = None # If extras[jwt,redis] are not installed, RedisManagedJWTAuth won't be available. -from .remote_managed_oauth2 import RemoteOAuth2 diff --git a/boxsdk/auth/ccg_auth.py b/boxsdk/auth/ccg_auth.py deleted file mode 100644 index fb7155057..000000000 --- a/boxsdk/auth/ccg_auth.py +++ /dev/null @@ -1,19 +0,0 @@ -from datetime import datetime -from typing import Optional - -from boxsdk.auth.server_auth import ServerAuth - - -class CCGAuth(ServerAuth): - _GRANT_TYPE = 'client_credentials' - - def _fetch_access_token(self, subject_id: str, subject_type: str, now_time: Optional[datetime] = None) -> str: - data = { - 'grant_type': self._GRANT_TYPE, - 'client_id': self._client_id, - 'client_secret': self._client_secret, - 'box_subject_id': subject_id, - 'box_subject_type': subject_type, - } - - return self.send_token_request(data, access_token=None, expect_refresh_token=False)[0] diff --git a/boxsdk/auth/cooperatively_managed_oauth2.py b/boxsdk/auth/cooperatively_managed_oauth2.py deleted file mode 100644 index f324a84e7..000000000 --- a/boxsdk/auth/cooperatively_managed_oauth2.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Callable, Tuple, Any - -from .oauth2 import OAuth2 - - -class CooperativelyManagedOAuth2Mixin(OAuth2): - """ - Box SDK OAuth2 mixin. - Allows for sharing auth tokens between multiple clients. - """ - def __init__(self, retrieve_tokens: Callable[[], Tuple[str, str]] = None, *args: Any, **kwargs: Any): - """ - :param retrieve_tokens: - Callback to get the current access/refresh token pair. - """ - # pylint:disable=keyword-arg-before-vararg - self._retrieve_tokens = retrieve_tokens - super().__init__(*args, **kwargs) - - def _get_tokens(self) -> Tuple[str, str]: - """ - Base class override. Get the tokens from the user-specified callback. - """ - return self._retrieve_tokens() - - -class CooperativelyManagedOAuth2(CooperativelyManagedOAuth2Mixin): - """ - Box SDK OAuth2 subclass. - Allows for sharing auth tokens between multiple clients. The retrieve_tokens callback should - return the current access/refresh token pair. - """ diff --git a/boxsdk/auth/developer_token_auth.py b/boxsdk/auth/developer_token_auth.py deleted file mode 100644 index 30a17132e..000000000 --- a/boxsdk/auth/developer_token_auth.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Callable, Any, Tuple, Optional - -from .oauth2 import OAuth2 - - -class DeveloperTokenAuth(OAuth2): - ENTER_TOKEN_PROMPT = 'Enter developer token: ' - - def __init__(self, get_new_token_callback: Callable[[], str] = None, **kwargs: Any): - self._get_new_token = get_new_token_callback - super().__init__( - client_id=None, - client_secret=None, - access_token=self._refresh_developer_token(), - **kwargs - ) - - def _refresh_developer_token(self) -> str: - if self._get_new_token is not None: - return self._get_new_token() - - return input(self.ENTER_TOKEN_PROMPT) - - def _refresh(self, access_token: str) -> Tuple[str, Optional[str]]: - """ - Base class override. - Ask for a new developer token. - """ - self._access_token = self._refresh_developer_token() - return self._access_token, None - - def revoke(self) -> None: - """ - Base class override. - Do nothing; developer tokens can't be revoked without client ID and secret. - """ diff --git a/boxsdk/auth/jwt_auth.py b/boxsdk/auth/jwt_auth.py deleted file mode 100644 index faf245828..000000000 --- a/boxsdk/auth/jwt_auth.py +++ /dev/null @@ -1,271 +0,0 @@ -from datetime import datetime, timedelta -import json -import random -import string -from io import IOBase -from typing import Optional, Union, Callable, TYPE_CHECKING, Any -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey -import jwt - -from boxsdk.auth.server_auth import ServerAuth - -if TYPE_CHECKING: - from boxsdk.network.network_interface import Network - from boxsdk.object.user import User - - -class JWTAuth(ServerAuth): - """ - Responsible for handling JWT Auth for Box Developer Edition. Can authenticate enterprise instances or app users. - """ - _GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer' - - def __init__( - self, - client_id: str, - client_secret: str, - enterprise_id: Optional[str], - jwt_key_id: str, - rsa_private_key_file_sys_path: Optional[str] = None, - rsa_private_key_passphrase: Optional[Union[str, bytes]] = None, - user: Optional[Union[str, 'User']] = None, - store_tokens: Optional[Callable[[str, str], None]] = None, - box_device_id: str = '0', - box_device_name: str = '', - access_token: str = None, - session: Optional['Network'] = None, - jwt_algorithm: str = 'RS256', - rsa_private_key_data: Union[bytes, IOBase, RSAPrivateKey] = None, - **kwargs - ): - """Extends baseclass method. - - Must pass exactly one of either `rsa_private_key_file_sys_path` or - `rsa_private_key_data`. - - If both `enterprise_id` and `user` are non-`None`, the `user` takes - precedence when `refresh()` is called. This can be overruled with a - call to `authenticate_instance()`. - - :param client_id: - Box API key used for identifying the application the user is authenticating with. - :param client_secret: - Box API secret used for making OAuth2 requests. - :param enterprise_id: - The ID of the Box Developer Edition enterprise. - - May be `None`, if the caller knows that it will not be - authenticating as an enterprise instance / service account. - - If `user` is passed, this value is not used, unless - `authenticate_instance()` is called to clear the user and - authenticate as the enterprise instance. - :param jwt_key_id: - Key ID for the JWT assertion. - :param rsa_private_key_file_sys_path: - (optional) Path to an RSA private key file, used for signing the JWT assertion. - :param rsa_private_key_passphrase: - Passphrase used to unlock the private key. Do not pass a unicode string - this must be bytes. - :param user: - (optional) The user to authenticate, expressed as a Box User ID or - as a :class:`User` instance. - - This value is not required. But if it is provided, then the user - will be auto-authenticated at the time of the first API call or - when calling `authenticate_user()` without any arguments. - - Should be `None` if the intention is to authenticate as the - enterprise instance / service account. If both `enterprise_id` and - `user` are non-`None`, the `user` takes precedense when `refresh()` - is called. - - May be one of this application's created App User. Depending on the - configured User Access Level, may also be any other App User or - Managed User in the enterprise. - - - - :param store_tokens: - Optional callback to get access to tokens and store them. Callback method should take two - paramaters - access_token: str and refresh_token: str - and it is not expected to return anything. - :param box_device_id: - Optional unique ID of this device. Used for applications that want to support device-pinning. - :param box_device_name: - Optional human-readable name for this device. - :param access_token: - Access token to use for auth until it expires. - :param session: - If specified, use it to make network requests. If not, the default session will be used. - :param jwt_algorithm: - Which algorithm to use for signing the JWT assertion. Must be one of 'RS256', 'RS384', 'RS512'. - :param rsa_private_key_data: - (optional) Contents of RSA private key, used for signing the JWT assertion. Do not pass a - unicode string. Can pass a byte string, or a file-like object that returns bytes, or an - already-loaded `RSAPrivateKey` object. - """ - rsa_private_key = self._normalize_rsa_private_key( - file_sys_path=rsa_private_key_file_sys_path, - data=rsa_private_key_data, - passphrase=rsa_private_key_passphrase, - ) - del rsa_private_key_data - del rsa_private_key_file_sys_path - super().__init__( - client_id=client_id, - client_secret=client_secret, - enterprise_id=enterprise_id, - user=user, - store_tokens=store_tokens, - box_device_id=box_device_id, - box_device_name=box_device_name, - access_token=access_token, - refresh_token=None, - session=session, - **kwargs - ) - self._rsa_private_key = rsa_private_key - self._jwt_algorithm = jwt_algorithm - self._jwt_key_id = jwt_key_id - - def _fetch_access_token(self, subject_id: str, subject_type: str, now_time: Optional[datetime] = None) -> str: - """ - Construct the claims used for JWT auth and send a request to get a JWT. - Pass an enterprise ID to get an enterprise token (which can be used to provision/deprovision users), - or a user ID to get a user token. - - :param subject_id: - The enterprise ID or user ID to auth. - :param subject_type: - Either 'enterprise' or 'user' - :param now_time: - Optional. The current UTC time is needed in order to construct the expiration time of the JWT claim. - If None, `datetime.utcnow()` will be used. - :return: - The access token for the enterprise or app user. - """ - system_random = random.SystemRandom() - jti_length = system_random.randint(16, 128) - ascii_alphabet = string.ascii_letters + string.digits - ascii_len = len(ascii_alphabet) - jti = ''.join(ascii_alphabet[int(system_random.random() * ascii_len)] for _ in range(jti_length)) - if now_time is None: - now_time = datetime.utcnow() - now_plus_30 = now_time + timedelta(seconds=30) - assertion = jwt.encode( - { - 'iss': self._client_id, - 'sub': subject_id, - 'box_sub_type': subject_type, - 'aud': 'https://api.box.com/oauth2/token', - 'jti': jti, - 'exp': int((now_plus_30 - datetime(1970, 1, 1)).total_seconds()), - }, - self._rsa_private_key, - algorithm=self._jwt_algorithm, - headers={ - 'kid': self._jwt_key_id, - }, - ) - data = { - 'grant_type': self._GRANT_TYPE, - 'client_id': self._client_id, - 'client_secret': self._client_secret, - 'assertion': assertion, - } - if self._box_device_id: - data['box_device_id'] = self._box_device_id - if self._box_device_name: - data['box_device_name'] = self._box_device_name - return self.send_token_request(data, access_token=None, expect_refresh_token=False)[0] - - @classmethod - def _normalize_rsa_private_key( - cls, - file_sys_path: str, - data: Union[bytes, IOBase, RSAPrivateKey], - passphrase: Optional[Union[str, bytes]] = None - ) -> Any: - if len(list(filter(None, [file_sys_path, data]))) != 1: - raise TypeError("must pass exactly one of either rsa_private_key_file_sys_path or rsa_private_key_data") - if file_sys_path: - with open(file_sys_path, 'rb') as key_file: - data = key_file.read() - if hasattr(data, 'read') and callable(data.read): - data = data.read() - if isinstance(data, str): - try: - data = data.encode('ascii') - except UnicodeError as unicode_error: - raise TypeError( - "rsa_private_key_data must contain binary data (bytes/str), not a text/unicode string" - ) from unicode_error - - if isinstance(data, bytes): - passphrase = cls._normalize_rsa_private_key_passphrase(passphrase) - return serialization.load_pem_private_key( - data, - password=passphrase, - backend=default_backend(), - ) - if isinstance(data, RSAPrivateKey): - return data - raise TypeError( - 'rsa_private_key_data must be binary data (bytes/str), ' - 'a file-like object with a read() method, ' - 'or an instance of RSAPrivateKey, ' - f'but got {data.__class__.__name__!r}' - ) - - @staticmethod - def _normalize_rsa_private_key_passphrase(passphrase: Any): - if isinstance(passphrase, str): - try: - return passphrase.encode('ascii') - except UnicodeError as unicode_error: - raise TypeError( - "rsa_private_key_passphrase must contain binary data (bytes/str), not a text/unicode string" - ) from unicode_error - - if not isinstance(passphrase, (bytes, type(None))): - raise TypeError( - f"rsa_private_key_passphrase must contain binary data (bytes/str), " - f"got {passphrase.__class__.__name__!r}" - ) - return passphrase - - @classmethod - def from_settings_dictionary(cls, settings_dictionary: dict, **kwargs: Any) -> 'JWTAuth': - """ - Create an auth instance as defined by the given settings dictionary. - - The dictionary should have the structure of the JSON file downloaded from the Box Developer Console. - - :param settings_dictionary: Dictionary containing settings for configuring app auth. - :return: Auth instance configured as specified by the config dictionary. - """ - if 'boxAppSettings' not in settings_dictionary: - raise ValueError('boxAppSettings not present in configuration') - return cls( - client_id=settings_dictionary['boxAppSettings']['clientID'], - client_secret=settings_dictionary['boxAppSettings']['clientSecret'], - enterprise_id=settings_dictionary.get('enterpriseID', None), - jwt_key_id=settings_dictionary['boxAppSettings']['appAuth'].get('publicKeyID', None), - rsa_private_key_data=settings_dictionary['boxAppSettings']['appAuth'].get('privateKey', None), - rsa_private_key_passphrase=settings_dictionary['boxAppSettings']['appAuth'].get('passphrase', None), - **kwargs - ) - - @classmethod - def from_settings_file(cls, settings_file_sys_path: str, **kwargs: Any) -> 'JWTAuth': - """ - Create an auth instance as defined by a JSON file downloaded from the Box Developer Console. - See https://developer.box.com/en/guides/authentication/jwt/ for more information. - - :param settings_file_sys_path: Path to the JSON file containing the configuration. - :return: Auth instance configured as specified by the JSON file. - """ - with open(settings_file_sys_path, encoding='utf-8') as config_file: - config_dictionary = json.load(config_file) - return cls.from_settings_dictionary(config_dictionary, **kwargs) diff --git a/boxsdk/auth/oauth2.py b/boxsdk/auth/oauth2.py deleted file mode 100644 index adbba8384..000000000 --- a/boxsdk/auth/oauth2.py +++ /dev/null @@ -1,455 +0,0 @@ -from contextlib import contextmanager -from logging import getLogger -import random -import string # pylint:disable=deprecated-module -from threading import Lock -from typing import Optional, Callable, ContextManager, Tuple, TYPE_CHECKING, Any, Union -from urllib.parse import urlunsplit, urlencode - -from ..config import API -from ..exception import BoxOAuthException, BoxAPIException -from ..object.base_api_json_object import BaseAPIJSONObject -from ..session.session import Session -from ..util.json import is_json_response -from ..util.text_enum import TextEnum - -if TYPE_CHECKING: - from boxsdk.session.box_response import BoxResponse - from boxsdk import NetworkResponse - - -class TokenScope(TextEnum): - """ Scopes used for a downscope token request. - - See https://developer.box.com/en/guides/authentication/access-tokens/downscope/. - """ - ITEM_READ = 'item_read' - ITEM_READWRITE = 'item_readwrite' - ITEM_PREVIEW = 'item_preview' - ITEM_UPLOAD = 'item_upload' - ITEM_SHARE = 'item_share' - ITEM_DELETE = 'item_delete' - ITEM_DOWNLOAD = 'item_download' - - -class TokenResponse(BaseAPIJSONObject): - """ Represents the response for a token request. """ - - -class OAuth2: - """ - Responsible for handling OAuth2 for the Box API. Can authenticate and refresh tokens. - - Can be used as a closeable resource, similar to a file. When `close()` is - called, the current tokens are revoked, and the object is put into a state - where it can no longer request new tokens. This action can also be managed - with the `closing()` context manager method. - """ - - def __init__( - self, - client_id: Optional[str], - client_secret: Optional[str], - store_tokens: Optional[Callable[[str, str], None]] = None, - box_device_id: str = '0', - box_device_name: str = '', - access_token: Optional[str] = None, - refresh_token: Optional[str] = None, - session: Optional[Session] = None, - refresh_lock: Optional[ContextManager] = None, - ): - """ - :param client_id: - Box API key used for identifying the application the user is authenticating with. - :param client_secret: - Box API secret used for making OAuth2 requests. - :param store_tokens: - Optional callback to get access to tokens and store them. Callback method should take two - paramaters - access_token: str and refresh_token: str - and it is not expected to return anything. - :param box_device_id: - Optional unique ID of this device. Used for applications that want to support device-pinning. - :param box_device_name: - Optional human-readable name for this device. - :param access_token: - Access token to use for auth until it expires. - :param refresh_token: - Refresh token to use for auth until it expires or is used. - :param session: - If specified, use it to make network requests. If not, the default session will be used. - :param refresh_lock: - Lock used to synchronize token refresh. If not specified, then a :class:`threading.Lock` will be used. - """ - self._client_id = client_id - self._client_secret = client_secret - self._store_tokens_callback = store_tokens - self._access_token = access_token - self._refresh_token = refresh_token - self._session = session or Session() - self._refresh_lock = refresh_lock or Lock() - self._box_device_id = box_device_id - self._box_device_name = box_device_name - self._closed = False - self._api_config = API() - self._logger = getLogger(__name__) - - @property - def access_token(self) -> str: - """ - Get the current access token. - - :return: - current access token - """ - return self._access_token - - @property - def closed(self) -> bool: - """True iff the auth object has been closed. - - When in the closed state, it can no longer request new tokens. - """ - return self._closed - - @property - def api_config(self) -> API: - return self._api_config - - def get_authorization_url(self, redirect_url: Optional[str]) -> Tuple[str, str]: - """ - Get the authorization url based on the client id and the redirect url passed in - - :param redirect_url: - An HTTPS URI or custom URL scheme where the response will be redirected. Optional if the redirect URI is - registered with Box already. - :return: - A tuple of the URL of Box's authorization page and the CSRF token. - This is the URL that your application should forward the user to in first leg of OAuth 2. - """ - csrf_token = self._get_state_csrf_token() - # For the query string parameters, use a sequence of two-element - # tuples, rather than a dictionary, in order to get a consistent and - # predictable order of parameters in the output of `urlencode()`. - params = [ - ('state', csrf_token), - ('response_type', 'code'), - ('client_id', self._client_id), - ] - if redirect_url: - params.append(('redirect_uri', redirect_url)) - # `urlencode()` doesn't work with non-ASCII unicode characters, so - # encode the parameters as ASCII bytes. - params = [(key.encode('utf-8'), value.encode('utf-8')) for (key, value) in params] - query_string = urlencode(params) - return urlunsplit(('', '', self._api_config.OAUTH2_AUTHORIZE_URL, query_string, '')), csrf_token - - def authenticate(self, auth_code: Optional[str]) -> Tuple[str, str]: - """ - Send token request and return the access_token, refresh_token tuple. The access token and refresh token will be - stored by calling the `store_tokens` callback if provided in __init__. - - :param auth_code: - An authorization code you retrieved in the first leg of OAuth 2. - :return: - (access_token, refresh_token) - """ - data = { - 'grant_type': 'authorization_code', - 'code': auth_code, - 'client_id': self._client_id, - 'client_secret': self._client_secret, - } - if self._box_device_id: - data['box_device_id'] = self._box_device_id - if self._box_device_name: - data['box_device_name'] = self._box_device_name - return self.send_token_request(data, access_token=None) - - def _refresh(self, access_token: str) -> Tuple[str, str]: - data = { - 'grant_type': 'refresh_token', - 'refresh_token': self._refresh_token, - 'client_id': self._client_id, - 'client_secret': self._client_secret, - } - if self._box_device_id: - data['box_device_id'] = self._box_device_id - if self._box_device_name: - data['box_device_name'] = self._box_device_name - - return self.send_token_request(data, access_token) - - def _get_tokens(self) -> Tuple[Optional[str], Optional[str]]: - """ - Get the current access and refresh tokens. - - This is a protected method that can be overridden to look up tokens - from an external source (the inverse of the `store_tokens` callback). - - This method does not need to update this object's private token - attributes. Its caller in :class:`OAuth2` is responsible for that. - - :return: - Tuple containing the current access token and refresh token. - One or both of them may be `None`, if they aren't set. - """ - return self._access_token, self._refresh_token - - def refresh(self, access_token_to_refresh: Optional[str]) -> Tuple[str, Optional[str]]: - """ - Refresh the access token and the refresh token and return the access_token, refresh_token tuple. The access - token and refresh token will be stored by calling the `store_tokens` callback if provided in __init__. - - :param access_token_to_refresh: - The expired access token, which needs to be refreshed. - Pass `None` if you don't have the access token. - :return: - Tuple containing the new access token and refresh token. - The refresh token may be `None`, if the authentication scheme - doesn't use one, or keeps it hidden from this client. - """ - self._check_closed() - with self._refresh_lock: - self._check_closed() - self._logger.debug('Refreshing tokens.') - access_token, refresh_token = self._get_and_update_current_tokens() - # The lock here is for handling that case that multiple requests fail, due to access token expired, at the - # same time to avoid multiple session renewals. - if (access_token is None) or (access_token_to_refresh == access_token): - # If the active access token is the same as the token that needs to - # be refreshed, or if we don't currently have any active access - # token, we make the request to refresh the token. - access_token, refresh_token = self._refresh(access_token_to_refresh) - # Else, if the active access token (self._access_token) is not the same as the token needs to be refreshed, - # it means the expired token has already been refreshed. Simply return the current active tokens. - return access_token, refresh_token - - @staticmethod - def _get_state_csrf_token() -> str: - """ Generate a random state CSRF token to be used in the authorization url. - Example: box_csrf_token_Iijw9aU31sNdgiQu - - :return: - The security token - """ - system_random = random.SystemRandom() - ascii_alphabet = string.ascii_letters + string.digits - ascii_len = len(ascii_alphabet) - return 'box_csrf_token_' + ''.join(ascii_alphabet[int(system_random.random() * ascii_len)] for _ in range(16)) - - def _store_tokens(self, access_token: Optional[str], refresh_token: Optional[str]) -> None: - self._update_current_tokens(access_token, refresh_token) - if self._store_tokens_callback is not None: - self._store_tokens_callback(access_token, refresh_token) - - def _get_and_update_current_tokens(self) -> Tuple[Optional[str], Optional[str]]: - """Get the current access and refresh tokens, while also storing them in this object's private attributes. - - :return: - Same as for :meth:`_get_tokens()`. - """ - tokens = self._get_tokens() - self._update_current_tokens(*tokens) - return tokens - - def _update_current_tokens(self, access_token: Optional[str], refresh_token: Optional[str]) -> None: - """Store the latest tokens in this object's private attributes. - - :param access_token: - The latest access token. - May be `None`, if it hasn't been provided. - :param refresh_token: - The latest refresh token. - May be `None`, if the authentication scheme doesn't use one, or if - it hasn't been provided. - """ - self._access_token, self._refresh_token = access_token, refresh_token - - def _execute_token_request( - self, - data: dict, - access_token: Optional[str], - expect_refresh_token: bool = True - ) -> TokenResponse: - """ - Send the request to acquire or refresh an access token. - - :param data: - Dictionary containing the request parameters as specified by the Box API. - :param access_token: - The current access token. - :return: - The response for the token request. - """ - self._check_closed() - url = f'{self._api_config.OAUTH2_API_URL}/token' - headers = {'content-type': 'application/x-www-form-urlencoded'} - try: - network_response = self._session.request( - 'POST', - url, - data=data, - headers=headers, - access_token=access_token, - ) - except BoxAPIException as box_api_exception: - raise self._oauth_exception(box_api_exception.network_response, url) from box_api_exception - - if not network_response.ok: - raise self._oauth_exception(network_response, url) - try: - token_response = TokenResponse(network_response.json()) - except ValueError as value_exception: - raise self._oauth_exception(network_response, url) from value_exception - - if ('access_token' not in token_response) or (expect_refresh_token and 'refresh_token' not in token_response): - raise self._oauth_exception(network_response, url) - - return token_response - - @staticmethod - def _oauth_exception(network_response: Union['NetworkResponse', 'BoxResponse'], url: str) -> BoxOAuthException: - """ - Create a BoxOAuthException instance to raise. If the error response is JSON, parse it and include the - code and message in the exception. - """ - exception_kwargs = dict( - status=network_response.status_code, - url=url, - method='POST', - network_response=network_response, - ) - if is_json_response(network_response): - json_response = network_response.json() - exception_kwargs.update(dict( - code=json_response.get('code') or json_response.get('error'), - message=json_response.get('message') or json_response.get('error_description'), - )) - else: - exception_kwargs['message'] = network_response.content - return BoxOAuthException(**exception_kwargs) - - def send_token_request( - self, - data: dict, - access_token: Optional[str], - expect_refresh_token: bool = True - ) -> Tuple[str, str]: - """ - Send the request to acquire or refresh an access token, and store the tokens. - - :param data: - Dictionary containing the request parameters as specified by the Box API. - :param access_token: - The current access token. - :param expect_refresh_token: - Information if shoul we expect refresh token - :return: - The access token and refresh token. - """ - token_response = self._execute_token_request(data, access_token, expect_refresh_token) - # pylint:disable=no-member - refresh_token = token_response.refresh_token if 'refresh_token' in token_response else None - self._store_tokens(token_response.access_token, refresh_token) - return self._access_token, self._refresh_token - - def revoke(self) -> None: - """ - Revoke the authorization for the current access/refresh token pair. - """ - with self._refresh_lock: - access_token, refresh_token = self._get_and_update_current_tokens() - token_to_revoke = access_token or refresh_token - if token_to_revoke is None: - return - url = f'{self._api_config.OAUTH2_API_URL}/revoke' - try: - network_response = self._session.request( - 'POST', - url, - data={ - 'client_id': self._client_id, - 'client_secret': self._client_secret, - 'token': token_to_revoke, - }, - access_token=access_token, - ) - except BoxAPIException as box_api_exception: - raise self._oauth_exception(box_api_exception.network_response, url) from box_api_exception - - if not network_response.ok: - raise BoxOAuthException( - network_response.status_code, - network_response.content, - url, - 'POST', - network_response, - ) - self._store_tokens(None, None) - - def close(self, revoke: bool = True) -> None: - """Close the auth object. - - After this action is performed, the auth object can no longer request - new tokens. - - This method may be called even if the auth object is already closed. - - :param revoke: - (optional) Whether the current tokens should be revoked, via `revoke()`. - Defaults to `True` as a security precaution, so that the tokens aren't usable - by any adversaries after you are done with them. - Note that the revoke isn't guaranteed to succeed (the network connection might - fail, or the API call might respond with a non-200 HTTP response), so this - isn't a fool-proof security mechanism. - If the revoke fails, an exception is raised. - The auth object is still considered to be closed, even if the revoke fails. - """ - self._closed = True - if revoke: - self.revoke() - - @contextmanager - def closing(self, **close_kwargs: Any) -> None: - """Context manager to close the auth object on exit. - - The behavior is somewhat similar to `contextlib.closing(self)`, but has - some differences. - - The context manager cannot be entered if the auth object is closed. - - If a non-`Exception` (e.g. `KeyboardInterrupt`) is caught from the - block, this context manager prioritizes re-raising the exception as - fast as possible, without blocking. Thus, in this case, the tokens will - not be revoked, even if `revoke=True` was passed to this method. - - If exceptions are raised both from the block and from `close()`, the - exception from the block will be reraised, and the exception from - `close()` will be swallowed. The assumption is that the exception from - the block is more relevant to the client, especially since the revoke - can fail if the network is unavailable. - - :param close_kwargs: Keyword arguments to pass to `close()`. - """ - self._check_closed() - exc_infos = [] - - # pylint:disable=broad-except - try: - yield self - except Exception as exception: - exc_infos.append(exception) - except BaseException as base_exception: - exc_infos.append(base_exception) - close_kwargs['revoke'] = False - - try: - self.close(**close_kwargs) - except Exception as exception: - exc_infos.append(exception) - - if exc_infos: - raise exc_infos[0] - - def _check_closed(self) -> None: - if self.closed: - raise ValueError("operation on a closed auth object") diff --git a/boxsdk/auth/redis_managed_jwt_auth.py b/boxsdk/auth/redis_managed_jwt_auth.py deleted file mode 100644 index d93c400b1..000000000 --- a/boxsdk/auth/redis_managed_jwt_auth.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Tuple - -from .jwt_auth import JWTAuth -from .redis_managed_oauth2 import RedisManagedOAuth2Mixin - - -class RedisManagedJWTAuth(RedisManagedOAuth2Mixin, JWTAuth): - """ - JWT Auth subclass which uses Redis to manage access tokens. - """ - def _auth_with_jwt(self, sub: str, sub_type: str) -> Tuple[str, None]: - """ - Base class override. Returns the access token in a tuple to match the OAuth2 interface. - """ - return super()._authenticate(subject_id=sub, subject_type=sub_type), None diff --git a/boxsdk/auth/redis_managed_oauth2.py b/boxsdk/auth/redis_managed_oauth2.py deleted file mode 100644 index 553f04927..000000000 --- a/boxsdk/auth/redis_managed_oauth2.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import Any, Tuple, Optional -from uuid import uuid4 - -from redis import StrictRedis, Redis -from redis.lock import Lock - -from .oauth2 import OAuth2 - - -class RedisManagedOAuth2Mixin(OAuth2): - """ - Box SDK OAuth2 subclass. - Allows for storing auth tokens in redis. - """ - def __init__(self, unique_id: str = uuid4(), redis_server: Redis = None, *args: Any, **kwargs: Any): - """ - :param unique_id: - An identifier for this auth object. Auth instances which wish to share tokens must use the same ID. - :param redis_server: - An instance of a Redis server, configured to talk to Redis. - """ - # pylint:disable=keyword-arg-before-vararg - self._unique_id = unique_id - self._redis_server = redis_server or StrictRedis() - refresh_lock = Lock(redis=self._redis_server, name=f'{self._unique_id}_lock') - super().__init__(*args, refresh_lock=refresh_lock, **kwargs) - if self._access_token is None: - self._get_and_update_current_tokens() - - @property - def unique_id(self) -> str: - """ - Get the unique ID used by this auth instance. Other instances can share tokens with this instance - if they share the ID with this instance. - """ - return self._unique_id - - def _get_tokens(self) -> Tuple[Optional[str], Optional[str]]: - """ - Base class override. - Gets the latest tokens from redis before returning them. - """ - return self._redis_server.hvals(self._unique_id) or (None, None) - - def _store_tokens(self, access_token: str, refresh_token: str) -> None: - """ - Base class override. - Saves the refreshed tokens in redis. - """ - super()._store_tokens(access_token, refresh_token) - self._redis_server.hmset(self._unique_id, {'access': access_token, 'refresh': refresh_token}) - - -class RedisManagedOAuth2(RedisManagedOAuth2Mixin): - """ - OAuth2 subclass which uses Redis to manage tokens. - """ diff --git a/boxsdk/auth/remote_managed_oauth2.py b/boxsdk/auth/remote_managed_oauth2.py deleted file mode 100644 index b716fbef4..000000000 --- a/boxsdk/auth/remote_managed_oauth2.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Callable, Any, Tuple - -from .oauth2 import OAuth2 - - -class RemoteOAuth2Mixin(OAuth2): - """ - Box SDK OAuth2 mixin. - Allows for storing auth tokens remotely. - - """ - def __init__(self, retrieve_access_token: Callable[[str], str] = None, *args: Any, **kwargs: Any): - """ - :param retrieve_access_token: - Callback to exchange an existing access token for a new one. - """ - # pylint:disable=keyword-arg-before-vararg - self._retrieve_access_token = retrieve_access_token - super().__init__(*args, **kwargs) - - def _refresh(self, access_token: str) -> Tuple[str, None]: - """ - Base class override. Ask the remote host for a new token. - """ - self._access_token = self._retrieve_access_token(access_token) - return self._access_token, None - - -class RemoteOAuth2(RemoteOAuth2Mixin): - """ - Box SDK OAuth2 subclass. - Allows for storing auth tokens remotely. The retrieve_access_token callback should - return an access token, presumably acquired from a remote server on which your auth credentials are available. - """ diff --git a/boxsdk/auth/server_auth.py b/boxsdk/auth/server_auth.py deleted file mode 100644 index 59e46e3a7..000000000 --- a/boxsdk/auth/server_auth.py +++ /dev/null @@ -1,212 +0,0 @@ -import time -from abc import ABC, abstractmethod -from datetime import datetime -from typing import Optional, Tuple, TYPE_CHECKING, Union, Any - -from boxsdk.auth.oauth2 import OAuth2 -from boxsdk.exception import BoxOAuthException -from boxsdk.config import API -from boxsdk.object.user import User - -if TYPE_CHECKING: - from boxsdk.network.network_interface import NetworkResponse - - -class ServerAuth(ABC, OAuth2): - USER_SUBJECT_TYPE = 'user' - ENTERPRISE_SUBJECT_TYPE = 'enterprise' - - def __init__( - self, - client_id: str, - client_secret: str, - enterprise_id: Optional[str] = None, - user: Optional[Union[str, 'User']] = None, - **kwargs: Any - ): - super().__init__(client_id=client_id, client_secret=client_secret, **kwargs) - self._enterprise_id = enterprise_id - self._user_id = self._normalize_user_id(user) - - def _refresh(self, access_token: str) -> Tuple[str, None]: - """ - Base class override. - - Instead of refreshing an access token using a refresh token, we just issue a new JWT request. - """ - # pylint:disable=unused-argument - if self._user_id is None: - new_access_token = self.authenticate_instance() - else: - new_access_token = self.authenticate_user() - return new_access_token, None - - def authenticate_user(self, user: Union[str, 'User'] = None) -> str: - """ - Get an access token for a User. - - May be one of this application's created App User. Depending on the - configured User Access Level, may also be any other App User or Managed - User in the enterprise. - - - - - :param user: - (optional) The user to authenticate, expressed as a Box User ID or - as a :class:`User` instance. - - If not given, then the most recently provided user ID, if - available, will be used. - :raises: - :exc:`ValueError` if no user ID was passed and the object is not - currently configured with one. - :return: - The access token for the user. - """ - sub = self._normalize_user_id(user) or self._user_id - if not sub: - raise ValueError("authenticate_user: Requires the user ID, but it was not provided.") - self._user_id = sub - return self._authenticate(sub, self.USER_SUBJECT_TYPE) - - authenticate_app_user = authenticate_user - - def authenticate_instance(self, enterprise: Optional[str] = None) -> str: - """ - Get an access token for a Box Developer Edition enterprise. - - :param enterprise: - The ID of the Box Developer Edition enterprise. - - Optional if the value was already given to `__init__`, - otherwise required. - :raises: - :exc:`ValueError` if `None` was passed for the enterprise ID here - and in `__init__`, or if the non-`None` value passed here does not - match the non-`None` value passed to `__init__`. - :return: - The access token for the enterprise which can provision/deprovision app users. - """ - enterprises = [enterprise, self._enterprise_id] - if not any(enterprises): - raise ValueError("authenticate_instance: Requires the enterprise ID, but it was not provided.") - if all(enterprises) and (enterprise != self._enterprise_id): - raise ValueError( - f"authenticate_instance: Given enterprise ID {enterprise!r}, " - f"but {self} already has ID {self._enterprise_id!r}" - ) - if not self._enterprise_id: - self._enterprise_id = enterprise - self._user_id = None - return self._authenticate(self._enterprise_id, self.ENTERPRISE_SUBJECT_TYPE) - - def _authenticate(self, subject_id: str, subject_type: str) -> str: - """ - Authenticate with server type authentication (JWT or CCG). - If authorization fails because the expiration time is out of sync with the Box servers, - retry using the time returned in the error response. - Pass an enterprise ID to get an enterprise token (which can be used to provision/deprovision users), - or a user ID to get a user token. - - :param subject_id: - The enterprise ID or user ID to auth. - :param subject_type: - Either 'enterprise' or 'user' - :return: - The access token for the enterprise or app user. - """ - attempt_number = 0 - date = None - while True: - try: - return self._fetch_access_token(subject_id, subject_type, date) - except BoxOAuthException as ex: - network_response = ex.network_response - code = network_response.status_code # pylint: disable=maybe-no-member - box_datetime = self._get_date_header(network_response) - - if attempt_number >= API.MAX_RETRY_ATTEMPTS: - raise ex - - if code == 429 or code >= 500: - date = None - elif box_datetime is not None and self._is_auth_error_retryable(network_response): - date = box_datetime - else: - raise ex - - time_delay = self._session.get_retry_after_time( - attempt_number, - network_response.headers.get('Retry-After', None) - ) - time.sleep(time_delay) - attempt_number += 1 - self._logger.debug('Retrying authentication request') - - @abstractmethod - def _fetch_access_token(self, subject_id: str, subject_type: str, now_time: Optional[datetime] = None) -> str: - pass - - @staticmethod - def _get_date_header(network_response: 'NetworkResponse') -> Optional[datetime]: - """ - Get datetime object for Date header, if the Date header is available. - - :param network_response: - The response from the Box API that should include a Date header. - :return: - The datetime parsed from the Date header, or None if the header is absent or if it couldn't be parsed. - """ - box_date_header = network_response.headers.get('Date', None) - if box_date_header is not None: - try: - return datetime.strptime(box_date_header, '%a, %d %b %Y %H:%M:%S %Z') - except ValueError: - pass - return None - - @staticmethod - def _is_auth_error_retryable(network_response: 'NetworkResponse') -> bool: - """ - Determine whether the network response indicates that the authorization request was rejected because of - the exp or jti claim and can be retried. Exp claim error can happen if the current system time is too - different from the Box server time. If got an error: "A unique 'jti' value is required", - we also retry auth in order to use new 'jti' claim. - - Returns True if the status code is 400, the error code is invalid_grant, and the error description indicates - a problem with the exp or jti claim; False, otherwise. - - :param network_response: - The response from the Box API that should include a Date header. - """ - status_code = network_response.status_code - try: - json_response = network_response.json() - except ValueError: - return False - error_code = json_response.get('error', '') - error_description = json_response.get('error_description', '') - return status_code == 400 and error_code == 'invalid_grant' \ - and ('exp' in error_description or 'jti' in error_description) - - @classmethod - def _normalize_user_id(cls, user: Any) -> Optional[str]: - """Get a Box user ID from a selection of supported param types. - - :param user: - An object representing the user or user ID. - - Currently supported types are `unicode` (which represents the user - ID) and :class:`User`. - - If `None`, returns `None`. - :raises: :exc:`TypeError` for unsupported types. - """ - if user is None: - return None - if isinstance(user, User): - return user.object_id - if isinstance(user, str): - return str(user) - raise TypeError(f"Got unsupported type {user.__class__.__name__!r} for user.") diff --git a/boxsdk/client/__init__.py b/boxsdk/client/__init__.py deleted file mode 100644 index 43e33c306..000000000 --- a/boxsdk/client/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .client import Client -from .developer_token_client import DeveloperTokenClient -from .development_client import DevelopmentClient -from .logging_client import LoggingClient diff --git a/boxsdk/client/client.py b/boxsdk/client/client.py deleted file mode 100644 index e6b12049a..000000000 --- a/boxsdk/client/client.py +++ /dev/null @@ -1,1904 +0,0 @@ -# pylint: disable=too-many-lines -import json -from datetime import datetime -from typing import TYPE_CHECKING, Optional, Iterable, Union, Any, IO -from warnings import warn - -from ..auth.oauth2 import TokenResponse -from ..session.session import Session, AuthorizedSession -from ..object.cloneable import Cloneable -from ..util.api_call_decorator import api_call -from ..object.search import Search -from ..object.events import Events -from ..object.collaboration_allowlist import CollaborationAllowlist -from ..object.trash import Trash -from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection -from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection -from ..util.datetime_formatter import normalize_date_to_rfc3339_format -from ..util.shared_link import get_shared_link_header -from ..util.deprecation_decorator import deprecated - -if TYPE_CHECKING: - from boxsdk import OAuth2 - from boxsdk.util.translator import Translator - from boxsdk.object.folder import Folder - from boxsdk.object.file import File - from boxsdk.object.file_request import FileRequest - from boxsdk.object.file_version import FileVersion - from boxsdk.object.upload_session import UploadSession - from boxsdk.object.comment import Comment - from boxsdk.object.legal_hold import LegalHold - from boxsdk.object.legal_hold_policy_assignment import LegalHoldPolicyAssignment - from boxsdk.object.legal_hold_policy import LegalHoldPolicy - from boxsdk.object.collaboration_allowlist_exempt_target import CollaborationAllowlistExemptTarget - from boxsdk.object.collaboration_allowlist_entry import CollaborationAllowlistEntry - from boxsdk.object.collaboration import Collaboration - from boxsdk.object.group import Group - from boxsdk.object.email_alias import EmailAlias - from boxsdk.object.invite import Invite - from boxsdk.object.user import User - from boxsdk.object.sign_request import SignRequest - from boxsdk.object.folder_lock import FolderLock - from boxsdk.object.metadata_template import MetadataTemplate, MetadataField - from boxsdk.object.metadata_cascade_policy import MetadataCascadePolicy - from boxsdk.object.device_pinner import DevicePinner - from boxsdk.auth.oauth2 import TokenScope - from boxsdk.session.box_response import BoxResponse - from boxsdk.object.item import Item - from boxsdk.object.web_link import WebLink - from boxsdk.object.retention_policy_assignment import RetentionPolicyAssignment - from boxsdk.object.file_version_retention import FileVersionRetention - from boxsdk.object.retention_policy import RetentionPolicy - from boxsdk.object.task_assignment import TaskAssignment - from boxsdk.object.task import Task - from boxsdk.object.terms_of_service_user_status import TermsOfServiceUserStatus - from boxsdk.object.terms_of_service import TermsOfService, TermsOfServiceType, TermsOfServiceStatus - from boxsdk.object.storage_policy_assignment import StoragePolicyAssignment - from boxsdk.object.storage_policy import StoragePolicy - from boxsdk.object.webhook import Webhook - from boxsdk.object.group_membership import GroupMembership - from boxsdk.object.enterprise import Enterprise - from boxsdk.object.collection import Collection - from boxsdk.object.sign_template import SignTemplate - from boxsdk.pagination.box_object_collection import BoxObjectCollection - - -class Client(Cloneable): - unauthorized_session_class = Session - authorized_session_class = AuthorizedSession - - def __init__(self, oauth: 'OAuth2', session: Session = None): - """ - :param oauth: - OAuth2 object used by the session to authorize requests. - :param session: - The session object to use. If None is provided then an instance of :class:`AuthorizedSession` will be used. - """ - super().__init__() - warn('Package \'boxsdk\' is going to be deprecated soon. Please use \'box-sdk-gen\' instead.', DeprecationWarning) - self._oauth = oauth - if session is not None: - self._session = session - else: - session = session or self.unauthorized_session_class() - self._session = self.authorized_session_class(self._oauth, **session.get_constructor_kwargs()) - - @property - def auth(self) -> 'OAuth2': - """ - Get the :class:`OAuth2` instance the client is using for auth to Box. - """ - return self._oauth - - @property - def session(self) -> Session: - """ - Get the :class:`BoxSession` instance the client is using. - """ - return self._session - - @property - def translator(self) -> 'Translator': - """The translator used for translating Box API JSON responses into `BaseAPIJSONObject` smart objects. - """ - return self._session.translator - - def folder(self, folder_id: str) -> 'Folder': - """ - Initialize a :class:`Folder` object, whose box id is folder_id. - - :param folder_id: - The box id of the :class:`Folder` object. Can use '0' to get the root folder on Box. - :return: - A :class:`Folder` object with the given folder id. - """ - return self.translator.get('folder')(session=self._session, object_id=folder_id) - - def root_folder(self) -> 'Folder': - """ - Returns a user's root folder object. - """ - return self.folder('0') - - def file(self, file_id: str) -> 'File': - """ - Initialize a :class:`File` object, whose box id is file_id. - - :param file_id: - The box id of the :class:`File` object. - :return: - A :class:`File` object with the given file id. - """ - return self.translator.get('file')(session=self._session, object_id=file_id) - - def file_request(self, request_id: str) -> 'FileRequest': - """ - Initialize a :class:`FileRequest` object, whose box id is request_id. - - :param request_id: - The box id of the :class:`FileRequest` object. - :return: - A :class:`FileRequest` object with the given file request id. - """ - return self.translator.get('file_request')(session=self._session, object_id=request_id) - - def file_version(self, version_id: str) -> 'FileVersion': - """ - Initialize a :class:`FileVersion` object, whose box id is version_id. - - :param version_id: - The box id of the :class:`FileVersion` object. - :return: - A :class:`FileVersion` object with the given file version id. - """ - return self.translator.get('file_version')(session=self._session, object_id=version_id) - - def upload_session(self, session_id: str) -> 'UploadSession': - """ - Initialize a :class:`UploadSession` object, whose box id is session_id. - - :param session_id: - The box id of the :class:`UploadSession` object. - :return: - A :class:`UploadSession` object with the given session id. - """ - return self.translator.get('upload_session')(session=self._session, object_id=session_id) - - def comment(self, comment_id: str) -> 'Comment': - """ - Initialize a :class:`Comment` object, whose Box ID is comment_id. - - :param comment_id: - The Box ID of the :class:`Comment` object. - :return: - A :class:`Comment` object with the given comment ID. - """ - return self.translator.get('comment')(session=self._session, object_id=comment_id) - - def user(self, user_id: str = 'me') -> 'User': - """ - Initialize a :class:`User` object, whose box id is user_id. - - :param user_id: - The user id of the :class:`User` object. Can use 'me' to get the User for the current/authenticated user. - :return: - A :class:`User` object with the given id. - """ - return self.translator.get('user')(session=self._session, object_id=user_id) - - def invite(self, invite_id: str) -> 'Invite': - """ - Initialize a :class:`Invite` object, whose box id is invite_id. - - :param invite_id: - The invite ID of the :class:`Invite` object. - :return: - A :class:`Invite` object with the given entry ID. - """ - return self.translator.get('invite')(session=self._session, object_id=invite_id) - - def email_alias(self, alias_id: str) -> 'EmailAlias': - """ - Initialize a :class: `EmailAlias` object, whose box id is alias_id. - - :param alias_id: - The aliad id of the :class:`EmailAlias` object. - :return: - A :class:`EmailAlias` object with the given entry ID. - """ - return self.translator.get('email_alias')(session=self._session, object_id=alias_id) - - def group(self, group_id: str) -> 'Group': - """ - Initialize a :class:`Group` object, whose box id is group_id. - - :param group_id: - The box id of the :class:`Group` object. - :return: - A :class:`Group` object with the given group id. - """ - return self.translator.get('group')(session=self._session, object_id=group_id) - - def collaboration(self, collab_id: str) -> 'Collaboration': - """ - Initialize a :class:`Collaboration` object, whose box id is collab_id. - - :param collab_id: - The box id of the :class:`Collaboration` object. - :return: - A :class:`Collaboration` object with the given group id. - """ - return self.translator.get('collaboration')(session=self._session, object_id=collab_id) - - def collaboration_allowlist(self): - """ - Initilializes a :class:`CollaborationAllowlist` object. - - :return: - A :class:`CollaborationAllowlist` object. - """ - return CollaborationAllowlist(self._session) - - def collaboration_allowlist_entry(self, entry_id: str) -> 'CollaborationAllowlistEntry': - """ - Initialize a :class:`CollaborationAllowlistEntry` object, whose box id is entry_id. - - :param entry_id: - The box id of the :class:`CollaborationAllowlistEntry` object. - :return: - A :class:`CollaborationAllowlistEntry` object with the given entry id. - """ - return self.translator.get('collaboration_whitelist_entry')(session=self._session, object_id=entry_id) - - def collaboration_allowlist_exempt_target(self, exemption_id: str) -> 'CollaborationAllowlistExemptTarget': - """ - Initialize a :class:`CollaborationAllowlistExemptTarget` object, whose box id is target_id. - - :param exemption_id: - The box id of the :class:`CollaborationAllowlistExemptTarget` object. - :return: - A :class:`CollaborationAllowlistExemptTarget` object with the given target id. - """ - return self.translator.get('collaboration_whitelist_exempt_target')( - session=self._session, - object_id=exemption_id - ) - - def trash(self) -> Trash: - """ - Initialize a :class:`Trash` object. - - :return: - A :class:`Trash` object. - """ - return Trash(self._session) - - def legal_hold_policy(self, policy_id: str) -> 'LegalHoldPolicy': - """ - Initialize a :class:`LegalHoldPolicy` object, whose box id is policy_id. - - :param policy_id: - The box ID of the :class:`LegalHoldPolicy` object. - :return: - A :class:`LegalHoldPolicy` object with the given entry ID. - """ - return self.translator.get('legal_hold_policy')(session=self._session, object_id=policy_id) - - def legal_hold_policy_assignment(self, policy_assignment_id: str) -> 'LegalHoldPolicyAssignment': - """ - Initialize a :class:`LegalHoldPolicyAssignment` object, whose box id is policy_assignment_id. - - :param policy_assignment_id: - The assignment ID of the :class:`LegalHoldPolicyAssignment` object. - :return: - A :class:`LegalHoldPolicyAssignment` object with the given entry ID. - """ - return self.translator.get('legal_hold_policy_assignment')(session=self._session, object_id=policy_assignment_id) - - def legal_hold(self, hold_id: str) -> 'LegalHold': - """ - Initialize a :class:`LegalHold` object, whose box id is policy_id. - - :param hold_id: - The legal hold ID of the :class:`LegalHold` object. - :return: - A :class:`LegalHold` object with the given entry ID. - """ - return self.translator.get('legal_hold')(session=self._session, object_id=hold_id) - - @api_call - def create_legal_hold_policy( - self, - policy_name: str, - description: Optional[str] = None, - filter_started_at: Union[datetime, str] = None, - filter_ended_at: Union[datetime, str] = None, - is_ongoing: Optional[bool] = None - ) -> 'LegalHoldPolicy': - """ - Create a legal hold policy. - - :param policy_name: - The legal hold policy's display name. - :param description: - The description of the legal hold policy. - :param filter_started_at: - The start date filter for legal hold policy. Takes a datetime string supported by the dateutil library - or a datetime.datetime object. If no timezone info provided, local timezone will be applied. - :param filter_ended_at: - The end date filter for legal hold policy. Takes a datetime string supported by the dateutil library - or a datetime.datetime object. If no timezone info provided, local timezone will be applied. - :param is_ongoing: - After initialization, Assignments under this Policy will continue applying to - files based on events, indefinitely. - :returns: - A legal hold policy object - """ - url = self.get_url('legal_hold_policies') - policy_attributes = {'policy_name': policy_name} - if description is not None: - policy_attributes['description'] = description - if filter_started_at is not None: - policy_attributes['filter_started_at'] = normalize_date_to_rfc3339_format(filter_started_at) - if filter_ended_at is not None: - policy_attributes['filter_ended_at'] = normalize_date_to_rfc3339_format(filter_ended_at) - if is_ongoing is not None: - policy_attributes['is_ongoing'] = is_ongoing - box_response = self._session.post(url, data=json.dumps(policy_attributes)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def get_legal_hold_policies( - self, - policy_name: Optional[str] = None, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the entries in the legal hold policy using limit-offset paging. - - :param policy_name: - The name of the legal hold policy case insensitive to search for - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from. - :param fields: - List of fields to request. - :returns: - An iterator of the entries in the legal hold policy - """ - additional_params = {} - if policy_name is not None: - additional_params['policy_name'] = policy_name - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('legal_hold_policies'), - additional_params=additional_params, - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - def collection(self, collection_id: str) -> 'Collection': - """ - Initialize a :class:`Collection` object, whose box ID is collection_id. - - :param collection_id: - The box id of the :class:`Collection` object. - :return: - A :class:`Collection` object with the given collection ID. - """ - return self.translator.get('collection')(session=self._session, object_id=collection_id) - - @api_call - def collections( - self, - limit: Optional[int] = None, - offset: int = 0, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get a list of collections for the current user. - - :param limit: - The maximum number of users to return. If not specified, the Box API will determine an appropriate limit. - :param offset: - The user index at which to start the response. - :param fields: - List of fields to request. - """ - return LimitOffsetBasedObjectCollection( - self.session, - self._session.get_url('collections'), - limit=limit, - fields=fields, - offset=offset, - return_full_pages=False, - ) - - def enterprise(self, enterprise_id: str) -> 'Enterprise': - """ - Initialize a :class:`Enterprise` object, whose box ID is enterprise_id. - - :param enterprise_id: - The box id of the :class:`Enterprise` object. - :return: - A :class:`Enterprise` object with the given enterprise ID. - """ - return self.translator.get('enterprise')(session=self._session, object_id=enterprise_id) - - @api_call - def get_current_enterprise(self) -> 'Enterprise': - """ - Get the enterprise of the current user. - - :returns: - The authenticated user's enterprise - """ - user = self.user().get(fields=['enterprise']) - enterprise_object = user['enterprise'] - return self.translator.translate( - session=self._session, - response_object=enterprise_object, - ) - - @api_call - def users( - self, - limit: Optional[int] = None, - offset: int = 0, - filter_term: Optional[str] = None, - user_type: Optional[str] = None, - fields: Iterable[str] = None, - use_marker: bool = False, - marker: Optional[str] = None - ) -> Iterable['User']: - """ - Get a list of all users for the Enterprise along with their user_id, public_name, and login. - - :param limit: - The maximum number of users to return. If not specified, the Box API will determine an appropriate limit. - :param offset: - The user index at which to start the response. - :param filter_term: - Filters the results to only users starting with the filter_term in either the name or the login. - :param user_type: - Filters the results to only users of the given type: 'managed', 'external', or 'all'. - :param fields: - List of fields to request on the :class:`User` objects. - :param use_marker: - Whether to use marker-based paging instead of offset-based paging, defaults to False. - :param marker: - The paging marker to start returning items from when using marker-based paging. - :return: - The list of all users in the enterprise. - """ - url = self.get_url('users') - additional_params = {} - if filter_term: - additional_params['filter_term'] = filter_term - if user_type: - additional_params['user_type'] = user_type - - if use_marker: - additional_params['usemarker'] = True - return MarkerBasedObjectCollection( - url=url, - session=self._session, - limit=limit, - marker=marker, - fields=fields, - additional_params=additional_params, - return_full_pages=False, - ) - return LimitOffsetBasedObjectCollection( - url=url, - session=self._session, - additional_params=additional_params, - limit=limit, - offset=offset, - fields=fields, - return_full_pages=False, - ) - - @api_call - def search(self) -> Search: - """ - Get a Search object that can be used for searching Box content. - - :return: - The Search object - """ - return Search(self._session) - - def events(self) -> Events: - """ - Get an events object that can get the latest events from Box or set up a long polling event subscription. - """ - return Events(self._session) - - def group_membership(self, group_membership_id: str) -> 'GroupMembership': - """ - Initialize a :class:`GroupMembership` object, whose box id is group_membership_id. - - :param group_membership_id: - The box id of the :class:`GroupMembership` object. - :return: - A :class:`GroupMembership` object with the given membership id. - """ - return self.translator.get('group_membership')( - session=self._session, - object_id=group_membership_id, - ) - - @api_call - def get_groups( - self, - name: Optional[str] = None, - limit: Optional[int] = None, - offset: Optional[int] = None, - fields: Iterable[str] = None - ) -> Iterable['Group']: - """ - Get a list of all groups for the current user. - - :param name: - Filter on the name of the groups to return. - :param limit: - The maximum number of groups to return. If not specified, the Box API will determine an appropriate limit. - :param offset: - The group index at which to start the response. - :param fields: - List of fields to request on the :class:`Group` objects. - :return: - The collection of all groups. - """ - url = self.get_url('groups') - additional_params = {} - if name: - additional_params['filter_term'] = name - return LimitOffsetBasedObjectCollection( - url=url, - session=self._session, - additional_params=additional_params, - limit=limit, - offset=offset, - fields=fields, - return_full_pages=False, - ) - - def webhook(self, webhook_id: str) -> 'Webhook': - """ - Initialize a :class:`Webhook` object, whose box id is webhook_id. - - :param webhook_id: - The box ID of the :class: `Webhook` object. - :return: - A :class:`Webhook` object with the given entry ID. - """ - return self.translator.get('webhook')(session=self._session, object_id=webhook_id) - - @api_call - def create_webhook(self, target: Union['File', 'Folder'], triggers: Union[list, str], address: str) -> 'Webhook': - """ - Create a webhook on the given file. - - :param target: - Either a :class:`File` or :class:`Folder` to assign a webhook to. - :param triggers: - Event types that trigger notifications for the target. - :param address: - The url to send the notification to. - :return: - A :class:`Webhook` object with the given entry ID. - """ - url = self.get_url('webhooks') - webhook_attributes = { - 'target': { - 'type': target.object_type, - 'id': target.object_id, - }, - 'triggers': triggers, - 'address': address, - } - box_response = self._session.post(url, data=json.dumps(webhook_attributes)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def get_webhooks( - self, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get all webhooks in an enterprise. - - :param limit: - The maximum number of entries to return. - :param marker: - The position marker at which to begin the response. - :param fields: - List of fields to request on the file or folder which the `RecentItem` references. - :returns: - An iterator of the entries in the webhook - """ - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('webhooks'), - limit=limit, - marker=marker, - fields=fields, - ) - - @api_call - def create_group( - self, - name: str, - provenance: Optional[str] = None, - external_sync_identifier: Optional[str] = None, - description: Optional[str] = None, - invitability_level: str = None, - member_viewability_level: str = None, - fields: Iterable[str] = None, - ) -> 'Group': - """ - Create a group with the given name. - - :param name: - The name of the group. - :param provenance: - Used to track the external source where the group is coming from. - :param external_sync_identifier: - Used as a group identifier for groups coming from an external source. - :param description: - Description of the group. - :param invitability_level: - Specifies who can invite this group to folders. - :param member_viewability_level: - Specifies who can view the members of this group. - :param fields: - List of fields to request on the :class:`Group` objects. - :return: - The newly created Group. - :raises: - :class:`BoxAPIException` if current user doesn't have permissions to create a group. - """ - url = self.get_url('groups') - additional_params = {} - body_attributes = { - 'name': name, - } - if provenance is not None: - body_attributes['provenance'] = provenance - if external_sync_identifier is not None: - body_attributes['external_sync_identifier'] = external_sync_identifier - if description is not None: - body_attributes['description'] = description - if invitability_level is not None: - body_attributes['invitability_level'] = invitability_level - if member_viewability_level is not None: - body_attributes['member_viewability_level'] = member_viewability_level - if fields is not None: - additional_params['fields'] = ','.join(fields) - box_response = self._session.post(url, data=json.dumps(body_attributes), params=additional_params) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - def storage_policy(self, policy_id: str) -> 'StoragePolicy': - """ - Initialize a :class:`StoragePolicy` object, whose box id is policy_id. - - :param policy_id: - The box ID of the :class:`StoragePolicy` object. - :return: - A :class:`StoragePolicy` object with the given entry ID. - """ - return self.translator.get('storage_policy')(session=self._session, object_id=policy_id) - - def storage_policy_assignment(self, assignment_id: str) -> 'StoragePolicyAssignment': - """ - Initialize a :class:`StoragePolicyAssignment` object, whose box id is assignment_id. - - :param assignment_id: - The box ID of the :class:`StoragePolicyAssignment` object. - :return: - A :class:`StoragePolicyAssignment` object with the given entry ID. - """ - return self.translator.get('storage_policy_assignment')(session=self._session, object_id=assignment_id) - - def get_storage_policies( - self, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the entries in the storage policy using marker-based paging. - - :param limit: - The maximum number of items to return. - :param marker: - The paging marker to start returning items from when using marker-based paging. - :param fields: - List of fields to request. - :returns: - Returns the storage policies available for the current enterprise. - """ - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('storage_policies'), - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - def terms_of_service(self, tos_id: str) -> 'TermsOfService': - """ - Initialize a :class:`TermsOfService` object, whose box id is tos_id. - - :param tos_id: - The box id of the :class:`TermsOfService` object. - :return: - A :class:`TermsOfService` object with the given terms of service id. - """ - return self.translator.get('terms_of_service')(session=self._session, object_id=tos_id) - - def terms_of_service_user_status(self, tos_user_status_id: str) -> 'TermsOfServiceUserStatus': - """ - Initialize a :class:`TermsOfServiceUserStatus` object, whose box id is tos_user_status_id. - - :param tos_user_status_id: - The box id of the :class:`TermsOfServiceUserStatus` object. - :return: - A :class:`TermsOfServiceUserStatus` object with the given terms of service user status id. - """ - return self.translator.get('terms_of_service_user_status')(session=self._session, object_id=tos_user_status_id) - - def get_terms_of_services( - self, - tos_type: 'TermsOfServiceType' = None, - limit: Optional[int] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the entries in the terms of service using limit-offset paging. - :param tos_type: - Can be set to `managed` or `external` for the type of terms of service. - :param limit: - The maximum number of items to return. If limit is set to None, then the default - limit (returned by Box in the response) is used. - :param fields: - List of fields to request - :returns: - An iterator of the entries in the terms of service - """ - additional_params = {} - if tos_type is not None: - additional_params['tos_type'] = tos_type - return MarkerBasedObjectCollection( - session=self._session, - url=self._session.get_url('terms_of_services'), - additional_params=additional_params, - limit=limit, - marker=None, - fields=fields, - return_full_pages=False, - ) - - def task(self, task_id: str) -> 'Task': - """ - Initialize a :class:`Task` object, whose box id is task_id. - - :param task_id: - The box ID of the :class:`Task` object. - :return: - A :class:`Task` object with the given entry ID. - """ - return self.translator.get('task')(session=self._session, object_id=task_id) - - def task_assignment(self, assignment_id: str) -> 'TaskAssignment': - """ - Initialize a :class:`TaskAssignment` object, whose box id is assignment_id. - - :param assignment_id: - The box ID of the :class:`TaskAssignment` object. - :return: - A :class:`TaskAssignment` object with the given entry ID. - """ - return self.translator.get('task_assignment')(session=self._session, object_id=assignment_id) - - def retention_policy(self, retention_id: str) -> 'RetentionPolicy': - """ - Initialize a :class:`RetentionPolicy` object, whose box id is retention_id. - - :param retention_id: - The box ID of the :class:`RetentionPolicy` object. - :return: - A :class:`RetentionPolicy` object with the given entry ID. - """ - return self.translator.get('retention_policy')(session=self._session, object_id=retention_id) - - def file_version_retention(self, retention_id: str) -> 'FileVersionRetention': - """ - Initialize a :class:`FileVersionRetention` object, whose box id is retention_id. - - :param retention_id: - The box ID of the :class:`FileVersionRetention` object. - :return: - A :class:`FileVersionRetention` object with the given retention ID. - """ - return self.translator.get('file_version_retention')(session=self._session, object_id=retention_id) - - def retention_policy_assignment(self, assignment_id: str) -> 'RetentionPolicyAssignment': - """ - Initialize a :class:`RetentionPolicyAssignment` object, whose box id is assignment_id. - - :param assignment_id: - The box ID of the :class:`RetentionPolicyAssignment` object. - :return: - A :class:`RetentionPolicyAssignment` object with the given assignment ID. - """ - return self.translator.get('retention_policy_assignment')(session=self._session, object_id=assignment_id) - - @api_call - def create_retention_policy( - self, - policy_name: str, - disposition_action: str, - retention_length: Union[int, float], - can_owner_extend_retention: Optional[bool] = None, - are_owners_notified: Optional[bool] = None, - custom_notification_recipients: Iterable['User'] = None, - retention_type: Optional[str] = None, - description: Optional[str] = None, - ) -> 'RetentionPolicy': - """ - Create a retention policy for the given enterprise. - - :param policy_name: - The name of the retention policy. - :param disposition_action: - For `finite` policy can be set to `permanently delete` or `remove retention`. - For `indefinite` policy this must be set to `remove_retention` - :param retention_length: - The amount of time in days to apply the retention policy to the selected content. - The retention_length should be set to float('inf') for indefinite policies. - :param can_owner_extend_retention: - The owner of a file will be allowed to extend the retention if set to true. - :param are_owners_notified: - The owner or co-owner will get notified when a file is nearing expiration. - :param custom_notification_recipients: - A custom list of user mini objects that should be notified when a file is nearing expiration. - :param retention_type: - Specifies the retention type. It can be one of the values: - - `modifiable`: You can modify the retention policy. For example, you can add or remove folders, - shorten or lengthen the policy duration, or delete the assignment. - - `non_modifiable`: You can modify the retention policy only in a limited way: add a folder, - lengthen the duration, retire the policy, change the disposition action or notification settings. - You cannot perform other actions, such as deleting the assignment or shortening the policy duration. - :param description: - The additional text description of the retention policy. - :return: - The newly created Retention Policy - """ - url = self.get_url('retention_policies') - retention_attributes = { - 'policy_name': policy_name, - 'disposition_action': disposition_action, - } - if retention_length == float('inf'): - retention_attributes['policy_type'] = 'indefinite' - else: - retention_attributes['policy_type'] = 'finite' - retention_attributes['retention_length'] = retention_length - if can_owner_extend_retention is not None: - retention_attributes['can_owner_extend_retention'] = can_owner_extend_retention - if are_owners_notified is not None: - retention_attributes['are_owners_notified'] = are_owners_notified - if custom_notification_recipients is not None: - user_list = [{'type': user.object_type, 'id': user.object_id} for user in custom_notification_recipients] - retention_attributes['custom_notification_recipients'] = user_list - if retention_type is not None: - retention_attributes['retention_type'] = retention_type - if description is not None: - retention_attributes['description'] = description - box_response = self._session.post(url, data=json.dumps(retention_attributes)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response - ) - - @api_call - def get_retention_policies( - self, - policy_name: Optional[str] = None, - policy_type: Optional[str] = None, - user: Optional['User'] = None, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None, - ) -> 'BoxObjectCollection': - """ - Get the entries in the retention policy using marker-based paging. - - :param policy_name: - The name of the retention policy. - :param policy_type: - Set to either `finite` or `indefinite` - :param user: - A user to filter the retention policies. - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from - :param fields: - List of fields to request - :returns: - An iterator of the entries in the retention policy - """ - additional_params = {} - if policy_name is not None: - additional_params['policy_name'] = policy_name - if policy_type is not None: - additional_params['policy_type'] = policy_type - if user is not None: - additional_params['created_by_user_id'] = user.object_id - return MarkerBasedObjectCollection( - session=self._session, - url=self._session.get_url('retention_policies'), - additional_params=additional_params, - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - def create_terms_of_service( - self, - status: 'TermsOfServiceStatus', - tos_type: 'TermsOfServiceType', - text: str - ) -> 'TermsOfService': - """ - Create a terms of service. - - :param status: - The status of the terms of service. - :param tos_type: - The type of the terms of service. Can be set to `managed` or `external`. - :param text: - The message of the terms of service. - :returns: - A newly created :class:`TermsOfService` object - """ - url = self.get_url('terms_of_services') - body = { - 'status': status, - 'tos_type': tos_type, - 'text': text - } - box_response = self._session.post(url, data=json.dumps(body)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @deprecated("Use RetentionPolicyAssignment.get_files_under_retention " - "or RetentionPolicyAssignment.get_file_versions_under_retention instead") - @api_call - def get_file_version_retentions( - self, - target_file: Optional['File'] = None, - file_version: Optional['FileVersion'] = None, - policy: Optional['RetentionPolicy'] = None, - disposition_action: Optional[str] = None, - disposition_before: Optional[str] = None, - disposition_after: Optional[str] = None, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None, - ) -> 'BoxObjectCollection': - """ - Get the entries in the file version retention. - - :param target_file: - The file to filter the file version. - :param file_version: - A file version to filter the file version retentions by. - :param policy: - A policy to filter the file version retentions by. - :param disposition_action: - Can be set to `permanently_delete` or `remove_retention`. - :param disposition_before: - A date time filter for disposition action. - :param disposition_after: - A date time filter for disposition action. - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from - :param fields: - List of fields to request - :returns: - An iterator of the entries in the file version retention. - """ - additional_params = {} - if target_file is not None: - additional_params['file_id'] = target_file.object_id - if file_version is not None: - additional_params['file_version_id'] = file_version.object_id - if policy is not None: - additional_params['policy_id'] = policy.object_id - if disposition_action is not None: - additional_params['disposition_action'] = disposition_action - if disposition_before is not None: - additional_params['disposition_before'] = disposition_before - if disposition_after is not None: - additional_params['disposition_after'] = disposition_after - return MarkerBasedObjectCollection( - session=self._session, - url=self._session.get_url('file_version_retentions'), - additional_params=additional_params, - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - def web_link(self, web_link_id: str) -> 'WebLink': - """ - Initialize a :class: `WebLink` object, whose box id is web_link_id. - :param web_link_id: - The box ID of the :class:`WebLink` object. - :return: - A :class:`WebLink` object with the given entry ID. - """ - return self.translator.get('web_link')(session=self._session, object_id=web_link_id) - - @api_call - def get_recent_items( - self, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None, - **collection_kwargs: Any - ) -> MarkerBasedObjectCollection: - """ - Get the user's recently accessed items. - - :param limit: - The maximum number of items to return. If limit is set to None, then the default - limit (returned by Box in the response) is used. See https://developer.box.com/en/reference/get-recent-items/ - for default. - :param marker: - The index at which to start returning items. - :param fields: - List of fields to request on the file or folder which the `RecentItem` references. - :param collection_kwargs: - Keyword arguments passed to `MarkerBasedObjectCollection`. - :returns: - An iterator on the user's recent items - """ - return MarkerBasedObjectCollection( - self.session, - self.get_url('recent_items'), - limit=limit, - fields=fields, - marker=marker, - **collection_kwargs - ) - - @api_call - def get_shared_item(self, shared_link: str, password: str = None) -> 'Item': - """ - Get information about a Box shared link. https://developer.box.com/en/reference/get-shared-items/ - - :param shared_link: - The shared link. - :param password: - The password for the shared link. - :return: - The item referred to by the shared link. - :raises: - :class:`BoxAPIException` if current user doesn't have permissions to view the shared link. - """ - response = self.make_request( - 'GET', - self.get_url('shared_items'), - headers=get_shared_link_header(shared_link, password), - ).json() - return self.translator.translate( - session=self._session.with_shared_link(shared_link, password), - response_object=response, - ) - - @api_call - def make_request(self, method: str, url: str, **kwargs: Any) -> 'BoxResponse': - """ - Make an authenticated request to the Box API. - - :param method: - The HTTP verb to use for the request. - :param url: - The URL for the request. - :return: - The network response for the given request. - :raises: - :class:`BoxAPIException` - """ - return self._session.request(method, url, **kwargs) - - @api_call - def create_user(self, name: str, login: Optional[str] = None, **user_attributes: Any) -> 'User': - """ - Create a new user. Can only be used if the current user is an enterprise admin, or the current authorization - scope is a Box developer edition instance. - - :param name: - The user's display name. - :param login: - The user's email address. Required for an enterprise user, but None for an app user. - :param user_attributes: - Additional attributes for the user. See the documentation at - https://developer.box.com/en/reference/post-users/ - :return - Newly created user - """ - url = self.get_url('users') - user_attributes['name'] = name - if login is not None: - user_attributes['login'] = login - else: - user_attributes['is_platform_access_only'] = True - box_response = self._session.post(url, data=json.dumps(user_attributes)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def get_pending_collaborations( - self, - limit: Optional[int] = None, - offset: Optional[int] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the entries in the pending collaborations using limit-offset paging. - - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param offset: - The offset of the item at which to begin the response. - :param fields: - List of fields to request. - :returns: - An iterator of the entries in the pending collaborations - """ - return LimitOffsetBasedObjectCollection( - session=self._session, - url=self.get_url('collaborations'), - additional_params={'status': 'pending'}, - limit=limit, - offset=offset, - fields=fields, - return_full_pages=False, - ) - - @api_call - def downscope_token( - self, - scopes: Iterable['TokenScope'], - item: 'Item' = None, - additional_data: dict = None, - shared_link: str = None - ) -> TokenResponse: - """ - Generate a downscoped token for the provided file or folder with the provided scopes. - There is no way of refreshing this token. You will need to add you own logic to - generate new token if this one expires. - - :param scopes: - The scope(s) to apply to the resulting token. - :param item: - (Optional) The file or folder to get a downscoped token for. If None and shared_link None, the resulting - token will not be scoped down to just a single item. - :param additional_data: - (Optional) Key value pairs which can be used to add/update the default data values in the request. - :param shared_link: - (Optional) The shared link to get a downscoped token for. If None and item None, the resulting token - will not be scoped down to just a single item. - :return: - The response for the downscope token request. - """ - url = f'{self._session.api_config.OAUTH2_API_URL}/token' - access_token = self.auth.access_token or self.auth.refresh(None) - data = { - 'subject_token': access_token, - 'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token', - 'scope': ' '.join(scopes), - 'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange', - } - - if item: - data['resource'] = item.get_url() - if shared_link: - data['box_shared_link'] = shared_link - if additional_data: - data.update(additional_data) - - box_response = self._session.post(url, data=data) - - return TokenResponse(box_response.json()) - - def clone(self, session: 'Session' = None) -> 'Client': - """Base class override.""" - return self.__class__(oauth=self._oauth, session=(session or self._session)) - - def get_url(self, endpoint: str, *args: Any) -> str: - """ - Return the URL for the given Box API endpoint. - - :param endpoint: - The name of the endpoint. - :param args: - Additional parts of the endpoint URL. - """ - # pylint:disable=no-self-use - return self._session.get_url(endpoint, *args) - - def device_pinner(self, device_pin_id: str) -> 'DevicePinner': - """ - Initialize a :class:`DevicePinner` object, whose box id is device_pin_id. - - :param device_pin_id: - The assignment ID of the :class:`DevicePin` object. - :return: - A :class:`DevicePinner` object with the given entry ID. - """ - return self.translator.get('device_pinner')(session=self._session, object_id=device_pin_id) - - def device_pinners( - self, - enterprise: Optional['Enterprise'] = None, - direction: Optional[str] = None, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Returns all of the device pins for the given enterprise. - - :param enterprise: - The enterprise to retrieve device pinners for, defaulting to the current enterprise. - :param direction: - The sorting direction. Set to `ASC` or `DESC` - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from. - :param fields: - List of fields to request. - :returns: - An iterator of the entries in the device pins. - """ - enterprise_id = enterprise.object_id if enterprise is not None else self.get_current_enterprise().id - additional_params = {} - if direction is not None: - additional_params['direction'] = direction - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('enterprises', enterprise_id, 'device_pinners'), - additional_params=additional_params, - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - def metadata_cascade_policy(self, policy_id: str) -> 'MetadataCascadePolicy': - """ - Initializes a :class:`MetadataCascadePolicy` object with the given policy ID. - - :param policy_id: - The ID of the cascade policy object - :returns: - The cascade policy object - """ - return self.translator.get('metadata_cascade_policy')( - session=self._session, - object_id=policy_id, - ) - - def metadata_template(self, scope: str, template_key: str) -> 'MetadataTemplate': - """ - Initialize a :class:`MetadataTemplate` object with the given scope and template key. - - :param scope: - The scope of the metadata template, e.g. 'enterprise' or 'global' - :param template_key: - The key of the metadata template - :returns: - The metadata template object - """ - return self.translator.get('metadata_template')( - session=self._session, - object_id=None, - response_object={ - 'type': 'metadata_template', - 'scope': scope, - 'templateKey': template_key, - }, - ) - - def metadata_template_by_id(self, template_id: str) -> 'MetadataTemplate': - """ - Retrieves a metadata template by ID - - :param template_id: - The ID of the template object - :returns: - The metadata template with data populated from the API - """ - return self.translator.get('metadata_template')( - session=self._session, - object_id=template_id, - ) - - @api_call - def get_metadata_templates( - self, - scope: str = 'enterprise', - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get all metadata templates for a given scope. By default, retrieves all metadata templates for the current - enterprise. - - :param scope: - The scope to retrieve templates for - :param limit: - The maximum number of entries to return per page. - :param marker: - The paging marker to start paging from. - :param fields: - List of fields to request. - :returns: - The collection of metadata templates for the given scope - """ - return MarkerBasedObjectCollection( - url=self._session.get_url('metadata_templates', scope), - session=self._session, - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - @api_call - def create_metadata_template( - self, - display_name: str, - fields: Iterable['MetadataField'], - template_key: str = None, - hidden: bool = False, - scope: str = 'enterprise', - copy_instance_on_item_copy: bool = False - ) -> 'MetadataTemplate': - """ - Create a new metadata template. By default, only the display name and fields are required; the template key - will be automatically generated based on the display name and the template will be created in the enterprise - scope. - - :param display_name: - The human-readable name of the template - :param fields: - The metadata fields for the template. - :param template_key: - An optional key for the template. If one is not provided, it will be derived from the display name. - :param hidden: - Whether the template should be hidden in the UI - :param scope: - The scope the template should be created in - :param copy_instance_on_item_copy: - Whether or not to include the metadata when a file or folder is copied. - """ - url = self._session.get_url('metadata_templates', 'schema') - body = { - 'scope': scope, - 'displayName': display_name, - 'hidden': hidden, - 'fields': [field.json() for field in fields], - 'copyInstanceOnItemCopy': copy_instance_on_item_copy - } - - if template_key is not None: - body['templateKey'] = template_key - - response = self._session.post(url, data=json.dumps(body)).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def __create_zip(self, name: str, items: Iterable) -> dict: - """ - Creates a zip file containing multiple files and/or folders for later download. - - :param name: - The name of the zip file to be created. - :param items: - List of files and/or folders to be contained in the zip file. - :returns: - A dictionary representing a created zip - """ - # pylint: disable=protected-access - url = self._session.get_url('zip_downloads') - zip_file_items = [] - for item in items: - zip_file_items.append({'type': item._item_type, 'id': item.object_id}) - data = { - 'download_file_name': name, - 'items': zip_file_items - } - return self._session.post(url, data=json.dumps(data), skip_retry_codes={202}).json() - - @api_call - def download_zip(self, name: str, items: Iterable, writeable_stream: IO) -> dict: - """ - Downloads a zip file containing multiple files and/or folders. - - :param name: - The name of the zip file to be created. - :param items: - List of files or folders to be part of the created zip. - :param writeable_stream: - Stream to pipe the readable stream of the zip file. - :returns: - A status response object - """ - created_zip = self.__create_zip(name, items) - response = self._session.get(created_zip['download_url'], expect_json_response=False, stream=True) - for chunk in response.network_response.response_as_stream.stream(decode_content=True): - writeable_stream.write(chunk) - status = self._session.get(created_zip['status_url']).json() - status.update(created_zip) - return self.translator.translate( - session=self._session, - response_object=status, - ) - - def folder_lock(self, folder_lock_id: str) -> 'FolderLock': - """ - Initialize a :class:`FolderLock` object, whose box id is folder_lock_id. - - :param folder_lock_id: - The ID of the :class:`FolderLock` object. - :return: - A :class:`FolderLock` object with the given entry ID. - """ - return self.translator.get('folder_lock')(session=self._session, object_id=folder_lock_id) - - def sign_request(self, sign_request_id: str) -> 'SignRequest': - """ - Initialize a :class:`SignRequest` object, whose box id is sign_request_id. - - :param sign_request_id: - The box id of the :class:`SignRequest` object. - :return: - A :class:`SignRequest` object with the given file id. - """ - return self.translator.get('sign_request')(session=self._session, object_id=sign_request_id) - - # pylint: disable=too-many-branches - def __create_sign_request( - self, - signers: Iterable, - files: Optional[Iterable] = None, - parent_folder_id: Optional[str] = None, - prefill_tags: Optional[Iterable] = None, - are_reminders_enabled: Optional[bool] = None, - are_text_signatures_enabled: Optional[bool] = None, - days_valid: Optional[str] = None, - email_message: Optional[Iterable] = None, - email_subject: Optional[str] = None, - external_id: Optional[str] = None, - is_document_preparation_needed: Optional[bool] = None, - redirect_url: Optional[str] = None, - declined_redirect_url: Optional[str] = None, - template_id: Optional[str] = None) -> 'SignRequest': - url = self._session.get_url('sign_requests') - - body = { - 'signers': signers, - } - - if files: - body['source_files'] = files - if parent_folder_id: - body['parent_folder'] = { - 'id': parent_folder_id, - 'type': 'folder' - } - if prefill_tags: - body['prefill_tags'] = prefill_tags - if are_reminders_enabled: - body['are_reminders_enabled'] = are_reminders_enabled - if are_text_signatures_enabled: - body['are_text_signatures_enabled'] = are_text_signatures_enabled - if days_valid: - body['days_valid'] = days_valid - if email_message: - body['email_message'] = email_message - if email_subject: - body['email_subject'] = email_subject - if external_id: - body['external_id'] = external_id - if is_document_preparation_needed: - body['is_document_preparation_needed'] = is_document_preparation_needed - if redirect_url: - body['redirect_url'] = redirect_url - if declined_redirect_url: - body['declined_redirect_url'] = declined_redirect_url - if template_id: - body['template_id'] = template_id - - box_response = self._session.post(url, data=json.dumps(body)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - @deprecated('Use create_sign_request_v2 instead') - def create_sign_request( - self, - files: Iterable, - signers: Iterable, - parent_folder_id: str, - prefill_tags: Optional[Iterable] = None, - are_reminders_enabled: Optional[bool] = None, - are_text_signatures_enabled: Optional[bool] = None, - days_valid: Optional[str] = None, - email_message: Optional[Iterable] = None, - email_subject: Optional[str] = None, - external_id: Optional[str] = None, - is_document_preparation_needed: Optional[bool] = None, - redirect_url: Optional[str] = None, - declined_redirect_url: Optional[str] = None, - template_id: Optional[str] = None, - ) -> 'SignRequest': - """ - Used to create a new sign request. - - :param files: - List of files to create a signing document from. - :param signers: - List of signers for the sign request. 35 is the max number of signers permitted. - :param parent_folder_id: - The id of the destination folder to place sign request specific data in. - :param prefill_tags: - When a document contains sign related tags in the content, - you can prefill them using this prefill_tags by referencing the 'id' of the tag as the external_id field of the prefill tag. - :param are_reminders_enabled: - Reminds signers to sign a document on day 3, 8, 13 and 18. Reminders are only sent to outstanding signers. - :param are_text_signatures_enabled: - Disables the usage of signatures generated by typing (text). - :param days_valid: - Number of days after which this request will automatically expire if not completed. - :param email_message: - Message to include in sign request email. The field is cleaned through sanitization of specific characters. - However, some html tags are allowed. Links included in the message are also converted to hyperlinks in the email. - The message may contain the following html tags including a, abbr, acronym, b, blockquote, code, em, i, ul, li, ol, and strong. - Be aware that when the text to html ratio is too high, the email may end up in spam filters. Custom styles on these tags are not allowed. - If this field is not passed, a default message will be used. - :param email_subject: - Subject of sign request email. This is cleaned by sign request. If this field is not passed, a default subject will be used. - :param external_id: - This can be used to reference an ID in an external system that the sign request is related to. - :param is_document_preparation_needed: - Indicates if the sender should receive a prepare_url in the response to complete document preparation via UI. - :param redirect_url: - The URL that a signer will be redirected to after signing a document. - If no declined redirect URL is specified, this URL will be used for decline actions as well. - :param declined_redirect_url: - The URL that a signer will be redirected to after declining to sign a document. - :param template_id: - The ID of the sign template to use for the sign request. - :returns: - A dictionary representing a created SignRequest - """ - return self.__create_sign_request( - signers, files, parent_folder_id, prefill_tags, are_reminders_enabled, are_text_signatures_enabled, days_valid, email_message, - email_subject, external_id, is_document_preparation_needed, redirect_url, declined_redirect_url, template_id) - - @api_call - def create_sign_request_v2( - self, - signers: Iterable, - files: Optional[Iterable] = None, - parent_folder_id: Optional[str] = None, - prefill_tags: Optional[Iterable] = None, - are_reminders_enabled: Optional[bool] = None, - are_text_signatures_enabled: Optional[bool] = None, - days_valid: Optional[str] = None, - email_message: Optional[Iterable] = None, - email_subject: Optional[str] = None, - external_id: Optional[str] = None, - is_document_preparation_needed: Optional[bool] = None, - redirect_url: Optional[str] = None, - declined_redirect_url: Optional[str] = None, - template_id: Optional[str] = None, - ) -> 'SignRequest': - """ - Used to create a new sign request. - - :param signers: - List of signers for the sign request. 35 is the max number of signers permitted. - :param files: - List of files to create a signing document from. - :param parent_folder_id: - The id of the destination folder to place sign request specific data in. - :param prefill_tags: - When a document contains sign related tags in the content, - you can prefill them using this prefill_tags by referencing the 'id' of the tag as the external_id field of the prefill tag. - :param are_reminders_enabled: - Reminds signers to sign a document on day 3, 8, 13 and 18. Reminders are only sent to outstanding signers. - :param are_text_signatures_enabled: - Disables the usage of signatures generated by typing (text). - :param days_valid: - Number of days after which this request will automatically expire if not completed. - :param email_message: - Message to include in sign request email. The field is cleaned through sanitization of specific characters. - However, some html tags are allowed. Links included in the message are also converted to hyperlinks in the email. - The message may contain the following html tags including a, abbr, acronym, b, blockquote, code, em, i, ul, li, ol, and strong. - Be aware that when the text to html ratio is too high, the email may end up in spam filters. Custom styles on these tags are not allowed. - If this field is not passed, a default message will be used. - :param email_subject: - Subject of sign request email. This is cleaned by sign request. If this field is not passed, a default subject will be used. - :param external_id: - This can be used to reference an ID in an external system that the sign request is related to. - :param is_document_preparation_needed: - Indicates if the sender should receive a prepare_url in the response to complete document preparation via UI. - :param redirect_url: - The URL that a signer will be redirected to after signing a document. - If no declined redirect URL is specified, this URL will be used for decline actions as well. - :param declined_redirect_url: - The URL that a signer will be redirected to after declining to sign a document. - :param template_id: - The ID of the sign template to use for the sign request. - :returns: - A dictionary representing a created SignRequest - """ - return self.__create_sign_request( - signers, files, parent_folder_id, prefill_tags, are_reminders_enabled, are_text_signatures_enabled, days_valid, email_message, - email_subject, external_id, is_document_preparation_needed, redirect_url, declined_redirect_url, template_id) - - @api_call - def get_sign_requests( - self, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Returns all the sign requests. - - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from. - :param fields: - List of fields to request. - :returns: - An iterator of the entries in the device pins. - """ - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('sign_requests'), - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - def sign_template(self, sign_template_id: str) -> 'SignTemplate': - """ - Initialize a :class:`SignTemplate` object, whose box id is sign_template_id. - - :param sign_template_id: - The box id of the :class:`SignTemplate` object. - :return: - A :class:`SignTemplate` object with the given sign_template_id. - """ - return self.translator.get('sign_template')(session=self._session, object_id=sign_template_id) - - @api_call - def get_sign_templates( - self, - limit: Optional[int] = None, - marker: Optional[str] = None, - ) -> 'BoxObjectCollection': - """ - Returns all sign templates - - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from. - :returns: - Sign templates - """ - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url("sign_templates"), - limit=limit, - marker=marker, - return_full_pages=False, - ) - - @api_call - def get_sign_template( - self, - sign_template_id: str, - ) -> Any: - """ - Returns a sign template - - :param sign_template_id: - ID of Sign template to fetch - :returns: - Sign template - """ - response = self._session.get(f"{self._session.get_url('sign_templates')}/{sign_template_id}") - return self.translator.translate( - session=self._session, - response_object=response.json(), - ) - - @api_call - def send_ai_question( - self, - items: Iterable, - prompt: str, - mode: Optional[str] = None, - ai_agent: Optional[dict] = None - ) -> Any: - """ - Sends an AI request to supported LLMs and returns an answer specifically focused on the user's - question given the provided context. - - :param items: - The items to be processed by the LLM, often files. - :param prompt: - The prompt provided by the client to be answered by the LLM. - The prompt's length is limited to 10000 characters. - :param mode: - The mode specifies if this request is for a single or multiple items. - If you select single_item_qa the items array can have one element only. - Selecting multiple_item_qa allows you to provide up to 25 items. - - Value is one of `multiple_item_qa`, `single_item_qa` - :param ai_agent: - The AI agent used to handle queries. - :returns: - A response including the answer from the LLM. - """ - url = self._session.get_url('ai/ask') - if mode is None: - mode = ('single_item_qa' if len(items) == 1 else 'multiple_item_qa') - body = { - 'items': items, - 'prompt': prompt, - 'mode': mode - } - - if ai_agent is not None: - body['ai_agent'] = ai_agent - - box_response = self._session.post(url, data=json.dumps(body)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def send_ai_text_gen( - self, - dialogue_history: Iterable, - items: Iterable, - prompt: str, - ai_agent: Optional[dict] = None - ): - """ - Sends an AI request to supported LLMs and returns an answer specifically focused on the creation of new text. - - :param dialogue_history: - The history of prompts and answers previously passed to the LLM. - This provides additional context to the LLM in generating the response. - :param items: - The items to be processed by the LLM, often files. The array can include exactly one element. - :param prompt: - The prompt provided by the client to be answered by the LLM. - The prompt's length is limited to 10000 characters. - :param ai_agent: - The AI agent used for generating text. - :returns: - A response including the generated text from the LLM. - """ - url = self._session.get_url('ai/text_gen') - body = { - 'dialogue_history': dialogue_history, - 'items': items, - 'prompt': prompt - } - - if ai_agent is not None: - body['ai_agent'] = ai_agent - - box_response = self._session.post(url, data=json.dumps(body)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def get_ai_agent_default_config( - self, - mode: str, - language: Optional[str] = None, - model: Optional[str] = None, - ): - """ - Get the AI agent default configuration. - - :param mode: - The mode to filter the agent config to return. - :param language: - The ISO language code to return the agent config for. - If the language is not supported the default agent configuration is returned. - :param model: - The model to return the default agent config for. - :returns: - A default agent configuration. - This can be one of the following two objects: - AI agent for questions and AI agent for text generation. - The response depends on the agent configuration requested in this endpoint. - """ - url = self._session.get_url('ai_agent_default') - params = {'mode': mode} - if language is not None: - params['language'] = language - if model is not None: - params['model'] = model - - box_response = self._session.get(url, params=params) - return self.translator.translate( - session=self._session, - response_object=box_response.json(), - ) diff --git a/boxsdk/client/developer_token_client.py b/boxsdk/client/developer_token_client.py deleted file mode 100644 index 6d3d5a861..000000000 --- a/boxsdk/client/developer_token_client.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import TYPE_CHECKING - -from ..auth import DeveloperTokenAuth -from .client import Client - -if TYPE_CHECKING: - from boxsdk import OAuth2 - from boxsdk.session.session import Session - - -class DeveloperTokenClient(Client): - """ - Box client subclass which authorizes with a developer token. - """ - def __init__(self, oauth: 'OAuth2' = None, session: 'Session' = None): - super().__init__(oauth=oauth or DeveloperTokenAuth(), session=session) diff --git a/boxsdk/client/development_client.py b/boxsdk/client/development_client.py deleted file mode 100644 index f7f273061..000000000 --- a/boxsdk/client/development_client.py +++ /dev/null @@ -1,9 +0,0 @@ -from .developer_token_client import DeveloperTokenClient -from .logging_client import LoggingClient - - -class DevelopmentClient(LoggingClient, DeveloperTokenClient): - """ - Client subclass that uses developer token auth and logs requests and responses. - Great for use in development! - """ diff --git a/boxsdk/client/logging_client.py b/boxsdk/client/logging_client.py deleted file mode 100644 index bb7859691..000000000 --- a/boxsdk/client/logging_client.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Any - -from .client import Client -from ..util.log import setup_logging - - -class LoggingClient(Client): - """ - Box client subclass which logs requests and responses. - """ - def __init__(self, *args: Any, **kwargs: Any): - setup_logging(None) - super().__init__(*args, **kwargs) diff --git a/boxsdk/config.py b/boxsdk/config.py deleted file mode 100644 index c58290d61..000000000 --- a/boxsdk/config.py +++ /dev/null @@ -1,26 +0,0 @@ -from sys import version_info as py_version - -from . import version - - -class API: - """Configuration object containing the URLs for the Box API.""" - BASE_API_URL = 'https://api.box.com/2.0' - UPLOAD_URL = 'https://upload.box.com/api/2.0' - OAUTH2_API_URL = 'https://api.box.com/oauth2' # - OAUTH2_AUTHORIZE_URL = 'https://account.box.com/api/oauth2/authorize' # - MAX_RETRY_ATTEMPTS = 5 - CHUNK_UPLOAD_THREADS = 5 - - -class Client: - """Configuration object containing the user agent string.""" - VERSION = version.__version__ - USER_AGENT_STRING = f'box-python-sdk-{VERSION}' - BOX_UA_STRING = f'agent=box-python-sdk/{VERSION}; ' \ - f'env=python/{py_version.major}.{py_version.minor}.{py_version.micro}' - - -class Proxy: - URL = None - AUTH = None diff --git a/boxsdk/exception.py b/boxsdk/exception.py deleted file mode 100644 index 31e138cc0..000000000 --- a/boxsdk/exception.py +++ /dev/null @@ -1,115 +0,0 @@ -from typing import Optional - -import attr - -from .network.network_interface import NetworkResponse -from .util.log import sanitize_dictionary - - -class BoxException(Exception): - """ - Base class exception for all errors raised from the SDK. - """ - def __str__(self): - return self.__class__.__name__ - - def __repr__(self): - return f'<{self.__class__.__name__}>' - - -class BoxValueError(ValueError): - """ - Exception raise by SDK value errors - """ - - -class BoxNetworkException(BoxException): - """ - Exception raised from the network layer. - """ - - -@attr.s(repr=True, slots=True, frozen=True) -class BoxAPIException(BoxException): - """ - Exception raised from the box session layer. - - :param status: - HTTP status code of the failed response - :param code: - The 'code' field of the failed response - :param message: - A message to associate with the exception, e.g. 'message' field of the json in the failed response - :param request_id: - The 'request_id' field of the json in the failed response - :param headers: - The HTTP headers in the failed response - :param url: - The url which raised the exception - :param method: - The HTTP verb used to make the request. - :param context_info: - The context_info returned in the failed response. - :param network_response: - The failed response - """ - status: int = attr.ib() - code: Optional[str] = attr.ib(default=None) - message: Optional[str] = attr.ib(default=None) - request_id: Optional[str] = attr.ib(default=None) - headers: dict = attr.ib(default=None, hash=False) - url: str = attr.ib(default=None) - method: str = attr.ib(default=None) - context_info: Optional[dict] = attr.ib(default=None) - network_response: 'NetworkResponse' = attr.ib(default=None, repr=False) - - def __str__(self): - return '\n'.join(( - f'Message: {self.message}', - f'Status: {self.status}', - f'Code: {self.code}', - f'Request ID: {self.request_id}', - f'Headers: {sanitize_dictionary(self.headers)}', - f'URL: {self.url}', - f'Method: {self.method}', - f'Context Info: {self.context_info}', - )) - - -@attr.s(repr=True, slots=True, frozen=True) -class BoxOAuthException(BoxException): - """ - Exception raised during auth. - - :param status: - HTTP status code of the auth response - :param message: - A message to associate with the exception, e.g. HTTP content of the auth response - :param url: - The url which raised the exception - :param method: - The HTTP verb used to make the request. - :param network_response: - The network response for the request. - :param code: - The 'code' field of the failed response - """ - status: int = attr.ib() - message: str = attr.ib(default=None) - url: str = attr.ib(default=None) - method: str = attr.ib(default=None) - network_response: NetworkResponse = attr.ib(default=None, repr=False) - code: Optional[str] = attr.ib(default=None) - - def __str__(self): - # pylint:disable=no-member - if self.network_response: - headers = sanitize_dictionary(self.network_response.headers) - # pylint:enable=no-member - else: - headers = 'N/A' - return f'\nMessage: {self.message}\nStatus: {self.status}\nURL: {self.url}\nMethod: {self.method}' \ - f'\nHeaders: {headers}' - - -__all__ = ['BoxException', 'BoxAPIException', 'BoxOAuthException', 'BoxNetworkException'] diff --git a/boxsdk/network/__init__.py b/boxsdk/network/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/boxsdk/network/default_network.py b/boxsdk/network/default_network.py deleted file mode 100644 index f71c484f2..000000000 --- a/boxsdk/network/default_network.py +++ /dev/null @@ -1,255 +0,0 @@ -import logging -from logging import getLogger -from pprint import pformat -import sys -import time -from typing import Any, Callable, Type, Optional - -import requests -from requests import Response -from requests.structures import CaseInsensitiveDict -from urllib3 import HTTPResponse - -from .network_interface import Network, NetworkResponse -from ..util.log import sanitize_dictionary - - -class DefaultNetwork(Network): - """Implements the network interface using the requests library.""" - - LOGGER_NAME = 'boxsdk.network' - REQUEST_FORMAT = '\x1b[36m%(method)s %(url)s %(request_kwargs)s\x1b[0m' - EXCEPTION_FORMAT = '\x1b[31mRequest "%(method)s %(url)s" failed with %(exc_type_name)s exception: %(exc_value)r\x1b[0m' - - def __init__(self): - super().__init__() - self._session = requests.Session() - self._logger = getLogger(__name__) - - def request(self, method: str, url: str, access_token: str, **kwargs: Any) -> NetworkResponse: - """Base class override. - - Make a network request using a requests.Session. Logs information about an API request and response. - - Also logs exceptions before re-raising them. - - The logging of the response is deferred to :class:`DefaultNetworkResponse`. - See that class's docstring for more info. - """ - log_response_content = kwargs.pop('log_response_content', True) - self._log_request(method, url, **kwargs) - # pylint:disable=abstract-class-instantiated - try: - return self.network_response_constructor( - request_response=self._session.request(method, url, **kwargs), - access_token_used=access_token, - log_response_content=log_response_content - ) - except Exception: - self._log_exception(method, url, sys.exc_info()) - raise - - def retry_after(self, delay: float, request_method: Callable, *args: Any, **kwargs: Any) -> Any: - """Base class override. - Retry after sleeping for delay seconds. - """ - time.sleep(delay) - return request_method(*args, **kwargs) - - @property - def network_response_constructor(self) -> Type['DefaultNetworkResponse']: - """Baseclass override. - - A callable that accepts `request_response` and `access_token_used` - keyword arguments for the :class:`DefaultNetworkResponse` constructor, - and returns an instance of :class:`DefaultNetworkResponse`. - """ - return DefaultNetworkResponse - - def _log_request(self, method: str, url: str, **kwargs: Any) -> None: - """ - Logs information about the Box API request. - - :param method: - The HTTP verb that should be used to make the request. - :param url: - The URL for the request. - """ - if self._logger.isEnabledFor(logging.INFO): - self._logger.info( - self.REQUEST_FORMAT, - {'method': method, 'url': url, 'request_kwargs': pformat(sanitize_dictionary(kwargs))}, - ) - - def _log_exception(self, method: str, url: str, exc_info: Any) -> None: - """Log information at WARNING level about the exception that was raised when trying to make the request. - - :param method: The HTTP verb that was used to make the request. - :param url: The URL for the request. - :param exc_info: The exception info returned from `sys.exc_info()`. - """ - if self._logger.isEnabledFor(logging.WARNING): - exc_type, exc_value, _ = exc_info - self._logger.warning( - self.EXCEPTION_FORMAT, - {'method': method, 'url': url, 'exc_type_name': exc_type.__name__, 'exc_value': exc_value}, - ) - - -class DefaultNetworkResponse(NetworkResponse): - """Implementation of the network interface using the requests library. - - :class:`requests.Response` has a few mutually-exclusive ways to read the - content of the response: - - - With the `Response.raw` attribute, an `io.IOBase` instance returned - from the `urllib3` library, that can be read once in chunks from - beginning to end. - - With `Response.iter_content()` and other iter_* generators, which - also can only be read once and advance the `Response.raw` IO stream. - - With the `Response.content` property (and other attributes such as - `Response.text` and `Response.json()`), which reads and caches the - remaining response content in memory. Can be accessed multiple times, - but cannot be safely accessed if any of the previous mechanisms have - been used at all. And if this property has already been accessed, - then the other mechanisms will have been exhausted, and attempting to - read from them will make it appear like the response content is - empty. - - Any of these mechanisms may be used to read any response, regardless of - whether `stream=True` or `stream=False` on the request. - - If the caller uses `Response.content`, then it is safe for - :class:`DefaultNetwork` to also access it. But if the caller uses any of - the streaming mechanisms, then it is not safe for :class:`DefaultNetwork` - to ever read any of the content. - - The SDK logs only the response content of type JSON. Non-JSON responses, e.g. - the content of the downloaded file should not be logged by the SDK at any time. - In that case the response will be logged with a placeholder for the actual content. - - :param: `log_response_content` specifies wheather the response content should be logged or not. - Its value is determined by `BoxRequest.expect_json_response` field, which is set to False, - only if explicitly specified inside SDK method, e.g. self._session.get(url, expect_json_response=False). - So the contenet of the response will be read by the logger only when the call inside an SDK method - expects a JSON response. In this case we can be sure that the SDK method will read the content - of the response using non-streaming mechanism and accessing content of the response by logger - with `response.json()` or 'response.content` is safe. - """ - - _COMMON_RESPONSE_FORMAT = '"%(method)s %(url)s" %(status_code)s %(content_length)s\n%(headers)s\n%(content)s\n' - SUCCESSFUL_RESPONSE_FORMAT = f'\x1b[32m{_COMMON_RESPONSE_FORMAT}\x1b[0m' - ERROR_RESPONSE_FORMAT = f'\x1b[31m{_COMMON_RESPONSE_FORMAT}\x1b[0m' - CONTENT_NOT_LOGGED = '' - - def __init__(self, request_response: 'Response', access_token_used: str, log_response_content: bool = True): - self._logger = getLogger(__name__) - self._request_response = request_response - self._access_token_used = access_token_used - self._did_log = False - self._json = None - self.log(can_safely_log_content=log_response_content) - - def json(self) -> dict: - """Base class override.""" - if self._json is None: - self._json = self._request_response.json() - return self._json - - @property - def content(self) -> Optional[bytes]: - """Base class override.""" - return self._request_response.content - - @property - def status_code(self) -> int: - """Base class override.""" - return self._request_response.status_code - - @property - def ok(self) -> bool: - """Base class override.""" - # pylint:disable=invalid-name - return self._request_response.ok - - @property - def headers(self) -> CaseInsensitiveDict: - """Base class override.""" - return self._request_response.headers - - @property - def response_as_stream(self) -> HTTPResponse: - """Base class override.""" - return self._request_response.raw - - @property - def access_token_used(self) -> str: - """Base class override.""" - return self._access_token_used - - @property - def request_response(self) -> Response: - """ - The response returned from the Requests library. - """ - return self._request_response - - def log(self, can_safely_log_content: bool = False) -> None: - """Logs information about the Box API response. - - Will only execute once. Subsequent calls will be no-ops. This is - partially because we only want to log responses once, and partially - because this is necessary to prevent this method from infinite - recursing with its use of the `content` property. - - :param can_safely_log_content: - (optional) `True` if the caller is accessing the `content` - property, `False` otherwise. - - As stated in the class docstring, it is unsafe for this logging - method to access `content` unless the caller is also accessing it. - - Defaults to `False`. - """ - if self._did_log: - return - self._did_log = True - - if self.ok: - logger_method, logger_level, response_format = self._logger.info, logging.INFO, self.SUCCESSFUL_RESPONSE_FORMAT - else: - logger_method, logger_level, response_format = self._logger.warning, logging.WARNING, self.ERROR_RESPONSE_FORMAT - - if not self._logger.isEnabledFor(logger_level): - return - - content_length = self.headers.get('Content-Length', None) - content = self.CONTENT_NOT_LOGGED - if can_safely_log_content: - if content_length is None: - content_length = str(len(self.content)) - - # If possible, get the content as a JSON `dict`, that way - # `pformat(content)` will return pretty-printed JSON. - try: - content = self.json() - except ValueError: - content = self.content - content = pformat(sanitize_dictionary(content)) - if content_length is None: - content_length = '?' - logger_method( - response_format, - { - 'method': self.request_response.request.method, - 'url': self.request_response.request.url, - 'status_code': self.status_code, - 'content_length': content_length, - 'headers': pformat(self.headers), - 'content': content, - }, - ) - - def __repr__(self) -> str: - return f'' diff --git a/boxsdk/network/network_interface.py b/boxsdk/network/network_interface.py deleted file mode 100644 index bc4531060..000000000 --- a/boxsdk/network/network_interface.py +++ /dev/null @@ -1,113 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Any, Callable, Union - - -class Network(ABC): - """ - Abstract base class specifying the interface of the network layer. - """ - - @abstractmethod - def request(self, method: str, url: str, access_token: str, **kwargs: Any) -> 'NetworkResponse': - """ - Make a network request to the given url with the given method. - - :param method: - The HTTP verb that should be used to make the request. - :param url: - The URL for the request. - :param access_token: - The OAuth2 access token used to authorize the request. - """ - raise NotImplementedError # pragma: no cover - - @abstractmethod - def retry_after(self, delay: float, request_method: Callable, *args: Any, **kwargs: Any) -> 'NetworkResponse': - """ - Make a network request after a given delay. - - :param delay: - How long until the request should be executed. - :param request_method: - A callable that will execute the request. - """ - raise NotImplementedError # pragma: no cover - - @property - def network_response_constructor(self) -> Union[type, Callable]: - """The constructor to use for creating NetworkResponse instances. - - This is not implemented by default, and is not a required part of the - interface. - - It is recommended that implementations of `request()` call this to - construct their responses, rather than hard-coding the construction. - That way, subclasses of the implementation can easily extend the - construction of :class:`NetworkResponse` instances, by overriding this - property, instead of needing to override `request()`. - - :return: - A callable that returns an instance of :class:`NetworkResponse`. - Most commonly, this will be a subclass of :class:`NetworkResponse`. - """ - return NetworkResponse - - -class NetworkResponse(ABC): - """Abstract base class specifying the interface for a network response.""" - - @abstractmethod - def json(self) -> Union[dict, list, str, int, float]: - """ - Return the parsed JSON response. - """ - raise NotImplementedError # pragma: no cover - - @property - @abstractmethod - def content(self) -> Any: - """ - Return the content of the response body. - """ - raise NotImplementedError # pragma: no cover - - @property - @abstractmethod - def status_code(self) -> int: - """ - Return the HTTP status code of the response. - """ - raise NotImplementedError # pragma: no cover - - @property - @abstractmethod - def ok(self) -> bool: - """ - Return whether or not the request was successful. - """ - # pylint:disable=invalid-name - raise NotImplementedError # pragma: no cover - - @property - @abstractmethod - def headers(self) -> dict: - """ - Return the response headers. - """ - raise NotImplementedError # pragma: no cover - - @property - @abstractmethod - def response_as_stream(self) -> Any: - """ - Return a stream containing the raw network response. - """ - raise NotImplementedError # pragma: no cover - - @property - @abstractmethod - def access_token_used(self) -> str: - """ - Return the access token used to make the request. - """ - raise NotImplementedError # pragma: no cover diff --git a/boxsdk/object/__init__.py b/boxsdk/object/__init__.py deleted file mode 100644 index d41b3ecd8..000000000 --- a/boxsdk/object/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -__all__ = [ - 'collaboration', - 'collaboration_allowlist', - 'collaboration_allowlist_entry', - 'collaboration_allowlist_exempt_target', - 'collection', - 'comment', - 'device_pinner', - 'enterprise', - 'events', - 'event', - 'file', - 'file_request', - 'file_version', - 'file_version_retention', - 'folder', - 'folder_lock', - 'group', - 'group_membership', - 'invite', - 'legal_hold', - 'legal_hold_policy', - 'legal_hold_policy_assignment', - 'metadata_cascade_policy', - 'metadata_template', - 'recent_item', - 'retention_policy', - 'retention_policy_assignment', - 'search', - 'sign_request', - 'storage_policy', - 'storage_policy_assignment', - 'terms_of_service', - 'terms_of_service_user_status', - 'trash', - 'task', - 'task_assignment', - 'user', - 'upload_session', - 'webhook', - 'watermark', - 'web_link' -] diff --git a/boxsdk/object/api_json_object.py b/boxsdk/object/api_json_object.py deleted file mode 100644 index ef1689a60..000000000 --- a/boxsdk/object/api_json_object.py +++ /dev/null @@ -1,18 +0,0 @@ -from abc import ABCMeta -from collections.abc import Mapping - -from .base_api_json_object import BaseAPIJSONObject, BaseAPIJSONObjectMeta - - -class APIJSONObjectMeta(BaseAPIJSONObjectMeta, ABCMeta): - """ - Avoid conflicting metaclass definitions for APIJSONObject. - http://code.activestate.com/recipes/204197-solving-the-metaclass-conflict/ - """ - - -class APIJSONObject(BaseAPIJSONObject, Mapping, metaclass=APIJSONObjectMeta): - """Class representing objects that are not part of the REST API.""" - - def __len__(self) -> int: - return len(self._response_object) diff --git a/boxsdk/object/base_api_json_object.py b/boxsdk/object/base_api_json_object.py deleted file mode 100644 index bc9903a2c..000000000 --- a/boxsdk/object/base_api_json_object.py +++ /dev/null @@ -1,157 +0,0 @@ -import copy -from typing import Any, Iterator, Iterable, Optional - -from ..util.translator import Translator - - -class BaseAPIJSONObjectMeta(type): - """ - Metaclass for Box API objects. - - Registers classes with the default translator, so that API responses can be - translated to the correct type. This relies on the _item_type field, which - must be defined in the class's namespace dict (and must be re-defined, in - order to register a custom subclass), to match the 'type' field of the - response json. But the type-class mapping will only be registered if the - module of the class is imported. - - For example, events returned from the API look like - - .. code-block:: json - - {'type': 'event', ...} - - so a class for that type could be created and registered with the default - translator like this: - - .. code-block:: python - - class Event(BaseAPIJSONObject): - _item_type = 'event' - ... - - NOTE: The default translator registration functionality is a private - implementation detail of the SDK, to make it easy to register the default - API object classes with the default translator. For convenience and - backwards-compatability, developers are allowed to re-define the _item_type - field in their own custom subclasses in order to take advantage of this - functionality, but are encouraged not to. Since this is a private - implementation detail, it may change or be removed in any major or minor - release. Additionally, it has the usual hazards of mutable global state. - The supported and recommended ways for registering custom subclasses are: - - - Constructing a new :class:`Translator`, calling `Translator.register()` - as necessary, and passing it to the :class:`BoxSession` constructor. - - Calling `session.translator.register()` on an existing - :class:`BoxSession`. - - Calling `client.translator.register()` on an existing :class:`Client`. - """ - - def __init__(cls, name, bases, attrs): - super().__init__(name, bases, attrs) - item_type = attrs.get('_item_type', None) - if item_type is not None: - Translator._default_translator.register(item_type, cls) # pylint:disable=protected-access,no-member - # Some types have - in them instead of _ in the API. - if "-" in item_type: - Translator._default_translator.register(item_type.replace("-", "_"), cls) # pylint:disable=protected-access,no-member - - -class BaseAPIJSONObject(metaclass=BaseAPIJSONObjectMeta): - """Base class containing basic logic shared between true REST objects and other objects (such as an Event)""" - - # :attr _item_type: - # (protected) The Box resource type that this class represents. - # For API object/resource classes this should equal the expected value - # of the 'type' field in API JSON responses. Otherwise, this should be - # `None`. - # - # NOTE: When defining a leaf class with an _item_type in this SDK, it's - # also important to add the module name to __all__ in object/__init__.py, - # so that it will be imported and registered with the default translator. - _item_type: Optional[str] = None - _untranslated_fields = () - - def __init__(self, response_object: dict = None, **kwargs: Any): - """ - :param response_object: - A JSON object representing the object returned from a Box API request. - """ - super().__init__(**kwargs) - self._response_object = response_object or {} - self.__dict__.update(self._response_object) - - def __getitem__(self, item: str) -> Any: - """ - Try to get the attribute from the API response object. - - :param item: - The attribute to retrieve from the API response object. - """ - return self._response_object[item] - - def __contains__(self, item: str) -> bool: - """ - Does the response object contains this item attribute? - - :param item: - The attribute to check for in the API response object. - - """ - return item in self._response_object - - def __iter__(self) -> Iterator: - """ - Get all of the keys of the API response object. - """ - return iter(self._response_object) - - def __repr__(self) -> str: - """Base class override. Return a human-readable representation using the Box ID or name of the object.""" - extra_description = f' - {self._description}' if self._description else '' - description = f'' - return description - - @property - def _description(self) -> str: - """Return a description of the object if one exists.""" - return "" - - @property - def object_type(self) -> str: - """Return the Box type for the object. - """ - return self._item_type - - @classmethod - def untranslated_fields(cls) -> tuple: - """ - The fields that should not be translated on this object. - """ - return cls._untranslated_fields - - @classmethod - def _untranslate(cls, value: Any) -> Iterable: - """ - Untranslates a given object into a dictionary. - - :param value: - The object to untranslate. - :return: - A dictionary containing the untranslated object. - """ - if isinstance(value, BaseAPIJSONObject): - return cls._untranslate(value._response_object) # pylint:disable=protected-access - if isinstance(value, dict): - return {k: cls._untranslate(v) for (k, v) in value.items()} - if isinstance(value, list): - return [cls._untranslate(entry) for entry in value] - - return copy.copy(value) - - @property - def response_object(self) -> Iterable: - """ - Return the JSON object representing the object returned from a Box API request. - """ - return self._untranslate(self) diff --git a/boxsdk/object/base_endpoint.py b/boxsdk/object/base_endpoint.py deleted file mode 100644 index 251dd7930..000000000 --- a/boxsdk/object/base_endpoint.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import TYPE_CHECKING, Any -from .cloneable import Cloneable - -if TYPE_CHECKING: - from boxsdk.session.session import Session - from boxsdk.util.translator import Translator - - -class BaseEndpoint(Cloneable): - """A Box API endpoint.""" - - def __init__(self, session: 'Session', **kwargs: Any): - """ - :param session: - The Box session used to make requests. - :param kwargs: - Keyword arguments for base class constructors. - """ - super().__init__(**kwargs) - self._session = session - - @property - def session(self) -> 'Session': - """ - Get the :class:`BoxSession` instance the object is using. - """ - return self._session - - @property - def translator(self) -> 'Translator': - """ - The translator used for translating Box API JSON responses into `BaseAPIJSONObject` smart objects. - """ - return self._session.translator - - def get_url(self, *args: Any) -> str: - """ - Return the URL used to access the endpoint. - - :param args: - Parts of the endpoint URL. - """ - return self._session.get_url(*args) - - def clone(self, session: 'Session' = None) -> 'BaseEndpoint': - """ - Returns a copy of this cloneable object using the specified session. - - :param session: - The Box session used to make requests. - """ - return self.__class__(session or self._session) diff --git a/boxsdk/object/base_item.py b/boxsdk/object/base_item.py deleted file mode 100644 index 05592066a..000000000 --- a/boxsdk/object/base_item.py +++ /dev/null @@ -1,186 +0,0 @@ -import json - -from typing import TYPE_CHECKING, Any, Union - -from .base_object import BaseObject -from ..exception import BoxValueError -from ..util.api_call_decorator import api_call -from ..util.datetime_formatter import normalize_date_to_rfc3339_format -from ..util.default_arg_value import SDK_VALUE_NOT_SET - -if TYPE_CHECKING: - from boxsdk.object.folder import Folder - from boxsdk.object.collection import Collection - - -class BaseItem(BaseObject): - - @api_call - def copy(self, *, parent_folder: 'Folder', name: str = None, **_kwargs) -> 'BaseItem': - """Copy the item to the given folder. - - :param parent_folder: - The folder to which the item should be copied. - :param name: - A new name for the item, in case there is already another item in the new parent folder with the same name. - """ - self.validate_item_id(self._object_id) - url = self.get_url('copy') - data = { - 'parent': {'id': parent_folder.object_id} - } - if name is not None: - data['name'] = name - box_response = self._session.post(url, data=json.dumps(data)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def move(self, parent_folder: 'Folder', name: str = None) -> 'BaseItem': - """ - Move the item to the given folder. - - :param parent_folder: - The parent `Folder` object, where the item will be moved to. - :param name: - A new name for the item, in case there is already another item in the new parent folder with the same name. - """ - data = { - 'parent': {'id': parent_folder.object_id} - } - if name is not None: - data['name'] = name - return self.update_info(data=data) - - @api_call - def rename(self, name: str) -> 'BaseItem': - """ - Rename the item to a new name. - - :param name: - The new name, you want the item to be renamed to. - """ - data = { - 'name': name, - } - return self.update_info(data=data) - - @api_call - def create_shared_link(self, **kwargs: Any) -> Any: - """ - Create a shared link for the item with the given access permissions. - - :param kwargs: - Keyword arguments passed from overriding method used to build request properties. - :return: - The updated object with shared link. - Returns a new object of the same type, without modifying the original object passed as self. - """ - shared_link = {} - - if kwargs.get('access') is not None: - shared_link['access'] = kwargs.get('access') - - if kwargs.get('unshared_at') is not SDK_VALUE_NOT_SET: - shared_link['unshared_at'] = normalize_date_to_rfc3339_format(kwargs.get('unshared_at')) - - permissions = {} - if kwargs.get('allow_download') is not None: - permissions['can_download'] = kwargs.get('allow_download') - if kwargs.get('allow_preview') is not None: - permissions['can_preview'] = kwargs.get('allow_preview') - if kwargs.get('allow_edit') is not None: - permissions['can_edit'] = kwargs.get('allow_edit') - if permissions: - shared_link['permissions'] = permissions - - if kwargs.get('password') is not None: - shared_link['password'] = kwargs.get('password') - - if kwargs.get('vanity_name') is not None: - shared_link['vanity_name'] = kwargs.get('vanity_name') - - data = {'shared_link': shared_link} - update_info_kwargs = {'etag': kwargs.get('etag')} if kwargs.get('etag') is not None else {} - - return self.update_info(data=data, **update_info_kwargs) - - @api_call - def get_shared_link(self, **kwargs: Any) -> str: - """ - Get a shared link for the item with the given access permissions. - This url leads to a Box.com shared link page, where the item can be previewed, downloaded, etc. - - :param kwargs: - Keyword arguments passed from overriding method used to create a new shared link. - :returns: - The URL of the shared link. - """ - item = self.create_shared_link(**kwargs) - return item.shared_link['url'] # pylint:disable=no-member - - @api_call - def remove_shared_link(self, **kwargs: Any) -> bool: - """ - Delete the shared link for the item. - - :param kwargs: - Keyword arguments passed from overriding method used to build request properties. - :returns: - Whether or not the update was successful. - """ - data = {'shared_link': None} - update_info_kwargs = {'etag': kwargs.get('etag')} if kwargs.get('etag') is not None else {} - - item = self.update_info(data=data, **update_info_kwargs) - return item.shared_link is None # pylint:disable=no-member - - @api_call - def add_to_collection(self, collection: 'Collection') -> 'BaseItem': - """ - Add the item to a collection. This method is not currently safe from race conditions. - - :param collection: - The collection to add the item to. - :return: - This item. - """ - collections = self.get(fields=['collections']).collections # pylint:disable=no-member - collections.append({'id': collection.object_id}) - data = { - 'collections': collections - } - return self.update_info(data=data) - - @api_call - def remove_from_collection(self, collection: 'Collection') -> 'BaseItem': - """ - Remove the item from a collection. This method is not currently safe from race conditions. - - :param collection: - The collection to remove the item from. - :return: - This item. - """ - collections = self.get(fields=['collections']).collections # pylint:disable=no-member - updated_collections = [c for c in collections if c['id'] != collection.object_id] - data = { - 'collections': updated_collections - } - return self.update_info(data=data) - - @staticmethod - def validate_item_id(item_id: Union[str, int]) -> None: - """ - Validates an item ID is numeric - - :param item_id - :raises: - BoxException: if item_id is not numeric - :returns: - """ - if not isinstance(item_id, int) and not item_id.isdigit(): - raise BoxValueError("Invalid item ID") diff --git a/boxsdk/object/base_object.py b/boxsdk/object/base_object.py deleted file mode 100644 index 6cd51bb49..000000000 --- a/boxsdk/object/base_object.py +++ /dev/null @@ -1,159 +0,0 @@ -import json -from typing import TYPE_CHECKING, Any, Iterable, Optional, Union, List - -from .base_endpoint import BaseEndpoint -from .base_api_json_object import BaseAPIJSONObject -from ..util.api_call_decorator import api_call - -if TYPE_CHECKING: - from boxsdk.session.session import Session - - -class BaseObject(BaseEndpoint, BaseAPIJSONObject): - """A Box API endpoint for interacting with a Box object.""" - - def __init__(self, session: 'Session', object_id: str, response_object: dict = None): - """ - :param session: - The Box session used to make requests. - :param object_id: - The Box ID for the object. - :param response_object: - A JSON object representing the object returned from a Box API request. - """ - super().__init__(session=session, response_object=response_object) - self._object_id = object_id - - @property - def _description(self) -> str: - """ - Base class override. Return a description for the object. - """ - if 'name' in self._response_object: - return f'{self._object_id} ({self.name})' # pylint:disable=no-member - return self._object_id - - def get_url(self, *args: Any) -> str: - """ - Base class override. - Return the given object's URL, appending any optional parts as specified by args. - """ - return super().get_url(f'{self._item_type}s', self._object_id, *args) - - def get_type_url(self) -> str: - """ - Return the URL for type of the given resource. - """ - return super().get_url(f'{self._item_type}s') - - @property - def object_id(self) -> str: - """ - Return the Box ID for the object. - """ - return self._object_id - - @api_call - def get(self, *, fields: Iterable[str] = None, headers: dict = None, **_kwargs) -> Any: - """ - Get information about the object, specified by fields. If fields is None, return the default fields. - - :param fields: - List of fields to request. - :param headers: - Additional headers to send with the request. - :return: - An object of the same type that has the requested information. - """ - url = self.get_url() - params = {'fields': ','.join(fields)} if fields else None - box_response = self._session.get(url, params=params, headers=headers) - return self.translator.translate( - session=self._session, - response_object=box_response.json(), - ) - - @api_call - def update_info( - self, - *, - data: Union[dict, List[dict]], - params: Optional[dict] = None, - headers: Optional[dict] = None, - **kwargs: Any - ) -> Any: - """Update information about this object. - - Send a PUT to the object's base endpoint to modify the provided - attributes. - - :param data: - The updated information about this object. - Must be JSON serializable. - Update the object attributes in data.keys(). The semantics of the - values depends on the type and attributes of the object being - updated. For details on particular semantics, refer to the Box - developer API documentation . - :param params: - (optional) Query string parameters for the request. - :param headers: - (optional) Extra HTTP headers for the request. - :param kwargs: - Optional arguments that ``put`` takes. - :return: - The updated object. - Return a new object of the same type, without modifying the - original object passed as self. - Construct the new object with all the default attributes that are - returned from the endpoint. - """ - url = self.get_url() - box_response = self._session.put(url, data=json.dumps(data), params=params, headers=headers, **kwargs) - if 'expect_json_response' in kwargs and not kwargs['expect_json_response']: - return box_response.ok - - return self.translator.translate( - session=self._session, - response_object=box_response.json(), - ) - - @api_call - def delete(self, *, params: Optional[dict] = None, headers: Optional[dict] = None, **_kwargs) -> bool: - """ Delete the object. - - :param params: - Additional parameters to send with the request. Can be None - :param headers: - Any customer headers to send with the request. Can be None - :returns: - Whether or not the delete was successful. - :raises: - :class:`BoxAPIException` in case of unexpected errors. - """ - url = self.get_url() - - box_response = self._session.delete(url, expect_json_response=False, params=params or {}, headers=headers) - return box_response.ok - - def __eq__(self, other: Any) -> bool: - """Equality as determined by object id and type""" - if isinstance(other, BaseObject): - # Two objects are considered the same if they have the same address in the API - return self.get_url() == other.get_url() - - return NotImplemented - - def __ne__(self, other: Any) -> bool: - """Equality as determined by object id and type""" - return not self == other - - def __hash__(self) -> int: - return hash((self._object_id, self._item_type)) - - def clone(self, session: 'Session' = None) -> 'BaseObject': - """Base class override.""" - return self.__class__( - session or self._session, - self._object_id, - self._response_object, - ) diff --git a/boxsdk/object/cloneable.py b/boxsdk/object/cloneable.py deleted file mode 100644 index 29711e903..000000000 --- a/boxsdk/object/cloneable.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import TYPE_CHECKING, NoReturn - -if TYPE_CHECKING: - from boxsdk.object.user import User - from boxsdk.session.session import Session - - -class Cloneable: - """ - Cloneable interface to be implemented by endpoint objects that should have ability to be cloned, but with a - different session member if desired. - """ - - def as_user(self, user: 'User') -> 'Cloneable': - """ - Returns a new endpoint object with default headers set up to make requests as the specified user. - - :param user: - The user to impersonate when making API requests. - """ - return self.clone(self.session.as_user(user)) - - def with_shared_link(self, shared_link: str, shared_link_password: str) -> 'Cloneable': - """ - Returns a new endpoint object with default headers set up to make requests using the shared link for auth. - - :param shared_link: - The shared link. - :param shared_link_password: - The password for the shared link. - """ - return self.clone(self.session.with_shared_link(shared_link, shared_link_password)) - - def clone(self, session: 'Session' = None) -> NoReturn: - """ - Returns a copy of this cloneable object using the specified session. - - :param session: - The Box session used to make requests. - """ - raise NotImplementedError - - @property - def session(self) -> NoReturn: - """ - Return the Box session being used to make requests. - """ - raise NotImplementedError diff --git a/boxsdk/object/collaboration.py b/boxsdk/object/collaboration.py deleted file mode 100644 index 6588e666d..000000000 --- a/boxsdk/object/collaboration.py +++ /dev/null @@ -1,75 +0,0 @@ -from typing import Optional, Any - -from boxsdk.object.base_object import BaseObject -from boxsdk.util.text_enum import TextEnum -from ..util.api_call_decorator import api_call - - -class CollaborationRole(TextEnum): - """An enum of possible collaboration roles""" - EDITOR = 'editor' - VIEWER = 'viewer' - - # Available to enterprise accounts: - PREVIEWER = 'previewer' - UPLOADER = 'uploader' - PREVIEWER_UPLOADER = 'previewer uploader' - VIEWER_UPLOADER = 'viewer uploader' - CO_OWNER = 'co-owner' - OWNER = 'owner' - - -class CollaborationStatus(TextEnum): - """An enum of possible statuses of a collaboration""" - PENDING = 'pending' - ACCEPTED = 'accepted' - REJECTED = 'rejected' - - -class Collaboration(BaseObject): - """An object that represents a collaboration between a folder and an individual or group""" - _item_type = 'collaboration' - - @api_call - def update_info( - self, - *, - data: dict = None, - role: Optional[CollaborationRole] = None, - status: Optional[CollaborationStatus] = None, - **kwargs: Any - ) -> 'BaseObject': - """Edit an existing collaboration on Box - :param data: - The updated information about this object. - :param role: - The new role for this collaboration or None to leave unchanged - :param status: - The new status for this collaboration or None to leave unchanged. A pending collaboration can be set to - accepted or rejected if permissions allow it. - :returns: - Whether or not the edit was successful. - :raises: - :class:`BoxAPIException` if current user doesn't have permissions to edit the collaboration. - """ - # pylint:disable=arguments-differ - if data is None: - data = {} - if role: - data['role'] = role - if status: - data['status'] = status - if data.get('role', None) == CollaborationRole.OWNER: - return super().update_info(data=data, expect_json_response=False, **kwargs) - - return super().update_info(data=data, **kwargs) - - @api_call - def accept(self) -> 'BaseObject': - """Accepts a pending collaboration""" - return self.update_info(status=CollaborationStatus.ACCEPTED) - - @api_call - def reject(self) -> 'BaseObject': - """Rejects a pending collaboration""" - return self.update_info(status=CollaborationStatus.REJECTED) diff --git a/boxsdk/object/collaboration_allowlist.py b/boxsdk/object/collaboration_allowlist.py deleted file mode 100644 index a5cf31c04..000000000 --- a/boxsdk/object/collaboration_allowlist.py +++ /dev/null @@ -1,127 +0,0 @@ -import json -from typing import Optional, Iterable, TYPE_CHECKING - -from .base_endpoint import BaseEndpoint -from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection -from ..util.api_call_decorator import api_call -from ..util.text_enum import TextEnum - -if TYPE_CHECKING: - from boxsdk.pagination.box_object_collection import BoxObjectCollection - from boxsdk.object.collaboration_allowlist_entry import CollaborationAllowlistEntry - from boxsdk.object.user import User - from boxsdk.object.collaboration_allowlist_exempt_target import CollaborationAllowlistExemptTarget - - -class AllowlistDirection(TextEnum): - """ - Used to determine the direction of the allowlist. - """ - INBOUND = 'inbound' - OUTBOUNT = 'outbound' - BOTH = 'both' - - -class CollaborationAllowlist(BaseEndpoint): - """Represents the allowlist of email domains that users in an enterprise may collaborate with.""" - - @api_call - def get_entries( - self, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the entries in the collaboration allowlist using limit-offset paging. - - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from. - :param fields: - List of fields to request. - :returns: - An iterator of the entries in the allowlist. - """ - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('collaboration_whitelist_entries'), - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - @api_call - def add_domain(self, domain: str, direction: str) -> 'CollaborationAllowlistEntry': - """ - Add a new domain to the collaboration allowlist. - - :param domain: - The email domain to add to the allowlist. - :param direction: - The direction in which collaboration should be allowed: 'inbound', 'outbound', or 'both'. - :returns: - The created allowlist entry for the domain. - """ - url = self.get_url('collaboration_whitelist_entries') - data = { - 'domain': domain, - 'direction': direction - } - response = self._session.post(url, data=json.dumps(data)).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def get_exemptions( - self, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the list of exempted users who are not subject to the collaboration allowlist rules. - - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from. - :param fields: - List of fields to request. - :returns: - An iterator of the exemptions to the allowlist. - """ - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('collaboration_whitelist_exempt_targets'), - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - @api_call - def add_exemption(self, user: 'User') -> 'CollaborationAllowlistExemptTarget': - """ - Exempt a user from the collaboration allowlist. - - :param user: - The user to exempt from the allowlist. - :returns: - The created allowlist exemption. - """ - url = self.get_url('collaboration_whitelist_exempt_targets') - data = { - 'user': { - 'id': user.object_id # pylint:disable=protected-access - } - } - response = self._session.post(url, data=json.dumps(data)).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) diff --git a/boxsdk/object/collaboration_allowlist_entry.py b/boxsdk/object/collaboration_allowlist_entry.py deleted file mode 100644 index 71eb45909..000000000 --- a/boxsdk/object/collaboration_allowlist_entry.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Any - -from .base_object import BaseObject - - -class CollaborationAllowlistEntry(BaseObject): - """Represents a allowlisted email domain for enterprise collaboration.""" - - _item_type = 'collaboration_whitelist_entry' - - def get_url(self, *args: Any) -> str: - """ - Gets the collaboration allowlist entries endpoint URL. - - :return: - The collaboration allowlist entries endpoint URL. - """ - return self._session.get_url('collaboration_whitelist_entries', self._object_id, *args) diff --git a/boxsdk/object/collaboration_allowlist_exempt_target.py b/boxsdk/object/collaboration_allowlist_exempt_target.py deleted file mode 100644 index 58c96ee64..000000000 --- a/boxsdk/object/collaboration_allowlist_exempt_target.py +++ /dev/null @@ -1,7 +0,0 @@ -from .base_object import BaseObject - - -class CollaborationAllowlistExemptTarget(BaseObject): - """Represents a user who is exempted from the collaboration allowlist.""" - - _item_type = 'collaboration_whitelist_exempt_target' diff --git a/boxsdk/object/collection.py b/boxsdk/object/collection.py deleted file mode 100644 index 7146f14a3..000000000 --- a/boxsdk/object/collection.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Optional, Iterable, TYPE_CHECKING - -from boxsdk.object.base_object import BaseObject -from boxsdk.pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection -from boxsdk.util.api_call_decorator import api_call - -if TYPE_CHECKING: - from boxsdk.pagination.box_object_collection import BoxObjectCollection - - -class Collection(BaseObject): - """Box API endpoint for interacting with collections.""" - - _item_type = 'collection' - - @api_call - def get_items( - self, - limit: Optional[int] = None, - offset: int = 0, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the items in a collection using limit-offset paging. - - :param limit: - The maximum number of items to return per page. If not specified, then will use the server-side default. - :param offset: - The index at which to start returning items. - :param fields: - List of fields to request. - :returns: - An iterator of the items in the folder. - """ - return LimitOffsetBasedObjectCollection( - self.session, - self.get_url('items'), - limit=limit, - fields=fields, - offset=offset, - return_full_pages=False, - ) diff --git a/boxsdk/object/comment.py b/boxsdk/object/comment.py deleted file mode 100644 index 53102f4c3..000000000 --- a/boxsdk/object/comment.py +++ /dev/null @@ -1,48 +0,0 @@ -import json - -from boxsdk.object.base_object import BaseObject -from ..util.api_call_decorator import api_call - - -class Comment(BaseObject): - """An object that represents a comment on an item""" - _item_type = 'comment' - - @staticmethod - def construct_params_from_message(message: str) -> dict: - message_type = 'tagged_message' if '@[' in message else 'message' - return { - message_type: message - } - - @api_call - def reply(self, message: str) -> 'Comment': - """ - Add a reply to the comment. - - :param message: - The content of the reply comment. - """ - url = self.get_type_url() - data = self.construct_params_from_message(message) - data['item'] = { - 'type': 'comment', - 'id': self.object_id - } - box_response = self._session.post(url, data=json.dumps(data)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def edit(self, message: str) -> 'Comment': - """ - Edit the message of the comment. - - :param message: - The content of the reply comment. - """ - data = self.construct_params_from_message(message) - return self.update_info(data=data) diff --git a/boxsdk/object/device_pinner.py b/boxsdk/object/device_pinner.py deleted file mode 100644 index 43b59547f..000000000 --- a/boxsdk/object/device_pinner.py +++ /dev/null @@ -1,7 +0,0 @@ -from .base_object import BaseObject - - -class DevicePinner(BaseObject): - """Represents the device pinner""" - - _item_type = 'device_pinner' diff --git a/boxsdk/object/email_alias.py b/boxsdk/object/email_alias.py deleted file mode 100644 index 5585edf9c..000000000 --- a/boxsdk/object/email_alias.py +++ /dev/null @@ -1,7 +0,0 @@ -from .base_object import BaseObject - - -class EmailAlias(BaseObject): - """Represents a Box email alias.""" - - _item_type = 'email_alias' diff --git a/boxsdk/object/enterprise.py b/boxsdk/object/enterprise.py deleted file mode 100644 index f0717a509..000000000 --- a/boxsdk/object/enterprise.py +++ /dev/null @@ -1,39 +0,0 @@ -import json -from typing import TYPE_CHECKING - -from .base_object import BaseObject -from ..util.api_call_decorator import api_call - -if TYPE_CHECKING: - from boxsdk.object.invite import Invite - - -class Enterprise(BaseObject): - - """Represents a single enterprise.""" - _item_type = 'enterprise' - - @api_call - def invite_user(self, user_email: str) -> 'Invite': - """ - Invites an existing user to an Enterprise. The user must already have a Box account. - - :param user_email: - The login email address of the user that will receive the invitation. - :returns: - The invitation record for the user - """ - url = self._session.get_url('invites') - body = { - 'enterprise': { - 'id': self.object_id, - }, - 'actionable_by': { - 'login': user_email, - }, - } - response = self._session.post(url, data=json.dumps(body)).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) diff --git a/boxsdk/object/event.py b/boxsdk/object/event.py deleted file mode 100644 index c18424c2a..000000000 --- a/boxsdk/object/event.py +++ /dev/null @@ -1,7 +0,0 @@ -from .api_json_object import APIJSONObject - - -class Event(APIJSONObject): - """Represents a single Box event.""" - - _item_type = 'event' diff --git a/boxsdk/object/events.py b/boxsdk/object/events.py deleted file mode 100644 index 302097703..000000000 --- a/boxsdk/object/events.py +++ /dev/null @@ -1,304 +0,0 @@ -from datetime import datetime -from typing import Any, Optional, Iterable, Generator, Union, TYPE_CHECKING - -from requests.exceptions import Timeout - -from .base_endpoint import BaseEndpoint -from ..util.api_call_decorator import api_call -from ..util.datetime_formatter import normalize_date_to_rfc3339_format -from ..util.enum import ExtendableEnumMeta -from ..util.lru_cache import LRUCache -from ..util.text_enum import TextEnum - -if TYPE_CHECKING: - from boxsdk.object.event import Event - from boxsdk.session.box_response import BoxResponse - - -# pylint:disable=too-many-ancestors -class EventsStreamType(TextEnum, metaclass=ExtendableEnumMeta): - """An enum of all possible values of the `stream_type` parameter for user events. - - The value of the `stream_type` parameter determines the type of events - returned by the endpoint. - - - """ - - -class UserEventsStreamType(EventsStreamType): - """An enum of all possible values of the `stream_type` parameter for user events. - - - ALL: Returns all user events. - - CHANGES: Returns tree changes. - - SYNC: Returns tree changes only for sync folders. - - - """ - ALL = 'all' - CHANGES = 'changes' - SYNC = 'sync' - - -class EnterpriseEventsStreamType(EventsStreamType): - """An enum of all possible values of the `stream_type` parameter for enterprise events. - - - ADMIN_LOGS: Retrieves up to a year's events for all users in the enterprise. High latency. - - ADMIN_LOGS_STREAMING: Retrieves up to a two weeks's events for all users in the enterprise. Low latency. - - NOTE: Requires Admin: These stream types will only work with an auth token - from an enterprise admin account. - - - """ - ADMIN_LOGS = 'admin_logs' - ADMIN_LOGS_STREAMING = 'admin_logs_streaming' -# pylint:enable=too-many-ancestors - - -class Events(BaseEndpoint): - """Box API endpoint for subscribing to changes in a Box account.""" - - def get_url(self, *args: Any) -> str: - """Base class override.""" - return super().get_url('events', *args) - - @api_call - def get_events( - self, - limit: int = 100, - stream_position: Union[str, int] = 0, - stream_type: EventsStreamType = UserEventsStreamType.ALL - ) -> dict: - """ - Get Box events from a given stream position for a given stream type. - - :param limit: - Maximum number of events to return. - :param stream_position: - The location in the stream from which to start getting events. 0 is the beginning of time. 'now' will - return no events and just current stream position. - :param stream_type: - (optional) Which type of events to return. - Defaults to `UserEventsStreamType.ALL`. - :returns: - Dictionary containing the next stream position along with a list of some number of events. - """ - url = self.get_url() - params = { - 'limit': limit, - 'stream_position': stream_position, - 'stream_type': stream_type, - } - box_response = self._session.get(url, params=params) - response = box_response.json().copy() - return self.translator.translate(self._session, response_object=response) - - @api_call - def get_admin_events( - self, - limit: Optional[int] = None, - stream_position: Union[str, int] = 0, - created_after: Union[datetime, str] = None, - created_before: Union[datetime, str] = None, - event_types: Iterable[str] = None - ) -> dict: - """ - Get Box Admin events from a datetime, to a datetime, or between datetimes with a given event type for a enterprise - stream type. Used for historical querying (up to one year). Works for Enterprise admin_logs type. - - :param limit: - (optional) Maximum number of events to return. If None, default API value limit=100 will be used. - :param stream_position: - The location in the stream from which to start getting events. 0 is the beginning of time. - :param created_after: - (optional) Start date in datetime format to pull events from - :param created_before: - (optional) End date in datetime format to pull events to - :param event_types: - (optional) Which events to return (ie. LOGIN) - :returns: - Dictionary containing the next stream position along with a list of some number of events. - """ - url = self.get_url() - params = { - 'created_after': normalize_date_to_rfc3339_format(created_after), - 'created_before': normalize_date_to_rfc3339_format(created_before), - 'stream_type': 'admin_logs', - } - if limit is not None: - params['limit'] = limit - if event_types is not None: - params['event_type'] = ','.join(event_types) - if stream_position is not None: - params['stream_position'] = stream_position - box_response = self._session.get(url, params=params) - response = box_response.json() - return self.translator.translate(self._session, response_object=response) - - @api_call - def get_admin_events_streaming( - self, - limit: Optional[int] = None, - stream_position: Union[str, int] = 0, - event_types: Iterable[str] = None - ) -> dict: - """ - Get Box Admin events with a given event type for a enterprise stream type. Used for live monitoring (up to two weeks). - Works for Enterprise admin_logs_streaming type. - - :param limit: - (optional) Maximum number of events to return. - :param stream_position: - The location in the stream from which to start getting events. 0 is the beginning of time. 'now' will - return no events and just current stream position. - :param event_types: - (optional) Which events to return (ie. LOGIN) - :returns: - Dictionary containing the next stream position along with a list of some number of events. - """ - url = self.get_url() - params = { - 'stream_type': 'admin_logs_streaming', - } - if limit is not None: - params['limit'] = limit - if stream_position is not None: - params['stream_position'] = stream_position - if event_types is not None: - params['event_type'] = ','.join(event_types) - box_response = self._session.get(url, params=params) - response = box_response.json() - return self.translator.translate(self._session, response_object=response) - - @api_call - def get_latest_stream_position(self, stream_type: UserEventsStreamType = UserEventsStreamType.ALL) -> int: - """ - Get the latest stream position. The return value can be used with :meth:`get_events` or - :meth:`generate_events_with_long_polling`. - - :param stream_type: - (optional) Which events stream to query. - - NOTE: Currently, the Box API requires this to be one of the user - events stream types. The request will fail if an enterprise events - stream type is passed. - :returns: - The latest stream position. - """ - return self.get_events(limit=0, stream_position='now', stream_type=stream_type)['next_stream_position'] - - def _get_all_events_since( - self, - stream_position: Union[str, int], - stream_type: EventsStreamType = UserEventsStreamType.ALL - ) -> Generator[tuple, None, None]: - """ - :param stream_position: - The location in the stream from which to start getting events. 0 is the beginning of time. 'now' will - return no events and just current stream position. - :param stream_type: - (optional) Which type of events to return. - """ - next_stream_position = stream_position - while True: - events = self.get_events(stream_position=next_stream_position, limit=100, stream_type=stream_type) - next_stream_position = events['next_stream_position'] - events = events['entries'] - if not events: - return - for event in events: - yield event, next_stream_position - if len(events) < 100: - return - - @api_call - def long_poll(self, options: dict, stream_position: Union[str, int]) -> 'BoxResponse': - """ - Set up a long poll connection at the specified url. - - :param options: - The long poll options which include a long pull url, retry timeout, etc. - :param stream_position: - The location in the stream from which to start getting events. 0 is the beginning of time. - 'now' will return no events and just current stream position. - :returns: - {"message": "new_change"}, which means there're new changes on Box or {"version": 1, "message": "reconnect"} - if nothing happens on Box during the long poll. - """ - url = options['url'] - long_poll_response = self._session.get( - url, - timeout=options['retry_timeout'], - params={'stream_position': stream_position} - ) - return long_poll_response - - @api_call - def generate_events_with_long_polling( - self, - stream_position: Union[str, int] = None, - stream_type: UserEventsStreamType = UserEventsStreamType.ALL - ) -> Generator['Event', None, None]: - """ - Subscribe to events from the given stream position. - - :param stream_position: - The location in the stream from which to start getting events. 0 is the beginning of time. 'now' will - return no events and just current stream position. - :param stream_type: - (optional) Which type of events to return. - - NOTE: Currently, the Box API requires this to be one of the user - events stream types. The request will fail if an enterprise events - stream type is passed. - :returns: - Events corresponding to changes on Box in realtime, as they come in. - """ - event_ids = LRUCache() - stream_position = stream_position if stream_position is not None else self.get_latest_stream_position(stream_type=stream_type) - while True: - options = self.get_long_poll_options(stream_type=stream_type) - while True: - try: - long_poll_response = self.long_poll(options, stream_position) - except Timeout: - break - - message = long_poll_response.json()['message'] - if message == 'new_change': - next_stream_position = stream_position - for event, next_stream_position in self._get_all_events_since(stream_position, stream_type=stream_type): - try: - event_ids.get(event['event_id']) - except KeyError: - yield event - event_ids.set(event['event_id']) - stream_position = next_stream_position - break - if message == 'reconnect': - continue - break - - @api_call - def get_long_poll_options(self, stream_type: EventsStreamType = UserEventsStreamType.ALL) -> dict: - """ - Get the url and retry timeout for setting up a long polling connection. - - :param stream_type: - (optional) Which type of events to return. - :returns: - A `dict` including a long poll url, retry timeout, etc. - E.g. - { - "type": "realtime_server", - "url": "http://2.realtime.services.box.net/subscribe?channel=cc807c9c4869ffb1c81a&stream_type=all", - "ttl": "10", - "max_retries": "10", - "retry_timeout": 610, - } - """ - url = self.get_url() - params = {'stream_type': stream_type} - box_response = self._session.options(url, params=params) - return box_response.json()['entries'][0] diff --git a/boxsdk/object/file.py b/boxsdk/object/file.py deleted file mode 100644 index c9128a056..000000000 --- a/boxsdk/object/file.py +++ /dev/null @@ -1,893 +0,0 @@ -import json -import os -from datetime import datetime -from typing import TYPE_CHECKING, Optional, Tuple, Union, IO, Iterable, List, Any -from boxsdk.exception import BoxAPIException - -from boxsdk.util.datetime_formatter import normalize_date_to_rfc3339_format -from .item import Item -from ..util.api_call_decorator import api_call -from ..util.default_arg_value import SDK_VALUE_NOT_SET -from ..util.deprecation_decorator import deprecated -from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection -from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection - -if TYPE_CHECKING: - from boxsdk.object.upload_session import UploadSession - from boxsdk.util.chunked_uploader import ChunkedUploader - from boxsdk.object.file_version import FileVersion - from boxsdk.pagination.box_object_collection import BoxObjectCollection - from boxsdk.object.comment import Comment - from boxsdk.object.task import Task - from boxsdk.object.folder import Folder - - -class File(Item): - """Box API endpoint for interacting with files.""" - - _item_type = 'file' - - @api_call - def preflight_check(self, size: int, name: Optional[str] = None) -> Optional[str]: - """ - Make an API call to check if the file can be updated with the new name and size of the file. - Returns an accelerator URL if one is available. - - :param size: - The size of the file in bytes. Specify 0 for unknown file-sizes. - :param name: - The name of the file to be updated. It's optional, if the name is not being changed. - :return: - The Accelerator upload url or None if cannot get the Accelerator upload url. - :raises: - :class:`BoxAPIException` when preflight check fails. - """ - return self._preflight_check( - size=size, - name=name, - file_id=self._object_id, - ) - - @api_call - def create_upload_session( - self, file_size: int, file_name: Optional[str] = None, use_upload_session_urls: bool = True - ) -> 'UploadSession': - """ - Create a new chunked upload session for uploading a new version of the file. - - :param file_size: - The size of the file in bytes that will be uploaded. - :param file_name: - The new name of the file version that will be uploaded. - :param use_upload_session_urls: - The parameter detrermining what urls to use to perform chunked upload. - If True, the urls returned by create_upload_session() endpoint response will be used, - unless a custom API.UPLOAD_URL was set in the config. - If False, the base upload url will be used. - :returns: - A :class:`UploadSession` object. - """ - body_params = { - 'file_id': self.object_id, - 'file_size': file_size, - } - if file_name is not None: - body_params['file_name'] = file_name - url = self.get_url('upload_sessions').replace(self.session.api_config.BASE_API_URL, self.session.api_config.UPLOAD_URL) - response = self._session.post(url, data=json.dumps(body_params)).json() - upload_session = self.translator.translate( - session=self._session, - response_object=response, - ) - # pylint:disable=protected-access - upload_session._use_upload_session_urls = use_upload_session_urls - return upload_session - - @api_call - def get_chunked_uploader( - self, file_path: str, rename_file: bool = False, use_upload_session_urls: bool = True - ) -> 'ChunkedUploader': - # pylint: disable=consider-using-with - """ - Instantiate the chunked upload instance and create upload session with path to file. - - :param file_path: - The local path to the file you wish to upload. - :param rename_file: - Indicates whether the file should be renamed or not. - :param use_upload_session_urls: - The parameter detrermining what urls to use to perform chunked upload. - If True, the urls returned by create_upload_session() endpoint response will be used, - unless a custom API.UPLOAD_URL was set in the config. - If False, the base upload url will be used. - :returns: - A :class:`ChunkedUploader` object. - """ - total_size = os.stat(file_path).st_size - content_stream = open(file_path, 'rb') - file_name = os.path.basename(file_path) if rename_file else None - upload_session = self.create_upload_session(total_size, file_name, use_upload_session_urls) - return upload_session.get_chunked_uploader_for_stream(content_stream, total_size) - - def _get_accelerator_upload_url_for_update(self) -> Optional[str]: - """ - Get Accelerator upload url for updating the file. - - :return: - The Accelerator upload url for updating the file or None if cannot get one - """ - return self._get_accelerator_upload_url(file_id=self._object_id) - - @staticmethod - def _construct_range_header(boundaries: Union[Tuple[int], Tuple[int, int]]) -> str: - """ - Construct the correct value for the Range header, given a closed or open-ended range. - - :param boundaries: - The range of bytes (inclusive) - :returns: - The value for the Range header - :raises ValueError: - """ - if len(boundaries) == 1: - return f'bytes={boundaries[0]}-' - if len(boundaries) == 2: - return f'bytes={boundaries[0]}-{boundaries[1]}' - - raise ValueError('Expected a 1-tuple or 2-tuple for byte range') - - @api_call - def content(self, file_version: Optional['FileVersion'] = None, byte_range: Tuple[int, int] = None) -> bytes: - """ - Get the content of a file on Box. - - :param file_version: - The specific version of the file to retrieve the contents of. - :param byte_range: - A tuple of inclusive byte offsets to download, e.g. (100, 199) to download the second 100 bytes of a file - :returns: - File content as bytes. - """ - url = self.get_url('content') - params = {'version': file_version.object_id} if file_version is not None else None - headers = {'Range': self._construct_range_header(byte_range)} if byte_range is not None else None - box_response = self._session.get(url, expect_json_response=False, params=params, headers=headers) - return box_response.content - - @api_call - def download_to( - self, - writeable_stream: IO[bytes], - file_version: Optional['FileVersion'] = None, - byte_range: Tuple[int, int] = None - ) -> None: - """ - Download the file; write it to the given stream. - - :param writeable_stream: - A file-like object where bytes can be written into. - :param file_version: - The specific version of the file to retrieve the contents of. - :param byte_range: - A tuple of inclusive byte offsets to download, e.g. (100, 199) to download the second 100 bytes of a file - """ - url = self.get_url('content') - params = {'version': file_version.object_id} if file_version is not None else None - headers = {'Range': self._construct_range_header(byte_range)} if byte_range is not None else None - box_response = self._session.get(url, expect_json_response=False, stream=True, params=params, headers=headers) - for chunk in box_response.network_response.response_as_stream.stream(decode_content=True): - writeable_stream.write(chunk) - - @api_call - def get_download_url(self, file_version: Optional['FileVersion'] = None) -> str: - """ - Get the url to download the file. - - :param file_version: - The specific version of the file to retrieve the contents of. - :return: Url to download the file - """ - url = self.get_url('content') - params = {'version': file_version.object_id} if file_version is not None else None - box_response = self._session.get( - url, - params=params, - expect_json_response=False, - allow_redirects=False, - ) - network_response = box_response.network_response - if 'location' not in box_response.headers: - raise BoxAPIException( - status=network_response.status_code, - headers=network_response.headers, - message='Download URL is not present in the response.', - url=url, - method='GET', - network_response=network_response, - ) - - return box_response.headers['location'] - - @api_call - def update_contents_with_stream( - self, - file_stream: IO[bytes], - etag: Optional[str] = None, - preflight_check: bool = False, - preflight_expected_size: int = 0, - upload_using_accelerator: bool = False, - file_name: Optional[str] = None, - content_modified_at: Union[datetime, str] = None, - additional_attributes: Optional[dict] = None, - sha1: Optional[str] = None, - ) -> 'File': - """ - Upload a new version of a file, taking the contents from the given file stream. - - :param file_stream: - The file-like object containing the bytes - :param etag: - If specified, instruct the Box API to update the item only if the current version's etag matches. - :param preflight_check: - If specified, preflight check will be performed before actually uploading the file. - :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. - :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. - :param file_name: - The new name to give the file on Box. - :param content_modified_at: - The A datetime string in a format supported by the dateutil library or datetime object, - which specifies when the file content was last modified. - If no timezone info provided, local timezone will be applied. - :param additional_attributes: - A dictionary containing attributes to add to the file that are not covered by other parameters. - :param sha1: - A sha1 checksum for the new content. - :returns: - A new file object - :raises: - :class:`BoxAPIException` if the specified etag doesn't match the latest version of the file or preflight - check fails. - """ - accelerator_upload_url = None - if preflight_check: - # Preflight check does double duty, returning the accelerator URL if one is available in the response. - accelerator_upload_url = self.preflight_check(size=preflight_expected_size) - elif upload_using_accelerator: - accelerator_upload_url = self._get_accelerator_upload_url_for_update() - - url = self.get_url('content').replace( - self._session.api_config.BASE_API_URL, - self._session.api_config.UPLOAD_URL, - ) - if upload_using_accelerator and accelerator_upload_url: - url = accelerator_upload_url - - attributes = { - 'name': file_name, - 'content_modified_at': normalize_date_to_rfc3339_format(content_modified_at), - } - if additional_attributes: - attributes.update(additional_attributes) - - data = {'attributes': json.dumps(attributes)} - files = {'file': ('unused', file_stream)} - headers = {} - if etag is not None: - headers['If-Match'] = etag - if sha1 is not None: - # The Content-MD5 field accepts sha1 - headers['Content-MD5'] = sha1 - if not headers: - headers = None - file_response = self._session.post( - url, - expect_json_response=False, - data=data, - files=files, - headers=headers, - ).json() - if 'entries' in file_response: - file_response = file_response['entries'][0] - return self.translator.translate( - session=self._session, - response_object=file_response, - ) - - @api_call - def update_contents( - self, - file_path: str, - etag: Optional[str] = None, - preflight_check: bool = False, - preflight_expected_size: int = 0, - upload_using_accelerator: bool = False, - file_name: Optional[str] = None, - content_modified_at: Union[datetime, str] = None, - additional_attributes: Optional[dict] = None, - sha1: Optional[str] = None, - ) -> 'File': - """Upload a new version of a file. The contents are taken from the given file path. - - :param file_path: - The path of the file that should be uploaded. - :param etag: - If specified, instruct the Box API to update the item only if the current version's etag matches. - :param preflight_check: - If specified, preflight check will be performed before actually uploading the file. - :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. - :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. - :param file_name: - The new name to give the file on Box. - :param content_modified_at: - A datetime string in a format supported by the dateutil library or a datetime.datetime object, - which specifies when the file content was last modified. - If no timezone info provided, local timezone will be applied. - :param additional_attributes: - A dictionary containing attributes to add to the file that are not covered by other parameters. - :param sha1: - A sha1 checksum for the new content. - :returns: - A new file object - :raises: - :class:`BoxAPIException` if the specified etag doesn't match the latest version of the file or preflight - check fails. - """ - with open(file_path, 'rb') as file_stream: - return self.update_contents_with_stream( - file_stream, - etag, - preflight_check, - preflight_expected_size=preflight_expected_size, - upload_using_accelerator=upload_using_accelerator, - file_name=file_name, - content_modified_at=content_modified_at, - additional_attributes=additional_attributes, - sha1=sha1, - ) - - @api_call - def lock(self, prevent_download: bool = False, expire_time: Union[datetime, str] = None) -> 'File': - """ - Lock a file, preventing others from modifying (or possibly even downloading) it. - - :param prevent_download: - Whether or not the lock should prevent other users from downloading the file. - :param expire_time: - A datetime string in a format supported by the dateutil library or a datetime.datetime object, - which specifies when the lock should automatically expire, unlocking the file. - If no timezone info provided, local timezone will be applied. - :return: - A new :class:`File` instance reflecting that the file has been locked. - """ - data = { - 'lock': { - 'type': 'lock', - 'is_download_prevented': prevent_download, - } - } - if expire_time is not None: - data['lock']['expires_at'] = normalize_date_to_rfc3339_format(expire_time) - return self.update_info(data=data) - - @api_call - def unlock(self) -> 'File': - """ - Unlock a file, releasing any restrictions that the lock maintained. - - :return: - A new :class:`File` instance reflecting that the file has been unlocked. - """ - data = {'lock': None} - return self.update_info(data=data) - - @api_call - def create_shared_link( - self, - *, - access: Optional[str] = None, - etag: Optional[str] = None, - unshared_at: Union[datetime, str, None] = SDK_VALUE_NOT_SET, - allow_download: Optional[bool] = None, - allow_preview: Optional[bool] = None, - allow_edit: Optional[bool] = None, - password: Optional[str] = None, - vanity_name: Optional[str] = None, - **kwargs: Any - ) -> 'File': - """ - Baseclass override. - - :param access: - Determines who can access the shared link. May be open, company, or collaborators. If no access is - specified, the default access will be used. - :param etag: - If specified, instruct the Box API to create the link only if the current version's etag matches. - :param unshared_at: - The date on which this link should be disabled. May only be set if the current user is not a free user - and has permission to set expiration dates. Takes a datetime string supported by the dateutil library - or a datetime.datetime object. If no timezone info provided, local timezone will be applied. - The time portion can be omitted, which defaults to midnight (00:00:00) on that date. - :param allow_download: - Whether the file being shared can be downloaded when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param allow_preview: - Whether the file being shared can be previewed when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param allow_edit: - Whether the file being shared can be edited when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param password: - The password required to view this link. If no password is specified then no password will be set. - Please notice that this is a premium feature, which might not be available to your app. - :param vanity_name: - Defines a custom vanity name to use in the shared link URL, eg. https://app.box.com/v/my-custom-vanity-name. - If this parameter is None, the standard shared link URL will be used. - :param kwargs: - Used to fulfill the contract of overriden method - :return: - The updated object with shared link. - Returns a new object of the same type, without modifying the original object passed as self. - :raises: :class:`BoxAPIException` if the specified etag doesn't match the latest version of the file. - """ - # pylint:disable=arguments-differ - return super().create_shared_link( - access=access, - etag=etag, - unshared_at=unshared_at, - allow_download=allow_download, - allow_preview=allow_preview, - allow_edit=allow_edit, - password=password, - vanity_name=vanity_name - ) - - @api_call - def get_shared_link( - self, - *, - access: Optional[str] = None, - etag: Optional[str] = None, - unshared_at: Union[datetime, str, None] = SDK_VALUE_NOT_SET, - allow_download: Optional[bool] = None, - allow_preview: Optional[bool] = None, - allow_edit: Optional[bool] = None, - password: Optional[str] = None, - vanity_name: Optional[str] = None, - **kwargs: Any - ) -> 'str': - """ - Baseclass override. - - :param access: - Determines who can access the shared link. May be open, company, or collaborators. If no access is - specified, the default access will be used. - :param etag: - If specified, instruct the Box API to create the link only if the current version's etag matches. - :param unshared_at: - The date on which this link should be disabled. May only be set if the current user is not a free user - and has permission to set expiration dates. Takes a datetime string supported by the dateutil library - or a datetime.datetime object. If no timezone info provided, local timezone will be applied. - The time portion can be omitted, which defaults to midnight (00:00:00) on that date. - :param allow_download: - Whether the file being shared can be downloaded when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param allow_preview: - Whether the file being shared can be previewed when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param allow_edit: - Whether the file being shared can be edited when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param password: - The password required to view this link. If no password is specified then no password will be set. - Please notice that this is a premium feature, which might not be available to your app. - :param vanity_name: - Defines a custom vanity name to use in the shared link URL, eg. https://app.box.com/v/my-custom-vanity-name. - If this parameter is None, the standard shared link URL will be used. - :param kwargs: - Used to fulfill the contract of overriden method - :returns: - The URL of the shared link. - :raises: :class:`BoxAPIException` if the specified etag doesn't match the latest version of the file. - """ - # pylint:disable=arguments-differ - return super().get_shared_link( - access=access, - etag=etag, - unshared_at=unshared_at, - allow_download=allow_download, - allow_preview=allow_preview, - allow_edit=allow_edit, - password=password, - vanity_name=vanity_name - ) - - @api_call - def get_shared_link_download_url( - self, - access: Optional[str] = None, - etag: Optional[str] = None, - unshared_at: Union[datetime, str, None] = SDK_VALUE_NOT_SET, - allow_preview: Optional[bool] = None, - password: Optional[str] = None, - vanity_name: Optional[str] = None - ) -> str: - """ - Get a shared link download url for the file with the given access permissions. - This url is a direct download url for the file. - - :param access: - Determines who can access the shared link. May be open, company, or collaborators. If no access is - specified, the default access will be used. - :param etag: - If specified, instruct the Box API to create the link only if the current version's etag matches. - :param unshared_at: - The date on which this link should be disabled. May only be set if the current user is not a free user - and has permission to set expiration dates. Takes a datetime string supported by the dateutil library - or a datetime.datetime object. If no timezone info provided, local timezone will be applied. - The time portion can be omitted, which defaults to midnight (00:00:00) on that date. - :param allow_preview: - Whether or not the item being shared can be previewed when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param password: - The password required to view this link. If no password is specified then no password will be set. - Please notice that this is a premium feature, which might not be available to your app. - :param vanity_name: - Defines a custom vanity name to use in the shared link URL, eg. https://app.box.com/v/my-custom-vanity-name. - If this parameter is None, the standard shared link URL will be used. - :returns: - The URL of the shared link that allows direct download. - :raises: :class:`BoxAPIException` if the specified etag doesn't match the latest version of the item. - """ - item = self.create_shared_link( - access=access, - etag=etag, - unshared_at=unshared_at, - allow_preview=allow_preview, - password=password, - vanity_name=vanity_name - ) - return item.shared_link['download_url'] # pylint:disable=no-member - - @api_call - def get_comments( - self, - limit: Optional[int] = None, - offset: int = 0, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the comments on the file. - - :param limit: - The maximum number of items to return per page. If not specified, then will use the server-side default. - :param offset: - The index at which to start returning items. - :param fields: - List of fields to request. - :returns: - An iterator of the items in the folder. - """ - return LimitOffsetBasedObjectCollection( - self.session, - self.get_url('comments'), - limit=limit, - fields=fields, - offset=offset, - return_full_pages=False, - ) - - @api_call - def add_comment(self, message: str) -> 'Comment': - """ - Add a comment to the file. - - :param message: - The content of the reply comment. - :return: Added comment - """ - url = self._session.get_url('comments') - comment_class = self._session.translator.get('comment') - data = comment_class.construct_params_from_message(message) - data['item'] = { - 'type': 'file', - 'id': self.object_id - } - box_response = self._session.post(url, data=json.dumps(data)) - response = box_response.json() - return self._session.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def create_task( - self, - message: Optional[str] = None, - due_at: Union[datetime, str] = None, - action: str = 'review', - completion_rule: Optional[str] = None - ) -> 'Task': - """ - Create a task on the given file. - - :param message: - An optional message to include in the task. - :param due_at: - When this task is due. Takes a datetime string supported by the dateutil library - or a datetime.datetime object. If no timezone info provided, local timezone will be applied - :param action: - The type of task the task assignee will be prompted to perform. - Value is one of review,complete - :param completion_rule: - Defines which assignees need to complete this task before the task - is considered completed. - Value is one of all_assignees,any_assignee - :return: - The newly created task - """ - url = self._session.get_url('tasks') - task_attributes = { - 'item': { - 'type': 'file', - 'id': self.object_id - }, - 'action': action, - } - if message is not None: - task_attributes['message'] = message - if due_at is not None: - task_attributes['due_at'] = normalize_date_to_rfc3339_format(due_at) - if completion_rule is not None: - task_attributes['completion_rule'] = completion_rule - box_response = self._session.post(url, data=json.dumps(task_attributes)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def get_tasks(self, fields: Iterable[str] = None) -> 'BoxObjectCollection': - """ - Get the entries in the file tasks. - - :param fields: - List of fields to request. - :returns: - An iterator of the entries in the file tasks - """ - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('tasks'), - limit=None, - marker=None, - fields=fields, - return_full_pages=False, - ) - - @api_call - def get_previous_versions( - self, - limit: Optional[int] = None, - offset: int = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get previous versions of the file. - - :param limit: - The maximum number of items to return per page. If not specified, then will use the server-side default. - :param offset: - The index at which to start returning items. - :param fields: - List of fields to request. - :returns: - An iterator of the previous versions of the file. - """ - return LimitOffsetBasedObjectCollection( - session=self.session, - url=self.get_url('versions'), - limit=limit, - fields=fields, - offset=offset, - return_full_pages=False, - ) - - @api_call - def promote_version(self, file_version: 'FileVersion') -> 'FileVersion': - """ - Promote a file version to become the current version of this file. This will create a new file version - identical to the previous version as the new current version. - - :param file_version: - The file version to promote. - :returns: - The new file version created as the current. - """ - url = self.get_url('versions', 'current') - body = { - 'type': 'file_version', - 'id': file_version.object_id, - } - response = self._session.post(url, data=json.dumps(body)).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def delete_version(self, file_version: 'FileVersion', etag: Optional[str] = None) -> bool: - """ - Delete a specific version of a file. - - :param file_version: - The file version to delete. - :param etag: - If specified, instruct the Box API to update the item only if the current version's etag matches. - :returns: - Whether the operation succeeded. - """ - url = self.get_url('versions', file_version.object_id) - headers = {'If-Match': etag} if etag is not None else None - response = self._session.delete(url, expect_json_response=False, headers=headers) - return response.ok - - @api_call - def get_embed_url(self) -> str: - """ - Get a URL suitable for embedding the file in an iframe in a web application. - - :returns: - The embed URL. - """ - url = self.get_url() - params = {'fields': 'expiring_embed_link'} - response = self._session.get(url, params=params).json() - return response['expiring_embed_link']['url'] - - @api_call - def get_representation_info(self, rep_hints: Optional[str] = None) -> List[dict]: - """ - Get information about the representations available for a file. - - :param rep_hints: - A formatted string describing which representations are desired. - :returns: - The representation information - """ - url = self.get_url() - params = {'fields': 'representations'} - headers = {'X-Rep-Hints': rep_hints} if rep_hints is not None else None - response = self._session.get(url, params=params, headers=headers).json() - return response['representations']['entries'] - - @deprecated('Use get_thumbnail_representation') - @api_call - def get_thumbnail( - self, - extension: str = 'png', - min_width: Optional[int] = None, - min_height: Optional[int] = None, - max_width: Optional[int] = None, - max_height: Optional[int] = None - ) -> bytes: - """ - Retrieve a thumbnail image for the file. - - :param extension: - The file extension for the thumbnail, e.g. 'png' or 'jpg' - :param min_width: - The minimum width required for the thumbnail image - :param min_height: - The minimum height required for the thumbnail image - :param max_width: - The maximum width required for the thumbnail image - :param max_height: - The maximum height required for the thumbnail image - :returns: - The file contents of the thumbnail image - """ - url = self.get_url('thumbnail.' + extension) - params = {} - if min_width is not None: - params['min_width'] = min_width - if min_height is not None: - params['min_height'] = min_height - if max_width is not None: - params['max_width'] = max_width - if max_height is not None: - params['max_height'] = max_height - - response = self._session.get(url, params=params, expect_json_response=False) - return response.content - - @api_call - def get_thumbnail_representation(self, dimensions: str, extension: str = 'png') -> bytes: - """ - Retrieve a thumbnail image for the file. - - :param dimensions: - The width by height size of this representation in pixels (e.g. '92x92') - :param extension: - The file extension for the thumbnail, e.g. 'png' or 'jpg' - :returns: - The file contents of the thumbnail image - """ - rep_hints = f'[{extension}?dimensions={dimensions}]' - representations = self.get_representation_info(rep_hints) - if representations: - representation = representations[0] - if representation['status'].get('code') in ('error_conversion_failed', 'error_password_protected'): - return b'' - url = representation['content']['url_template'] - url = url.replace('{+asset_path}', '') - response = self._session.get(url, expect_json_response=False) - return response.content - return b'' - - @api_call - def copy( - self, - *, - parent_folder: 'Folder', - name: Optional[str] = None, - file_version: 'FileVersion' = None, - **_kwargs - ) -> 'File': - # pylint: disable=arguments-differ - """Copy the item to the given folder. - - :param parent_folder: - The folder to which the item should be copied. - :param name: - A new name for the item, in case there is already another item in the new parent folder with the same name. - :param file_version: - A specific version of the file to copy - :returns: - The copy of the file - """ - # pylint: disable=arguments-differ - url = self.get_url('copy') - data = { - 'parent': {'id': parent_folder.object_id} - } - if name is not None: - data['name'] = name - if file_version is not None: - data['version'] = file_version.object_id - box_response = self._session.post(url, data=json.dumps(data)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def set_disposition_at(self, date_time: Union[datetime, str]) -> 'File': - """ - Modifies the retention expiration timestamp for the given file. This date can't be shortened once set on a file. - - :param date_time: - A datetime string in a format supported by the dateutil library or a datetime.datetime object. - If no timezone info provided, local timezone will be applied. - :return: - Updated 'File' object - """ - data = {'disposition_at': normalize_date_to_rfc3339_format(date_time)} - return self.update_info(data=data) diff --git a/boxsdk/object/file_request.py b/boxsdk/object/file_request.py deleted file mode 100644 index 1ae4c90ff..000000000 --- a/boxsdk/object/file_request.py +++ /dev/null @@ -1,78 +0,0 @@ -import json -from datetime import datetime -from typing import TYPE_CHECKING, Optional, Union - -from boxsdk.util.datetime_formatter import normalize_date_to_rfc3339_format -from boxsdk.util.text_enum import TextEnum - -from ..util.api_call_decorator import api_call -from .base_object import BaseObject - -if TYPE_CHECKING: - from boxsdk.object.folder import Folder - - -class StatusState(TextEnum): - """An enum of possible status states""" - ACTIVE = 'active' - INACTIVE = 'inactive' - - -class FileRequest(BaseObject): - """Represents the file request.""" - _item_type = 'file_request' - - @api_call - def copy( - self, - *, - folder: 'Folder', - description: Optional[str] = None, - expires_at: Union[datetime, str] = None, - require_description: Optional[bool] = None, - require_email: Optional[bool] = None, - status: Optional[str] = None, - title: Optional[str] = None, - **_kwargs - ) -> 'FileRequest': - """Copy an existing file request already present on one folder, and applies it to another folder. - - :param description: - A new description for the file request. - :param title: - A new title for the file request. - :param expires_at: - A expiration time for file request which no longer accepts new files. - :param folder: - The folder to which the file request will be saved. - :param require_description: - A flag indicating whether the file submitted must have a description. - :param require_email: - A flag indicating whether the file submitted must have sender email. - :param status: - The status of the file request. - :returns: - The copy of the file request - """ - url = self.get_url('copy') - data = { - 'folder': {'id': folder.object_id, 'type': folder.object_type}, - } - if description is not None: - data['description'] = description - if title is not None: - data['title'] = title - if expires_at is not None: - data['expires_at'] = normalize_date_to_rfc3339_format(expires_at) - if require_description is not None: - data['is_description_required'] = require_description - if require_email is not None: - data['is_email_required'] = require_email - if status is not None: - data['status'] = status - box_response = self._session.post(url, data=json.dumps(data)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) diff --git a/boxsdk/object/file_version.py b/boxsdk/object/file_version.py deleted file mode 100644 index 7299d99df..000000000 --- a/boxsdk/object/file_version.py +++ /dev/null @@ -1,6 +0,0 @@ -from .base_object import BaseObject - - -class FileVersion(BaseObject): - """Represents a Box file version.""" - _item_type = 'file_version' diff --git a/boxsdk/object/file_version_retention.py b/boxsdk/object/file_version_retention.py deleted file mode 100644 index 08f0473fe..000000000 --- a/boxsdk/object/file_version_retention.py +++ /dev/null @@ -1,6 +0,0 @@ -from .base_object import BaseObject - - -class FileVersionRetention(BaseObject): - """Represents a Box file version retention.""" - _item_type = 'file_version_retention' diff --git a/boxsdk/object/folder.py b/boxsdk/object/folder.py deleted file mode 100644 index d20f5b008..000000000 --- a/boxsdk/object/folder.py +++ /dev/null @@ -1,783 +0,0 @@ -import json -import os -from datetime import datetime -from typing import TYPE_CHECKING, Any, Tuple, Optional, Iterable, IO, Union - -from boxsdk.object.group import Group -from boxsdk.object.item import Item -from boxsdk.object.user import User -from boxsdk.pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection -from boxsdk.pagination.marker_based_object_collection import MarkerBasedObjectCollection -from boxsdk.util.api_call_decorator import api_call -from boxsdk.util.datetime_formatter import normalize_date_to_rfc3339_format -from boxsdk.util.default_arg_value import SDK_VALUE_NOT_SET -from boxsdk.util.text_enum import TextEnum - -if TYPE_CHECKING: - from boxsdk.object.upload_session import UploadSession - from boxsdk.util.chunked_uploader import ChunkedUploader - from boxsdk.object.file import File - from boxsdk.object.collaboration import CollaborationRole, Collaboration - from boxsdk.object.web_link import WebLink - from boxsdk.object.enterprise import Enterprise - from boxsdk.pagination.box_object_collection import BoxObjectCollection - from boxsdk.object.metadata_template import MetadataTemplate - from boxsdk.object.metadata_cascade_policy import MetadataCascadePolicy - from boxsdk.object.folder_lock import FolderLock - - -class FolderSyncState(TextEnum): - """An enum of all possible values of a folder's ``sync_state`` attribute. - - The value of the ``sync_state`` attribute determines whether the folder - will be synced by sync clients. - """ - IS_SYNCED = 'synced' - NOT_SYNCED = 'not_synced' - PARTIALLY_SYNCED = 'partially_synced' - - -class _CollaborationType(TextEnum): - """The type of a collaboration""" - USER = 'user' - GROUP = 'group' - - -class _Collaborator: - """This helper class represents a collaborator on Box. A Collaborator can be a User, Group, or an email address""" - - def __init__(self, collaborator: Any): - if isinstance(collaborator, User): - self._setup(user=collaborator) - elif isinstance(collaborator, Group): - self._setup(group=collaborator) - elif isinstance(collaborator, str): - self._setup(email_address=collaborator) - else: - raise TypeError('Collaborator must be User, Group, or unicode string') - - def _setup(self, user: User = None, group: Group = None, email_address: str = None) -> None: - """ - :param user: - The Box user if applicable - :param group: - The Box group if applicable - :param email_address: - The email address of the user if not a user of Box - """ - self._type = _CollaborationType.GROUP if group else _CollaborationType.USER - id_object = user or group - if id_object: - self._key = 'id' - self._identifier = id_object.object_id - else: - self._key = 'login' - self._identifier = email_address - - @property - def access(self) -> Tuple[str, str]: - """Return a tuple for how to access collaborator - - The first element is the key for access, the second is the value - """ - return self._key, self._identifier - - @property - def type(self) -> str: - """Return the type of collaborator (user or group)""" - return self._type - - -class Folder(Item): - """Box API endpoint for interacting with folders.""" - - _item_type = 'folder' - - @api_call - def preflight_check(self, size: int, name: str) -> Optional[str]: - """ - Make an API call to check if a new file with given name and size can be uploaded to this folder. - Returns an accelerator URL if one is available. - - :param size: - The size of the file in bytes. Specify 0 for unknown file-sizes. - :param name: - The name of the file to be uploaded. - :return: - The Accelerator upload url or None if cannot get the Accelerator upload url. - :raises: - :class:`BoxAPIException` when preflight check fails. - """ - return self._preflight_check( - size=size, - name=name, - parent_id=self._object_id, - ) - - @api_call - def create_upload_session(self, file_size: int, file_name: str, use_upload_session_urls: bool = True) -> 'UploadSession': - """ - Creates a new chunked upload session for upload a new file. - - :param file_size: - The size of the file in bytes that will be uploaded. - :param file_name: - The name of the file that will be uploaded. - :param use_upload_session_urls: - The parameter detrermining what urls to use to perform chunked upload. - If True, the urls returned by create_upload_session() endpoint response will be used, - unless a custom API.UPLOAD_URL was set in the config. - If False, the base upload url will be used. - :returns: - A :class:`UploadSession` object. - """ - url = f'{self.session.api_config.UPLOAD_URL}/files/upload_sessions' - body_params = { - 'folder_id': self.object_id, - 'file_size': file_size, - 'file_name': file_name, - } - response = self._session.post(url, data=json.dumps(body_params)).json() - upload_session = self.translator.translate( - session=self._session, - response_object=response, - ) - # pylint:disable=protected-access - upload_session._use_upload_session_urls = use_upload_session_urls - return upload_session - - @api_call - def get_chunked_uploader( - self, file_path: str, file_name: Optional[str] = None, use_upload_session_urls: bool = True - ) -> 'ChunkedUploader': - # pylint: disable=consider-using-with - """ - Instantiate the chunked upload instance and create upload session with path to file. - - :param file_path: - The local path to the file you wish to upload. - :param file_name: - The name with extention of the file that will be uploaded, e.g. new_file_name.zip. - If not specified, the name from the local system is used. - :param use_upload_session_urls: - The parameter detrermining what urls to use to perform chunked upload. - If True, the urls returned by create_upload_session() endpoint response will be used, - unless a custom API.UPLOAD_URL was set in the config. - If False, the base upload url will be used. - :returns: - A :class:`ChunkedUploader` object. - """ - total_size = os.stat(file_path).st_size - upload_file_name = file_name if file_name else os.path.basename(file_path) - content_stream = open(file_path, 'rb') - - try: - upload_session = self.create_upload_session(total_size, upload_file_name, use_upload_session_urls) - return upload_session.get_chunked_uploader_for_stream(content_stream, total_size) - except Exception: - content_stream.close() - raise - - def _get_accelerator_upload_url_fow_new_uploads(self) -> Optional[str]: - """ - Get Accelerator upload url for uploading new files. - - :return: - The Accelerator upload url or None if cannot get one - """ - return self._get_accelerator_upload_url() - - @api_call - def get_items( - self, - limit: Optional[int] = None, - offset: int = 0, - marker: Optional[str] = None, - use_marker: bool = False, - sort: Optional[str] = None, - direction: Optional[str] = None, - fields: Iterable[str] = None - ) -> Iterable[Item]: - """ - Get the items in a folder. - - :param limit: - The maximum number of items to return per page. If not specified, then will use the server-side default. - :param offset: - The index at which to start returning items when using offset-based pagin. - :param marker: - The paging marker to start returning items from when using marker-based paging. - :param use_marker: - Whether to use marker-based paging instead of offset-based paging, defaults to False. - :param sort: - Item field to sort results on: 'id', 'name', or 'date'. - :param direction: - Sort direction for the items returned. - :param fields: - List of fields to request. - :returns: - The collection of items in the folder. - """ - url = self.get_url('items') - additional_params = {} - if limit is not None: - additional_params['limit'] = limit - if sort: - additional_params['sort'] = sort - if direction: - additional_params['direction'] = direction - - if use_marker: - additional_params['usemarker'] = True - return MarkerBasedObjectCollection( - url=url, - session=self._session, - limit=limit, - marker=marker, - fields=fields, - additional_params=additional_params, - return_full_pages=False, - ) - - return LimitOffsetBasedObjectCollection( - url=url, - session=self._session, - limit=limit, - offset=offset, - fields=fields, - additional_params=additional_params, - return_full_pages=False, - ) - - @api_call - def upload_stream( - self, - file_stream: IO[bytes], - file_name: str, - file_description: Optional[str] = None, - preflight_check: bool = False, - preflight_expected_size: int = 0, - upload_using_accelerator: bool = False, - content_created_at: Union[datetime, str] = None, - content_modified_at: Union[datetime, str] = None, - additional_attributes: Optional[dict] = None, - sha1: Optional[str] = None, - etag: Optional[str] = None, - stream_file_content: bool = True, - ) -> 'File': - """ - 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 - :param file_name: - The name to give the file on Box. - :param file_description: - The description to give the file on Box. - :param preflight_check: - If specified, preflight check will be performed before actually uploading the file. - :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. - :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. - :param content_created_at: - A datetime string in a format supported by the dateutil library or a datetime.datetime object, - which specifies when the file was created. If no timezone info provided, local timezone will be applied. - :param content_modified_at: - A datetime string in a format supported by the dateutil library or a datetime.datetime object, which - specifies when the file was last modified. If no timezone info provided, local timezone will be applied. - :param additional_attributes: - A dictionary containing attributes to add to the file that are not covered by other parameters. - :param sha1: - A sha1 checksum for the file. - :param etag: - If specified, instruct the Box API to update the item only if the current version's etag matches. - :param stream_file_content: - If True, the upload will be performed as a stream request. If False, the file will be read into memory - before being uploaded, but this may be required if using some proxy servers to handle redirects correctly. - :returns: - The newly uploaded file. - """ - accelerator_upload_url = None - if preflight_check: - # Preflight check does double duty, returning the accelerator URL if one is available in the response. - accelerator_upload_url = self.preflight_check(size=preflight_expected_size, name=file_name) - elif upload_using_accelerator: - accelerator_upload_url = self._get_accelerator_upload_url_fow_new_uploads() - - url = f'{self._session.api_config.UPLOAD_URL}/files/content' - if upload_using_accelerator and accelerator_upload_url: - url = accelerator_upload_url - - attributes = { - 'name': file_name, - 'parent': {'id': self._object_id}, - 'description': file_description, - 'content_created_at': normalize_date_to_rfc3339_format(content_created_at), - 'content_modified_at': normalize_date_to_rfc3339_format(content_modified_at), - } - if additional_attributes: - attributes.update(additional_attributes) - - data = {'attributes': json.dumps(attributes)} - files = { - 'file': ('unused', file_stream), - } - headers = {} - if etag is not None: - headers['If-Match'] = etag - if sha1 is not None: - # The Content-MD5 field accepts sha1 - headers['Content-MD5'] = sha1 - if not headers: - headers = None - file_response = self._session.post( - url, data=data, files=files, expect_json_response=False, headers=headers, stream_file_content=stream_file_content, - ).json() - if 'entries' in file_response: - file_response = file_response['entries'][0] - return self.translator.translate( - session=self._session, - response_object=file_response, - ) - - @api_call - def upload( - self, - file_path: str = None, - file_name: str = None, - file_description: Optional[str] = None, - preflight_check: bool = False, - preflight_expected_size: int = 0, - upload_using_accelerator: bool = False, - content_created_at: Union[datetime, str] = None, - content_modified_at: Union[datetime, str] = None, - additional_attributes: Optional[dict] = None, - sha1: Optional[str] = None, - etag: Optional[str] = None, - stream_file_content: bool = True, - ) -> 'File': - """ - Upload a file to the folder. - The contents are taken from the given file path, and it will have the given name. - If file_name is not specified, the uploaded file will take its name from file_path. - - :param file_path: - The file path of the file to upload to Box. - :param file_name: - The name to give the file on Box. If None, then use the leaf name of file_path - :param file_description: - The description to give the file on Box. If None, then no description will be set. - :param preflight_check: - If specified, preflight check will be performed before actually uploading the file. - :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. - :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. - :param content_created_at: - A datetime string in a format supported by the dateutil library or a datetime.datetime object, - which specifies when the file was created. If no timezone info provided, local timezone will be applied. - :param content_modified_at: - A datetime string in a format supported by the dateutil library or a datetime.datetime object, which - specifies when the file was last modified.If no timezone info provided, local timezone will be applied. - :param additional_attributes: - A dictionary containing attributes to add to the file that are not covered by other parameters. - :param sha1: - A sha1 checksum for the new content. - :param etag: - If specified, instruct the Box API to update the item only if the current version's etag matches. - :param stream_file_content: - If True, the upload will be performed as a stream request. If False, the file will be read into memory - before being uploaded, but this may be required if using some proxy servers to handle redirects correctly. - :returns: - The newly uploaded file. - """ - if file_name is None: - file_name = os.path.basename(file_path) - with open(file_path, 'rb') as file_stream: - return self.upload_stream( - file_stream, - file_name, - file_description, - preflight_check, - preflight_expected_size=preflight_expected_size, - upload_using_accelerator=upload_using_accelerator, - content_created_at=content_created_at, - content_modified_at=content_modified_at, - additional_attributes=additional_attributes, - sha1=sha1, - etag=etag, - stream_file_content=stream_file_content, - ) - - @api_call - def create_subfolder(self, name: str) -> 'Folder': - """ - Create a subfolder with the given name in the folder. - - :param name: - The name of the new folder - """ - url = self.get_type_url() - data = { - 'name': name, - 'parent': { - 'id': self._object_id, - } - } - box_response = self._session.post(url, data=json.dumps(data)) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def update_sync_state(self, sync_state: FolderSyncState) -> 'Folder': - """Update the ``sync_state`` attribute of this folder. - - Change whether this folder will be synced by sync clients. - - :param sync_state: - The desired sync state of this folder. - Must be a member of the `FolderSyncState` enum. - :return: - A new :class:`Folder` instance with updated information reflecting the new sync state. - """ - data = { - 'sync_state': sync_state, - } - return self.update_info(data=data) - - @api_call - def create_shared_link( - self, - *, - access: Optional[str] = None, - etag: Optional[str] = None, - unshared_at: Union[datetime, str, None] = SDK_VALUE_NOT_SET, - allow_download: Optional[bool] = None, - allow_preview: Optional[bool] = None, - password: Optional[str] = None, - vanity_name: Optional[str] = None, - **kwargs: Any - ) -> 'Folder': - """ - Baseclass override. - - :param access: - Determines who can access the shared link. May be open, company, or collaborators. If no access is - specified, the default access will be used. - :param etag: - If specified, instruct the Box API to create the link only if the current version's etag matches. - :param unshared_at: - The date on which this link should be disabled. May only be set if the current user is not a free user - and has permission to set expiration dates. Takes a datetime string supported by the dateutil library - or a datetime.datetime object. If no timezone info provided, local timezone will be applied. - The time portion can be omitted, which defaults to midnight (00:00:00) on that date. - :param allow_download: - Whether the folder being shared can be downloaded when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param allow_preview: - Whether the folder being shared can be previewed when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param password: - The password required to view this link. If no password is specified then no password will be set. - Please notice that this is a premium feature, which might not be available to your app. - :param vanity_name: - Defines a custom vanity name to use in the shared link URL, eg. https://app.box.com/v/my-custom-vanity-name. - If this parameter is None, the standard shared link URL will be used. - :param kwargs: - Used to fulfill the contract of overriden method - :return: - The updated object with shared link. - Returns a new object of the same type, without modifying the original object passed as self. - :raises: :class:`BoxAPIException` if the specified etag doesn't match the latest version of the folder. - """ - # pylint:disable=arguments-differ - return super().create_shared_link( - access=access, - etag=etag, - unshared_at=unshared_at, - allow_download=allow_download, - allow_preview=allow_preview, - password=password, - vanity_name=vanity_name - ) - - @api_call - def get_shared_link( - self, - *, - access: Optional[str] = None, - etag: Optional[str] = None, - unshared_at: Union[datetime, str, None] = SDK_VALUE_NOT_SET, - allow_download: Optional[bool] = None, - allow_preview: Optional[bool] = None, - password: Optional[str] = None, - vanity_name: Optional[str] = None, - **kwargs: Any - ) -> 'str': - """ - Baseclass override. - - :param access: - Determines who can access the shared link. May be open, company, or collaborators. If no access is - specified, the default access will be used. - :param etag: - If specified, instruct the Box API to create the link only if the current version's etag matches. - :param unshared_at: - The date on which this link should be disabled. May only be set if the current user is not a free user - and has permission to set expiration dates. Takes a datetime string supported by the dateutil library - or a datetime.datetime object. If no timezone info provided, local timezone will be applied. - The time portion can be omitted, which defaults to midnight (00:00:00) on that date. - :param allow_download: - Whether the folder being shared can be downloaded when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param allow_preview: - Whether the folder being shared can be previewed when accessed via the shared link. - If this parameter is None, the default setting will be used. - :param password: - The password required to view this link. If no password is specified then no password will be set. - Please notice that this is a premium feature, which might not be available to your app. - :param vanity_name: - Defines a custom vanity name to use in the shared link URL, eg. https://app.box.com/v/my-custom-vanity-name. - If this parameter is None, the standard shared link URL will be used. - :param kwargs: - Used to fulfill the contract of overriden method - :returns: - The URL of the shared link. - :raises: :class:`BoxAPIException` if the specified etag doesn't match the latest version of the folder. - """ - # pylint:disable=arguments-differ - return super().get_shared_link( - access=access, - etag=etag, - unshared_at=unshared_at, - allow_download=allow_download, - allow_preview=allow_preview, - password=password, - vanity_name=vanity_name - ) - - @api_call - def add_collaborator( - self, - collaborator: Union[User, Group, str], - role: 'CollaborationRole', - notify: bool = False, - can_view_path: bool = False - ) -> 'Collaboration': - """Add a collaborator to the folder - - :param collaborator: - collaborator to add. It may be a User, Group, or email address (unicode string) - :param role: - The collaboration role - :param notify: - Whether to send a notification email to the collaborator - :param can_view_path: - Whether view path collaboration feature is enabled or not. Note - only - folder owners can create collaborations with can_view_path. - :return: - The new collaboration - """ - collaborator_helper = _Collaborator(collaborator) - url = self._session.get_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, - } - body_params = { - 'item': item, - 'accessible_by': accessible_by, - 'role': role, - } - if can_view_path: - body_params['can_view_path'] = True - data = json.dumps(body_params) - params = {'notify': notify} - box_response = self._session.post(url, expect_json_response=True, data=data, params=params) - collaboration_response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=collaboration_response, - ) - - @api_call - def create_web_link( - self, - target_url: str, - name: Optional[str] = None, - description: Optional[str] = None - ) -> 'WebLink': - """ - Create a WebLink with a given url. - - :param target_url: - The url the web link points to. - :param name: - The name of the web link. Optional, the API will give it a default if not specified. - :param description: - Description of the web link - :return: - A :class:`WebLink` object. - """ - url = self._session.get_url('web_links') - web_link_attributes = { - 'url': target_url, - 'parent': { - 'id': self.object_id - } - } - if name is not None: - web_link_attributes['name'] = name - if description is not None: - web_link_attributes['description'] = description - response = self._session.post(url, data=json.dumps(web_link_attributes)).json() - return self.translator.translate( - session=self._session, - response_object=response - ) - - @api_call - def delete( - self, - *, - recursive: bool = True, - etag: Optional[str] = None, - **kwargs - ) -> bool: - """Base class override. Delete the folder. - - :param recursive: - Whether or not the folder should be deleted if it isn't empty. - :param etag: - If specified, instruct the Box API to delete the folder only if the current version's etag matches. - :returns: - Whether or not the update was successful. - :raises: :class:`BoxAPIException` if the specified etag doesn't match the latest version of the folder. - """ - # pylint:disable=arguments-differ,arguments-renamed - return super().delete(params={'recursive': recursive}, etag=etag, **kwargs) - - @api_call - def get_metadata_cascade_policies( - self, - owner_enterprise: 'Enterprise' = None, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the metadata cascade policies current applied to the folder. - - :param owner_enterprise: - Which enterprise's metadata templates to get cascade policies for. This defauls to the current - enterprise. - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from. - :param fields: - List of fields to request. - :returns: - An iterator of the cascade policies attached on the folder. - """ - additional_params = { - 'folder_id': self.object_id, - } - if owner_enterprise is not None: - additional_params['owner_enterprise_id'] = owner_enterprise.object_id - - return MarkerBasedObjectCollection( - url=self._session.get_url('metadata_cascade_policies'), - session=self._session, - additional_params=additional_params, - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - @api_call - def cascade_metadata(self, metadata_template: 'MetadataTemplate') -> 'MetadataCascadePolicy': - """ - Create a metadata cascade policy to apply the metadata instance values on the folder for the given metadata - template to all files within the folder. - - :param metadata_template: - The metadata template to cascade values for - :returns: - The created metadata cascade policy - """ - url = self._session.get_url('metadata_cascade_policies') - - body = { - 'folder_id': self.object_id, - 'scope': metadata_template.scope, - 'templateKey': metadata_template.template_key, - } - - response = self._session.post(url, data=json.dumps(body)).json() - return self.translator.translate(self._session, response) - - @api_call - def create_lock(self) -> 'FolderLock': - """ - Creates a folder lock on a folder, preventing it from being moved and/or deleted. - - :returns: - The created folder lock - """ - url = self._session.get_url('folder_locks') - - body = { - 'folder': { - 'type': 'folder', - 'id': self.object_id - }, - 'locked_operations': { - 'move': True, - 'delete': True - } - } - - response = self._session.post(url, data=json.dumps(body)).json() - return self.translator.translate(self._session, response) - - @api_call - def get_locks(self) -> 'BoxObjectCollection': - """ - Lists all folder locks for a given folder. - - :returns: - The collection of locks for a folder. - """ - url = self._session.get_url('folder_locks') - - additional_params = { - 'folder_id': self.object_id, - } - - return MarkerBasedObjectCollection( - url=url, - session=self._session, - additional_params=additional_params, - return_full_pages=False, - ) diff --git a/boxsdk/object/folder_lock.py b/boxsdk/object/folder_lock.py deleted file mode 100644 index dd646a888..000000000 --- a/boxsdk/object/folder_lock.py +++ /dev/null @@ -1,7 +0,0 @@ -from .base_object import BaseObject - - -class FolderLock(BaseObject): - """Represents the folder lock""" - - _item_type = 'folder_lock' diff --git a/boxsdk/object/group.py b/boxsdk/object/group.py deleted file mode 100644 index 93d3d689a..000000000 --- a/boxsdk/object/group.py +++ /dev/null @@ -1,116 +0,0 @@ -import json -from typing import Optional, Iterable, TYPE_CHECKING - -from boxsdk.util.text_enum import TextEnum -from .base_object import BaseObject -from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection -from ..util.api_call_decorator import api_call -from ..util.default_arg_value import SDK_VALUE_NOT_SET - -if TYPE_CHECKING: - from boxsdk.object.group_membership import GroupMembership - from boxsdk.object.user import User - from boxsdk.pagination.box_object_collection import BoxObjectCollection - - -class GroupRole(TextEnum): - """The role in the group.""" - ADMIN = 'admin' - MEMBER = 'member' - - -class Group(BaseObject): - """Represents a Box group.""" - - _item_type = 'group' - - @api_call - def get_memberships( - self, - limit: Optional[int] = None, - offset: Optional[int] = None, - fields: Optional[Iterable[str]] = None - ) -> Iterable['GroupMembership']: - """ - Get the membership records for the group, which indicate which users are included in the group. - - :param limit: - The maximum number of items to return in a page. - :param offset: - The index at which to begin. - :param fields: - List of fields to request. If None, will return the default fields for the object. - :returns: - The collection of membership objects for the group. - """ - return LimitOffsetBasedObjectCollection( - self._session, - url=self.get_url('memberships'), - limit=limit, - offset=offset, - fields=fields, - return_full_pages=False, - ) - - @api_call - def add_member( - self, - user: 'User', - role: GroupRole = GroupRole.MEMBER, - configurable_permissions: Optional[str] = SDK_VALUE_NOT_SET - ) -> 'GroupMembership': - """ - Add the given user to this group under the given role - - :param user: - The User to add to the group. - :param role: - The role for the user. - :param configurable_permissions: - This is a group level permission that is configured for Group members with - admin role only. - :returns: - The new GroupMembership instance. - """ - url = self._session.get_url('group_memberships') - body_attributes = { - 'user': {'id': user.object_id}, - 'group': {'id': self.object_id}, - 'role': role, - } - if configurable_permissions is not SDK_VALUE_NOT_SET: - body_attributes['configurable_permissions'] = configurable_permissions - box_response = self._session.post(url, data=json.dumps(body_attributes)) - response = box_response.json() - return self.translator.translate(self._session, response) - - @api_call - def get_collaborations( - self, - limit: Optional[int] = None, - offset: Optional[int] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the entries in the collaboration for the group using limit-offset paging. - - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param offset: - The offset of the item at which to begin the response. - :param fields: - List of fields to request. - :returns: - An iterator of the entries in the collaboration for the group. - """ - additional_params = {} - if fields is not None: - additional_params['fields'] = ','.join(fields) - return LimitOffsetBasedObjectCollection( - session=self._session, - url=self.get_url('collaborations'), - additional_params=additional_params, - limit=limit, - offset=offset, - return_full_pages=False, - ) diff --git a/boxsdk/object/group_membership.py b/boxsdk/object/group_membership.py deleted file mode 100644 index b439851cb..000000000 --- a/boxsdk/object/group_membership.py +++ /dev/null @@ -1,7 +0,0 @@ -from .base_object import BaseObject - - -class GroupMembership(BaseObject): - """Represents a Box group_membership, which relates a user & group under a specific role.""" - - _item_type = 'group_membership' diff --git a/boxsdk/object/invite.py b/boxsdk/object/invite.py deleted file mode 100644 index 4f9a701be..000000000 --- a/boxsdk/object/invite.py +++ /dev/null @@ -1,7 +0,0 @@ -from .base_object import BaseObject - - -class Invite(BaseObject): - """Represents the invite""" - - _item_type = 'invite' diff --git a/boxsdk/object/item.py b/boxsdk/object/item.py deleted file mode 100644 index e79aad1c0..000000000 --- a/boxsdk/object/item.py +++ /dev/null @@ -1,450 +0,0 @@ -import json -from typing import TYPE_CHECKING, Optional, Iterable, Any, Union - -from boxsdk.util.text_enum import TextEnum -from .base_item import BaseItem -from ..exception import BoxAPIException -from .metadata import Metadata -from ..util.api_call_decorator import api_call -from ..pagination.marker_based_dict_collection import MarkerBasedDictCollection -from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection - -if TYPE_CHECKING: - from boxsdk.object.watermark import Watermark - from boxsdk.object.group import Group - from boxsdk.object.user import User - from boxsdk.object.collaboration import Collaboration - from boxsdk.pagination.box_object_collection import BoxObjectCollection - - -class ClassificationType(TextEnum): - """An enum of possible classification types""" - PUBLIC = 'Public' - INTERNAL = 'Internal' - CONFIDENTIAL = 'Confidential' - NONE = 'None' - - -class Item(BaseItem): - """Box API endpoint for interacting with files and folders.""" - - _classification_template_key = 'securityClassification-6VMVochwUWo' - - def _get_accelerator_upload_url(self, file_id: Optional[str] = None) -> Optional[str]: - """ - Make an API call to get the Accelerator upload url for either upload a new file or updating an existing file. - - :param file_id: - Box id of the file to be uploaded. Not required for new file uploads. - :return: - The Accelerator upload url or None if cannot get the Accelerator upload url. - """ - if file_id: - self.validate_item_id(file_id) - endpoint = f'{file_id}/content' if file_id else 'content' - url = f'{self._session.api_config.BASE_API_URL}/files/{endpoint}' - try: - response_json = self._session.options( - url=url, - expect_json_response=True, - ).json() - return response_json.get('upload_url', None) - except BoxAPIException: - return None - - def _preflight_check( - self, size: int, - name: str = None, - file_id: str = None, - parent_id: str = None - ) -> Optional[str]: - """ - Make an API call to check if certain file can be uploaded to Box or not. - (https://developer.box.com/en/guides/uploads/check/) - - Returns an accelerator URL if available, which comes for free in the response. - - :param size: - The size of the file to be uploaded in bytes. Specify 0 for unknown file sizes. - :param name: - The name of the file to be uploaded. This is optional if `file_id` is specified, - but required for new file uploads. - :param file_id: - Box id of the file to be uploaded. Not required for new file uploads. - :param parent_id: - The ID of the parent folder. Required only for new file uploads. - :return: - The Accelerator upload url or None if cannot get the Accelerator upload url. - :raises: - :class:`BoxAPIException` when preflight check fails. - """ - if file_id: - self.validate_item_id(file_id) - endpoint = f'{file_id}/content' if file_id else 'content' - url = f'{self._session.api_config.BASE_API_URL}/files/{endpoint}' - data = {'size': size} - if name: - data['name'] = name - if parent_id: - data['parent'] = {'id': parent_id} - - response_json = self._session.options( - url=url, - expect_json_response=True, - data=json.dumps(data), - ).json() - return response_json.get('upload_url', None) - - @api_call - def update_info(self, *, data: dict, etag: Optional[str] = None, **kwargs: Any) -> 'Item': - """ - Baseclass override. - :param data: - The updated information about this object. - Must be JSON serializable. - Update the object attributes in data.keys(). The semantics of the - values depends on the the type and attributes of the object being - updated. For details on particular semantics, refer to the Box - developer API documentation . - :param etag: - If specified, instruct the Box API to perform the update only if - the current version's etag matches. - :return: - The updated object. - Return a new object of the same type, without modifying the original object passed as self. - Construct the new object with all the default attributes that are returned from the endpoint. - """ - # pylint:disable=arguments-differ - self.validate_item_id(self._object_id) - headers = {'If-Match': etag} if etag is not None else None - return super().update_info(data=data, headers=headers, **kwargs) - - @api_call - def get(self, *, fields: Iterable[str] = None, etag: Optional[str] = None, **kwargs) -> 'Item': - """ - Base class override. - - :param fields: - List of fields to request. - :param etag: - If specified, instruct the Box API to get the info only if the current version's etag doesn't match. - :returns: - Information about the file or folder. - :raises: :class:`BoxAPIException` if the specified etag matches the latest version of the item. - """ - # pylint:disable=arguments-differ,arguments-renamed - self.validate_item_id(self._object_id) - headers = {'If-None-Match': etag} if etag is not None else None - return super().get(fields=fields, headers=headers, **kwargs) - - @api_call - def remove_shared_link(self, *, etag: Optional[str] = None, **kwargs: Any) -> bool: - """ - Baseclass override. - - :param etag: - If specified, instruct the Box API to delete the link only if the current version's etag matches. - :param kwargs: - Used to fulfill the contract of overriden method - :returns: - Whether or not the update was successful. - :raises: :class:`BoxAPIException` if the specified etag doesn't match the latest version of the item. - """ - # pylint:disable=arguments-differ - return super().remove_shared_link(etag=etag) - - @api_call - def delete(self, *, params: dict = None, etag: Optional[str] = None, **kwargs) -> bool: - """Delete the item. - - :param params: - Additional parameters to send with the request. - :param etag: - If specified, instruct the Box API to delete the item only if the current version's etag matches. - :returns: - Whether or not the delete was successful. - :raises: :class:`BoxAPIException` if the specified etag doesn't match the latest version of the item. - """ - # pylint:disable=arguments-differ,arguments-renamed - self.validate_item_id(self._object_id) - headers = {'If-Match': etag} if etag is not None else None - return super().delete(params=params, headers=headers, **kwargs) - - def metadata(self, scope: str = 'global', template: str = 'properties') -> Metadata: - """ - Instantiate a :class:`Metadata` object associated with this item. - - :param scope: - Scope of the metadata. Must be either 'global' or 'enterprise'. - :param template: - The name of the metadata template. - See https://developer.box.com/en/reference/resources/metadata/ for more details. - :return: - A new metadata instance associated with this item. - """ - self.validate_item_id(self._object_id) - return Metadata(self._session, self, scope, template) - - def get_all_metadata(self) -> MarkerBasedDictCollection: - """ - Get all metadata attached to the item. - """ - self.validate_item_id(self._object_id) - return MarkerBasedDictCollection( - session=self._session, - url=self.get_url('metadata'), - limit=None, - marker=None, - return_full_pages=False, - ) - - @api_call - def get_watermark(self) -> 'Watermark': - """ - Return the watermark info for a Box file - - :return: - Watermark object. - """ - self.validate_item_id(self._object_id) - url = self.get_url('watermark') - box_response = self._session.get(url) - response = box_response.json() - return self.translator.get('watermark')(response['watermark']) - - @api_call - def apply_watermark(self) -> 'Watermark': - """ - Apply watermark on a Box file - - :return: - Watermark object. - """ - self.validate_item_id(self._object_id) - url = self.get_url('watermark') - body_attributes = { - 'watermark': { - 'imprint': 'default' - } - } - box_response = self._session.put(url, data=json.dumps(body_attributes)) - response = box_response.json() - return self.translator.get('watermark')(response['watermark']) - - @api_call - def delete_watermark(self) -> bool: - """ - Deletes the watermark info for a Box file - - :return: - Whether or not the delete succeeded. - """ - self.validate_item_id(self._object_id) - url = self.get_url('watermark') - box_response = self._session.delete(url, expect_json_response=False) - return box_response.ok - - @api_call - def collaborate( - self, - accessible_by: Union['User', 'Group'], - role: str, - can_view_path: Optional[bool] = None, - notify: Optional[bool] = None, - fields: Iterable[str] = None - ) -> 'Collaboration': - """Collaborate user or group onto a Box item. - - :param accessible_by: - An object containing the collaborator. - :param role: - The permission level to grant the collaborator. - :param can_view_path: - Indicates whether the user can view the path of the item collaborated into. This can only be set for - collaborations on folders. - :param notify: - Determines if the collaborator should receive a notification for the collaboration. - :param fields: - List of fields to request. - :return: - The new collaboration - """ - self.validate_item_id(self._object_id) - url = self._session.get_url('collaborations') - body = { - 'item': { - 'type': self.object_type, - 'id': self.object_id, - }, - 'accessible_by': { - 'type': accessible_by.object_type, - 'id': accessible_by.object_id, - }, - 'role': role, - } - if can_view_path is not None: - body['can_view_path'] = can_view_path - params = {} - if fields is not None: - params['fields'] = ','.join(fields) - if notify is not None: - params['notify'] = notify - response = self._session.post(url, data=json.dumps(body), params=params).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def collaborate_with_login( - self, - login: str, - role: str, - can_view_path: Optional[bool] = None, - notify: Optional[bool] = None, - fields: Iterable[str] = None - ) -> 'Collaboration': - """Collaborate user onto a Box item with the user login. - - :param login: - The email address of the person to grant access to. - :param role: - The permission level to grant the collaborator. - :param can_view_path: - Indicates whether the user can view the path of the folder collaborated into. - :param notify: - Determines if the collaborator should receive a notification for the collaboration. - :param fields: - List of fields to request. - :return: - The new collaboration with the user login - """ - self.validate_item_id(self._object_id) - url = self._session.get_url('collaborations') - body = { - 'item': { - 'type': self.object_type, - 'id': self.object_id, - }, - 'accessible_by': { - 'type': 'user', - 'login': login, - }, - 'role': role, - } - if can_view_path is not None: - body['can_view_path'] = can_view_path - params = {} - if fields is not None: - params['fields'] = ','.join(fields) - if notify is not None: - params['notify'] = notify - response = self._session.post(url, data=json.dumps(body), params=params).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def get_collaborations( - self, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the entries in the collaboration. - - :param limit: - The maximum number of items to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start returning items from when using marker-based paging. - :param fields: - List of fields to request. - :returns: - An iterator of the entries in the collaboration. - """ - self.validate_item_id(self._object_id) - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('collaborations'), - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - def add_classification(self, classification: str) -> str: - """ - Applies metadata classification for the specified :class:`File` or :class:`Folder` object. - - :param classification: - The classification to add to the :class:`File` or :class:`Folder` - :return: - The classification added to the :class:`File` or :class:`Folder. - """ - classification_metadata = { - 'Box__Security__Classification__Key': classification, - } - metadata_classification = self.metadata( - scope='enterprise', - template=self._classification_template_key - ).create(classification_metadata) - return metadata_classification['Box__Security__Classification__Key'] - - def update_classification(self, classification: str) -> str: - """ - Updates metadata classification for the specified :class:`File` or :class:`Folder` object. - - :param classification: - The classification to add to the :class:`File` or :class:`Folder` - :return: - The classification updated on the :class:`File` or :class:`Folder. - """ - classification_metadata = self.metadata('enterprise', self._classification_template_key) - updates = classification_metadata.start_update() - updates.add('/Box__Security__Classification__Key', classification) - metadata_classification = classification_metadata.update(updates) - return metadata_classification['Box__Security__Classification__Key'] - - def set_classification(self, classification: str) -> str: - """ - Attempts to add a metadata classification to a :class:`File` or :class:`Folder`, if classification exists, then - do update. - - :param classification: - The classification to add to the :class:`File` or :class:`Folder` - :return: - The classification set on the :class:`File` or :class:`Folder. - """ - classification_metadata = { - 'Box__Security__Classification__Key': classification, - } - return self.metadata( - scope='enterprise', - template=self._classification_template_key - ).set(metadata=classification_metadata)['Box__Security__Classification__Key'] - - def get_classification(self) -> Optional[str]: - """ - Retrieves the classification specified for the :class:`File` or :class:`Folder` - - :return: - The classification on the :class:`File` or :class:`Folder. - """ - try: - classification = self.metadata('enterprise', self._classification_template_key).get() - except BoxAPIException as err: - if err.status == 404 and err.code == "instance_not_found": - return None - raise - return classification.get('Box__Security__Classification__Key', None) - - def remove_classification(self) -> bool: - """ - Removes a metadata classification from a :class:`File` or :class:`Folder`. - - :returns: - Whether or not the delete was successful. - """ - return self.metadata('enterprise', self._classification_template_key).delete() diff --git a/boxsdk/object/legal_hold.py b/boxsdk/object/legal_hold.py deleted file mode 100644 index cd1f917d1..000000000 --- a/boxsdk/object/legal_hold.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Any - -from .base_object import BaseObject - - -class LegalHold(BaseObject): - - """Represents the legal hold policy for a file version""" - _item_type = 'legal_hold' - - def get_url(self, *args: Any) -> str: - return self._session.get_url('file_version_legal_holds', self._object_id, *args) diff --git a/boxsdk/object/legal_hold_policy.py b/boxsdk/object/legal_hold_policy.py deleted file mode 100644 index 90d863c90..000000000 --- a/boxsdk/object/legal_hold_policy.py +++ /dev/null @@ -1,120 +0,0 @@ -import json -from typing import Any, Union, TYPE_CHECKING, Optional, Iterable - -from .base_object import BaseObject -from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection -from ..util.api_call_decorator import api_call - -if TYPE_CHECKING: - from boxsdk.object.user import User - from boxsdk.object.folder import Folder - from boxsdk.object.file import File - from boxsdk.object.file_version import FileVersion - from boxsdk.object.legal_hold_policy_assignment import LegalHoldPolicyAssignment - from boxsdk.pagination.box_object_collection import BoxObjectCollection - - -class LegalHoldPolicy(BaseObject): - """Represents a Box legal_hold_policy""" - - _item_type = 'legal_hold_policy' - - def get_url(self, *args: Any) -> str: - return self._session.get_url('legal_hold_policies', self._object_id, *args) - - @api_call - def assign(self, assignee: Union['FileVersion', 'File', 'Folder', 'User']) -> 'LegalHoldPolicyAssignment': - """Assign legal hold policy - - :param assignee: - The `file_version`, `file`, `folder`, or `user` to assign the legal hold policy to. - :returns: - A legal hold policy assignment object - """ - url = self._session.get_url('legal_hold_policy_assignments') - body = { - 'policy_id': self.object_id, - 'assign_to': { - 'type': assignee.object_type, - 'id': assignee.object_id - } - } - response = self._session.post(url, data=json.dumps(body)).json() - return self.translator.translate( - self._session, - response, - ) - - @api_call - def get_assignments( - self, - assign_to_type: Optional[str] = None, - assign_to_id: Optional[str] = None, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the entries in the legal hold policy assignment using limit-offset paging. - - :param assign_to_type: - Filter assignments of this type only. Can be `file_version`, `file`, `folder`, or `user` - :param assign_to_id: - Filter assignments to this ID only - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from - :param fields: - List of fields to request - :returns: - An iterator of the entries in the legal hold policy assignment - """ - additional_params = { - 'policy_id': self.object_id, - } - if assign_to_type is not None: - additional_params['assign_to_type'] = assign_to_type - if assign_to_id is not None: - additional_params['assign_to_id'] = assign_to_id - return MarkerBasedObjectCollection( - session=self._session, - url=self._session.get_url('legal_hold_policy_assignments'), - additional_params=additional_params, - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) - - @api_call - def get_file_version_legal_holds( - self, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get legal holds for a file version. - - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param marker: - The paging marker to start paging from - :param fields: - List of fields to request - :returns: - An iterator of the entries in the file version legal holds - """ - additional_params = { - 'policy_id': self.object_id, - } - return MarkerBasedObjectCollection( - session=self._session, - url=self._session.get_url('file_version_legal_holds'), - additional_params=additional_params, - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) diff --git a/boxsdk/object/legal_hold_policy_assignment.py b/boxsdk/object/legal_hold_policy_assignment.py deleted file mode 100644 index db760aafa..000000000 --- a/boxsdk/object/legal_hold_policy_assignment.py +++ /dev/null @@ -1,7 +0,0 @@ -from .base_object import BaseObject - - -class LegalHoldPolicyAssignment(BaseObject): - - """Represents the legal hold policy assignment""" - _item_type = 'legal_hold_policy_assignment' diff --git a/boxsdk/object/metadata.py b/boxsdk/object/metadata.py deleted file mode 100644 index 674b10a1a..000000000 --- a/boxsdk/object/metadata.py +++ /dev/null @@ -1,194 +0,0 @@ -import json -from typing import TYPE_CHECKING, List, Any -from boxsdk.object.base_endpoint import BaseEndpoint -from boxsdk.exception import BoxAPIException -from ..util.api_call_decorator import api_call - -if TYPE_CHECKING: - from boxsdk.session.session import Session - from boxsdk.object.base_object import BaseObject - - -class MetadataUpdate: - """ - Helper class for updating Box metadata. - See https://developer.box.com/en/guides/metadata/instances/update/ for more details. - See http://jsonpatch.com/ for details about JSON patch. - """ - def __init__(self): - self._ops = [] - - @property - def ops(self) -> List[dict]: - """ - Get a list of json patch operations in this update. - - :return: - The list of json patch operations in this update. - """ - return self._ops - - def add(self, path: str, value: str) -> None: - """ - Insert an add operation to this metadata update. - - :param path: - JSON pointer specifying where to add the new value. - :param value: - The value to add to the metadata document. - """ - self._ops.append({'op': 'add', 'path': path, 'value': value}) - - def remove(self, path: str, old_value: str = None) -> None: - """ - Insert a remove operation to this metadata update. - - :param path: - JSON pointer specifying where to remove the value from. - :param old_value: - If specified, only remove the key/value pair if the current value is equal to oldValue. - """ - if old_value is not None: - self._ops.append({'op': 'test', 'path': path, 'value': old_value}) - self._ops.append({'op': 'remove', 'path': path}) - - def update(self, path: str, value: str, old_value: str = None) -> None: - """ - Insert an update operation to this metadata update. - - :param path: - JSON pointer specifying where the value is in the metadata document that should be updated. - :param value: - The updated value. - :param old_value: - If specified, only update the key/value pair if the current value is equal to oldValue. - """ - if old_value is not None: - self._ops.append({'op': 'test', 'path': path, 'value': old_value}) - self._ops.append({'op': 'replace', 'path': path, 'value': value}) - - def test(self, path: str, value: str) -> None: - """ - Insert a test operation to this metadata update. - A test operation can invalidate the following operation if the value at the specified path does not match - the supplied value. - - :param path: - JSON pointer specifying where the value is in the metadata document to test. - :param value: - The value to match against. - """ - self._ops.append({'op': 'test', 'path': path, 'value': value}) - - -class Metadata(BaseEndpoint): - def __init__(self, session: 'Session', box_object: 'BaseObject', scope: str, template: str): - """ - :param session: - The Box session used to make requests. - :param box_object: - The Box object this metadata instance will be associated with. - :param scope: - Scope of the metadata. Must be either 'global' or 'enterprise'. - :param template: - The name of the metadata template. - See https://developer.box.com/en/guides/metadata/ for more details. - """ - super().__init__(session) - self._object = box_object - self._scope = scope - self._template = template - - def get_url(self, *args: Any) -> str: - """ Base class override. """ - return self._object.get_url('metadata', self._scope, self._template) - - @staticmethod - def start_update() -> MetadataUpdate: - """ - Get a :class:`MetadataUpdate` for use with the :meth:`update` method. - - :return: - A metadata update object that can be used to update this metadata object. - """ - return MetadataUpdate() - - @api_call - def update(self, metadata_update: MetadataUpdate) -> 'Metadata': - """ - Update the key/value pairs associated with this metadata object. - See https://developer.box.com/en/guides/metadata/instances/update/ for more details. - - :param metadata_update: - A metadata update object containing the changes that should be made to the metadata. - :return: - A dictionary containing the updated key/value pairs for this metadata object. - """ - return self._session.put( - self.get_url(), - data=json.dumps(metadata_update.ops), - headers={b'Content-Type': b'application/json-patch+json'}, - ).json() - - @api_call - def get(self) -> 'Metadata': - """ - Get the key/value pairs that make up this metadata instance. - - :return: - A dictionary containing the key/value pairs for this metadata object. - """ - return self._session.get(self.get_url()).json() - - @api_call - def delete(self) -> bool: - """ - Delete the metadata object. - - :returns: - Whether or not the delete was successful. - """ - return self._session.delete(self.get_url()).ok - - @api_call - def create(self, metadata: dict) -> 'Metadata': - """ - Create the metadata instance on Box. If the instance already exists, use :meth:`update` instead. - - :param metadata: - The key/value pairs to be stored in this metadata instance on Box. - :return: - A dictionary containing the key/value pairs for this metadata object. - """ - return self._session.post( - self.get_url(), - data=json.dumps(metadata), - headers={b'Content-Type': b'application/json'}, - ).json() - - @api_call - def set(self, metadata: dict) -> 'Metadata': - """ - Set the metadata instance on a :class:`Folder` or :class:`File`. Attempts to first create metadata on a - :class:`Folder` or :class:`File`. If metadata already exists then attempt an update. - - :param metadata: - The key/value pairs to be stored in this metadata instance on Box. - :return: - A dictionary containing the key/value pairs for this metadata object. - """ - try: - metadata_value = self.create(metadata) - except BoxAPIException as err: - if err.status == 409: - updates = self.start_update() - for key, value in metadata.items(): - updates.add('/' + key, value) - metadata_value = self.update(updates) - else: - raise - return metadata_value - - def clone(self, session: 'Session' = None) -> 'Metadata': - """ Base class override. """ - return self.__class__(session or self._session, self._object, self._scope, self._template) diff --git a/boxsdk/object/metadata_cascade_policy.py b/boxsdk/object/metadata_cascade_policy.py deleted file mode 100644 index defd5964c..000000000 --- a/boxsdk/object/metadata_cascade_policy.py +++ /dev/null @@ -1,43 +0,0 @@ -import json -from typing import Any - -from .base_object import BaseObject -from ..util.text_enum import TextEnum -from ..util.api_call_decorator import api_call - - -class CascadePolicyConflictResolution(TextEnum): - PRESERVE_EXISTING = 'none' - OVERWRITE = 'overwrite' - - -class MetadataCascadePolicy(BaseObject): - """Represents a metadata cascade policy, which applies folder metadata to files in that folder.""" - - _item_type = 'metadata_cascade_policy' - - def get_url(self, *args: Any) -> str: - """ - Base class override to account for the correct pluralization. - - """ - return self._session.get_url('metadata_cascade_policies', self.object_id, *args) - - @api_call - def force_apply(self, conflict_resolution: CascadePolicyConflictResolution) -> bool: - """ - Applies the metadata values on the folder to all files within the folder. The conflict resolution - parameter determines how conflicts when the same metadata template is already applied to a file will be - handled; either the file's existing values or the folder values can be given precendence. - - :param conflict_resolution: - How conflicting metadata values should be reolved - :returns: - Whether the force application succeeded. - """ - url = self.get_url('apply') - body = { - 'conflict_resolution': conflict_resolution, - } - response = self._session.post(url, data=json.dumps(body), expect_json_response=False) - return response.ok diff --git a/boxsdk/object/metadata_template.py b/boxsdk/object/metadata_template.py deleted file mode 100644 index 750bbd74e..000000000 --- a/boxsdk/object/metadata_template.py +++ /dev/null @@ -1,294 +0,0 @@ -from typing import TYPE_CHECKING, List, Optional, Iterable, Any -from .base_object import BaseObject -from ..util.api_call_decorator import api_call -from ..util.text_enum import TextEnum - -if TYPE_CHECKING: - from boxsdk.session.session import Session - - -class MetadataTemplateUpdate: - """Represents a set of update operations to a metadata template.""" - - def __init__(self): - super().__init__() - self._ops = [] - - def json(self) -> list: - return self._ops - - def add_enum_option(self, field_key: str, option_key: str) -> None: - """ - Adds a new option to an enum field. - - :param field_key: - The key of the template field to add the option to - :param option_key: - The option to add - """ - self.add_operation({ - 'op': 'addEnumOption', - 'fieldKey': field_key, - 'data': { - 'key': option_key, - }, - }) - - def add_field(self, field: 'MetadataField') -> None: - """ - Add a new field to the template. - - :param field: - The new field to add - """ - self.add_operation({ - 'op': 'addField', - 'data': field.json(), - }) - - def edit_template(self, data: dict) -> None: - """ - Edit top-level template properties. - - :param data: - The properties to modify - """ - self.add_operation({ - 'op': 'editTemplate', - 'data': data, - }) - - def reorder_enum_options(self, field_key: str, option_keys: List[str]) -> None: - """ - Reorders the options in an enum field, which affects their display in UI. - - :param field_key: - The key of the enum field to reorder - :param option_keys: - The option keys in the desired order - """ - self.add_operation({ - 'op': 'reorderEnumOptions', - 'fieldKey': field_key, - 'enumOptionKeys': option_keys, - }) - - def reorder_fields(self, field_keys: List[str]) -> None: - """ - Reorders the fields in a metadata template, which affects their display in UI. - - :param field_keys: - The field keys in the desired order - """ - self.add_operation({ - 'op': 'reorderFields', - 'fieldKeys': field_keys, - }) - - def edit_field(self, field_key: str, field: 'MetadataField') -> None: - """ - Edits a field in the template. - - :param field_key: - The key of the field to update - :param field: - The updated field values - """ - self.add_operation({ - 'op': 'editField', - 'fieldKey': field_key, - 'data': field.json(), - }) - - def edit_enum_option_key(self, field_key: str, old_option_key: str, new_option_key: str) -> None: - """ - Change the key of an enum field option. - - :param field_key: - The key of the template field in which the option appears - :param old_option_key: - The old option key - :param new_option_key: - The new option key - """ - self.add_operation({ - 'op': 'editEnumOption', - 'fieldKey': field_key, - 'enumOptionKey': old_option_key, - 'data': { - 'key': new_option_key, - }, - }) - - def remove_enum_option(self, field_key: str, option_key: str) -> None: - """ - Remove an option from an enum field. - - :param field_key: - The key of the template field in which the option appears - :param option_key: - The key of the enum option to remove - """ - self.add_operation({ - 'op': 'removeEnumOption', - 'fieldKey': field_key, - 'enumOptionKey': option_key, - }) - - def remove_field(self, field_key: str) -> None: - """ - Remove a field from the metadata template. - - :param field_key: - The key of the field to remove - """ - self.add_operation({ - 'op': 'removeField', - 'fieldKey': field_key, - }) - - def add_operation(self, operation: dict) -> None: - """ - Adds an update operation. - - :param operation: - The operation to add. - """ - self._ops.append(operation) - - -class MetadataFieldType(TextEnum): - STRING = 'string' - DATE = 'date' - ENUM = 'enum' - MULTISELECT = 'multiSelect' - FLOAT = 'float' - - -class MetadataField: - """Represents a metadata field when creating or updating a metadata template.""" - - def __init__( - self, - field_type: MetadataFieldType, - display_name: str, - key: Optional[str] = None, - options: Iterable[str] = None, - description: Optional[str] = None, - hidden: Optional[bool] = None - ): - """ - :param field_type: - The type of the metadata field - :param display_name: - The human-readable name of the metadata field - :param key: - The machine-readable key for the metadata field - :param options: - For 'enum' or 'multiSelect' fields, the selectable options - :param description: - A description of the field - :param hidden: - Whether this field is hidden in the UI for the user and can only be set through the API instead - """ - super().__init__() - self.type = field_type - self.name = display_name - self.key = key - self.options = options - self.description = description - self.hidden = hidden - - def json(self) -> dict: - """ - Returns the correct representation of the template field for the API. - """ - values = {} - - if self.type is not None: - values['type'] = self.type - - if self.name is not None: - values['displayName'] = self.name - - if self.key is not None: - values['key'] = self.key - - if self.type in ['enum', 'multiSelect']: - values['options'] = [{'key': opt} for opt in self.options or ()] - - if self.description is not None: - values['description'] = self.description - - if self.hidden is not None: - values['hidden'] = self.hidden - - return values - - -class MetadataTemplate(BaseObject): - """Represents a metadata template, which contains the the type information for associated metadata fields.""" - - _item_type = 'metadata_template' - _untranslated_fields = ('fields',) - _scope = None - _template_key = None - - def __init__(self, session: 'Session', object_id: Optional[str], response_object: Optional[dict] = None): - """ - :param session: - The Box session used to make requests. - :param object_id: - The primary GUID key for the metadata template - :param response_object: - A JSON object representing the object returned from a Box API request. This should - contain 'scope' and 'templateKey' properties if the instance is being constructed without - a primary GUID object_id. - """ - super().__init__(session, object_id, response_object) - if response_object: - self._scope = response_object.get('scope', None) - self._template_key = response_object.get('templateKey', None) - elif not object_id: - raise ValueError('Metadata template must be constructed with an ID or scope and templateKey') - - @property - def scope(self) -> Optional[str]: - return self._scope - - @property - def template_key(self) -> Optional[str]: - return self._template_key - - def get_url(self, *args: Any) -> str: - """ - Base class override, since metadata templates have a weird compound ID and non-standard URL format - """ - if self._scope and self._template_key: - return self._session.get_url('metadata_templates', self._scope, self._template_key, 'schema', *args) - - return super().get_url(*args) - - @staticmethod - def start_update() -> MetadataTemplateUpdate: - """ - Start an update operation on the template. - - :returns: - An update object to collect the desired update operations. - """ - return MetadataTemplateUpdate() - - @api_call - def update_info(self, *, updates: MetadataTemplateUpdate, **kwargs) -> 'MetadataTemplate': - # pylint: disable=arguments-differ - """ - Update a metadata template with a set of update operations. - - :param updates: - The update operations to apply to the template - :returns: - The updated metadata template object - """ - # pylint: disable=arguments-differ - return super().update_info(data=updates.json(), **kwargs) diff --git a/boxsdk/object/recent_item.py b/boxsdk/object/recent_item.py deleted file mode 100644 index 8f3e29d8f..000000000 --- a/boxsdk/object/recent_item.py +++ /dev/null @@ -1,7 +0,0 @@ -from .base_api_json_object import BaseAPIJSONObject - - -class RecentItem(BaseAPIJSONObject): - """Represents a single recent item accessed by a Box user.""" - - _item_type = 'recent_item' diff --git a/boxsdk/object/retention_policy.py b/boxsdk/object/retention_policy.py deleted file mode 100644 index 5541cb603..000000000 --- a/boxsdk/object/retention_policy.py +++ /dev/null @@ -1,95 +0,0 @@ -import json -from typing import Any, Union, TYPE_CHECKING, Iterable, Optional -from .base_object import BaseObject -from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection -from ..util.api_call_decorator import api_call - -if TYPE_CHECKING: - from boxsdk.object.metadata_template import MetadataTemplate - from boxsdk.object.enterprise import Enterprise - from boxsdk.object.folder import Folder - from boxsdk.object.retention_policy_assignment import RetentionPolicyAssignment - from boxsdk.pagination.box_object_collection import BoxObjectCollection - - -class RetentionPolicy(BaseObject): - """Represents a Box retention policy.""" - _item_type = 'retention_policy' - - def get_url(self, *args: Any) -> str: - """ - Returns the url for this retention policy. - """ - return self._session.get_url('retention_policies', self._object_id, *args) - - @api_call - def assign( - self, - assignee: Union['Folder', 'Enterprise', 'MetadataTemplate'], - fields: Iterable[str] = None, - start_date_field: Optional[str] = None, - ) -> 'RetentionPolicyAssignment': - """Assign a retention policy to a Box item - - :param assignee: - The item to assign the retention policy on. - :param fields: - List of fields to request. - :param start_date_field: - The date the retention policy assignment begins. - If the assigned_to type is metadata_template, this field can be a date field's metadata attribute key id. - :returns: - A :class:`RetentionPolicyAssignment` object. - """ - url = self._session.get_url('retention_policy_assignments') - body = { - 'policy_id': self.object_id, - 'assign_to': { - 'type': assignee.object_type, - 'id': assignee.object_id, - } - } - params = {} - if fields is not None: - params['fields'] = ','.join(fields) - if start_date_field is not None: - body['start_date_field'] = start_date_field - response = self._session.post(url, data=json.dumps(body), params=params).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def assignments( - self, - assignment_type: Optional[str] = None, - limit: Optional[int] = None, - marker: Optional[str] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """Get the assignments for the retention policy. - - :param assignment_type: - The type of retention policy assignment to retrieve. Can be set to 'folder', 'enterprise', or 'metadata_template'. - :param limit: - The maximum number of items to return. - :param marker: - The position marker at which to begin the response. - :param fields: - List of fields to request. - :returns: - An iterable of assignments in the retention policy. - """ - additional_params = {} - if assignment_type is not None: - additional_params['assignment_type'] = assignment_type - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('assignments'), - additional_params=additional_params, - limit=limit, - marker=marker, - fields=fields, - return_full_pages=False, - ) diff --git a/boxsdk/object/retention_policy_assignment.py b/boxsdk/object/retention_policy_assignment.py deleted file mode 100644 index ab79d0276..000000000 --- a/boxsdk/object/retention_policy_assignment.py +++ /dev/null @@ -1,50 +0,0 @@ -from typing import Optional - -from .base_object import BaseObject -from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection -from ..util.api_call_decorator import api_call - - -class RetentionPolicyAssignment(BaseObject): - """Represents a Box retention policy assignment.""" - _item_type = 'retention_policy_assignment' - - @api_call - def get_files_under_retention( - self, - limit: Optional[int] = None, - marker: Optional[str] = None - ) -> 'MarkerBasedObjectCollection': - """ - Retrieves all files under retention for a retention policy assignment - - :param limit: the limit of retrieved entries per page. Default 100. - :param marker: the paging marker to start paging from. - :return: An iterator of the entries with information about all files under retention. - """ - return MarkerBasedObjectCollection( - self.session, - self.get_url('files_under_retention'), - limit=limit, - marker=marker - ) - - @api_call - def get_file_versions_under_retention( - self, - limit: Optional[int] = None, - marker: Optional[str] = None - ) -> 'MarkerBasedObjectCollection': - """ - Retrieves all file versions under retention for a retention policy assignment - - :param limit: the limit of retrieved entries per page. Default 100. - :param marker: the paging marker to start paging from. - :return: An iterator of the entries with information about all files uversions nder retention. - """ - return MarkerBasedObjectCollection( - self.session, - self.get_url('file_versions_under_retention'), - limit=limit, - marker=marker - ) diff --git a/boxsdk/object/search.py b/boxsdk/object/search.py deleted file mode 100644 index b934d91c5..000000000 --- a/boxsdk/object/search.py +++ /dev/null @@ -1,425 +0,0 @@ -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, - ) diff --git a/boxsdk/object/sign_request.py b/boxsdk/object/sign_request.py deleted file mode 100644 index c5e7e4493..000000000 --- a/boxsdk/object/sign_request.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import Any - -from .base_object import BaseObject -from ..util.api_call_decorator import api_call - - -class SignRequest(BaseObject): - """ - Represents a Sign Request used by Box Sign - Sign Requests are used to request e-signatures on documents from signers. - A Sign Request can refer to one or more Box Files and can be sent to one or more Box Sign Request Signers. - """ - _item_type = 'sign-request' - - def get_url(self, *args: Any) -> str: - """ - Returns the url for this sign request. - """ - return self._session.get_url('sign_requests', self._object_id, *args) - - @api_call - def cancel(self) -> 'SignRequest': - """ - Cancels a sign request if it has not yet been signed or declined. - Any outstanding signers will no longer be able to sign the document. - - :returns: - The cancelled SignRequest object. - """ - url = self.get_url('cancel') - response = self._session.post(url).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def resend(self) -> bool: - """ - Attempts to resend a Sign Request to all signers that have not signed yet. - There is a 10 minute cooling-off period between each resend request. - - :returns: - Whether the operation succeeded. - """ - url = self.get_url('resend') - response = self._session.post(url, skip_retry_codes={202}, expect_json_response=False) - return response.ok diff --git a/boxsdk/object/sign_template.py b/boxsdk/object/sign_template.py deleted file mode 100644 index a5b8d0094..000000000 --- a/boxsdk/object/sign_template.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Any - -from .base_object import BaseObject - - -class SignTemplate(BaseObject): - """ - Represents a Sign Template used by Box Sign - """ - _item_type = 'sign-template' - - def get_url(self, *args: Any) -> str: - """ - Returns the url for this sign template. - """ - return self._session.get_url('sign_templates', self._object_id, *args) diff --git a/boxsdk/object/storage_policy.py b/boxsdk/object/storage_policy.py deleted file mode 100644 index bf4b976a4..000000000 --- a/boxsdk/object/storage_policy.py +++ /dev/null @@ -1,70 +0,0 @@ -import json -from typing import Any, TYPE_CHECKING -from .base_object import BaseObject - -if TYPE_CHECKING: - from boxsdk.object.user import User - from boxsdk.object.storage_policy_assignment import StoragePolicyAssignment - - -class StoragePolicy(BaseObject): - """Represents the storage policy""" - - _item_type = 'storage_policy' - - def get_url(self, *args: Any) -> str: - """ - Get url for storage policies. - """ - return self._session.get_url('storage_policies', self._object_id, *args) - - def assign(self, user: 'User') -> 'StoragePolicyAssignment': - """ - Checks to see if a user is already assigned a storage policy or if the storage policy assigned - to user belongs to the enterprise. If neither, then update the user storage policy to the new one. - - :param user: - The class:`User` to assign the storage policy to - :returns: - Information about the :class:`StoragePolicyAssignment` object. - """ - assignment = user.get_storage_policy_assignment() - if assignment.id == self.object_id: - return assignment - - if assignment.assigned_to['type'] == 'enterprise': - return self.create_assignment(user) - - update_object = { - 'storage_policy': { - 'type': self.object_type, - 'id': self.object_id, - }, - } - return assignment.update_info(data=update_object) - - def create_assignment(self, user: 'User') -> 'StoragePolicyAssignment': - """ - Assign a storage policy to a :class:`User`. - - :param user: - The :class:'User` to assign the storage policy to. - :returns: - Information about the :class:`StoragePolicyAssignment` object - """ - url = self._session.get_url('storage_policy_assignments') - body = { - 'storage_policy': { - 'type': 'storage_policy', - 'id': self.object_id, - }, - 'assigned_to': { - 'type': user.object_type, - 'id': user.object_id, - } - } - response = self._session.post(url, data=json.dumps(body)).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) diff --git a/boxsdk/object/storage_policy_assignment.py b/boxsdk/object/storage_policy_assignment.py deleted file mode 100644 index f928182c6..000000000 --- a/boxsdk/object/storage_policy_assignment.py +++ /dev/null @@ -1,7 +0,0 @@ -from .base_object import BaseObject - - -class StoragePolicyAssignment(BaseObject): - """Represents the storage policy assignment""" - - _item_type = 'storage_policy_assignment' diff --git a/boxsdk/object/task.py b/boxsdk/object/task.py deleted file mode 100644 index 9af632534..000000000 --- a/boxsdk/object/task.py +++ /dev/null @@ -1,87 +0,0 @@ -import json -from typing import TYPE_CHECKING, Optional, Iterable - -from boxsdk.object.base_object import BaseObject -from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection -from ..util.api_call_decorator import api_call - -if TYPE_CHECKING: - from boxsdk.object.user import User - from boxsdk.object.task_assignment import TaskAssignment - from boxsdk.pagination.box_object_collection import BoxObjectCollection - - -class Task(BaseObject): - """Represents a Box task.""" - _item_type = 'task' - - @api_call - def assign(self, assignee: 'User') -> 'TaskAssignment': - """ - Assign a task to a single user on a single file. - - :param assignee: - The :class:`User` to assign the task to. - :returns: - A task assignment object. - """ - url = self._session.get_url('task_assignments') - body = { - 'task': { - 'type': 'task', - 'id': self.object_id, - }, - 'assign_to': { - 'id': assignee.object_id, - }, - } - response = self._session.post(url, data=json.dumps(body)).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def assign_with_login(self, assignee_login: Optional[str]) -> 'TaskAssignment': - """ - Used to assign a task to a single user with the login email address of the assignee. - - :param assignee_login: - The login of the user to assign the task to. - :returns: - A task assignment object. - """ - url = self._session.get_url('task_assignments') - body = { - 'task': { - 'type': 'task', - 'id': self.object_id, - }, - 'assign_to': { - 'login': assignee_login, - }, - } - response = self._session.post(url, data=json.dumps(body)).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def get_assignments(self, fields: Iterable[str] = None) -> 'BoxObjectCollection': - """ - Get the entries in the file task assignment. - - :param fields: - List of fields to request. - :returns: - An iterator of the entries in the file task assignment. - """ - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('assignments'), - limit=None, - marker=None, - fields=fields, - return_full_pages=False, - ) diff --git a/boxsdk/object/task_assignment.py b/boxsdk/object/task_assignment.py deleted file mode 100644 index 50b30dc1f..000000000 --- a/boxsdk/object/task_assignment.py +++ /dev/null @@ -1,16 +0,0 @@ -from boxsdk.util.text_enum import TextEnum -from .base_object import BaseObject - - -class ResolutionState(TextEnum): - """An enum of possible resolution states""" - COMPLETED = 'completed' - INCOMPLETE = 'incomplete' - APPROVED = 'approved' - REJECTED = 'rejected' - - -class TaskAssignment(BaseObject): - """Represents a Box task.""" - - _item_type = 'task_assignment' diff --git a/boxsdk/object/terms_of_service.py b/boxsdk/object/terms_of_service.py deleted file mode 100644 index 50ac32a52..000000000 --- a/boxsdk/object/terms_of_service.py +++ /dev/null @@ -1,116 +0,0 @@ -import json -from typing import TYPE_CHECKING, Optional - -from boxsdk.util.text_enum import TextEnum -from boxsdk.exception import BoxAPIException -from .base_object import BaseObject - -if TYPE_CHECKING: - from boxsdk.object.user import User - from boxsdk.object.terms_of_service_user_status import TermsOfServiceUserStatus - - -class TermsOfServiceType(TextEnum): - """An enum of possible terms of service types""" - MANAGED = 'managed' - EXTERNAL = 'external' - - -class TermsOfServiceStatus(TextEnum): - """An enum of possible terms of service status""" - ENABLED = 'enabled' - DISABLED = 'disabled' - - -class TermsOfService(BaseObject): - """Represents a Box terms of service.""" - - _item_type = 'terms_of_service' - - def get_user_status(self, user: Optional['User'] = None) -> Optional['TermsOfServiceUserStatus']: - """ - Get the terms of service user status. - - :param user: - This is the user to get the status of the terms of service for. This defaults to current - user. - :returns: - A :class:`TermsOfServiceUserStatus` object or None if no user status found. - """ - url = self._session.get_url('terms_of_service_user_statuses') - additional_params = { - 'tos_id': self.object_id, - } - if user is not None: - additional_params['user_id'] = user.object_id - box_response = self._session.get(url, params=additional_params) - response_object = box_response.json() - - if not response_object['entries']: - return None - - response = response_object['entries'][0] - return self.translator.translate( - session=self._session, - response_object=response, - ) - - def accept(self, user: Optional['User'] = None) -> 'TermsOfServiceUserStatus': - """ - Accept a terms of service. - - :param user: - The :class:`User` to assign the terms of service to. - :returns: - A newly created :class:`TermsOfServiceUserStatus` object - """ - return self.set_user_status(is_accepted=True, user=user) - - def reject(self, user: Optional['User'] = None) -> 'TermsOfServiceUserStatus': - """ - Reject a terms of service. - - :param user: - The :class:`User` to assign the terms of service to. - :returns: - A newly created :class:`TermsOfServiceUserStatus` object - """ - return self.set_user_status(is_accepted=False, user=user) - - def set_user_status(self, is_accepted: bool, user: Optional['User'] = None) -> 'TermsOfServiceUserStatus': - """ - Create a terms of service user status. - - :param is_accepted: - Indicates whether a use has accepted or rejected a terms of service. - :param user: - The :class:`User` to assign the terms of service to. - :returns: - A newly created :class:`TermsOfServiceUserStatus` object - """ - url = self._session.get_url('terms_of_service_user_statuses') - body = { - 'tos': { - 'type': self.object_type, - 'id': self.object_id, - }, - 'is_accepted': is_accepted, - } - if user is not None: - body['user'] = { - 'type': user.object_type, - 'id': user.object_id, - } - translated_response = None - try: - box_response = self._session.post(url, data=json.dumps(body)) - response = box_response.json() - translated_response = self.translator.translate( - session=self._session, - response_object=response, - ) - except BoxAPIException as err: - if err.status == 409: - user_status = self.get_user_status(user) - translated_response = user_status.update_info(data={'is_accepted': is_accepted}) - return translated_response diff --git a/boxsdk/object/terms_of_service_user_status.py b/boxsdk/object/terms_of_service_user_status.py deleted file mode 100644 index 81b46561f..000000000 --- a/boxsdk/object/terms_of_service_user_status.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Any - -from .base_object import BaseObject - - -class TermsOfServiceUserStatus(BaseObject): - """Represents a Box terms of service user status.""" - - _item_type = 'terms_of_service_user_status' - - def get_url(self, *args: Any) -> str: - return self._session.get_url('terms_of_service_user_statuses', self._object_id, *args) - - def accept(self) -> 'TermsOfServiceUserStatus': - """ - Accept a term of service. - """ - body = { - 'is_accepted': True - } - return self.update_info(data=body) - - def reject(self) -> 'TermsOfServiceUserStatus': - """ - Reject a term of service. - """ - body = { - 'is_accepted': False - } - return self.update_info(data=body) diff --git a/boxsdk/object/trash.py b/boxsdk/object/trash.py deleted file mode 100644 index d15c26ee4..000000000 --- a/boxsdk/object/trash.py +++ /dev/null @@ -1,155 +0,0 @@ -import json -from typing import Iterable, TYPE_CHECKING, Optional - -from boxsdk.pagination.marker_based_object_collection import MarkerBasedObjectCollection - -from .base_endpoint import BaseEndpoint - -from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection -from ..util.api_call_decorator import api_call - -if TYPE_CHECKING: - from boxsdk.object.base_item import BaseItem - from boxsdk.object.folder import Folder - from boxsdk.pagination.box_object_collection import BoxObjectCollection - - -class Trash(BaseEndpoint): - """Box API endpoint for performing trash related actions in Box.""" - - @api_call - def get_item(self, item: 'BaseItem', fields: Iterable[str] = None) -> 'BaseItem': - """ - Get item from trash. - - :param item: - The :class:`BaseItem` object to get info on. - :param fields: - List of fields to request - :returns: - Information for a trashed :class:`BaseItem` object. - """ - url = item.get_url('trash') - params = {} - if fields: - params['fields'] = ','.join(fields) - box_response = self._session.get(url, params=params) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def restore_item( - self, - item: 'BaseItem', - name: Optional[str] = None, - parent_folder: Optional['Folder'] = None, - fields: Iterable[str] = None - ) -> 'BaseItem': - """ - Restores an item from the trash. Could be files, folders, or weblinks. - - :param item: - The :class:`BaseItem` object to restore from trash. - :param name: - The new name for this item. Only used if the item can't be restored due to name conflict. - :param parent_folder: - The new parent folder. Only used if the previous parent folder no longer exists. - :param fields: - List of fields to request - :returns: - A restored :class:`BaseItem`. - """ - url = item.get_url() - body = {} - if name is not None: - body['name'] = name - if parent_folder is not None: - body['parent'] = {'id': parent_folder.object_id} - params = {} - if fields: - params['fields'] = ','.join(fields) - box_response = self._session.post( - url, data=json.dumps(body), params=params) - response = box_response.json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def permanently_delete_item(self, item: 'BaseItem') -> bool: - """ - Permanently delete an item that is in the trash. The item will no longer exist in Box. - - :param item: - The :class:`BaseItem` to delete from trash. - :returns: - Whether or not the delete was successful. - """ - url = item.get_url('trash') - box_response = self._session.delete(url, expect_json_response=False) - return box_response.ok - - @api_call - def get_items( - self, - limit: Optional[int] = None, - offset: Optional[int] = None, - fields: Iterable[str] = None, - marker: Optional[str] = None, - use_marker: bool = False, - sort: Optional[str] = None, - direction: Optional[str] = None - ) -> 'BoxObjectCollection': - """ - Using limit-offset paging, get the files, folders and web links that are in the user's trash. - - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param offset: - The offset of the item at which to begin the response. - :param fields: - List of fields to request. - :param marker: - The marker at which to begin the response. - :param use_marker: - Whether or not to use marker-based paging. - :param sort: - The field to sort by. Can be 'id', 'name', 'date' or 'size'. - :param direction: - The direction to sort. Can be 'ASC' or 'DESC'. - :returns: - An iterator of the entries in the trash - """ - additional_params = {} - if limit is not None: - additional_params['limit'] = limit - if direction: - additional_params['direction'] = direction - if sort: - additional_params['sort'] = sort - - if use_marker: - additional_params['usemarker'] = True - return MarkerBasedObjectCollection( - url=self._session.get_url('folders', 'trash', 'items'), - session=self._session, - limit=limit, - marker=marker, - fields=fields, - additional_params=additional_params, - return_full_pages=False, - ) - - return LimitOffsetBasedObjectCollection( - session=self._session, - url=self._session.get_url('folders', 'trash', 'items'), - limit=limit, - offset=offset, - fields=fields, - return_full_pages=False, - additional_params=additional_params, - ) diff --git a/boxsdk/object/upload_session.py b/boxsdk/object/upload_session.py deleted file mode 100644 index 8ccbe5476..000000000 --- a/boxsdk/object/upload_session.py +++ /dev/null @@ -1,205 +0,0 @@ -import base64 -import hashlib -import json -import os -from http import HTTPStatus -from typing import Any, Optional, TYPE_CHECKING, Iterable, IO - -from boxsdk import BoxAPIException -from boxsdk.util.api_call_decorator import api_call -from boxsdk.util.chunked_uploader import ChunkedUploader -from boxsdk.session.session import Session -from boxsdk.config import API -from .base_object import BaseObject -from ..pagination.limit_offset_based_dict_collection import LimitOffsetBasedDictCollection - -if TYPE_CHECKING: - from boxsdk.pagination.box_object_collection import BoxObjectCollection - from boxsdk.object.file import File - - -class UploadSession(BaseObject): - _item_type = 'upload_session' - _parent_item_type = 'file' - _default_upload_url = API.UPLOAD_URL - - def __init__( - self, session: Session, object_id: str, response_object: dict = None, use_upload_session_urls: bool = True - ): - super().__init__(session, object_id, response_object) - self._use_upload_session_urls = use_upload_session_urls - - def get_url(self, *args: Any, url_key: str = None) -> str: - """ - Base class override. Endpoint is a little different - it's /files/upload_sessions. - """ - session_endpoints = getattr(self, 'session_endpoints', {}) - if self._use_upload_session_urls and url_key in session_endpoints and self.session.api_config.UPLOAD_URL == self._default_upload_url: - return session_endpoints[url_key] - - return self._session.get_url( - f'{self._parent_item_type}s/{self._item_type}s', - self._object_id, - *args - ).replace(self.session.api_config.BASE_API_URL, self.session.api_config.UPLOAD_URL) - - @api_call - def get_parts(self, limit: Optional[int] = None, offset: Optional[int] = None) -> 'BoxObjectCollection': - """ - Get a list of parts uploaded so far. - - :param limit: - The maximum number of items to return per page. If not specified, then will use the server-side default. - :param offset: - The index at which to start returning items. - :returns: - Returns a :class:`BoxObjectCollection` object containing the uploaded parts. - """ - return LimitOffsetBasedDictCollection( - session=self.session, - url=self.get_url('parts', url_key='list_parts'), - limit=limit, - offset=offset, - fields=None, - return_full_pages=False, - ) - - @api_call - def upload_part_bytes( - self, - part_bytes: bytes, - offset: int, - total_size: int, - part_content_sha1: Optional[bytes] = None - ) -> dict: - """ - Upload a part of a file. - - :param part_bytes: - Part bytes - :param offset: - Offset, in number of bytes, of the part compared to the beginning of the file. This number should be a - multiple of the part size. - :param total_size: - The size of the file that this part belongs to. - :param part_content_sha1: - SHA-1 hash of the part's content. If not specified, this will be calculated. - :returns: - The uploaded part record. - """ - - if part_content_sha1 is None: - sha1 = hashlib.sha1() - sha1.update(part_bytes) - part_content_sha1 = sha1.digest() - - range_end = min(offset + self.part_size - 1, total_size - 1) # pylint:disable=no-member - headers = { - 'Content-Type': 'application/octet-stream', - 'Digest': f'SHA={base64.b64encode(part_content_sha1).decode("utf-8")}', - 'Content-Range': f'bytes {offset}-{range_end}/{total_size}', - } - response = self._session.put( - self.get_url(url_key='upload_part'), - headers=headers, - data=part_bytes, - ) - return response.json()['part'] - - @api_call - def commit( - self, - content_sha1: bytes, - parts: Iterable[Optional[dict]] = None, - file_attributes: dict = None, - etag: Optional[str] = None - ) -> Optional['File']: - """ - Commit a multiput upload. - - :param content_sha1: - SHA-1 hash of the file contents that was uploaded. - :param parts: - List of parts that were uploaded. - :param file_attributes: - A `dict` of attributes to set on the uploaded file. - :param etag: - If specified, instruct the Box API to delete the folder only if the current version's etag matches. - :returns: - The newly-uploaded file object or None if commit was not processed - """ - body = {} - if file_attributes is not None: - body['attributes'] = file_attributes - if parts is not None: - body['parts'] = parts - else: - body['parts'] = list(self.get_parts()) - headers = { - 'Content-Type': 'application/json', - 'Digest': f'SHA={base64.b64encode(content_sha1).decode("utf-8")}', - } - if etag is not None: - headers['If-Match'] = etag - - try: - response = self._session.post( - self.get_url('commit', url_key='commit'), - headers=headers, - data=json.dumps(body), - ) - except BoxAPIException as box_api_exc: - if box_api_exc.status == HTTPStatus.ACCEPTED: - return None - raise box_api_exc - - entry = response.json()['entries'][0] - return self.translator.translate( - session=self._session, - response_object=entry, - ) - - @api_call - def abort(self) -> bool: - """ - Abort an upload session, cancelling the upload and removing any parts that have already been uploaded. - - :returns: - A boolean indication success of the upload abort. - """ - - box_response = self._session.delete( - self.get_url(url_key='abort'), - expect_json_response=False - ) - return box_response.ok - - def get_chunked_uploader_for_stream(self, content_stream: IO[bytes], file_size: int) -> ChunkedUploader: - """ - Instantiate the chunked upload instance and create upload session. - - :param content_stream: - File-like object containing the content of the part to be uploaded. - :param file_size: - The size of the file that this part belongs to. - :returns: - A :class:`ChunkedUploader` object. - """ - return ChunkedUploader(self, content_stream, file_size) - - def get_chunked_uploader(self, file_path: str) -> ChunkedUploader: - # pylint: disable=consider-using-with - """ - Instantiate the chunked upload instance and create upload session with path to file. - - :param file_path: - The local path to the file you wish to upload. - :returns: - A :class:`ChunkedUploader` object. - """ - total_size = os.stat(file_path).st_size - content_stream = open(file_path, 'rb') - return self.get_chunked_uploader_for_stream( - content_stream=content_stream, - file_size=total_size, - ) diff --git a/boxsdk/object/user.py b/boxsdk/object/user.py deleted file mode 100644 index 9c8c7ba2d..000000000 --- a/boxsdk/object/user.py +++ /dev/null @@ -1,231 +0,0 @@ -import json -import os -from typing import TYPE_CHECKING, Optional, Iterable, IO - -from .base_object import BaseObject -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 - -if TYPE_CHECKING: - from boxsdk.object.email_alias import EmailAlias - from boxsdk.pagination.box_object_collection import BoxObjectCollection - from boxsdk.object.folder import Folder - from boxsdk.object.storage_policy_assignment import StoragePolicyAssignment - - -class User(BaseObject): - """Represents a Box user.""" - - _item_type = 'user' - - @api_call - def add_email_alias(self, email: str) -> 'EmailAlias': - """ - Adds a new email alias to the given user's account. - - :param email: - The email alias to add to the user. - :returns: - The new email alias object - """ - url = self.get_url('email_aliases') - body = { - 'email': email, - } - response = self._session.post(url, data=json.dumps(body)).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def get_email_aliases(self, limit: Optional[int] = None, fields: Iterable[str] = None) -> 'BoxObjectCollection': - """ - Gets an list of email aliases for a user. - - :param limit: - The maximum number of users to return. If not specified, the Box API will determine an appropriate limit. - :param fields: - List of fields to request - :returns: - An iterator of the user's email aliases - """ - return MarkerBasedObjectCollection( - session=self._session, - url=self.get_url('email_aliases'), - limit=limit, - marker=None, - fields=fields, - return_full_pages=False, - ) - - @api_call - def remove_email_alias(self, email_alias: 'EmailAlias') -> bool: - """ - Remove an email alias from the user. - - :param email_alias: - The email alias to remove. - :returns: - Whether the removal succeeded. - """ - url = self.get_url('email_aliases', email_alias.object_id) - response = self._session.delete(url, expect_json_response=False) - return response.ok - - @api_call - def transfer_content( - self, - destination_user: 'User', - notify: Optional[bool] = None, - fields: Iterable[str] = None - ) -> 'Folder': - """ - Move all of the items owned by a user into a new folder in another user's account. - - :param destination_user: - The id of the user to transfer content to. - :param notify: - Whether the destination user should receive email notification of the transfer. - :param fields: - Fields to return on the resulting :class:`Folder` object - :returns: - A :class:`Folder` object that was transferred to another user. - """ - url = self.get_url('folders', '0') - body = { - 'owned_by': { - 'id': destination_user.object_id, - }, - } - params = {} - if notify is not None: - params['notify'] = notify - if fields is not None: - params['fields'] = ','.join(fields) - response = self._session.put(url, data=json.dumps(body), params=params).json() - return self.translator.translate( - session=self._session, - response_object=response, - ) - - def get_storage_policy_assignment(self) -> 'StoragePolicyAssignment': - """ - Get the storage policy assignment assigned to the user. - - :returns: - The :class:`StoragePolicyAssignment` object information - """ - url = self._session.get_url('storage_policy_assignments') - additional_params = { - 'resolved_for_type': self.object_type, - 'resolved_for_id': self.object_id, - } - box_response = self._session.get(url, params=additional_params) - response = box_response.json()['entries'][0] - return self.translator.translate( - session=self._session, - response_object=response, - ) - - @api_call - def get_group_memberships( - self, - limit: Optional[int] = None, - offset: Optional[int] = None, - fields: Iterable[str] = None - ) -> 'BoxObjectCollection': - """ - Get the entries in the user group membership using limit-offset paging. - - :param limit: - The maximum number of entries to return per page. If not specified, then will use the server-side default. - :param offset: - The offset of the item at which to begin the response. - :param fields: - List of fields to request. - :returns: - An iterator of the entries in the groups - """ - additional_params = {} - if fields is not None: - additional_params['fields'] = ','.join(fields) - return LimitOffsetBasedObjectCollection( - session=self._session, - url=self.get_url('memberships'), - additional_params=additional_params, - limit=limit, - offset=offset, - return_full_pages=False, - ) - - @api_call - def get_avatar(self) -> bytes: - """ - Get the avatar for the User. - - :returns: - Avatar content as bytes. - """ - url = self.get_url('avatar') - response = self._session.get(url, expect_json_response=False) - return response.content - - @api_call - def upload_avatar(self, image_path: str) -> str: - """ - Upload avatar image to user account. Supported formats are JPG, JPEG and PNG. - Maximum allowed file size is 1MB and resolution 1024x1024 pixels. - - :param image_path: Path of the avatar image to upload - :return: URLs to existing user avatars that were updated - """ - with open(image_path, 'rb') as image_stream: - image_extension = os.path.splitext(image_path)[-1][1:] - return self.upload_avatar_stream(image_stream=image_stream, image_extension=image_extension) - - @api_call - def upload_avatar_stream(self, image_stream: IO[bytes], image_extension: str) -> str: - """ - Upload avatar image to user account. Supported formats are JPG, JPEG and PNG. - Maximum allowed file size is 1MB and resolution 1024x1024 pixels. - - :param image_extension: Extension of the image. Supported: 'jpg', 'jpeg' and 'png'. - :param image_stream: The file-like object containing the bytes of the image - :return: URLs to existing user avatars that were updated - """ - url = self.get_url('avatar') - files = {'pic': (f'avatar.{image_extension}', image_stream, f'image/{image_extension}')} - response = self._session.post(url, files=files) - return response.json()['pic_urls'] - - @api_call - def delete_avatar(self) -> bool: - """ - Remove avatar from user account - - :return: Whether the deletion succeeded - """ - url = self.get_url('avatar') - response = self._session.delete(url) - return response.ok - - @api_call - def delete(self, *, notify: bool = True, force: bool = False, **kwargs) -> bool: - # pylint: disable=arguments-differ,arguments-renamed - """ - Delete a user's account. This user will no longer be able to access Box. - - :param notify: - Whether a notification should be sent about the deletion - :param force: - Whether the user should be deleted even if they still own files - :returns: - Whether the deletion succeeded - """ - params = { - 'notify': notify, - 'force': force, - } - return super().delete(params=params, **kwargs) diff --git a/boxsdk/object/watermark.py b/boxsdk/object/watermark.py deleted file mode 100644 index a8e53a30b..000000000 --- a/boxsdk/object/watermark.py +++ /dev/null @@ -1,7 +0,0 @@ -from .api_json_object import APIJSONObject - - -class Watermark(APIJSONObject): - """Box API endpoint for applying watermark in a Box account.""" - - _item_type = 'watermark' diff --git a/boxsdk/object/web_link.py b/boxsdk/object/web_link.py deleted file mode 100644 index 999de75f5..000000000 --- a/boxsdk/object/web_link.py +++ /dev/null @@ -1,106 +0,0 @@ -from datetime import datetime -from typing import Optional, Any, Union - -from ..util.api_call_decorator import api_call -from ..util.default_arg_value import SDK_VALUE_NOT_SET -from .base_item import BaseItem - - -class WebLink(BaseItem): - """Box API endpoint for interacting with WebLinks.""" - - _item_type = 'web_link' - - @api_call - def create_shared_link( - self, - *, - access: Optional[str] = None, - unshared_at: Union[datetime, str, None] = SDK_VALUE_NOT_SET, - password: Optional[str] = None, - vanity_name: Optional[str] = None, - **kwargs: Any - ) -> 'WebLink': - """ - Baseclass override. - - :param access: - Determines who can access the shared link. May be open, company, or collaborators. If no access is - specified, the default access will be used. - :param unshared_at: - The date on which this link should be disabled. May only be set if the current user is not a free user - and has permission to set expiration dates. Takes a datetime string supported by the dateutil library - or a datetime.datetime object. If no timezone info provided, local timezone will be applied. - The time portion can be omitted, - which defaults to midnight (00:00:00) on that date. - :param password: - The password required to view this link. If no password is specified then no password will be set. - Please notice that this is a premium feature, which might not be available to your app. - :param vanity_name: - Defines a custom vanity name to use in the shared link URL, eg. https://app.box.com/v/my-custom-vanity-name. - If this parameter is None, the standard shared link URL will be used. - :param kwargs: - Used to fulfill the contract of overriden method - :return: - The updated object with s shared link. - Returns a new object of the same type, without modifying the original object passed as self. - """ - # pylint:disable=arguments-differ - return super().create_shared_link( - access=access, - unshared_at=unshared_at, - password=password, - vanity_name=vanity_name - ) - - @api_call - def get_shared_link( - self, - *, - access: Optional[str] = None, - unshared_at: Union[datetime, str, None] = SDK_VALUE_NOT_SET, - password: Optional[str] = None, - vanity_name: Optional[str] = None, - **kwargs: Any - ) -> str: - """ - Baseclass override. - - :param access: - Determines who can access the shared link. May be open, company, or collaborators. If no access is - specified, the default access will be used. - :param unshared_at: - The date on which this link should be disabled. May only be set if the current user is not a free user - and has permission to set expiration dates. Takes a datetime string supported by the dateutil library - or a datetime.datetime object. If no timezone info provided, local timezone will be applied. - The time portion can be omitted, which defaults to midnight (00:00:00) on that date. - :param password: - The password required to view this link. If no password is specified then no password will be set. - Please notice that this is a premium feature, which might not be available to your app. - :param vanity_name: - Defines a custom vanity name to use in the shared link URL, eg. https://app.box.com/v/my-custom-vanity-name. - If this parameter is None, the standard shared link URL will be used. - :param kwargs: - Used to fulfill the contract of overriden method - :returns: - The URL of the shared link. - """ - # pylint:disable=arguments-differ - return super().get_shared_link( - access=access, - unshared_at=unshared_at, - password=password, - vanity_name=vanity_name - ) - - @api_call - def remove_shared_link(self, **kwargs: Any) -> bool: - """ - Baseclass override. - - :param kwargs: - Used to fulfill the contract of overriden method - :returns: - Whether or not the update was successful. - """ - return super().remove_shared_link() diff --git a/boxsdk/object/webhook.py b/boxsdk/object/webhook.py deleted file mode 100644 index 5df32ffec..000000000 --- a/boxsdk/object/webhook.py +++ /dev/null @@ -1,74 +0,0 @@ -import base64 -import hashlib -import hmac -from typing import Optional - -from .base_object import BaseObject - - -class Webhook(BaseObject): - """Represents a Box Webhook.""" - - _item_type = 'webhook' - - @staticmethod - def validate_message( - body: bytes, - headers: dict, - primary_signature_key: str, - secondary_signature_key: str = None - ) -> bool: - """ - Validates a `Webhook` message. - - :param body: - The encoded webhook body. - :param headers: - The headers for the `Webhook` notification. - :param primary_signature_key: - The `Webhook` primary signature key for this application. - :param secondary_signature_key: - The `Webhook` secondary signature key for this application. - :return: - A `bool` indicating whether a webhook message was validated or not - """ - - primary_signature = _compute_signature(body, headers, primary_signature_key) - if primary_signature is not None and hmac.compare_digest(primary_signature, headers.get('box-signature-primary')): - return True - - if secondary_signature_key: - secondary_signature = _compute_signature(body, headers, secondary_signature_key) - if secondary_signature is not None and hmac.compare_digest(secondary_signature, headers.get('box-signature-secondary')): - return True - return False - - return False - - -def _compute_signature(body: bytes, headers: dict, signature_key: str) -> Optional[str]: - """ - Computes the Hmac for the webhook notification given one signature key. - - :param body: - The encoded webhook body. - :param headers: - The headers for the `Webhook` notification. - :param signature_key: - The `Webhook` signature key for this application. - :return: - An Hmac signature. - """ - if signature_key is None: - return None - if headers.get('box-signature-version') != '1': - return None - if headers.get('box-signature-algorithm') != 'HmacSHA256': - return None - - encoded_signature_key = signature_key.encode('utf-8') - encoded_delivery_time_stamp = headers.get('box-delivery-timestamp').encode('utf-8') - new_hmac = hmac.new(encoded_signature_key, digestmod=hashlib.sha256) - new_hmac.update(body + encoded_delivery_time_stamp) - signature = base64.b64encode(new_hmac.digest()).decode() - return signature diff --git a/boxsdk/pagination/__init__.py b/boxsdk/pagination/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/boxsdk/pagination/box_object_collection.py b/boxsdk/pagination/box_object_collection.py deleted file mode 100644 index e7da45512..000000000 --- a/boxsdk/pagination/box_object_collection.py +++ /dev/null @@ -1,166 +0,0 @@ -import json -from abc import ABC, abstractmethod -from collections.abc import Iterator -from typing import TYPE_CHECKING, Optional, Union, Any, Iterator as Iter -from boxsdk.pagination.page import Page - -if TYPE_CHECKING: - from boxsdk.session.session import Session - from boxsdk.object.base_object import BaseObject - - -class BoxObjectCollection(Iterator, ABC): - """ - An iterator that represents a collection of Box objects (BaseObject). - - A BoxObjectCollection instance contains everything it needs in order to retrieve and page through - responses from Box API endpoints that return collections of Box objects. - - This class only has two public methods: - - 1). next(), which returns either a Page (sequence of BaseObjects) or individual BaseObjects based on - the constructor argument 'return_full_pages'. - - 2). next_pointer(), which returns the pointer (either an offset or a marker, based on the endpoint) that - will be used to retrieve the next page of Box objects. This pointer can be used when requesting new - BoxObjectCollection instances that start off from a particular page, instead of from the very beginning. - """ - _page_constructor = Page - - def __init__( - self, - session: 'Session', - url: str, - limit: Optional[int] = None, - fields: Optional[Iter[str]] = None, - additional_params: Optional[dict] = None, - return_full_pages: bool = False, - use_post: bool = False - ): - """ - :param session: - The Box session used to make requests. - :param url: - The endpoint url to hit. - :param limit: - The number of entries for each page to return. The default, as well as the upper limit of this value, - differs by endpoint. See https://developer.box.com/en/reference. If limit is set to None, then the default - limit (returned by Box in the response) is used. - :param fields: - List of fields to request. If None, will return the default fields for the object. - :param additional_params: - Additional HTTP params to send in the request. - :param return_full_pages: - If True, then the returned iterator for this collection will return full pages of Box objects on each - call to next(). If False, the iterator will return a single Box object on each next() call. - :param use_post: - If True, then the returned iterator will make POST requests with all the data in the body on each - call to next(). - If False, the iterator will make GET requets with all the data as query params on each call to next(). - """ - super().__init__() - self._session = session - self._url = url - self._limit = limit - self._fields = fields - self._additional_params = additional_params - self._return_full_pages = return_full_pages - self._has_retrieved_all_items = False - self._all_items = None - self._use_post = use_post - - def next(self) -> Union[Page, 'BaseObject']: - """ - Returns either a Page (a Sequence of BaseObjects) or a BaseObject depending on self._return_full_pages. - - Invoking this method may make an API call to Box. Any exceptions that can occur while making requests - may be raised in this method. - """ - if self._all_items is None: - self._all_items = self._items_generator() - return next(self._all_items) - - __next__ = next - - def _items_generator(self) -> Union[Page, 'BaseObject']: - while not self._has_retrieved_all_items: - response_object = self._load_next_page() - - self._update_pointer_to_next_page(response_object) - self._has_retrieved_all_items = not self._has_more_pages(response_object) - page = self._page_constructor(self._session, response_object) - - if self._return_full_pages: - yield page - else: - # It's possible for the Box API to return 0 items in a page, even if there are more items to be - # retrieved on subsequent pages. When self._return_full_pages is True, then yielding a 0-item - # page is fine because that's what the page returned. - # But when we are iterating over individual items, and not pages, it's odd to yield a sequence of - # Nones (for that page that had 0 items). So instead, we continue to request more pages until we - # have Box objects to yield. - if not page: - continue - yield from page - - def _load_next_page(self) -> dict: - """ - Request the next page of entries from Box. Raises any network-related exceptions, including BoxAPIException. - Returns a parsed dictionary of the JSON response from Box - """ - params = {} - if self._limit is not None: - params['limit'] = self._limit - if self._additional_params: - params.update(self._additional_params) - params.update(self._next_page_pointer_params()) - if self._use_post: - if self._fields: - params['fields'] = self._fields - box_response = self._session.post(self._url, data=json.dumps(params), headers={b'Content-Type': b'application/json'}) - else: - if self._fields: - params['fields'] = ','.join(self._fields) - box_response = self._session.get(self._url, params=params) - return box_response.json() - - @abstractmethod - def _update_pointer_to_next_page(self, response_object: dict) -> None: - """ - Update the internal pointer attribute of this class to what will be used to request the next page - of Box objects. - - A "pointer" can either be a marker (for marker-based paging) or an offset (for limit-offset paging). - - :param response_object: - The parsed HTTP response from Box after requesting more pages. - """ - raise NotImplementedError - - @abstractmethod - def _has_more_pages(self, response_object: dict) -> bool: - """ - Are there more pages of entries to query Box for? This gets invoked after self._update_pointer_to_next_page(). - - :param response_object: - The parsed HTTP response from Box after requesting more pages. - """ - raise NotImplementedError - - @abstractmethod - def _next_page_pointer_params(self) -> dict: - """ - The dict of HTTP params that specify which page of Box objects to retrieve. - """ - raise NotImplementedError - - @abstractmethod - def next_pointer(self) -> Any: - """ - The pointer that will be used to request the next page of Box objects. - - For limit-offset based paging, this is an offset. For marker-based paging, this is a marker. - - The pointer only gets progressed upon successful page requests to Box. - """ - raise NotImplementedError diff --git a/boxsdk/pagination/dict_page.py b/boxsdk/pagination/dict_page.py deleted file mode 100644 index 7568fcfd5..000000000 --- a/boxsdk/pagination/dict_page.py +++ /dev/null @@ -1,8 +0,0 @@ -from typing import Any - -from .page import Page - - -class DictPage(Page): - def __getitem__(self, key: str) -> Any: - return self._response_object[self._item_entries_key_name][key] diff --git a/boxsdk/pagination/limit_offset_based_dict_collection.py b/boxsdk/pagination/limit_offset_based_dict_collection.py deleted file mode 100644 index 5732f110f..000000000 --- a/boxsdk/pagination/limit_offset_based_dict_collection.py +++ /dev/null @@ -1,7 +0,0 @@ -from .dict_page import DictPage -from .limit_offset_based_object_collection import LimitOffsetBasedObjectCollection - - -class LimitOffsetBasedDictCollection(LimitOffsetBasedObjectCollection): - """Represents a limit/offset-based collection of simple dicts, which are not translated into objects.""" - _page_constructor = DictPage diff --git a/boxsdk/pagination/limit_offset_based_object_collection.py b/boxsdk/pagination/limit_offset_based_object_collection.py deleted file mode 100644 index 73c94bc1b..000000000 --- a/boxsdk/pagination/limit_offset_based_object_collection.py +++ /dev/null @@ -1,98 +0,0 @@ -from typing import Optional, Iterator, TYPE_CHECKING - -from .box_object_collection import BoxObjectCollection - -if TYPE_CHECKING: - from boxsdk.session.session import Session - - -class LimitOffsetBasedObjectCollection(BoxObjectCollection): - """ - An iterator of Box objects (BaseObjects) that were retrieved from a Box API endpoint that supports - limit-offset type of pagination. - - See https://developer.box.com/en/guides/api-calls/pagination/ for more details. - """ - - def __init__( - self, - session: 'Session', - url: str, - limit: Optional[int] = None, - fields: Iterator[str] = None, - additional_params: Optional[dict] = None, - return_full_pages: bool = False, - offset: int = 0, - ): - """ - :param session: - The Box session used to make requests. - :param url: - The endpoint url to hit. - :param limit: - The number of entries for each page to return. The default, as well as the upper limit of this value, - differs by endpoint. See https://developer.box.com/en/reference. If limit is set to None, then the default - limit (returned by Box in the response) is used. - :param fields: - List of fields to request. If None, will return the default fields for the object. - :param additional_params: - Additional HTTP params to send in the request. - :param return_full_pages: - If True, then the returned iterator for this collection will return full pages of Box objects on each - call to next(). If False, the iterator will return a single Box object on each next() call. - :param offset: - The offset index to start paging from. - """ - super().__init__( - session, - url, - limit=limit, - fields=fields, - additional_params=additional_params, - return_full_pages=return_full_pages, - ) - self._offset = offset - - def _update_pointer_to_next_page(self, response_object: dict) -> None: - """Baseclass override.""" - total_count = response_object['total_count'] - - if 'limit' in response_object: - self._limit, old_limit = int(response_object['limit']), self._limit - - # The API might use a lower limit than the client asked for, if the - # client asked for a limit above the maximum limit for that endpoint. - # The API is supposed to respond with the limit that it actually used. - # If that is given, then use that limit for the offset calculation, and - # also for the remainder of the paging. - - # Do not apply this same logic to "offset". Offset is not documented to be - # changed in the response, so respecting that value can lead to undefined - # behavior. - - # If the API erroneously sends a bad value for limit, we want to - # avoid getting into an infinite chain of API calls. So abort with - # a runtime error. - if self._limit <= 0 < old_limit: - self._offset = total_count # Disable additional paging. - raise RuntimeError(f'API returned limit={self._limit}, cannot continue paging') - - # de-none-ify the _offset value so that the arthimatic below works - self._offset = self._offset or 0 - - if total_count >= self._offset + self._limit: - self._offset += self._limit - else: - self._offset = total_count - - def _has_more_pages(self, response_object: dict) -> bool: - """Baseclass override.""" - return self._offset < response_object['total_count'] - - def _next_page_pointer_params(self) -> dict: - """Baseclass override.""" - return {'offset': self._offset} - - def next_pointer(self) -> int: - """Baseclass override.""" - return self._offset diff --git a/boxsdk/pagination/marker_based_dict_collection.py b/boxsdk/pagination/marker_based_dict_collection.py deleted file mode 100644 index c68b9b8c5..000000000 --- a/boxsdk/pagination/marker_based_dict_collection.py +++ /dev/null @@ -1,7 +0,0 @@ -from .dict_page import DictPage -from .marker_based_object_collection import MarkerBasedObjectCollection - - -class MarkerBasedDictCollection(MarkerBasedObjectCollection): - """Represents a marker-based collection of simple dicts, which are not translated into objects.""" - _page_constructor = DictPage diff --git a/boxsdk/pagination/marker_based_object_collection.py b/boxsdk/pagination/marker_based_object_collection.py deleted file mode 100644 index 082c831a6..000000000 --- a/boxsdk/pagination/marker_based_object_collection.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import Optional, Iterator, TYPE_CHECKING - -from .box_object_collection import BoxObjectCollection - -if TYPE_CHECKING: - from boxsdk.session.session import Session - - -class MarkerBasedObjectCollection(BoxObjectCollection): - """ - An iterator of Box objects (BaseObjects) that were retrieved from a Box API endpoint that supports - marker type of pagination. - - See https://developer.box.com/en/guides/api-calls/pagination/ for more details. - """ - - def __init__( - self, - session: 'Session', - url: str, - limit: Optional[int] = None, - fields: Iterator[str] = None, - additional_params: Optional[dict] = None, - return_full_pages: bool = False, - marker: Optional[str] = None, - supports_limit_offset_paging: bool = False, - use_post: bool = False - ): - """ - :param session: - The Box session used to make requests. - :param url: - The endpoint url to hit. - :param limit: - The number of entries for each page to return. The default, as well as the upper limit of this value, - differs by endpoint. See https://developer.box.com/en/reference. If limit is set to None, then the default - limit (returned by Box in the response) is used. - :param fields: - List of fields to request. If None, will return the default fields for the object. - :param additional_params: - Additional HTTP params to send in the request. - :param return_full_pages: - If True, then the returned iterator for this collection will return full pages of Box objects on each - call to next(). If False, the iterator will return a single Box object on each next() call. - :param marker: - The offset index to start paging from. - :param supports_limit_offset_paging: - Does this particular endpoint also support limit-offset paging? This information is needed, as - the endpoints that support both require an special extra request parameter. - :param use_post: - If True, then the returned iterator will make POST requests with all the data in the body on each - call to next(). - If False, the iterator will make GET requets with all the data as query params on each call to next(). - """ - super().__init__( - session, - url, - limit=limit, - fields=fields, - additional_params=additional_params, - return_full_pages=return_full_pages, - use_post=use_post - ) - self._marker = marker - self._supports_limit_offset_paging = supports_limit_offset_paging - - def _update_pointer_to_next_page(self, response_object: dict) -> None: - """Baseclass override.""" - self._marker = self._get_next_marker_from_response_object(response_object) - - def _has_more_pages(self, response_object) -> bool: - """Baseclass override.""" - return bool(self._get_next_marker_from_response_object(response_object)) - - @staticmethod - def _get_next_marker_from_response_object(response_object) -> Optional[str]: - """Get the marker that should be used to retrieve the next page. - - When we've just retrieved the last page, the API is inconsistent about - what it returns. Some endpoints return "next_marker":"", some return - "next_marker":null, some don't give any "next_marker" value. In all of - these cases, this method will return `None`. - - Otherwise, this method returns the string value of the "next_marker" - field. - """ - return response_object.get('next_marker') or None - - def _next_page_pointer_params(self) -> dict: - """Baseclass override.""" - pointer_params = {} - # For transitioning endpoints that support both marker and limit-offset paging, we must specify an - # additional 'useMarker' parameter to the Box API. - if self._supports_limit_offset_paging: - pointer_params['useMarker'] = True - if self._marker is not None: - pointer_params['marker'] = self._marker - return pointer_params - - def next_pointer(self) -> Optional[str]: - """Baseclass override.""" - return self._marker diff --git a/boxsdk/pagination/page.py b/boxsdk/pagination/page.py deleted file mode 100644 index ef032db6c..000000000 --- a/boxsdk/pagination/page.py +++ /dev/null @@ -1,59 +0,0 @@ -import copy - -from collections.abc import Sequence -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from boxsdk.session.session import Session - from boxsdk.util.translator import Translator - from boxsdk.object.base_object import BaseObject - - -class Page(Sequence): - """ - A sequence of BaseObjects that belong to a page returned from a paging api call. - - The Page makes available detailed response data for page requests. - """ - _item_entries_key_name = "entries" - - def __init__(self, session: 'Session', response_object: dict): - """ - :param session: - The Box session used to make the request that generated the response. - :param response_object: - The parsed HTTP response from Box after requesting more pages. - """ - super().__init__() - self._session = session - self._response_object = response_object - - @property - def _translator(self) -> 'Translator': - """ - The translator used for translating Box API JSON responses into `BaseAPIJSONObject` smart objects. - """ - return self._session.translator - - @property - def response_object(self) -> dict: - """ - Return a copy of the response object for this Page. - """ - return copy.deepcopy(self._response_object) - - def __getitem__(self, key: str) -> 'BaseObject': - """ - Try to get the attribute from the API response object. - - :param key: - The attribute to retrieve from the API response object. - """ - item_json = self._response_object[self._item_entries_key_name][key] - return self._translator.translate(self._session, item_json) - - def __len__(self) -> int: - """ - Get the number of items in the page. - """ - return len(self._response_object[self._item_entries_key_name]) diff --git a/boxsdk/py.typed b/boxsdk/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/boxsdk/session/__init__.py b/boxsdk/session/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/boxsdk/session/box_request.py b/boxsdk/session/box_request.py deleted file mode 100644 index b255c5c2b..000000000 --- a/boxsdk/session/box_request.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Optional - -import attr - -from ..util.log import sanitize_dictionary - - -@attr.s(slots=True) -class BoxRequest: - """Represents a Box API request. - - :param url: The URL being requested. - :param method: The HTTP method to use for the request. - :param headers: HTTP headers to include with the request. - :param auto_session_renewal: Whether or not the session can be automatically renewed if the request fails. - :param expect_json_response: Whether or not the API response must be JSON. - """ - url: str = attr.ib() - method: Optional[str] = attr.ib(default='GET') - headers: Optional[dict] = attr.ib(default=attr.Factory(dict)) - auto_session_renewal: Optional[bool] = attr.ib(default=True) - expect_json_response: Optional[bool] = attr.ib(default=True) - access_token: Optional[str] = attr.ib(default=None) - - def __repr__(self) -> str: - return f' Any: - """ - Return the parsed JSON response. - """ - return self._network_response.json() - - @property - def content(self) -> Any: - """ - Return the content of the response body. - """ - return self._network_response.content - - @property - def ok(self) -> bool: - """ - Return whether or not the request was successful. - """ - # pylint:disable=invalid-name - return self._network_response.ok - - @property - def status_code(self) -> int: - """ - Return the HTTP status code of the response. - """ - return self._network_response.status_code - - @property - def headers(self) -> dict: - """ - Get the response headers. - """ - return self._network_response.headers - - @property - def network_response(self) -> 'NetworkResponse': - """ - Return the underlying network response. - """ - return self._network_response - - def __repr__(self) -> str: - return f'' diff --git a/boxsdk/session/session.py b/boxsdk/session/session.py deleted file mode 100644 index e60f5f0a6..000000000 --- a/boxsdk/session/session.py +++ /dev/null @@ -1,587 +0,0 @@ -import random -import math - -from functools import partial -from logging import getLogger -from numbers import Number -from typing import TYPE_CHECKING, Optional, Any, Type, Callable, Set - -from requests.exceptions import RequestException -from boxsdk.exception import BoxException -from .box_request import BoxRequest as _BoxRequest -from .box_response import BoxResponse as _BoxResponse -from ..config import API, Client, Proxy -from ..exception import BoxAPIException -from ..network.default_network import DefaultNetwork -from ..util.json import is_json_response -from ..util.multipart_stream import MultipartStream -from ..util.shared_link import get_shared_link_header -from ..util.translator import Translator - -if TYPE_CHECKING: - from boxsdk.network.network_interface import Network - from boxsdk.object.user import User - from boxsdk import NetworkResponse, OAuth2 - - -class Session: - - _retry_randomization_factor = 0.5 - _retry_base_interval = 1 - _JWT_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer' - _CCG_GRANT_TYPE = 'client_credentials' - - """ - Box API session. Provides automatic retry of failed requests. - """ - def __init__( - self, - network_layer: 'Network' = None, - default_headers: Optional['dict'] = None, - translator: Translator = None, - default_network_request_kwargs: Optional['dict'] = None, - api_config: API = None, - client_config: Client = None, - proxy_config: Optional[Proxy] = None, - ): - """ - :param network_layer: - Network implementation used by the session to make requests. - :param default_headers: - A dictionary containing default values to be used as headers when this session makes an API request. - :param translator: - (optional) The translator to use for translating Box API JSON - responses into :class:`BaseAPIJSONObject` smart objects. - Defaults to a new :class:`Translator` that inherits the - registrations of the default translator. - :param default_network_request_kwargs: - A dictionary containing default values to be passed to the network layer - when this session makes an API request. - :param api_config: - Object containing URLs for the Box API. - :param client_config: - Object containing client information, including user agent string. - :param proxy_config: - Object containing proxy information. - """ - if translator is None: - translator = Translator(extend_default_translator=True, new_child=True) - self._api_config = api_config or API() - self._client_config = client_config or Client() - self._proxy_config = proxy_config or Proxy() - super().__init__() - self._network_layer = network_layer or DefaultNetwork() - self._default_headers = { - 'User-Agent': self._client_config.USER_AGENT_STRING, - 'X-Box-UA': self._client_config.BOX_UA_STRING, - } - self._translator = translator - self._default_network_request_kwargs = {} - if default_headers: - self._default_headers.update(default_headers) - if default_network_request_kwargs: - self._default_network_request_kwargs.update(default_network_request_kwargs) - self._logger = getLogger(__name__) - - def get(self, url: str, **kwargs: Any) -> '_BoxResponse': - """Make a GET request to the Box API. - - :param url: - The URL for the request. - """ - return self.request('GET', url, **kwargs) - - def post(self, url: str, **kwargs: Any) -> '_BoxResponse': - """Make a POST request to the Box API. - - :param url: - The URL for the request. - """ - return self.request('POST', url, **kwargs) - - def put(self, url: str, **kwargs: Any) -> '_BoxResponse': - """Make a PUT request to the Box API. - - :param url: - The URL for the request. - """ - return self.request('PUT', url, **kwargs) - - def delete(self, url: str, **kwargs: Any) -> '_BoxResponse': - """Make a DELETE request to the Box API. - - :param url: - The URL for the request. - """ - if 'expect_json_response' not in kwargs: - kwargs['expect_json_response'] = False - if 'skip_retry_codes' not in kwargs: - kwargs['skip_retry_codes'] = {202} - return self.request('DELETE', url, **kwargs) - - def options(self, url: str, **kwargs: Any) -> '_BoxResponse': - """Make an OPTIONS request to the Box API. - - :param url: - The URL for the request. - """ - return self.request('OPTIONS', url, **kwargs) - - def request(self, method: str, url: str, **kwargs: Any) -> '_BoxResponse': - """Make a request to the Box API. - - :param method: - The HTTP verb for the request. - :param url: - The URL for the request. - """ - response = self._prepare_and_send_request(method, url, **kwargs) - return self.box_response_constructor(response) - - @property - def box_request_constructor(self) -> Type[_BoxRequest]: - """Get the constructor for the container class representing an API request""" - return _BoxRequest - - @property - def box_response_constructor(self) -> Type[_BoxResponse]: - """Get the constructor for the container class representing an API response""" - return _BoxResponse - - @property - def translator(self) -> Translator: - """ - The translator used for translating Box API JSON responses into `BaseAPIJSONObject` smart objects. - """ - return self._translator - - @property - def api_config(self) -> API: - return self._api_config - - @property - def client_config(self) -> Client: - return self._client_config - - @property - def proxy_config(self) -> Proxy: - return self._proxy_config - - def get_url(self, endpoint: str, *args: Any) -> str: - """ - Return the URL for the given Box API endpoint. - - :param endpoint: - The name of the endpoint. - :param args: - Additional parts of the endpoint URL. - """ - # pylint:disable=no-self-use - url = [f'{self._api_config.BASE_API_URL}/{endpoint}'] - url.extend([f'/{x}' for x in args]) - return ''.join(url) - - def get_constructor_kwargs(self) -> dict: - return dict( - network_layer=self._network_layer, - translator=self._translator, - default_network_request_kwargs=self._default_network_request_kwargs.copy(), - api_config=self._api_config, - client_config=self._client_config, - proxy_config=self._proxy_config, - default_headers=self._default_headers.copy(), - ) - - def as_user(self, user: 'User') -> 'Session': - """ - Returns a new session object with default headers set up to make requests as the specified user. - - :param user: - The user to impersonate when making API requests. - """ - kwargs = self.get_constructor_kwargs() - kwargs['default_headers']['As-User'] = user.object_id - return self.__class__(**kwargs) - - def with_shared_link(self, shared_link: str, shared_link_password: str = None) -> 'Session': - """ - Returns a new session object with default headers set up to make requests using the shared link for auth. - - :param shared_link: - The shared link. - :param shared_link_password: - The password for the shared link. - """ - kwargs = self.get_constructor_kwargs() - kwargs['default_headers'].update(get_shared_link_header(shared_link, shared_link_password)) - return self.__class__(**kwargs) - - def with_default_network_request_kwargs(self, extra_network_parameters: dict) -> 'Session': - kwargs = self.get_constructor_kwargs() - kwargs['default_network_request_kwargs'].update(extra_network_parameters) - return self.__class__(**kwargs) - - # We updated our retry strategy to use exponential backoff instead of the header returned from the API response. - # This is something we can remove in latter major bumps. - # pylint: disable=unused-argument - def get_retry_after_time(self, attempt_number: int, retry_after_header: Optional[str]) -> Number: - """ - Get the amount of time to wait before retrying the API request, using the attempt number that failed to - calculate the retry time for the next retry attempt. - - If the Retry-After header is supplied, use it; otherwise, use exponential backoff - For 202 Accepted (thumbnail or file not ready) and 429 (too many requests), retry later, after a delay - specified by the Retry-After header. - For 5xx Server Error, retry later, after a delay; use exponential backoff to determine the delay. - - :param attempt_number: How many attempts at this request have already been tried. - :param retry_after_header: Value of the 'Retry-After` response header. - :return: Number of seconds to wait before retrying. - """ - if retry_after_header is not None: - try: - return int(retry_after_header) - except (ValueError, TypeError): - pass - min_randomization = 1 - self._retry_randomization_factor - max_randomization = 1 + self._retry_randomization_factor - randomization = (random.uniform(0, 1) * (max_randomization - min_randomization)) + min_randomization - exponential = math.pow(2, attempt_number) - return exponential * self._retry_base_interval * randomization - - @staticmethod - def _raise_on_unsuccessful_request(network_response: 'NetworkResponse', request: '_BoxRequest', raised_exception: Exception) -> None: - """ - Raise an exception if the request was unsuccessful. - - :param network_response: - The network response which is being tested for success. - :param request: - The API request that could be unsuccessful. - """ - if network_response is None: - raise raised_exception - - if not network_response.ok: - response_json = {} - try: - response_json = network_response.json() - except ValueError: - pass - raise BoxAPIException( - status=network_response.status_code, - headers=network_response.headers, - code=response_json.get('code', None) or response_json.get('error', None), - message=response_json.get('message', None) or response_json.get('error_description', None), - request_id=response_json.get('request_id', None), - url=request.url, - method=request.method, - context_info=response_json.get('context_info', None), - network_response=network_response - ) - - if not Session._is_json_response_if_expected(network_response, request): - raise BoxAPIException( - status=network_response.status_code, - headers=network_response.headers, - message='Non-json response received, while expecting json response.', - url=request.url, - method=request.method, - network_response=network_response, - ) - - @staticmethod - def _is_json_response_if_expected(network_response: 'NetworkResponse', request: '_BoxRequest') -> bool: - """ - Validate that the response is json if the request expects json response. - - :param network_response: - The network response which is being tested for success. - :param request: - The API request that could be unsuccessful. - """ - return not request.expect_json_response or is_json_response(network_response) - - def _prepare_and_send_request( - self, - method: str, - url: str, - headers: dict = None, - auto_session_renewal: bool = True, - expect_json_response: bool = True, - **kwargs: Any - ) -> 'NetworkResponse': - """ - Prepare a request to be sent to the Box API. - - :param method: - The HTTP verb to use to make the request. - :param url: - The request URL. - :param headers: - Headers to include with the request. - :param auto_session_renewal: - Whether to automatically renew the session if the request fails due to an expired access token. - :param expect_json_response: - Whether the response content should be json. - """ - files = kwargs.get('files') - kwargs['file_stream_positions'] = None - if files: - kwargs['file_stream_positions'] = {name: file_tuple[1].tell() for name, file_tuple in files.items()} - attempt_number = 0 - request_headers = self._get_request_headers() - request_headers.update(headers or {}) - - request = self.box_request_constructor( - url=url, - method=method, - headers=request_headers, - auto_session_renewal=auto_session_renewal, - expect_json_response=expect_json_response, - ) - - skip_retry_codes = kwargs.pop('skip_retry_codes', set()) - - raised_exception = None - try: - network_response = self._send_request(request, **kwargs) - reauthentication_needed = network_response.status_code == 401 - except RequestException as request_exc: - raised_exception = request_exc - network_response = None - if 'EOF occurred in violation of protocol' in str(request_exc): - reauthentication_needed = True - elif any(text in str(request_exc) for text in [ - 'Connection aborted', 'Connection broken', 'Connection reset' - ]): - reauthentication_needed = False - else: - raise - - while True: - retry = self._get_retry_request_callable( - network_response, attempt_number, request, skip_retry_codes, reauthentication_needed, **kwargs) - - if retry is None or attempt_number >= API.MAX_RETRY_ATTEMPTS: - if network_response is None: - raise raised_exception - break - - attempt_number += 1 - self._logger.debug('Retrying request') - network_response = retry(request, **kwargs) - - self._raise_on_unsuccessful_request(network_response, request, raised_exception) - - return network_response - - def _get_retry_request_callable( - self, - network_response: Optional['NetworkResponse'], - attempt_number: int, - request: '_BoxRequest', - skip_retry_codes: Set[int], - session_renewal_needed: bool = False, - **kwargs: Any - ) -> Optional[Callable]: - """ - Get a callable that retries a request for certain types of failure. - - For 202 Accepted (thumbnail or file not ready) and 429 (too many requests), retry later, after a delay - specified by the Retry-After header. - For 5xx Server Error, retry later, after a delay; use exponential backoff to determine the delay. - - Otherwise, return None. - - :param network_response: - The response from the Box API. - :param attempt_number: - How many attempts at this request have already been tried. Used for exponential backoff calculations. - :param request: - The API request that could require retrying. - :return: - Callable that, when called, will retry the request. Takes the same parameters as :meth:`_send_request`. - """ - # pylint:disable=unused-argument - # pylint:disable=line-too-long - if network_response is None or (network_response.ok and request.method == 'GET' and not self._is_json_response_if_expected(network_response, request)): - return partial( - self._network_layer.retry_after, - self.get_retry_after_time(attempt_number, None), - self._send_request, - ) - code = network_response.status_code - - if (code in (202, 429) or code >= 500) and code not in skip_retry_codes and not self._is_server_auth_type(kwargs): - return partial( - self._network_layer.retry_after, - self.get_retry_after_time(attempt_number, network_response.headers.get('Retry-After', None)), - self._send_request, - ) - return None - - def _is_server_auth_type(self, kwargs: dict) -> bool: - data = kwargs.get('data', {}) - grant_type = None - try: - if 'grant_type' in data: - grant_type = data['grant_type'] - except TypeError: - pass - return grant_type in (self._JWT_GRANT_TYPE, self._CCG_GRANT_TYPE) - - def _get_request_headers(self) -> dict: - return self._default_headers.copy() - - def _prepare_proxy(self) -> Optional[dict]: - """ - Prepares basic authenticated and unauthenticated proxies for requests. - - :return: - A prepared proxy dict to send along with the request. None if incorrect parameters were passed. - """ - proxy = {} - if self._proxy_config.URL is None: - return None - if self._proxy_config.AUTH and {'user', 'password'} <= set(self._proxy_config.AUTH): - host = self._proxy_config.URL - address = host.split('//')[1] - proxy_string = f'http://{self._proxy_config.AUTH.get("user", None)}:' \ - f'{self._proxy_config.AUTH.get("password", None)}@{address}' - elif self._proxy_config.AUTH is None: - proxy_string = self._proxy_config.URL - else: - raise BoxException("The proxy auth dict you provided does not match pattern " - "{'user': 'example_user', 'password': 'example_password'}") - proxy['http'] = proxy_string - proxy['https'] = proxy['http'] - - return proxy - - def _send_request(self, request: '_BoxRequest', **kwargs: Any) -> 'NetworkResponse': - """ - Make a request to the Box API. - - :param request: - The API request to send. - """ - # Reset stream positions to what they were when the request was made so the same data is sent even if this - # is a retried attempt. - files, file_stream_positions, stream_file_content = ( - kwargs.get('files'), kwargs.pop('file_stream_positions'), kwargs.pop('stream_file_content', True)) - request_kwargs = self._default_network_request_kwargs.copy() - request_kwargs.update(kwargs) - proxy_dict = self._prepare_proxy() - if proxy_dict is not None: - request_kwargs.update({'proxies': proxy_dict}) - if files and file_stream_positions: - for name, position in file_stream_positions.items(): - files[name][1].seek(position) - if stream_file_content: - data = request_kwargs.pop('data', {}) - multipart_stream = MultipartStream(data, files) - request_kwargs['data'] = multipart_stream - del request_kwargs['files'] - request.headers['Content-Type'] = multipart_stream.content_type - request.access_token = request_kwargs.pop('access_token', None) - - # send the request - network_response = self._network_layer.request( - request.method, - request.url, - access_token=request.access_token, - headers=request.headers, - log_response_content=request.expect_json_response, - **request_kwargs - ) - - return network_response - - -class AuthorizedSession(Session): - """ - Box API authorized session. Provides auth, automatic retry of failed requests, and session renewal. - """ - - def __init__(self, oauth: 'OAuth2', **kwargs: Any): - """ - :param oauth: - OAuth2 object used by the session to authorize requests. - :param session: - The Box API session to wrap for authorization. - """ - super().__init__(**kwargs) - self._oauth = oauth - - def get_constructor_kwargs(self) -> dict: - kwargs = super().get_constructor_kwargs() - kwargs['oauth'] = self._oauth - return kwargs - - def _renew_session(self, access_token_used: Optional[str]) -> str: - """ - Renews the session by refreshing the access token. - - :param access_token_used: - The access token that's currently being used by the session, that needs to be refreshed. - """ - new_access_token, _ = self._oauth.refresh(access_token_used) - return new_access_token - - def _get_retry_request_callable( - self, - network_response: Optional['NetworkResponse'], - attempt_number: int, - request: '_BoxRequest', - skip_retry_codes: Set[int], - session_renewal_needed: bool = False, - **kwargs: Any - ) -> Callable: - """ - Get a callable that retries a request for certain types of failure. - - For 401 Unauthorized responses, renew the session by refreshing the access token; then retry. - - Otherwise, defer to baseclass implementation. - - :param network_response: - The response from the Box API. - :param attempt_number: - How many attempts at this request have already been tried. Used for exponential backoff calculations. - :param request: - The API request that could require retrying. - :return: - Callable that, when called, will retry the request. Takes the same parameters as :meth:`_send_request`. - """ - if request.auto_session_renewal and session_renewal_needed: - self._renew_session(request.access_token) - request.auto_session_renewal = False - return self._send_request - - return super()._get_retry_request_callable( - network_response, - attempt_number, - request, - skip_retry_codes, - session_renewal_needed, - **kwargs - ) - - def _send_request(self, request: '_BoxRequest', **kwargs: Any) -> 'NetworkResponse': - """ - Make a request to the Box API. - - :param request: - The API request to send. - """ - # Since there can be session renewal happening in the middle of preparing the request, it's important to be - # consistent with the access_token being used in the request. - access_token = self._oauth.access_token - if request.auto_session_renewal and access_token is None: - access_token = self._renew_session(None) - request.auto_session_renewal = False - authorization_header = {'Authorization': f'Bearer {access_token}'} - request.headers.update(authorization_header) - kwargs['access_token'] = access_token - return super()._send_request(request, **kwargs) diff --git a/boxsdk/util/__init__.py b/boxsdk/util/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/boxsdk/util/api_call_decorator.py b/boxsdk/util/api_call_decorator.py deleted file mode 100644 index db963b4fc..000000000 --- a/boxsdk/util/api_call_decorator.py +++ /dev/null @@ -1,68 +0,0 @@ -from functools import update_wrapper, wraps -from typing import Callable, Any - -from ..object.cloneable import Cloneable - - -def api_call(method: Callable) -> Any: - """ - Designates the decorated method as one that makes a Box API call. - The decorated method can then accept a new keyword argument `extra_network_parameters`, - a dictionary of key-value pairs to be passed to the network layer for API - calls made by the method. - - The decorated method must belong to a subclass of `Cloneable` as using this - decorator and then passing a `extra_network_parameters` parameter to the method will cause - the object's clone method to be called. - - :param method: - The method to decorate. - :return: - A wrapped method that can pass extra request data to the network layer. - """ - return APICallWrapper(method) - - -class APICallWrapper: - - def __init__(self, func_that_makes_an_api_call: Callable): - super().__init__() - self._func_that_makes_an_api_call = func_that_makes_an_api_call - self.__name__ = func_that_makes_an_api_call.__name__ - update_wrapper(self, func_that_makes_an_api_call) - - def __call__(self, cloneable_instance: 'Cloneable', *args: Any, **kwargs: Any) -> Any: - return self.__get__(cloneable_instance, type(cloneable_instance))(*args, **kwargs) - - def __get__(self, _instance: Any, owner: Any) -> Any: - # `APICallWrapper` is imitating a function. For native functions, - # ```func.__get__(None, cls)``` always returns `func`. - if _instance is None: - return self - - if isinstance(owner, type) and not issubclass(owner, Cloneable): - raise TypeError( - f"descriptor {self.__name__!r} must be owned by a 'Cloneable' subclass, not {owner.__name__}" - ) - expected_type = owner or Cloneable - if not isinstance(_instance, expected_type): - raise TypeError( - f"descriptor {self.__name__!r} for {expected_type.__name__!r} objects " - f"doesn't apply to {_instance.__class__.__name__!r} object" - ) - - @wraps(self._func_that_makes_an_api_call) - def call(instance, *args, **kwargs): - extra_network_parameters = kwargs.pop('extra_network_parameters', None) - if extra_network_parameters: - # If extra_network_parameters is specified, then clone the instance, and specify the parameters - # as the defaults to be used. - instance = instance.clone(instance.session.with_default_network_request_kwargs(extra_network_parameters)) - - method = self._func_that_makes_an_api_call.__get__(instance, owner) - return method(*args, **kwargs) - - # Since the caller passed a non-`None` instance to `__get__()`, they - # want a bound method back, not an unbound function. Thus, we must bind - # `call()` to `_instance` and then return that bound method. - return call.__get__(_instance, owner) diff --git a/boxsdk/util/chunked_uploader.py b/boxsdk/util/chunked_uploader.py deleted file mode 100644 index 85c4d860c..000000000 --- a/boxsdk/util/chunked_uploader.py +++ /dev/null @@ -1,205 +0,0 @@ -import hashlib -from concurrent.futures import ThreadPoolExecutor, as_completed, Future -from threading import Lock -from typing import IO, TYPE_CHECKING, Optional, List - -from boxsdk.exception import BoxException -from boxsdk.config import API - -if TYPE_CHECKING: - from boxsdk.object.file import File - from boxsdk.object.upload_session import UploadSession - - -class ChunkedUploader: - - def __init__(self, upload_session: 'UploadSession', content_stream: IO[bytes], file_size: int): - """ - The initializer for the :class:`ChunkedUploader` - - :param upload_session: - The upload session for doing the chunked uploader. - :param content_stream: - The file-like object to upload. - :param file_size: - The total size of the file for the chunked upload. - :returns: - An intialized`ChunkedUploader` object. - """ - self._upload_session = upload_session - self._content_stream = content_stream - self._file_size = file_size - self._part_array = [] - self._sha1 = hashlib.sha1() - self._part_definitions = {} - self._is_aborted = False - self._lock = Lock() - self._inflight_parts = {} - self._chunk_index = 0 - self._executor = ThreadPoolExecutor(max_workers=API.CHUNK_UPLOAD_THREADS) - - def start(self) -> Optional['File']: - """ - Starts the process of chunk uploading a file. Should return file. If commit was not processed will return None. - You can call ChunkedUploader.resume to retry committing upload. - - :returns: - An uploaded :class:`File` or None if session was not processed - """ - if self._is_aborted: - raise BoxException('The upload has been previously aborted. Please retry upload with a new upload session.') - - futures = [self._executor.submit(self._upload_part) for _ in range(self._upload_session.total_parts)] - self._upload(futures) - return self._commit_and_erase_stream_reference_when_succeed() - - def resume(self) -> Optional['File']: - """ - Resumes the process of chunk uploading a file from where upload failed. - Should return file. If commit was not processed will return None. - You can call ChunkedUploader.resume to retry committing upload. - - :returns: - An uploaded :class:`File` or None if session was not processed - """ - if self._is_aborted: - raise BoxException('The upload has been previously aborted. Please retry upload with a new upload session.') - - self._executor = ThreadPoolExecutor(max_workers=API.CHUNK_UPLOAD_THREADS) - parts = self._upload_session.get_parts() - for part in parts: - self._part_definitions[part['offset']] = part - - with self._lock: - futures = [self._executor.submit(lambda item=part: self._upload_part(item)) for part in self._inflight_parts.values()] - for _ in range(self._upload_session.total_parts - self._chunk_index - len(self._inflight_parts)): - futures.append(self._executor.submit(self._upload_part)) - - self._upload(futures) - return self._commit_and_erase_stream_reference_when_succeed() - - def abort(self) -> bool: - """ - Abort an upload session, cancelling the upload and removing any parts that have already been uploaded. - - :returns: - A boolean indication success of the upload abort. - """ - self._content_stream = None - self._part_definitions = {} - self._inflight_parts = {} - self._is_aborted = True - return self._upload_session.abort() - - def _upload(self, futures: List[Future]) -> None: - """ - Utility function for looping through all parts of the upload session and uploading them. - """ - try: - for future in as_completed(futures): - future.result() - except Exception as exc: - self._executor.shutdown(wait=True) - raise exc - - self._part_array = sorted(self._part_definitions.values(), key=lambda part: part['offset']) - - def _upload_part(self, task=None): - if isinstance(task, InflightPart): - next_part = task - else: - with self._lock: - next_part = self._get_next_part() - self._sha1.update(next_part.chunk) - - with self._lock: - if self._part_definitions.get(next_part.offset): - return - self._inflight_parts[next_part.offset] = next_part - - uploaded_part = next_part.upload() - - with self._lock: - self._part_definitions[next_part.offset] = uploaded_part - del self._inflight_parts[next_part.offset] - - def _get_next_part(self) -> 'InflightPart': - """ - Retrieves the next :class:`InflightPart` that needs to be uploaded - - :returns: - The :class:`InflightPart` object to be uploaded next. - """ - copied_length = 0 - chunk = b'' - offset = self._chunk_index * self._upload_session.part_size - self._chunk_index += 1 - while copied_length < self._upload_session.part_size: - bytes_read = self._content_stream.read(self._upload_session.part_size - copied_length) - if bytes_read is None: - # stream returns none when no bytes are ready currently but there are - # potentially more bytes in the stream to be read. - continue - if not bytes_read: - # stream is exhausted. - break - chunk += bytes_read - copied_length += len(bytes_read) - return InflightPart(offset, chunk, self._upload_session, self._file_size) - - def _commit_and_erase_stream_reference_when_succeed(self): - content_sha1 = self._sha1.digest() - commit_result = self._upload_session.commit(content_sha1=content_sha1, parts=self._part_array) - # Remove file stream reference when uploading file succeeded - if commit_result is not None: - self._content_stream = None - self._executor.shutdown(wait=True) - return commit_result - - -class InflightPart: - - def __init__(self, offset: int, chunk: bytes, upload_session: 'UploadSession', total_size: int): - """ - The initializer for the :class:`InflightPart` object. - - :param offset: - The offset for the :class:`InflightPart` that represents the position of the part to be uploaded - :param chunk: - The chunk in bytes to be uploaded. - :param upload_session: - The :class:`UploadSession` for the :class:`InflightPart`. - :param total_size: - The total size of the file to be chunked uploaded. - """ - self._offset = offset - self._chunk = chunk - self._upload_session = upload_session - self._total_size = total_size - - @property - def offset(self) -> int: - """ - Getter for the offset of the :class:`InflightPart` - """ - return self._offset - - @property - def chunk(self) -> bytes: - """ - Getter for the chunk of the :class:`InflightPart` - """ - return self._chunk - - def upload(self) -> dict: - """ - Upload method for the :class:`InflightPart` - - :returns: - The uploaded part record. - """ - return self._upload_session.upload_part_bytes( - part_bytes=self.chunk, - offset=self.offset, - total_size=self._total_size - ) diff --git a/boxsdk/util/datetime_formatter.py b/boxsdk/util/datetime_formatter.py deleted file mode 100644 index ba3f0ab5c..000000000 --- a/boxsdk/util/datetime_formatter.py +++ /dev/null @@ -1,24 +0,0 @@ -from datetime import datetime -from typing import Union, Optional - -from dateutil import parser - - -def normalize_date_to_rfc3339_format(date: Union[None, datetime, str]) -> Optional[str]: - """ - Normalizes any datetime string supported by dateutil library or a datetime.datetime object to rfc3339 format. - - :param date: datetime str or datetime object - :return: date-time str in rfc3339 format - """ - if date is None: - return None - - if isinstance(date, str): - date = parser.parse(date) - - if not isinstance(date, datetime): - raise TypeError(f"Got unsupported type {date.__class__.__name__!r} for date.") - - timezone_aware_datetime = date if date.tzinfo is not None else date.astimezone() - return timezone_aware_datetime.isoformat(timespec='seconds') diff --git a/boxsdk/util/default_arg_value.py b/boxsdk/util/default_arg_value.py deleted file mode 100644 index ca217a8df..000000000 --- a/boxsdk/util/default_arg_value.py +++ /dev/null @@ -1 +0,0 @@ -SDK_VALUE_NOT_SET = object() diff --git a/boxsdk/util/deprecation_decorator.py b/boxsdk/util/deprecation_decorator.py deleted file mode 100644 index 89e893caa..000000000 --- a/boxsdk/util/deprecation_decorator.py +++ /dev/null @@ -1,29 +0,0 @@ -import warnings -from typing import Callable, Any - - -def deprecated(message: str): - def deprecated_decorator(func: Callable) -> Any: - def deprecated_func(*args, **kwargs): - warnings.simplefilter('default', DeprecationWarning) - warnings.warn(f'{func.__name__} is a deprecated function. {message}', - category=DeprecationWarning, - stacklevel=2) - return func(*args, **kwargs) - return deprecated_func - return deprecated_decorator - - -def deprecated_param(*, name: str, position: int, message: str): - def deprecated_decorator(func: Callable) -> Any: - def deprecated_func(*args, **kwargs): - if len(args) >= position + 1 or name in kwargs: - warnings.simplefilter('default', DeprecationWarning) - warnings.warn( - f'{func.__name__} function parameter `{name}` at position {position} is deprecated. {message}', - category=DeprecationWarning, - stacklevel=2 - ) - return func(*args, **kwargs) - return deprecated_func - return deprecated_decorator diff --git a/boxsdk/util/enum.py b/boxsdk/util/enum.py deleted file mode 100644 index 2296c5ae5..000000000 --- a/boxsdk/util/enum.py +++ /dev/null @@ -1,134 +0,0 @@ -# pylint:disable=no-value-for-parameter - -from collections import OrderedDict -from itertools import chain - -from enum import EnumMeta - -__all__ = ['ExtendableEnumMeta'] - -from typing import Any - - -class ExtendableEnumMeta(EnumMeta): - """A metaclass for enum hierarchies. - - This allows you to define hierarchies such as this: - - from box.util.compat import with_metaclass - - class EnumBase(with_metaclass(ExtendableEnumMeta, Enum)): pass - - class Enum1(EnumBase): - A = 'A' - - class Enum2(EnumBase): pass - - class Enum2_1(Enum2): - B = 'B' - - class Enum2_2(Enum2): - C = 'C' - - and have all members be accessible on EnumBase (as well as have all members - of Enum2_1 and Enum2_2 be available on Enum2) as if they had been defined - there. - - Non-leaf classes still may not have members directly defined on them, as - with standard enums. - - Most of the usual enum magic methods are extended: __contains__, __dir__, - __getitem__, __getattr__, __iter__, __len__, and __reversed__. Only __new__ - is not extended; instead, a new method `lookup` is provided. The - __members__ property is also extended. - """ - - def lookup(cls, value: Any) -> Any: - """Custom value lookup, which does recursive lookups on subclasses. - - If this is a leaf enum class with defined members, this acts the same - as __new__(). - - But if this is a base class with no defined members of its own, it - tries doing a value lookup on all its subclasses until it finds the - value. - - NOTE: Because of the implementation details of Enum, this must be a new - classmethod, and can't be implemented as __new__() [1]. - - [1] - - :param value: - The value to look up. Can be a value, or an enum instance. - :raises: - :class:`ValueError` if the value isn't found anywhere. - """ - try: - return cls(value) - except (ValueError, TypeError) as value_error: - for subclass in cls.__subclasses__(): - try: - return subclass.lookup(value) - except (ValueError, TypeError): - pass - raise value_error - - @property - def __members__(cls): - members = OrderedDict(super().__members__) - for subclass in cls.__subclasses__(): - members.update(subclass.__members__) - return members - - def __contains__(cls, member): - try: - if super().__contains__(member): - return True - except TypeError: - return False - - def in_(subclass): - return member in subclass - - return any(map(in_, cls.__subclasses__())) - - def __dir__(cls): - return list(set(super().__dir__()).union(*map(dir, cls.__subclasses__()))) - - def __getitem__(cls, name): - try: - return super().__getitem__(name) - except KeyError as key_error: - for subclass in cls.__subclasses__(): - try: - return subclass[name] - except KeyError: - pass - raise key_error - - def __getattr__(cls, name): - try: - return super().__getattr__(name) - except AttributeError as attribute_error: - try: - # If the super() call fails, don't call getattr() on all of the - # subclasses. Instead, use __getitem__ to do this. This is - # because we don't want to grab arbitrary attributes from - # subclasses, only enum members. For enum members, __getattr__ - # and __getitem__ have the same behavior. And __getitem__ has - # the advantage of never grabbing anything other than enum - # members. - return cls[name] # pylint:disable=unsubscriptable-object - except KeyError: - pass - - raise attribute_error - - def __iter__(cls): - return chain(super().__iter__(), chain.from_iterable(map(iter, cls.__subclasses__()))) - - def __len__(cls): - return super().__len__() + sum(map(len, cls.__subclasses__())) - - def __reversed__(cls): - return reversed(list(cls)) diff --git a/boxsdk/util/json.py b/boxsdk/util/json.py deleted file mode 100644 index 8ad709471..000000000 --- a/boxsdk/util/json.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import TYPE_CHECKING, Union - -if TYPE_CHECKING: - from boxsdk.session.box_response import BoxResponse - from boxsdk import NetworkResponse - - -def is_json_response(network_response: Union['BoxResponse', 'NetworkResponse']) -> bool: - """Return whether or not the network response content is json. - - :param network_response: - The response from the Box API. - """ - try: - network_response.json() - return True - except ValueError: - return False diff --git a/boxsdk/util/log.py b/boxsdk/util/log.py deleted file mode 100644 index 49278adcb..000000000 --- a/boxsdk/util/log.py +++ /dev/null @@ -1,115 +0,0 @@ -import logging -import re -import sys - -from collections.abc import Mapping -from typing import Union, IO, Optional - -_no_logger = object() - - -class Logging: - _has_setup = False - KEYS_TO_SANITIZE = ( - 'Authorization', - 'access_token', - 'refresh_token', - 'subject_token', - 'token', - 'client_id', - 'client_secret', - 'code', - 'shared_link', - 'download_url', - 'jwt_private_key', - 'jwt_private_key_passphrase', - 'password', - ) - - PROXY_KEYS_TO_SANITIZE = ( - 'http', - 'https', - ) - - def setup_logging(self, stream_or_file=_no_logger, debug=False, name=None): - if not self._has_setup: - self._has_setup = True - self._setup_logging(stream_or_file, debug, name) - - @staticmethod - def _setup_logging(stream_or_file=_no_logger, debug=False, name=None): - logger = logging.getLogger(name) - if isinstance(stream_or_file, str): - logger.addHandler(logging.FileHandler(stream_or_file, mode='a')) - elif stream_or_file is not _no_logger: - logger.addHandler(logging.StreamHandler(stream_or_file or sys.stdout)) - logger.setLevel(logging.DEBUG if debug else logging.INFO) - - @staticmethod - def sanitize_value(value): - return f'---{value[-4:]}' - - @staticmethod - def sanitize_proxy_value(value: str) -> str: - return re.sub( - '^(.*://)(.*):(.*)(@.*)$', - lambda repl: f'{repl.group(1)}{"---"}:{"---"}{repl.group(4)}', - value - ) - - def sanitize_dictionary(self, dictionary: Mapping) -> Mapping: - if not isinstance(dictionary, Mapping): - return dictionary - sanitized_dictionary = {} - for key, value in dictionary.items(): - if key in self.KEYS_TO_SANITIZE and isinstance(value, str): - sanitized_dictionary[key] = self.sanitize_value(value) - elif key in self.PROXY_KEYS_TO_SANITIZE and isinstance(value, str): - sanitized_dictionary[key] = self.sanitize_proxy_value(value) - elif isinstance(value, Mapping): - sanitized_dictionary[key] = self.sanitize_dictionary(value) - else: - sanitized_dictionary[key] = value - return sanitized_dictionary - - -_logging = Logging() - - -def setup_logging( - stream_or_file: Optional[Union[str, IO]] = _no_logger, - debug: Optional[bool] = False, - name: Optional[str] = None -) -> None: - """ - Create a logger for communicating with the user or writing to log files. - Sets the level to INFO or DEBUG, depending on the debug flag. - - If a stream or file is passed (or None is passed to stream_or_file), then - a handler to that stream or file (stdout for None) is added to the logger. - - :param stream_or_file: - The destination of the log messages. If None, stdout will be used. - :param debug: - Whether or not the logger will be at the DEBUG level (if False, the logger will be at the INFO level). - :param name: - The logging channel. If None, a root logger will be created. - """ - _logging.setup_logging(stream_or_file, debug, name) - - -def sanitize_dictionary(dictionary: Mapping) -> dict: - """ - Get a copy of a dictionary that has sensitive information redacted. Should be called on objects that will be - logged or printed. - - :param dictionary: Dictionary that may contain sensitive information. - :return: Copy of the dictionary with sensitive information redacted. - """ - return _logging.sanitize_dictionary(dictionary) - - -logging.getLogger(__name__).addHandler(logging.NullHandler()) - - -__all__ = ['setup_logging', 'sanitize_dictionary'] diff --git a/boxsdk/util/lru_cache.py b/boxsdk/util/lru_cache.py deleted file mode 100644 index 88581909e..000000000 --- a/boxsdk/util/lru_cache.py +++ /dev/null @@ -1,45 +0,0 @@ -from collections import OrderedDict -from typing import Any - - -class LRUCache: - def __init__(self, capacity: int = 512): - """ - :param capacity: - The Maximum number of key-value pairs can be cached. - """ - super().__init__() - self.capacity = capacity - self.cache = OrderedDict() - - def get(self, key: str) -> Any: - """ - Look up the value in cache using the associated key. Returns the value if found. - Raises :class:`KeyError` otherwise. - - :param key: - The key used to look up the cache. - :return: - The value associated with the key if exists. - :raises: - :class:`KeyError` if the key doesn't exist. - """ - value = self.cache.pop(key) - self.cache[key] = value - return value - - def set(self, key: str, value: Any = None): - """ - Store the key-value pair to cache. - - :param key: - The key associated with the value to be stored. It's used to look up the cache. - :param value: - The value to be stored. - """ - try: - self.cache.pop(key) - except KeyError: - if len(self.cache) >= self.capacity: - self.cache.popitem(last=False) - self.cache[key] = value diff --git a/boxsdk/util/multipart_stream.py b/boxsdk/util/multipart_stream.py deleted file mode 100644 index b7b7a5be0..000000000 --- a/boxsdk/util/multipart_stream.py +++ /dev/null @@ -1,18 +0,0 @@ -from collections import OrderedDict - -from requests_toolbelt.multipart.encoder import MultipartEncoder - - -class MultipartStream(MultipartEncoder): - """ - Subclass of the requests_toolbelt's :class:`MultipartEncoder` that ensures that data - is encoded before files. This allows a server to process information in the data before - receiving the file bytes. - """ - def __init__(self, data, files): - fields = OrderedDict() - for k in data: - fields[k] = data[k] - for k in files: - fields[k] = files[k] - super().__init__(fields) diff --git a/boxsdk/util/shared_link.py b/boxsdk/util/shared_link.py deleted file mode 100644 index 03b4e2f7e..000000000 --- a/boxsdk/util/shared_link.py +++ /dev/null @@ -1,14 +0,0 @@ -def get_shared_link_header(shared_link: str, password: str = None) -> dict: - """ - Gets the HTTP header required to use a shared link to grant access to a shared item. - - :param shared_link: - The shared link. - :param password: - The password for the shared link. - :return: - The item referred to by the shared link. - """ - shared_link_password = f'&shared_link_password={password}' if password is not None else '' - box_api_header = f'shared_link={shared_link}{shared_link_password}' - return {'BoxApi': box_api_header} diff --git a/boxsdk/util/text_enum.py b/boxsdk/util/text_enum.py deleted file mode 100644 index f9f3a40f3..000000000 --- a/boxsdk/util/text_enum.py +++ /dev/null @@ -1,9 +0,0 @@ -from enum import Enum - - -class TextEnum(str, Enum): - def __repr__(self): - return self._value_ # pylint:disable=no-member - - def __str__(self): - return str(self.value) # pylint:disable=no-member diff --git a/boxsdk/util/translator.py b/boxsdk/util/translator.py deleted file mode 100644 index 8e39cd858..000000000 --- a/boxsdk/util/translator.py +++ /dev/null @@ -1,172 +0,0 @@ -from collections import ChainMap -import inspect - -__all__ = list(map(str, ['Translator'])) - -from typing import Any, TYPE_CHECKING, Dict - -if TYPE_CHECKING: - from boxsdk.object.base_api_json_object import BaseAPIJSONObjectMeta - from boxsdk.session.session import Session - - -def _get_object_id(obj: dict) -> Any: - """ - Gets the ID for an API object. - - :param obj: - The API object - :return: - """ - return obj.get('id', None) - - -class Translator(ChainMap): - """ - Translate item responses from the Box API to Box objects. - - Also acts as a :class:`Mapping` from type names to Box object classes. - - There exists a global default `Translator`, containing the default API - object classes defined by the SDK. Custom `Translator` instances can be - created to extend the default `Translator` with custom subclasses. - - A `Translator` is a :class:`ChainMap`, so that one translator can "extend" - others. The most common scenario would be a custom, non-global - `Translator` that extends only the default translator, to register 0 or - more new classes. But more complex inheritance is also allowed, in case - that is useful. - """ - - __slots__ = () - - # :attr _default_translator: - # A global `Translator` containing the default API object classes - # defined by the SDK. By default, new `Translator` instances will - # "extend" this one, so that the global registrations are reflected - # automatically. - # - # NOTE: For convenience and backwards-compatability, developers are - # allowed to register their own custom subclasses with - # `_default_translator`, but are encouraged not to. The default - # translator may change or be removed in any major or minor release. - # Additionally, it has the usual hazards of mutable global state. - # The supported and recommended ways for registering custom subclasses - # are: - # - # - Constructing a new `Translator`, calling `Translator.register()` as - # necessary, and passing it to the `BoxSession` constructor. - # - Calling `session.translator.register()` on an existing - # `BoxSession`. - # - Calling `client.translator.register()` on an existing `Client`. - # :type _default_translator: :class:`Translator` - _default_translator = {} # Will be set to a `Translator` instance below, after the class is defined. - - def __init__(self, *translation_maps: Dict[str, 'BaseAPIJSONObjectMeta'], **kwargs: Any): - """Baseclass override. - - :param translation_maps: - (variadic) The same as the `maps` variadic parameter to - :class:`ChainMap`, except restricted to maps from type names to Box - object classes. - :param extend_default_translator: - (optional, keyword-only) If `True` (the default), - `_default_translator` is appended to the end of `translation_maps`. - When this functionality is used, the new `Translator` will inherit - all of the global registrations. - :type extend_default_translator: `bool` - :param new_child: - (optional, keyword-only) If `True` (the default), a new empty - `dict` is prepended to the front of `translation_maps`. Either way, - the resulting `Translator` starts out with the same key-value - pairs. But when this is `False`, the first item in - `translation_maps` will be mutated by `__setitem__()` and - `__delitem()__` calls, which will affect other references to it. - Whereas when this is `True`, all items in `translation_maps` are - safe from mutation in normal usage scenarios. - :type new_child: `bool` - """ - translation_maps = list(translation_maps) - extend_default_translator = kwargs.pop('extend_default_translator', True) - new_child = kwargs.pop('new_child', True) - if extend_default_translator: - translation_maps.append(self._default_translator) - if new_child: - translation_maps.insert(0, {}) - super().__init__(*translation_maps, **kwargs) - - def register(self, type_name: str, box_cls: 'BaseAPIJSONObjectMeta') -> Any: - """Associate a Box object class to handle Box API item responses with the given type name. - - :param type_name: - The type name to be registered. - :param box_cls: - The Box object class, which will be associated with the type name provided. - """ - self[type_name] = box_cls - - def get(self, key: str, default: 'BaseAPIJSONObjectMeta' = None) -> 'BaseAPIJSONObjectMeta': - """Get the box object class associated with the given type name. - - :param key: - The type name to be translated. - :param default: - (optional) The default Box object class to return. - Defaults to `BaseObject`. - """ - # pylint:disable=import-outside-toplevel - from boxsdk.object.base_object import BaseObject - if default is None: - default = BaseObject - return super().get(key, default) - - def translate(self, session: 'Session', response_object: dict) -> Any: - """ - Translate a given API response object into SDK classes, rescursively translating any subobjects. - - :param session: - The SDK session to use for any objects that require a session (i.e. classes that make API calls) - :param response_object: - The JSON response object from the API, which will be translated - :return: - The translated object - """ - - if not isinstance(response_object, dict): - return response_object - - translated_obj = {} - object_type = response_object.get('type', None) - object_class = self.get(object_type) if object_type is not None else None - # Parent classes have the ability to skip fields that they do not want translated - fields_to_skip = object_class.untranslated_fields() if object_class is not None else () - for key in response_object: - if key in fields_to_skip: - translated_obj[key] = response_object[key] - continue - if isinstance(response_object[key], dict): - translated_obj[key] = self.translate(session, response_object[key]) - elif isinstance(response_object[key], list): - translated_obj[key] = [self.translate(session, o) for o in response_object[key]] - else: - translated_obj[key] = response_object[key] - - # Try to translate any API object with a `type` property, except for metadata instances - # The $type value in metadata instances isn't directly usable, so we avoid it altogether - # NOTE: Currently, we represent metadata as just a `dict`, so there's no need to translate it anyway - # Metadata field objects are another issue; they contain a 'type' property that doesn't really - # map to a Box object. We probably want to treat these as just `dict`s, so they're excluded here - if object_class is not None and '$type' not in translated_obj: - param_values = { - 'session': session, - 'response_object': translated_obj, - 'object_id': _get_object_id(translated_obj), - } - params = inspect.signature(object_class.__init__).parameters - param_values = {p: param_values[p] for p in params if p in param_values} - return object_class(**param_values) - - return translated_obj - - -Translator._default_translator = Translator(extend_default_translator=False) # pylint:disable=protected-access diff --git a/boxsdk/version.py b/boxsdk/version.py deleted file mode 100644 index 6e9b3ce48..000000000 --- a/boxsdk/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '3.14.0' diff --git a/demo/Advanced Auth Techniques.ipynb b/demo/Advanced Auth Techniques.ipynb deleted file mode 100644 index 6407b7a26..000000000 --- a/demo/Advanced Auth Techniques.ipynb +++ /dev/null @@ -1,193 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Python SDK tries to make it easy to make requests to the Box API.\n", - "\n", - "Part of the ease is the fact that the SDK handles the OAuth2 dance, including token refresh, even for multithreaded applications.\n", - "\n", - "New to the SDK are some OAuth2 subclasses and mixins that enable the SDK to handle auth in a variety of advanced use cases:\n", - "* Cooperative multiprocessing - share token pairs across python processes\n", - "* Remote auth - make API requests on a local client, but handle auth on a remote server to avoid exposing a client secret on clients\n", - "* Distributed cooperative multiprocessing - store token pairs in Redis; share among multiple processes or machines" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from boxsdk import Client\n", - "from boxsdk.auth.redis_managed_oauth2 import RedisManagedOAuth2" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Get a client ID and secret from a text file (to avoid exposing them in the notebook)\n", - "with open('secrets.txt') as secrets:\n", - " client_id = secrets.readline().strip()\n", - " client_secret = secrets.readline().strip()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# Instantiate a redis managed auth client\n", - "auth = RedisManagedOAuth2(client_id=client_id, client_secret=client_secret)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "(u'g9katDPiv0C5DxcoDmzxxFzumMKpiH2z',\n", - " u'pvGLrhHFdd70Ka7kPY1xxSAQ5RHAWrZBAuA4zbimV1o2Xrouvu0Ixl7iCERU7yTw')" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Authenticate the instance using an auth code (obtained manually)\n", - "auth.authenticate(auth_code='thZWxpiTRPhJpqZRNidr1vZLsmWpHRLI')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The auth instance automatically saves the tokens to redis.\n", - "\n", - "We can see the values directly in redis." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1) \"g9katDPiv0C5DxcoDmzxxFzumMKpiH2z\"\r\n", - "2) \"pvGLrhHFdd70Ka7kPY1xxSAQ5RHAWrZBAuA4zbimV1o2Xrouvu0Ixl7iCERU7yTw\"\r\n" - ] - } - ], - "source": [ - "!redis-cli hvals {str(auth.unique_id)}" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "u'g9katDPiv0C5DxcoDmzxxFzumMKpiH2z'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "auth.access_token" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can spin up another auth instance that will share tokens.\n", - "\n", - "We just need to use the same unique id." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'g9katDPiv0C5DxcoDmzxxFzumMKpiH2z'" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "auth2 = RedisManagedOAuth2(client_id=client_id, client_secret=client_secret, unique_id=auth.unique_id)\n", - "auth2.access_token" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.9" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/demo/Development Client.ipynb b/demo/Development Client.ipynb deleted file mode 100644 index 283b5b5d0..000000000 --- a/demo/Development Client.ipynb +++ /dev/null @@ -1,152 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `Client` is the centerpiece of the Box SDK. We provide several subclasses of the `Client` that can be especially useful during development of applications using the SDK.\n", - "\n", - "* The `LoggingClient` sets up logging to `stdout` to help visualize API calls made by the SDK.\n", - "* The `DeveloperTokenClient` sets up auth using a developer token that you can get from the Box Developer Console.\n", - "* The `DevelopmentClient` does both!" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Enter developer token: ERTP2iXmvV6cPMAAkLfwufBiy3k0VbUb\n" - ] - } - ], - "source": [ - "from boxsdk import DevelopmentClient\n", - "client = DevelopmentClient()" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[36mGET https://api.box.com/2.0/users/me {'headers': {'Authorization': 'Bearer ERTP2iXmvV6cPMAAkLfwufBiy3k0VbUb',\n", - " 'User-Agent': 'box-python-sdk-2.0.0a12'},\n", - " 'params': None}\u001b[0m\n", - "\u001b[32m\"GET https://api.box.com/2.0/users/me\" 200 427\n", - "{'Strict-Transport-Security': 'max-age=31536000', 'Cache-Control': 'no-cache, no-store', 'Content-Encoding': 'gzip', 'Vary': 'Accept-Encoding', 'BOX-REQUEST-ID': '05vmol15bp0htusbqk406r8e335', 'Date': 'Mon, 04 Jun 2018 23:44:03 GMT', 'Content-Type': 'application/json', 'Age': '0', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive'}\n", - "{'address': '',\n", - " 'avatar_url': 'https://cloud.app.box.com/api/avatar/large/202476009',\n", - " 'created_at': '2013-09-09T14:35:35-07:00',\n", - " 'id': '202476009',\n", - " 'job_title': '',\n", - " 'language': 'en',\n", - " 'login': 'jmeadows@box.com',\n", - " 'max_upload_size': 34359738368,\n", - " 'modified_at': '2018-06-04T10:26:57-07:00',\n", - " 'name': 'Jeffrey Meadows',\n", - " 'phone': '',\n", - " 'space_amount': 1000000000000000.0,\n", - " 'space_used': 131650134148,\n", - " 'status': 'active',\n", - " 'timezone': 'America/Los_Angeles',\n", - " 'type': 'user'}\n", - "\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client.user('me').get()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[36mGET https://api.box.com/2.0/files/1234 {'headers': {'Authorization': 'Bearer ERTP2iXmvV6cPMAAkLfwufBiy3k0VbUb',\n", - " 'User-Agent': 'box-python-sdk-2.0.0a12'},\n", - " 'params': None}\u001b[0m\n", - "\u001b[31m\"GET https://api.box.com/2.0/files/1234\" 404 304\n", - "{'Strict-Transport-Security': 'max-age=31536000', 'Cache-Control': 'no-cache, no-store', 'Content-Encoding': 'gzip', 'Vary': 'Accept-Encoding', 'BOX-REQUEST-ID': '0bc0o82ru5o7hegpivej1dcsmbf', 'Date': 'Mon, 04 Jun 2018 23:45:37 GMT', 'Content-Type': 'application/json', 'Age': '0', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive'}\n", - "{'code': 'not_found',\n", - " 'context_info': {'errors': [{'message': \"Invalid value 'f_1234'. 'item' with \"\n", - " \"value 'f_1234' not found\",\n", - " 'name': 'item',\n", - " 'reason': 'invalid_parameter'}]},\n", - " 'help_url': 'http://developers.box.com/docs/#errors',\n", - " 'message': 'Not Found',\n", - " 'request_id': 'r5fkoyfrzypwe2pe',\n", - " 'status': 404,\n", - " 'type': 'error'}\n", - "\u001b[0m\n" - ] - } - ], - "source": [ - "from boxsdk.exception import BoxAPIException\n", - "try:\n", - " client.file('1234').get()\n", - "except BoxAPIException as e:\n", - " pass" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As you can see, **requests** are logged in blue; **successful responses** in green, **error responses** in red." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/demo/Shared Link.ipynb b/demo/Shared Link.ipynb deleted file mode 100644 index 584f58d28..000000000 --- a/demo/Shared Link.ipynb +++ /dev/null @@ -1,135 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Python SDK has the capability to operate on files and folders shared with a user via shared link.\n", - "\n", - "Simply use `client.with_shared_link()` to get a client instance that can access the shared resource." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "from boxsdk import Client, OAuth2\n", - "# Create an SDK client using a developer token\n", - "client = Client(OAuth2(None, None, access_token='KoquazbCU7fxrMM0wSgGiAlWKZNeQGgq'))" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# This is a shared link created with a different Box account\n", - "SHARED_LINK_URL = 'https://app.box.com/s/gjkefibtegdn318xqnsu3koc35qt18z3'" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "u'Book1.xls'" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "shared_item = client.get_shared_item(SHARED_LINK_URL)\n", - "shared_item.name" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "ename": "BoxAPIException", - "evalue": "\nMessage: Not Found\nStatus: 404\nCode: not_found\nRequest id: 135510722156675a3fa6c6c\nHeaders: {'Content-Length': '242', 'Content-Encoding': 'gzip', 'Age': '0', 'Vary': 'Accept-Encoding', 'Server': 'ATS', 'Connection': 'keep-alive', 'Cache-Control': 'no-cache, no-store', 'Date': 'Tue, 08 Dec 2015 22:31:27 GMT', 'Content-Type': 'application/json'}\nURL: https://api.box.com/2.0/files/39001292145\nMethod: GET\nContext info: {u'errors': [{u'reason': u'invalid_parameter', u'message': u\"Invalid value 'f_39001292145'. 'item' with value 'f_39001292145' not found\", u'name': u'item'}]}", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mBoxAPIException\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# This account doesn't actually have permissions on this file outside of the shared link context\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mclient\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mshared_item\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mid\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m/Users/jmeadows/.virtualenvs/sdknotebooks/lib/python2.7/site-packages/boxsdk/object/item.pyc\u001b[0m in \u001b[0;36mget\u001b[0;34m(self, fields, etag)\u001b[0m\n\u001b[1;32m 124\u001b[0m \u001b[0;31m# pylint:disable=arguments-differ\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 125\u001b[0m \u001b[0mheaders\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m'If-None-Match'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0metag\u001b[0m\u001b[0;34m}\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0metag\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mNone\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 126\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mItem\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfields\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mfields\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mheaders\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 127\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 128\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mcopy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mparent_folder\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/jmeadows/.virtualenvs/sdknotebooks/lib/python2.7/site-packages/boxsdk/object/base_object.pyc\u001b[0m in \u001b[0;36mget\u001b[0;34m(self, fields, headers)\u001b[0m\n\u001b[1;32m 103\u001b[0m \u001b[0murl\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_url\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 104\u001b[0m \u001b[0mparams\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m'fields'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m','\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfields\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m}\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mfields\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 105\u001b[0;31m \u001b[0mbox_response\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_session\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mparams\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mparams\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mheaders\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 106\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__class__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_session\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_object_id\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbox_response\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjson\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 107\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/jmeadows/.virtualenvs/sdknotebooks/lib/python2.7/site-packages/boxsdk/session/box_session.pyc\u001b[0m in \u001b[0;36mget\u001b[0;34m(self, url, **kwargs)\u001b[0m\n\u001b[1;32m 370\u001b[0m \u001b[0;34m`\u001b[0m\u001b[0municode\u001b[0m\u001b[0;34m`\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 371\u001b[0m \"\"\"\n\u001b[0;32m--> 372\u001b[0;31m \u001b[0mresponse\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_prepare_and_send_request\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'GET'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 373\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mBoxResponse\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresponse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 374\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/jmeadows/.virtualenvs/sdknotebooks/lib/python2.7/site-packages/boxsdk/session/box_session.pyc\u001b[0m in \u001b[0;36m_prepare_and_send_request\u001b[0;34m(self, method, url, headers, auto_session_renewal, expect_json_response, attempt_number, **kwargs)\u001b[0m\n\u001b[1;32m 274\u001b[0m \u001b[0mattempt_number\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 275\u001b[0m \u001b[0mfile_stream_positions\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mfile_stream_positions\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 276\u001b[0;31m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 277\u001b[0m )\n\u001b[1;32m 278\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/jmeadows/.virtualenvs/sdknotebooks/lib/python2.7/site-packages/boxsdk/session/box_session.pyc\u001b[0m in \u001b[0;36m_make_request\u001b[0;34m(self, method, url, headers, auto_session_renewal, expect_json_response, attempt_number, **kwargs)\u001b[0m\n\u001b[1;32m 358\u001b[0m )\n\u001b[1;32m 359\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 360\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_raise_on_unsuccessful_request\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnetwork_response\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mexpect_json_response\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 361\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 362\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mnetwork_response\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/jmeadows/.virtualenvs/sdknotebooks/lib/python2.7/site-packages/boxsdk/session/box_session.pyc\u001b[0m in \u001b[0;36m_raise_on_unsuccessful_request\u001b[0;34m(self, network_response, expect_json_response, method, url)\u001b[0m\n\u001b[1;32m 213\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 214\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 215\u001b[0;31m \u001b[0mcontext_info\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mresponse_json\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'context_info'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 216\u001b[0m )\n\u001b[1;32m 217\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mexpect_json_response\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_is_json_response\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnetwork_response\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mBoxAPIException\u001b[0m: \nMessage: Not Found\nStatus: 404\nCode: not_found\nRequest id: 135510722156675a3fa6c6c\nHeaders: {'Content-Length': '242', 'Content-Encoding': 'gzip', 'Age': '0', 'Vary': 'Accept-Encoding', 'Server': 'ATS', 'Connection': 'keep-alive', 'Cache-Control': 'no-cache, no-store', 'Date': 'Tue, 08 Dec 2015 22:31:27 GMT', 'Content-Type': 'application/json'}\nURL: https://api.box.com/2.0/files/39001292145\nMethod: GET\nContext info: {u'errors': [{u'reason': u'invalid_parameter', u'message': u\"Invalid value 'f_39001292145'. 'item' with value 'f_39001292145' not found\", u'name': u'item'}]}" - ] - } - ], - "source": [ - "# This account doesn't actually have permissions on this file outside of the shared link context\n", - "client.file(shared_item.id).get()" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Operations on the item are allowed using client.get_shared_item() or client.with_shared_link()\n", - "client.with_shared_link(SHARED_LINK_URL, None).file(shared_item.id).get()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.9" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/demo/__init__.py b/demo/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/demo/auth.py b/demo/auth.py deleted file mode 100644 index 4e33bae7a..000000000 --- a/demo/auth.py +++ /dev/null @@ -1,64 +0,0 @@ -import bottle -import os -from threading import Thread, Event -import webbrowser -from wsgiref.simple_server import WSGIServer, WSGIRequestHandler, make_server - -from boxsdk import OAuth2 - - -CLIENT_ID = '' # Insert Box client ID here -CLIENT_SECRET = '' # Insert Box client secret here - - -def authenticate(oauth_class=OAuth2): - class StoppableWSGIServer(bottle.ServerAdapter): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._server = None - - def run(self, app): - server_cls = self.options.get('server_class', WSGIServer) - handler_cls = self.options.get('handler_class', WSGIRequestHandler) - self._server = make_server(self.host, self.port, app, server_cls, handler_cls) - self._server.serve_forever() - - def stop(self): - self._server.shutdown() - - auth_code = {} - auth_code_is_available = Event() - - local_oauth_redirect = bottle.Bottle() - - @local_oauth_redirect.get('/') - def get_token(): - auth_code['auth_code'] = bottle.request.query.code - auth_code['state'] = bottle.request.query.state - auth_code_is_available.set() - - local_server = StoppableWSGIServer(host='localhost', port=8080) - server_thread = Thread(target=lambda: local_oauth_redirect.run(server=local_server)) - server_thread.start() - - oauth = oauth_class( - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - ) - auth_url, csrf_token = oauth.get_authorization_url('http://localhost:8080') - webbrowser.open(auth_url) - - auth_code_is_available.wait() - local_server.stop() - assert auth_code['state'] == csrf_token - access_token, refresh_token = oauth.authenticate(auth_code['auth_code']) - - print('access_token: ' + access_token) - print('refresh_token: ' + refresh_token) - - return oauth, access_token, refresh_token - - -if __name__ == '__main__': - authenticate() - os._exit(0) diff --git a/demo/cooperative_auth.py b/demo/cooperative_auth.py deleted file mode 100644 index 5c7ea7c32..000000000 --- a/demo/cooperative_auth.py +++ /dev/null @@ -1,71 +0,0 @@ -from logging import getLogger -from multiprocessing import Manager, Process -from os import getpid - -from boxsdk.auth.cooperatively_managed_oauth2 import CooperativelyManagedOAuth2 -from boxsdk.util.log import setup_logging -from boxsdk import Client - -from .auth import authenticate, CLIENT_ID, CLIENT_SECRET - - -def main(): - # Create a multiprocessing manager to use as the token store - global tokens, refresh_lock - manager = Manager() - tokens = manager.Namespace() - refresh_lock = manager.Lock() - - # Authenticate in main process - oauth2, tokens.access, tokens.refresh = authenticate(CooperativelyManagedOAuth2) - - # Create 2 worker processes and wait on them to finish - workers = [] - for _ in range(2): - worker_process = Process(target=worker) - worker_process.start() - workers.append(worker_process) - for worker_process in workers: - worker_process.join() - - -def _retrive_tokens(): - return tokens.access, tokens.refresh - - -def _store_tokens(access_token, refresh_token): - tokens.access, tokens.refresh = access_token, refresh_token - - -def worker(): - # Set up a logging network, but use the LoggingProxy so we can see which PID is generating messages - logger = getLogger(f'boxsdk.network.{getpid()}') - setup_logging(name=logger.name) - - # Create a coop oauth2 instance. - # Tokens will be retrieved from and stored to the multiprocessing Namespace. - # A multiprocessing Lock will be used to synchronize token refresh. - # The tokens from the main process are used for initial auth. - # Whichever process needs to refresh - oauth2 = CooperativelyManagedOAuth2( - retrieve_tokens=_retrive_tokens, - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - store_tokens=_store_tokens, - access_token=tokens.access, - refresh_token=tokens.refresh, - refresh_lock=refresh_lock, - ) - client = Client(oauth2) - _do_work(client) - - -def _do_work(client): - # Do some work in a worker process. - # To see token refresh, perhaps put this in a loop (and don't forget to sleep for a bit between requests). - me = client.user(user_id='me').get() - items = client.folder('0').get_items(10) - - -if __name__ == '__main__': - main() diff --git a/demo/example.py b/demo/example.py deleted file mode 100644 index 12d4ff0ce..000000000 --- a/demo/example.py +++ /dev/null @@ -1,296 +0,0 @@ -import os -from boxsdk import Client -from boxsdk.exception import BoxAPIException -from boxsdk.object.collaboration import CollaborationRole -from demo.auth import authenticate - - -def run_user_example(client): - # 'me' is a handy value to get info on the current authenticated user. - me = client.user(user_id='me').get(fields=['login']) - print(f'The email of the user is: {me["login"]}') - - -def run_folder_examples(client): - root_folder = client.folder(folder_id='0').get() - print(f'The root folder is owned by: {root_folder.owned_by["login"]}') - - items = root_folder.get_items(limit=100, offset=0) - print('This is the first 100 items in the root folder:') - for item in items: - print(" " + item.name) - - -def run_collab_examples(client): - root_folder = client.folder(folder_id='0') - collab_folder = root_folder.create_subfolder('collab folder') - try: - print(f'Folder {collab_folder.get()["name"]} created') - collaboration = collab_folder.add_collaborator('someone@example.com', CollaborationRole.VIEWER) - print('Created a collaboration') - try: - modified_collaboration = collaboration.update_info(role=CollaborationRole.EDITOR) - print(f'Modified a collaboration: {modified_collaboration.role}') - finally: - collaboration.delete() - print('Deleted a collaboration') - finally: - # Clean up - print(f'Delete folder collab folder succeeded: {collab_folder.delete()}') - - -def rename_folder(client): - root_folder = client.folder(folder_id='0') - foo = root_folder.create_subfolder('foo') - try: - print(f'Folder {foo.get()["name"]} created') - - bar = foo.rename('bar') - print(f'Renamed to {bar.get()["name"]}') - finally: - print(f'Delete folder bar succeeded: {foo.delete()}') - - -def get_folder_shared_link(client): - root_folder = client.folder(folder_id='0') - collab_folder = root_folder.create_subfolder('shared link folder') - try: - print(f'Folder {collab_folder.get().name} created') - - shared_link = collab_folder.get_shared_link() - print('Got shared link:' + shared_link) - finally: - print(f'Delete folder collab folder succeeded: {collab_folder.delete()}') - - -def upload_file(client): - root_folder = client.folder(folder_id='0') - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'file.txt') - a_file = root_folder.upload(file_path, file_name='i-am-a-file.txt') - try: - print(f'{a_file.get()["name"]} uploaded: ') - finally: - print(f'Delete i-am-a-file.txt succeeded: {a_file.delete()}') - - -def upload_accelerator(client): - root_folder = client.folder(folder_id='0') - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'file.txt') - a_file = root_folder.upload(file_path, file_name='i-am-a-file.txt', upload_using_accelerator=True) - try: - print(f'{a_file.get()["name"]} uploaded via Accelerator: ') - file_v2_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'file_v2.txt') - a_file = a_file.update_contents(file_v2_path, upload_using_accelerator=True) - print(f'{a_file.get()["name"]} updated via Accelerator: ') - finally: - print(f'Delete i-am-a-file.txt succeeded: {a_file.delete()}') - - -def rename_file(client): - root_folder = client.folder(folder_id='0') - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'file.txt') - foo = root_folder.upload(file_path, file_name='foo.txt') - try: - print(f'{foo.get()["name"]} uploaded ') - bar = foo.rename('bar.txt') - print(f'Rename succeeded: {bool(bar)}') - finally: - foo.delete() - - -def update_file(client): - root_folder = client.folder(folder_id='0') - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'file.txt') - file_v1 = root_folder.upload(file_path, file_name='file_v1.txt') - try: - # print f'File content after upload: {file_v1.content()}' - file_v2_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'file_v2.txt') - file_v2 = file_v1.update_contents(file_v2_path) - # print f'File content after update: {file_v2.content()}' - finally: - file_v1.delete() - - -def search_files(client): - search_results = client.search().query( - 'i-am-a-file.txt', - limit=2, - offset=0, - ancestor_folders=[client.folder(folder_id='0')], - file_extensions=['txt'], - ) - for item in search_results: - item_with_name = item.get(fields=['name']) - print('matching item: ' + item_with_name.id) - else: - print('no matching items') - - -def copy_item(client): - root_folder = client.folder(folder_id='0') - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'file.txt') - a_file = root_folder.upload(file_path, file_name='a file.txt') - try: - subfolder1 = root_folder.create_subfolder('copy_sub') - try: - a_file.copy(subfolder1) - print(subfolder1.get_items(limit=10, offset=0)) - subfolder2 = root_folder.create_subfolder('copy_sub2') - try: - subfolder1.copy(subfolder2) - print(subfolder2.get_items(limit=10, offset=0)) - finally: - subfolder2.delete() - finally: - subfolder1.delete() - finally: - a_file.delete() - - -def move_item(client): - root_folder = client.folder(folder_id='0') - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'file.txt') - a_file = root_folder.upload(file_path, file_name='a file.txt') - try: - subfolder1 = root_folder.create_subfolder('move_sub') - try: - a_file.move(subfolder1) - print(subfolder1.get_items(limit=10, offset=0)) - subfolder2 = root_folder.create_subfolder('move_sub2') - try: - subfolder1.move(subfolder2) - print(subfolder2.get_items(limit=10, offset=0)) - finally: - subfolder2.delete() - finally: - try: - subfolder1.delete() - except BoxAPIException: - pass - finally: - try: - a_file.delete() - except BoxAPIException: - pass - - -def get_events(client): - print(client.events().get_events(limit=100, stream_position='now')) - - -def get_latest_stream_position(client): - print(client.events().get_latest_stream_position()) - - -def long_poll(client): - print(client.events().long_poll()) - - -def _delete_leftover_group(existing_groups, group_name): - """ - delete group if it already exists - """ - existing_group = next((g for g in existing_groups if g.name == group_name), None) - if existing_group: - existing_group.delete() - - -def run_groups_example(client): - """ - Shows how to interact with 'Groups' in the Box API. How to: - - Get info about all the Groups to which the current user belongs - - Create a Group - - Rename a Group - - Add a member to the group - - Remove a member from a group - - Delete a Group - """ - try: - # First delete group if it already exists - original_groups = client.groups() - _delete_leftover_group(original_groups, 'box_sdk_demo_group') - _delete_leftover_group(original_groups, 'renamed_box_sdk_demo_group') - - new_group = client.create_group('box_sdk_demo_group') - except BoxAPIException as ex: - if ex.status != 403: - raise - print('The authenticated user does not have permissions to manage groups. Skipping the test of this demo.') - return - - print('New group:', new_group.name, new_group.id) - - new_group = new_group.update_info({'name': 'renamed_box_sdk_demo_group'}) - print("Group's new name:", new_group.name) - - me_dict = client.user().get(fields=['login']) - me = client.user(user_id=me_dict['id']) - group_membership = new_group.add_member(me, 'member') - - members = list(new_group.membership()) - - print('The group has a membership of: ', len(members)) - print('The id of that membership: ', group_membership.object_id) - - group_membership.delete() - print('After deleting that membership, the group has a membership of: ', len(list(new_group.membership()))) - - new_group.delete() - groups_after_deleting_demo = client.groups() - has_been_deleted = not any(g.name == 'renamed_box_sdk_demo_group' for g in groups_after_deleting_demo) - print('The new group has been deleted: ', has_been_deleted) - - -def run_metadata_example(client): - root_folder = client.folder(folder_id='0') - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'file.txt') - foo = root_folder.upload(file_path, file_name='foo.txt') - print(f'{foo.get()["name"]} uploaded ') - try: - metadata = foo.metadata() - metadata.create({'foo': 'bar'}) - print(f'Created metadata: {metadata.get()}') - update = metadata.start_update() - update.update('/foo', 'baz', 'bar') - print(f'Updated metadata: {metadata.update(update)}') - finally: - foo.delete() - - -def run_examples(oauth): - - client = Client(oauth) - - run_user_example(client) - run_folder_examples(client) - run_collab_examples(client) - rename_folder(client) - get_folder_shared_link(client) - upload_file(client) - rename_file(client) - update_file(client) - search_files(client) - copy_item(client) - move_item(client) - get_events(client) - get_latest_stream_position(client) - # long_poll(client) - - # Enterprise accounts only - run_groups_example(client) - run_metadata_example(client) - - # Premium Apps only - upload_accelerator(client) - - -def main(): - - # Please notice that you need to put in your client id and client secret in demo/auth.py in order to make this work. - oauth, _, _ = authenticate() - run_examples(oauth) - os._exit(0) - - -if __name__ == '__main__': - main() diff --git a/demo/file.txt b/demo/file.txt deleted file mode 100644 index 2cadabd91..000000000 --- a/demo/file.txt +++ /dev/null @@ -1 +0,0 @@ -I am a file diff --git a/demo/file_v2.txt b/demo/file_v2.txt deleted file mode 100644 index e7bd22dd8..000000000 --- a/demo/file_v2.txt +++ /dev/null @@ -1 +0,0 @@ -I am updated \ No newline at end of file diff --git a/demo/music_player.py b/demo/music_player.py deleted file mode 100644 index d30ba9fee..000000000 --- a/demo/music_player.py +++ /dev/null @@ -1,37 +0,0 @@ -from random import shuffle -import subprocess -import tempfile -from boxsdk.client import Client -from demo.auth import authenticate - - -class MusicPlayer: - def __init__(self, folder_path): - self._folder_path = folder_path - self._client = self._get_client() - self._mp3_files = self._get_all_mp3_files(self._client) - shuffle(self._mp3_files) - - def _get_client(self): - oauth, _, _ = self._authenticate() - return Client(oauth) - - def _authenticate(self): - return authenticate() - - def _get_all_mp3_files(self, client): - # music_folder = client.folder(folder_id='0').get_subfolder('music') - return client.search().query(query='*.mp3', limit=100, offset=0, file_extensions=['mp3']) - - def play(self): - for item in self._mp3_files: - temp_file = tempfile.NamedTemporaryFile() - temp_file.write(item.content()) - item_with_name = item.get() - print(item_with_name.name) - subprocess.check_call(['afplay', temp_file.name]) - - -if __name__ == '__main__': - music_player = MusicPlayer('music') - music_player.play() diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 00910543f..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,177 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/BoxPythonSDK.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/BoxPythonSDK.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/BoxPythonSDK" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BoxPythonSDK" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..7b54f27b4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,86 @@ +# Documentation + +## High-level Documentation + +General explanations of the available functionality and examples of how to use +the SDK are available by topic: + +- [Ai](ai.md) +- [Ai studio](ai_studio.md) +- [App item associations](app_item_associations.md) +- [Archives](archives.md) +- [Authorization](authorization.md) +- [Avatars](avatars.md) +- [Chunked uploads](chunked_uploads.md) +- [Classifications](classifications.md) +- [Collaboration allowlist entries](collaboration_allowlist_entries.md) +- [Collaboration allowlist exempt targets](collaboration_allowlist_exempt_targets.md) +- [Collections](collections.md) +- [Comments](comments.md) +- [Device pinners](device_pinners.md) +- [Docgen](docgen.md) +- [Docgen template](docgen_template.md) +- [Downloads](downloads.md) +- [Email aliases](email_aliases.md) +- [Events](events.md) +- [File classifications](file_classifications.md) +- [File metadata](file_metadata.md) +- [File requests](file_requests.md) +- [File version legal holds](file_version_legal_holds.md) +- [File version retentions](file_version_retentions.md) +- [File versions](file_versions.md) +- [File watermarks](file_watermarks.md) +- [Files](files.md) +- [Folder classifications](folder_classifications.md) +- [Folder locks](folder_locks.md) +- [Folder metadata](folder_metadata.md) +- [Folder watermarks](folder_watermarks.md) +- [Folders](folders.md) +- [Groups](groups.md) +- [Hub collaborations](hub_collaborations.md) +- [Hub items](hub_items.md) +- [Hubs](hubs.md) +- [Integration mappings](integration_mappings.md) +- [Invites](invites.md) +- [Legal hold policies](legal_hold_policies.md) +- [Legal hold policy assignments](legal_hold_policy_assignments.md) +- [List collaborations](list_collaborations.md) +- [Memberships](memberships.md) +- [Metadata cascade policies](metadata_cascade_policies.md) +- [Metadata templates](metadata_templates.md) +- [Recent items](recent_items.md) +- [Retention policies](retention_policies.md) +- [Retention policy assignments](retention_policy_assignments.md) +- [Search](search.md) +- [Session termination](session_termination.md) +- [Shared links app items](shared_links_app_items.md) +- [Shared links files](shared_links_files.md) +- [Shared links folders](shared_links_folders.md) +- [Shared links web links](shared_links_web_links.md) +- [Shield information barrier reports](shield_information_barrier_reports.md) +- [Shield information barrier segment members](shield_information_barrier_segment_members.md) +- [Shield information barrier segment restrictions](shield_information_barrier_segment_restrictions.md) +- [Shield information barrier segments](shield_information_barrier_segments.md) +- [Shield information barriers](shield_information_barriers.md) +- [Shield lists](shield_lists.md) +- [Sign requests](sign_requests.md) +- [Sign templates](sign_templates.md) +- [Skills](skills.md) +- [Storage policies](storage_policies.md) +- [Storage policy assignments](storage_policy_assignments.md) +- [Task assignments](task_assignments.md) +- [Tasks](tasks.md) +- [Terms of service user statuses](terms_of_service_user_statuses.md) +- [Terms of services](terms_of_services.md) +- [Transfer](transfer.md) +- [Trashed files](trashed_files.md) +- [Trashed folders](trashed_folders.md) +- [Trashed items](trashed_items.md) +- [Trashed web links](trashed_web_links.md) +- [Uploads](uploads.md) +- [User collaborations](user_collaborations.md) +- [Users](users.md) +- [Web links](web_links.md) +- [Webhooks](webhooks.md) +- [Workflows](workflows.md) +- [Zip downloads](zip_downloads.md) diff --git a/docs/ai.md b/docs/ai.md new file mode 100644 index 000000000..59784e916 --- /dev/null +++ b/docs/ai.md @@ -0,0 +1,267 @@ +# AiManager + +- [Ask question](#ask-question) +- [Generate text](#generate-text) +- [Get AI agent default configuration](#get-ai-agent-default-configuration) +- [Extract metadata (freeform)](#extract-metadata-freeform) +- [Extract metadata (structured)](#extract-metadata-structured) + +## Ask question + +Sends an AI request to supported LLMs and returns an answer specifically focused on the user's question given the provided context. + +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/). + + + +```python +client.ai.create_ai_ask( + CreateAiAskMode.SINGLE_ITEM_QA, + "which direction sun rises", + [ + AiItemAsk( + id=file_to_ask.id, + type=AiItemAskTypeField.FILE, + content="Sun rises in the East", + ) + ], + ai_agent=ai_ask_agent_config, +) +``` + +### Arguments + +- mode `CreateAiAskMode` + - Box AI handles text documents with text representations up to 1MB in size, or a maximum of 25 files, whichever comes first. If the text file size exceeds 1MB, the first 1MB of text representation will be processed. Box AI handles image documents with a resolution of 1024 x 1024 pixels, with a maximum of 5 images or 5 pages for multi-page images. If the number of image or image pages exceeds 5, the first 5 images or pages will be processed. If you set mode parameter to `single_item_qa`, the items array can have one element only. Currently Box AI does not support multi-modal requests. If both images and text are sent Box AI will only process the text. +- prompt `str` + - The prompt provided by the client to be answered by the LLM. The prompt's length is limited to 10000 characters. +- items `List[AiItemAsk]` + - The items to be processed by the LLM, often files. +- dialogue_history `Optional[List[AiDialogueHistory]]` + - The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response. +- include_citations `Optional[bool]` + - A flag to indicate whether citations should be returned. +- ai_agent `Optional[Union[AiAgentAsk, AiAgentReference]]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Optional[AiResponseFull]`. + +A successful response including the answer from the LLM.No content is available to answer the question. This is returned when the request item is a hub, but content in the hubs is not indexed. To ensure content in the hub is indexed, make sure Box AI for Hubs in the Admin Console was enabled before hub creation. + +## Generate text + +Sends an AI request to supported Large Language Models (LLMs) and returns generated text based on the provided prompt. + +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/). + + + +```python +client.ai.create_ai_text_gen( + "Parapharse the document.s", + [ + CreateAiTextGenItems( + id=file_to_ask.id, + type=CreateAiTextGenItemsTypeField.FILE, + content="The Earth goes around the sun. Sun rises in the East in the morning.", + ) + ], + dialogue_history=[ + AiDialogueHistory( + prompt="What does the earth go around?", + answer="The sun", + created_at=date_time_from_string("2021-01-01T00:00:00Z"), + ), + AiDialogueHistory( + prompt="On Earth, where does the sun rise?", + answer="East", + created_at=date_time_from_string("2021-01-01T00:00:00Z"), + ), + ], + ai_agent=ai_text_gen_agent_config, +) +``` + +### Arguments + +- prompt `str` + - The prompt provided by the client to be answered by the LLM. The prompt's length is limited to 10000 characters. +- items `List[CreateAiTextGenItems]` + - The items to be processed by the LLM, often files. The array can include **exactly one** element. **Note**: Box AI handles documents with text representations up to 1MB in size. If the file size exceeds 1MB, the first 1MB of text representation will be processed. +- dialogue_history `Optional[List[AiDialogueHistory]]` + - The history of prompts and answers previously passed to the LLM. This parameter provides the additional context to the LLM when generating the response. +- ai_agent `Optional[Union[AiAgentReference, AiAgentTextGen]]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AiResponse`. + +A successful response including the answer from the LLM. + +## Get AI agent default configuration + +Get the AI agent default config. + +This operation is performed by calling function `get_ai_agent_default_config`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-ai-agent-default/). + + + +```python +client.ai.get_ai_agent_default_config(GetAiAgentDefaultConfigMode.ASK, language="en-US") +``` + +### Arguments + +- mode `GetAiAgentDefaultConfigMode` + - The mode to filter the agent config to return. +- language `Optional[str]` + - The ISO language code to return the agent config for. If the language is not supported the default agent config is returned. +- model `Optional[str]` + - The model to return the default agent config for. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Union[AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured]`. + +A successful response including the default agent configuration. +This response can be one of the following four objects: + +- AI agent for questions +- AI agent for text generation +- AI agent for freeform metadata extraction +- AI agent for structured metadata extraction. + The response depends on the agent configuration requested in this endpoint. + +## Extract metadata (freeform) + +Sends an AI request to supported Large Language Models (LLMs) and extracts metadata in form of key-value pairs. +In this request, both the prompt and the output can be freeform. +Metadata template setup before sending the request is not required. + +This operation is performed by calling function `create_ai_extract`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-ai-extract/). + + + +```python +client.ai.create_ai_extract( + "firstName, lastName, location, yearOfBirth, company", + [AiItemBase(id=file.id)], + ai_agent=agent_ignoring_overriding_embeddings_model, +) +``` + +### Arguments + +- prompt `str` + - The prompt provided to a Large Language Model (LLM) in the request. The prompt can be up to 10000 characters long and it can be an XML or a JSON schema. +- items `List[AiItemBase]` + - The items that LLM will process. Currently, you can use files only. +- ai_agent `Optional[Union[AiAgentReference, AiAgentExtract]]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AiResponse`. + +A response including the answer from the LLM. + +## Extract metadata (structured) + +Sends an AI request to supported Large Language Models (LLMs) and returns extracted metadata as a set of key-value pairs. +For this request, you either need a metadata template or a list of fields you want to extract. +Input is **either** a metadata template or a list of fields to ensure the structure. +To learn more about creating templates, see [Creating metadata templates in the Admin Console](https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates) +or use the [metadata template API](g://metadata/templates/create). + +This operation is performed by calling function `create_ai_extract_structured`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-ai-extract-structured/). + + + +```python +client.ai.create_ai_extract_structured( + [AiItemBase(id=file.id)], + fields=[ + CreateAiExtractStructuredFields( + key="firstName", + display_name="First name", + description="Person first name", + prompt="What is the your first name?", + type="string", + ), + CreateAiExtractStructuredFields( + key="lastName", + display_name="Last name", + description="Person last name", + prompt="What is the your last name?", + type="string", + ), + CreateAiExtractStructuredFields( + key="dateOfBirth", + display_name="Birth date", + description="Person date of birth", + prompt="What is the date of your birth?", + type="date", + ), + CreateAiExtractStructuredFields( + key="age", + display_name="Age", + description="Person age", + prompt="How old are you?", + type="float", + ), + CreateAiExtractStructuredFields( + key="hobby", + display_name="Hobby", + description="Person hobby", + prompt="What is your hobby?", + type="multiSelect", + options=[ + CreateAiExtractStructuredFieldsOptionsField(key="guitar"), + CreateAiExtractStructuredFieldsOptionsField(key="books"), + ], + ), + ], + ai_agent=agent_ignoring_overriding_embeddings_model, +) +``` + +### Arguments + +- items `List[AiItemBase]` + - The items to be processed by the LLM. Currently you can use files only. +- metadata_template `Optional[CreateAiExtractStructuredMetadataTemplate]` + - The metadata template containing the fields to extract. For your request to work, you must provide either `metadata_template` or `fields`, but not both. +- fields `Optional[List[CreateAiExtractStructuredFields]]` + - The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both. +- ai_agent `Optional[Union[AiAgentReference, AiAgentExtractStructured]]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AiExtractStructuredResponse`. + +A successful response including the answer from the LLM. diff --git a/docs/ai_studio.md b/docs/ai_studio.md new file mode 100644 index 000000000..92e42046e --- /dev/null +++ b/docs/ai_studio.md @@ -0,0 +1,192 @@ +# AiStudioManager + +- [List AI agents](#list-ai-agents) +- [Create AI agent](#create-ai-agent) +- [Update AI agent](#update-ai-agent) +- [Get AI agent by agent ID](#get-ai-agent-by-agent-id) +- [Delete AI agent](#delete-ai-agent) + +## List AI agents + +Lists AI agents based on the provided parameters. + +This operation is performed by calling function `get_ai_agents`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-ai-agents/). + + + +```python +client.ai_studio.get_ai_agents() +``` + +### Arguments + +- mode `Optional[List[str]]` + - The mode to filter the agent config to return. Possible values are: `ask`, `text_gen`, and `extract`. +- fields `Optional[List[str]]` + - The fields to return in the response. +- agent_state `Optional[List[str]]` + - The state of the agents to return. Possible values are: `enabled`, `disabled` and `enabled_for_selected_users`. +- include_box_default `Optional[bool]` + - Whether to include the Box default agents in the response. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AiMultipleAgentResponse`. + +A successful response including the agents list. + +## Create AI agent + +Creates an AI agent. At least one of the following capabilities must be provided: `ask`, `text_gen`, `extract`. + +This operation is performed by calling function `create_ai_agent`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-ai-agents/). + + + +```python +client.ai_studio.create_ai_agent( + agent_name, + "enabled", + ask=AiStudioAgentAsk(access_state="enabled", description="desc1"), +) +``` + +### Arguments + +- type `CreateAiAgentType` + - The type of agent used to handle queries. +- name `str` + - The name of the AI Agent. +- access_state `str` + - The state of the AI Agent. Possible values are: `enabled`, `disabled`, and `enabled_for_selected_users`. +- icon_reference `Optional[str]` + - The icon reference of the AI Agent. It should have format of the URL `https://cdn01.boxcdn.net/app-assets/aistudio/avatars/` where possible values of `file_name` are: `logo_boxAi.png`,`logo_stamp.png`,`logo_legal.png`,`logo_finance.png`,`logo_config.png`,`logo_handshake.png`,`logo_analytics.png`,`logo_classification.png`. +- allowed_entities `Optional[List[AiAgentAllowedEntity]]` + - List of allowed users or groups. +- ask `Optional[AiStudioAgentAsk]` +- text_gen `Optional[AiStudioAgentTextGen]` +- extract `Optional[AiStudioAgentExtract]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AiSingleAgentResponseFull`. + +Definition of created AI agent. + +## Update AI agent + +Updates an AI agent. + +This operation is performed by calling function `update_ai_agent_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-ai-agents-id/). + + + +```python +client.ai_studio.update_ai_agent_by_id( + created_agent.id, + agent_name, + "enabled", + ask=AiStudioAgentAsk(access_state="disabled", description="desc2"), +) +``` + +### Arguments + +- agent_id `str` + - The ID of the agent to update. Example: "1234" +- type `UpdateAiAgentByIdType` + - The type of agent used to handle queries. +- name `str` + - The name of the AI Agent. +- access_state `str` + - The state of the AI Agent. Possible values are: `enabled`, `disabled`, and `enabled_for_selected_users`. +- icon_reference `Optional[str]` + - The icon reference of the AI Agent. It should have format of the URL `https://cdn01.boxcdn.net/app-assets/aistudio/avatars/` where possible values of `file_name` are: `logo_boxAi.png`,`logo_stamp.png`,`logo_legal.png`,`logo_finance.png`,`logo_config.png`,`logo_handshake.png`,`logo_analytics.png`,`logo_classification.png`. +- allowed_entities `Optional[List[AiAgentAllowedEntity]]` + - List of allowed users or groups. +- ask `Optional[AiStudioAgentAsk]` +- text_gen `Optional[AiStudioAgentTextGen]` +- extract `Optional[AiStudioAgentExtract]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AiSingleAgentResponseFull`. + +Definition of created AI agent. + +## Get AI agent by agent ID + +Gets an AI Agent using the `agent_id` parameter. + +This operation is performed by calling function `get_ai_agent_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-ai-agents-id/). + + + +```python +client.ai_studio.get_ai_agent_by_id(created_agent.id, fields=["ask"]) +``` + +### Arguments + +- agent_id `str` + - The agent id to get. Example: "1234" +- fields `Optional[List[str]]` + - The fields to return in the response. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AiSingleAgentResponseFull`. + +A successful response including the agent. + +## Delete AI agent + +Deletes an AI agent using the provided parameters. + +This operation is performed by calling function `delete_ai_agent_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-ai-agents-id/). + + + +```python +client.ai_studio.delete_ai_agent_by_id(created_agent.id) +``` + +### Arguments + +- agent_id `str` + - The ID of the agent to delete. Example: "1234" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A successful response with no content. diff --git a/docs/app_item_associations.md b/docs/app_item_associations.md new file mode 100644 index 000000000..371483db1 --- /dev/null +++ b/docs/app_item_associations.md @@ -0,0 +1,84 @@ +# AppItemAssociationsManager + +- [List file app item associations](#list-file-app-item-associations) +- [List folder app item associations](#list-folder-app-item-associations) + +## List file app item associations + +**This is a beta feature, which means that its availability might be limited.** +Returns all app items the file is associated with. This includes app items +associated with ancestors of the file. Assuming the context user has access +to the file, the type/ids are revealed even if the context user does not +have **View** permission on the app item. + +This operation is performed by calling function `get_file_app_item_associations`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-app-item-associations/). + + + +```python +client.app_item_associations.get_file_app_item_associations(file_id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- limit `Optional[int]` + - The maximum number of items to return per page. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- application_type `Optional[str]` + - If given, only return app items for this application type. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AppItemAssociations`. + +Returns a collection of app item objects. If there are no +app items on this file, an empty collection will be returned. +This list includes app items on ancestors of this File. + +## List folder app item associations + +**This is a beta feature, which means that its availability might be limited.** +Returns all app items the folder is associated with. This includes app items +associated with ancestors of the folder. Assuming the context user has access +to the folder, the type/ids are revealed even if the context user does not +have **View** permission on the app item. + +This operation is performed by calling function `get_folder_app_item_associations`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-id-app-item-associations/). + + + +```python +client.app_item_associations.get_folder_app_item_associations(folder_id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- limit `Optional[int]` + - The maximum number of items to return per page. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- application_type `Optional[str]` + - If given, returns only app items for this application type. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AppItemAssociations`. + +Returns a collection of app item objects. If there are no +app items on this folder an empty collection will be returned. +This list includes app items on ancestors of this folder. diff --git a/docs/archives.md b/docs/archives.md new file mode 100644 index 000000000..cc730a340 --- /dev/null +++ b/docs/archives.md @@ -0,0 +1,97 @@ +# ArchivesManager + +- [List archives](#list-archives) +- [Create archive](#create-archive) +- [Delete archive](#delete-archive) + +## List archives + +Retrieves archives for an enterprise. + +This operation is performed by calling function `get_archives_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-archives/). + + + +```python +client.archives.get_archives_v2025_r0(limit=100) +``` + +### Arguments + +- limit `Optional[int]` + - The maximum number of items to return per page. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ArchivesV2025R0`. + +Returns a list of archives in the enterprise. + +## Create archive + +Creates an archive. + +This operation is performed by calling function `create_archive_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/post-archives/). + + + +```python +client.archives.create_archive_v2025_r0(archive_name) +``` + +### Arguments + +- name `str` + - The name of the archive. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ArchiveV2025R0`. + +Returns a new archive object. + +## Delete archive + +Permanently deletes an archive. + +This operation is performed by calling function `delete_archive_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/delete-archives-id/). + + + +```python +client.archives.delete_archive_by_id_v2025_r0(archive.id) +``` + +### Arguments + +- archive_id `str` + - The ID of the archive. Example: "982312" +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the archive has been deleted. diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 000000000..cbe78f305 --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,501 @@ +# Authentication + + + + +- [Authentication](#authentication) +- [Authentication methods](#authentication-methods) + - [Developer Token](#developer-token) + - [JWT Auth](#jwt-auth) + - [Authenticate Enterprise](#authenticate-enterprise) + - [Authenticate user](#authenticate-user) + - [Client Credentials Grant](#client-credentials-grant) + - [Obtaining Service Account token](#obtaining-service-account-token) + - [Obtaining User token](#obtaining-user-token) + - [Switching between Service Account and User](#switching-between-service-account-and-user) + - [OAuth 2.0 Auth](#oauth-20-auth) + - [Authentication with OAuth2](#authentication-with-oauth2) + - [Injecting existing token into BoxOAuth](#injecting-existing-token-into-boxoauth) +- [Retrieve current access token](#retrieve-current-access-token) +- [Refresh access token](#refresh-access-token) +- [Revoke token](#revoke-token) +- [Downscope token](#downscope-token) +- [Token storage](#token-storage) + - [In-memory token storage](#in-memory-token-storage) + - [File token storage](#file-token-storage) + - [File with in-memory token storage](#file-with-in-memory-token-storage) + - [Custom storage](#custom-storage) + + + +# Authentication methods + +## Developer Token + +The fastest way to get started using the API is with developer token. A +developer token is simply a short-lived access token that cannot be refreshed +and can only be used with your own account. Therefore, they're only useful for +testing an app and aren't suitable for production. You can obtain a developer +token from your application's [developer console][dev_console] page. + +To create a `BoxClient` with a developer token, construct an `BoxDeveloperTokenAuth` +object with the `token` set to the developer token and construct the client with that. + + + +```python +from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth + +auth = BoxDeveloperTokenAuth(token="DEVELOPER_TOKEN_GOES_HERE") +client = BoxClient(auth=auth) + +me = client.users.get_user_me() +print(f"My user ID is {me.id}") +``` + +[dev_console]: https://app.box.com/developers/console + +## JWT Auth + +Authenticating with a JWT requires some extra dependencies. To get them, use + +``` +pip install "box-sdk-gen[jwt]" +``` + +Before using JWT Auth make sure you set up correctly your Box platform app. +The guide with all required steps can be found here: [Setup with JWT][jwt_guide] + +### Authenticate Enterprise + +JWT auth allows your application to authenticate itself with the Box API +for a given enterprise. By default, your application has a [Service Account][service_account] +that represents it and can perform API calls. The Service Account is separate +from the Box accounts of the application developer and the enterprise admin of +any enterprise that has authorized the app — files stored in that account are +not accessible in any other account by default, and vice versa. + +If you generated your public and private keys automatically through the +[Box Developer Console][dev_console], you can use the JSON file created there +to configure your SDK instance and create a client to make calls as the +Service Account. Call one of static `BoxJWTAuth` method: +`JWTConfig.from_config_file(config_file_path='/path/to/settings.json')` and pass JSON file local path +or `JWTConfig.from_config_json_string(config_json_string)` and pass JSON config file content as string. + +```python +from box_sdk_gen import BoxClient, BoxJWTAuth, JWTConfig + +jwt_config = JWTConfig.from_config_file(config_file_path="/path/to/settings.json") +auth = BoxJWTAuth(config=jwt_config) +client = BoxClient(auth=auth) + +service_account = client.users.get_user_me() +print(f"Service Account user ID is {service_account.id}") +``` + +Otherwise, you'll need to provide the necessary configuration fields directly to the `JWTConfig` constructor: + +```python +from box_sdk_gen import BoxClient, BoxJWTAuth, JWTConfig + +jwt_config = JWTConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + jwt_key_id="YOUR_JWT_KEY_ID", + private_key="YOUR_PRIVATE_KEY", + private_key_passphrase="PASSPHRASE", + enterprise_id="YOUR_ENTERPRISE_ID", +) + +auth = BoxJWTAuth(config=jwt_config) +service_account_client = BoxClient(auth=auth) +``` + +### Authenticate user + +App auth applications also often have associated [App Users][app_user], which are +created and managed directly by the application — they do not have normal login credentials, +and can only be accessed through the Box API by the application that created them. +You may authenticate as the Service Account to provision and manage users, or as an individual app user to +make calls as that user. See the [API documentation](https://developer.box.com/) +for detailed instructions on how to use app auth. + +Clients for making calls as an App User can be created with the same JSON JWT config file generated through the +[Box Developer Console][dev_console]. Calling `auth.with_user_subject('USER_ID')` method will return a new auth object, +which is authenticated as the user with provided id, leaving the original object unchanged. + +```python +from box_sdk_gen import BoxClient, BoxJWTAuth, JWTConfig + +jwt_config = JWTConfig.from_config_file(config_file_path="/path/to/settings.json") +auth = BoxJWTAuth(config=jwt_config) +user_auth = auth.with_user_subject("USER_ID") +user_client = BoxClient(auth=user_auth) +``` + +Alternatively, clients for making calls as an App User can be created with the same `JWTConfig` +constructor as in the above examples, similarly to creating a Service Account client. Simply pass the +`user_id` instead of `enterprise_id` when constructing the auth config instance: + +```python +from box_sdk_gen import BoxClient, BoxJWTAuth, JWTConfig + +jwt_config = JWTConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + jwt_key_id="YOUR_JWT_KEY_ID", + private_key="YOUR_PRIVATE_KEY", + private_key_passphrase="PASSPHRASE", + user_id="USER_ID", +) + +auth = BoxJWTAuth(config=jwt_config) +user_client = BoxClient(auth=auth) +``` + +[jwt_guide]: https://developer.box.com/guides/authentication/jwt/jwt-setup/ +[service_account]: https://developer.box.com/guides/getting-started/user-types/service-account/ +[app_user]: https://developer.box.com/guides/getting-started/user-types/app-users/ + +## Client Credentials Grant + +Before using Client Credentials Grant Auth make sure you set up correctly your Box platform app. +The guide with all required steps can be found here: [Setup with Client Credentials Grant][ccg_guide] + +Client Credentials Grant Auth method allows you to obtain an access token by having client credentials +and secret with enterprise or user ID, which allows you to work using service or user account. + +You can use `BoxCCGAuth` to initialize a client object the same way as for other authentication types: + +```python +from box_sdk_gen import BoxClient, BoxCCGAuth, CCGConfig + +ccg_config = CCGConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + user_id="YOUR_USER_ID", +) +auth = BoxCCGAuth(config=ccg_config) +client = BoxClient(auth=auth) + +print(f"Id of the authenticated user is: {client.users.get_user_me().id}") +``` + +Obtained token is valid for specified amount of time, it will be refreshed automatically by default. + +### Obtaining Service Account token + +The [Service Account](https://developer.box.com/guides/getting-started/user-types/service-account//) +is separate from the Box accounts of the application developer and the +enterprise admin of any enterprise that has authorized the app — files stored in that account +are not accessible in any other account by default, and vice versa. +To obtain service account you will have to provide enterprise ID with client id and secret: + +```python +from box_sdk_gen import BoxClient, BoxCCGAuth, CCGConfig + +ccg_config = CCGConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + enterprise_id="YOUR_ENTERPRISE_ID", +) +auth = BoxCCGAuth(config=ccg_config) +client = BoxClient(auth=auth) +``` + +### Obtaining User token + +In order to enable obtaining user token you have to go to your application configuration that can be found +[here][dev_console]. In `Configuration` tab, in section `Advanced Features` +select `Generate user access tokens`. Do not forget to re-authorize application if it was already authorized. + +To obtain user account you will have to provide user ID with client id and secret. + +```python +from box_sdk_gen import BoxClient, BoxCCGAuth, CCGConfig + +ccg_config = CCGConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + user_id="YOUR_USER_ID", +) +auth = BoxCCGAuth(config=ccg_config) +client = BoxClient(auth=auth) +``` + +### Switching between Service Account and User + +You can easily switch to be authenticated as a Service Account or as a User. +To create a new auth object authenticated as Service Account you can call: + +```python +enterprise_auth = auth.with_enterprise_subject(enterprise_id="YOUR_ENTERPRISE_ID") +enterprise_client = BoxClient(auth=enterprise_auth) +``` + +To authenticate with user subject call: + +```python +user_auth = auth.with_user_subject(user_id="YOUR_USER_ID") +user_client = BoxClient(auth=user_auth) +``` + +The new token will be automatically fetched with a next API call. + +[ccg_guide]: https://developer.box.com/guides/authentication/client-credentials/client-credentials-setup/ + +## OAuth 2.0 Auth + +### Authentication with OAuth2 + +If your application needs to integrate with existing Box users who will provide +their login credentials to grant your application access to their account, you +will need to go through the standard OAuth2 login flow. A detailed guide for +this process is available in the +[Authentication with OAuth API documentation](https://developer.box.com/en/guides/authentication/oauth2/). + +Using an auth code is the most common way of authenticating with the Box API for +existing Box users, to integrate with their accounts. +Your application must provide a way for the user to login to Box (usually with a +browser or web view) in order to obtain an auth code. + + + +```python +from box_sdk_gen import BoxOAuth, OAuthConfig + +auth = BoxOAuth( + OAuthConfig(client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET") +) +auth_url = auth.get_authorize_url() +``` + +After a user logs in and grants your application access to their Box account, +they will be redirected to your application's `redirect_uri` which will contain +an auth code. This auth code can then be used along with your client ID and +client secret to establish an API connection. +You need to provide the auth code to the SDK to obtain an access token. +Calling `auth.get_tokens_authorization_code_grant('YOUR_ACCESS_CODE')` will exchange the auth code for an access token +and save it in the `BoxOAuth` token storage. The SDK will automatically refresh the token when needed. +All you need to do is create a client object with the `BoxOAuth` object and start making API calls. + + + +```python +from box_sdk_gen import BoxClient + +auth.get_tokens_authorization_code_grant("YOUR_ACCESS_CODE") +client = BoxClient(auth=auth) +``` + +Here you can find a Flask app example, which handles complete BoxOAuth workflow to authenticate and +list names of all items in a root folder. + +```python +from flask import Flask, request, redirect + +from box_sdk_gen import BoxClient, BoxOAuth, OAuthConfig, GetAuthorizeUrlOptions + +app = Flask(__name__) + +AUTH = BoxOAuth( + OAuthConfig(client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET") +) + + +@app.route("/") +def get_auth(): + auth_url = AUTH.get_authorize_url( + options=GetAuthorizeUrlOptions(redirect_uri="YOUR_REDIRECT_URL") + ) + return redirect(auth_url, code=302) + + +@app.route("/oauth2callback") +def callback(): + AUTH.get_tokens_authorization_code_grant(request.args.get("code")) + client = BoxClient(auth=AUTH) + + items_in_root_folder = [ + item.name for item in client.folders.get_folder_items(folder_id="0").entries + ] + return ", ".join(items_in_root_folder) + + +if __name__ == "__main__": + app.run(port=4999) +``` + +### Injecting existing token into BoxOAuth + +If you already have an access token and refresh token, you can inject them into the `BoxOAuth` token storage +to avoid repeating the authentication process. This can be useful when you want to reuse the token +between runs of your application. + +```python +from box_sdk_gen import BoxClient, AccessToken + +access_token = AccessToken(accessToken="", refreshToken="") +auth.token_storage.store(access_token) +client = BoxClient(auth=auth) +``` + +Alternatively, you can create a custom implementation of `TokenStorage` interface and pass it to the `BoxOAuth` object. +See the [Custom storage](#custom-storage) section for more information. + +# Retrieve current access token + +After initializing the authentication object, the SDK will be able to retrieve the access token. +To retrieve the current access token you can use the following code: + + + +```python +auth.retrieve_token() +``` + +# Refresh access token + +Access tokens are short-lived and need to be refreshed periodically. The SDK will automatically refresh the token when needed. +If you want to manually refresh the token, you can use the following code: + + + +```python +auth.refresh_token() +``` + +# Revoke token + +Access tokens for a client can be revoked when needed. This call invalidates old token. +For BoxCCGAuth and BoxJWTAuth you can still reuse the `auth` object to retrieve a new token. +If you make any new call after revoking the token, a new token will be automatically retrieved. +For BoxOAuth it would be necessary to manually go through the authentication process again. +For BoxDeveloperTokenAuth, it is necessary to provide a DeveloperTokenConfig during initialization, +containing the client ID and client secret. + +To revoke current client's tokens in the storage use the following code: + + + +```python +client.auth.revoke_token() +``` + +# Downscope token + +You can exchange a client's access token for one with a lower scope, in order +to restrict the permissions for a child client or to pass to a less secure +location (e.g. a browser-based app). + +A downscoped token does not include a refresh token. +In such a scenario, to obtain a new downscoped token, refresh the original token +and utilize the newly acquired token to obtain the downscoped token. + +More information about downscoping tokens can be found [here](https://developer.box.com/guides/authentication/tokens/downscope/). +If you want to learn more about available scopes please go [here](https://developer.box.com/guides/api-calls/permissions-and-errors/scopes/#scopes-for-downscoping). + +For example to get a new token with only `item_preview` scope, restricted to a single file, suitable for the +[Content Preview UI Element](https://developer.box.com/en/guides/embed/ui-elements/preview/) you can use the following code. +You can also initialize `BoxDeveloperTokenAuth` with the retrieved access token and use it to create a new Client. + + + +```python +from box_sdk_gen import BoxDeveloperTokenAuth, AccessToken, BoxClient + +resource = "https://api.box.com/2.0/files/123456789" +downscoped_token: AccessToken = auth.downscope_token( + scopes=["item_preview"], + resource=resource, +) +downscoped_auth = BoxDeveloperTokenAuth(token=downscoped_token.access_token) +client = BoxClient(auth=downscoped_auth) +``` + +# Token storage + +## In-memory token storage + +By default, the SDK stores the access token in volatile memory. When rerunning your application, +the access token won't be reused from the previous run; a new token has to be obtained again. +To use in-memory token storage, you don't need to do anything more than +create an Auth class using AuthConfig, for example, for BoxOAuth: + +```python +from box_sdk_gen import BoxOAuth, OAuthConfig + +auth = BoxOAuth( + OAuthConfig(client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET") +) +``` + +## File token storage + +If you want to keep an up-to-date access token in a file, allowing it to be reused after rerunning your application, +you can use the `FileTokenStorage` class. To enable storing the token in a file, you need to pass an object of type +`FileTokenStorage` to the AuthConfig class. For example, for BoxOAuth: + +```python +from box_sdk_gen import BoxOAuth, OAuthConfig, FileTokenStorage + +auth = BoxOAuth( + OAuthConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + token_storage=FileTokenStorage(), + ) +) +``` + +## File with in-memory token storage + +If you want to keep an up-to-date access token in a file and also maintain a valid access token in in-memory cache, +allowing you to reuse the token after rerunning your application while maintaining fast access times to the token, +you can use the `FileWithInMemoryCacheTokenStorage` class. To enable storing the token in a file, +you need to pass an object of type `FileWithInMemoryCacheTokenStorage` to the AuthConfig class. For example, for BoxOAuth: + +```python +from box_sdk_gen import BoxOAuth, OAuthConfig, FileWithInMemoryCacheTokenStorage + +auth = BoxOAuth( + OAuthConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + token_storage=FileWithInMemoryCacheTokenStorage(), + ) +) +``` + +## Custom storage + +You can also provide a custom token storage class. All you need to do is create a class that inherits from `TokenStorage` +and implements all of its abstract methods. Then, pass an instance of your class to the AuthConfig constructor. + +```python +from typing import Optional +from box_sdk_gen import BoxOAuth, OAuthConfig, TokenStorage, AccessToken + + +class MyCustomTokenStorage(TokenStorage): + def store(self, token: AccessToken) -> None: + # store token in your custom storage + pass + + def get(self) -> Optional[AccessToken]: + # retrieve token from your custom storage + pass + + def clear(self) -> None: + # clear token from your custom storage + pass + + +auth = BoxOAuth( + OAuthConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + token_storage=MyCustomTokenStorage(), + ) +) +``` diff --git a/docs/authorization.md b/docs/authorization.md new file mode 100644 index 000000000..65da2275c --- /dev/null +++ b/docs/authorization.md @@ -0,0 +1,170 @@ +# AuthorizationManager + +- [Authorize user](#authorize-user) +- [Request access token](#request-access-token) +- [Refresh access token](#refresh-access-token) +- [Revoke access token](#revoke-access-token) + +## Authorize user + +Authorize a user by sending them through the [Box](https://box.com) +website and request their permission to act on their behalf. + +This is the first step when authenticating a user using +OAuth 2.0. To request a user's authorization to use the Box APIs +on their behalf you will need to send a user to the URL with this +format. + +This operation is performed by calling function `authorize_user`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-authorize/). + +_Currently we don't have an example for calling `authorize_user` in integration tests_ + +### Arguments + +- response_type `AuthorizeUserResponseType` + - The type of response we'd like to receive. +- client_id `str` + - The Client ID of the application that is requesting to authenticate the user. To get the Client ID for your application, log in to your Box developer console and click the **Edit Application** link for the application you're working with. In the OAuth 2.0 Parameters section of the configuration page, find the item labelled `client_id`. The text of that item is your application's Client ID. +- redirect_uri `Optional[str]` + - The URI to which Box redirects the browser after the user has granted or denied the application permission. This URI match one of the redirect URIs in the configuration of your application. It must be a valid HTTPS URI and it needs to be able to handle the redirection to complete the next step in the OAuth 2.0 flow. Although this parameter is optional, it must be a part of the authorization URL if you configured multiple redirect URIs for the application in the developer console. A missing parameter causes a `redirect_uri_missing` error after the user grants application access. +- state `Optional[str]` + - A custom string of your choice. Box will pass the same string to the redirect URL when authentication is complete. This parameter can be used to identify a user on redirect, as well as protect against hijacked sessions and other exploits. +- scope `Optional[str]` + - A space-separated list of application scopes you'd like to authenticate the user for. This defaults to all the scopes configured for the application in its configuration page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Does not return any data, but rather should be used in the browser. + +## Request access token + +Request an Access Token using either a client-side obtained OAuth 2.0 +authorization code or a server-side JWT assertion. + +An Access Token is a string that enables Box to verify that a +request belongs to an authorized session. In the normal order of +operations you will begin by requesting authentication from the +[authorize](#get-authorize) endpoint and Box will send you an +authorization code. + +You will then send this code to this endpoint to exchange it for +an Access Token. The returned Access Token can then be used to to make +Box API calls. + +This operation is performed by calling function `request_access_token`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-oauth2-token/). + +_Currently we don't have an example for calling `request_access_token` in integration tests_ + +### Arguments + +- grant_type `RequestAccessTokenGrantType` + - The type of request being made, either using a client-side obtained authorization code, a refresh token, a JWT assertion, client credentials grant or another access token for the purpose of downscoping a token. +- client_id `Optional[str]` + - The Client ID of the application requesting an access token. Used in combination with `authorization_code`, `client_credentials`, or `urn:ietf:params:oauth:grant-type:jwt-bearer` as the `grant_type`. +- client_secret `Optional[str]` + - The client secret of the application requesting an access token. Used in combination with `authorization_code`, `client_credentials`, or `urn:ietf:params:oauth:grant-type:jwt-bearer` as the `grant_type`. +- code `Optional[str]` + - The client-side authorization code passed to your application by Box in the browser redirect after the user has successfully granted your application permission to make API calls on their behalf. Used in combination with `authorization_code` as the `grant_type`. +- refresh_token `Optional[str]` + - A refresh token used to get a new access token with. Used in combination with `refresh_token` as the `grant_type`. +- assertion `Optional[str]` + - A JWT assertion for which to request a new access token. Used in combination with `urn:ietf:params:oauth:grant-type:jwt-bearer` as the `grant_type`. +- subject_token `Optional[str]` + - The token to exchange for a downscoped token. This can be a regular access token, a JWT assertion, or an app token. Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` as the `grant_type`. +- subject_token_type `Optional[RequestAccessTokenSubjectTokenType]` + - The type of `subject_token` passed in. Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` as the `grant_type`. +- actor_token `Optional[str]` + - The token used to create an annotator token. This is a JWT assertion. Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` as the `grant_type`. +- actor_token_type `Optional[RequestAccessTokenActorTokenType]` + - The type of `actor_token` passed in. Used in combination with `urn:ietf:params:oauth:grant-type:token-exchange` as the `grant_type`. +- scope `Optional[str]` + - The space-delimited list of scopes that you want apply to the new access token. The `subject_token` will need to have all of these scopes or the call will error with **401 Unauthorized**.. +- resource `Optional[str]` + - Full URL for the file that the token should be generated for. +- box_subject_type `Optional[RequestAccessTokenBoxSubjectType]` + - Used in combination with `client_credentials` as the `grant_type`. +- box_subject_id `Optional[str]` + - Used in combination with `client_credentials` as the `grant_type`. Value is determined by `box_subject_type`. If `user` use user ID and if `enterprise` use enterprise ID. +- box_shared_link `Optional[str]` + - Full URL of the shared link on the file or folder that the token should be generated for. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AccessToken`. + +Returns a new Access Token that can be used to make authenticated +API calls by passing along the token in a authorization header as +follows `Authorization: Bearer `. + +## Refresh access token + +Refresh an Access Token using its client ID, secret, and refresh token. + +This operation is performed by calling function `refresh_access_token`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-oauth2-token--refresh/). + +_Currently we don't have an example for calling `refresh_access_token` in integration tests_ + +### Arguments + +- grant_type `RefreshAccessTokenGrantType` + - The type of request being made, in this case a refresh request. +- client_id `str` + - The client ID of the application requesting to refresh the token. +- client_secret `str` + - The client secret of the application requesting to refresh the token. +- refresh_token `str` + - The refresh token to refresh. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AccessToken`. + +Returns a new Access Token that can be used to make authenticated +API calls by passing along the token in a authorization header as +follows `Authorization: Bearer `. + +## Revoke access token + +Revoke an active Access Token, effectively logging a user out +that has been previously authenticated. + +This operation is performed by calling function `revoke_access_token`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-oauth2-revoke/). + +_Currently we don't have an example for calling `revoke_access_token` in integration tests_ + +### Arguments + +- client_id `Optional[str]` + - The Client ID of the application requesting to revoke the access token. +- client_secret `Optional[str]` + - The client secret of the application requesting to revoke an access token. +- token `Optional[str]` + - The access token to revoke. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the token was successfully revoked. diff --git a/docs/avatars.md b/docs/avatars.md new file mode 100644 index 000000000..702da5049 --- /dev/null +++ b/docs/avatars.md @@ -0,0 +1,105 @@ +# AvatarsManager + +- [Get user avatar](#get-user-avatar) +- [Add or update user avatar](#add-or-update-user-avatar) +- [Delete user avatar](#delete-user-avatar) + +## Get user avatar + +Retrieves an image of a the user's avatar. + +This operation is performed by calling function `get_user_avatar`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-users-id-avatar/). + + + +```python +client.avatars.get_user_avatar(user.id) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ByteStream`. + +When an avatar can be found for the user the +image data will be returned in the body of the +response. + +## Add or update user avatar + +Adds or updates a user avatar. + +This operation is performed by calling function `create_user_avatar`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-users-id-avatar/). + + + +```python +client.avatars.create_user_avatar( + user.id, + decode_base_64_byte_stream( + "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAAA1BMVEW10NBjBBbqAAAAH0lEQVRoge3BAQ0AAADCoPdPbQ43oAAAAAAAAAAAvg0hAAABmmDh1QAAAABJRU5ErkJggg==" + ), + pic_file_name="avatar.png", + pic_content_type="image/png", +) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- pic `ByteStream` + - The image file to be uploaded to Box. Accepted file extensions are `.jpg` or `.png`. The maximum file size is 1MB. +- pic_file_name `Optional[str]` +- pic_content_type `Optional[str]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UserAvatar`. + +`ok`: Returns the `pic_urls` object with URLs to existing +user avatars that were updated.`created`: Returns the `pic_urls` object with URLS to user avatars +uploaded to Box with the request. + +## Delete user avatar + +Removes an existing user avatar. +You cannot reverse this operation. + +This operation is performed by calling function `delete_user_avatar`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-users-id-avatar/). + + + +```python +client.avatars.delete_user_avatar(user.id) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +`no_content`: Removes the avatar and returns an empty response. diff --git a/docs/chunked_uploads.md b/docs/chunked_uploads.md new file mode 100644 index 000000000..2d965c739 --- /dev/null +++ b/docs/chunked_uploads.md @@ -0,0 +1,478 @@ +# ChunkedUploadsManager + +This is a manager for chunked uploads (allowed for files at least 20MB). + +- [Create upload session](#create-upload-session) +- [Create upload session for existing file](#create-upload-session-for-existing-file) +- [Get upload session by URL](#get-upload-session-by-url) +- [Get upload session](#get-upload-session) +- [Upload part of file by URL](#upload-part-of-file-by-url) +- [Upload part of file](#upload-part-of-file) +- [Remove upload session by URL](#remove-upload-session-by-url) +- [Remove upload session](#remove-upload-session) +- [List parts by URL](#list-parts-by-url) +- [List parts](#list-parts) +- [Commit upload session by URL](#commit-upload-session-by-url) +- [Commit upload session](#commit-upload-session) +- [Upload big file](#upload-big-file) + +## Create upload session + +Creates an upload session for a new file. + +This operation is performed by calling function `create_file_upload_session`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-upload-sessions/). + + + +```python +client.chunked_uploads.create_file_upload_session( + parent_folder_id, file_size, file_name +) +``` + +### Arguments + +- folder_id `str` + - The ID of the folder to upload the new file to. +- file_size `int` + - The total number of bytes of the file to be uploaded. +- file_name `str` + - The name of new file. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadSession`. + +Returns a new upload session. + +## Create upload session for existing file + +Creates an upload session for an existing file. + +This operation is performed by calling function `create_file_upload_session_for_existing_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-id-upload-sessions/). + +_Currently we don't have an example for calling `create_file_upload_session_for_existing_file` in integration tests_ + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- file_size `int` + - The total number of bytes of the file to be uploaded. +- file_name `Optional[str]` + - The optional new name of new file. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadSession`. + +Returns a new upload session. + +## Get upload session by URL + +Return information about an upload session. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) endpoint. + +This operation is performed by calling function `get_file_upload_session_by_url`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-upload-sessions-id/). + + + +```python +client.chunked_uploads.get_file_upload_session_by_url(status_url) +``` + +### Arguments + +- url `str` + - URL of getFileUploadSessionById method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadSession`. + +Returns an upload session object. + +## Get upload session + +Return information about an upload session. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) endpoint. + +This operation is performed by calling function `get_file_upload_session_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-upload-sessions-id/). + + + +```python +client.chunked_uploads.get_file_upload_session_by_id(upload_session_id) +``` + +### Arguments + +- upload_session_id `str` + - The ID of the upload session. Example: "D5E3F7A" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadSession`. + +Returns an upload session object. + +## Upload part of file by URL + +Uploads a chunk of a file for an upload session. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) +and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + +This operation is performed by calling function `upload_file_part_by_url`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-upload-sessions-id/). + + + +```python +client.chunked_uploads.upload_file_part_by_url( + acc.upload_part_url, + generate_byte_stream_from_buffer(chunk_buffer), + digest, + content_range, +) +``` + +### Arguments + +- url `str` + - URL of uploadFilePart method +- request_body `ByteStream` + - Request body of uploadFilePart method +- digest `str` + - The [RFC3230][1] message digest of the chunk uploaded. Only SHA1 is supported. The SHA1 digest must be base64 encoded. The format of this header is as `sha=BASE64_ENCODED_DIGEST`. To get the value for the `SHA` digest, use the openSSL command to encode the file part: `openssl sha1 -binary | base64`. [1]: https://tools.ietf.org/html/rfc3230 +- content_range `str` + - The byte range of the chunk. Must not overlap with the range of a part already uploaded this session. Each part’s size must be exactly equal in size to the part size specified in the upload session that you created. One exception is the last part of the file, as this can be smaller. When providing the value for `content-range`, remember that: _ The lower bound of each part's byte range must be a multiple of the part size. _ The higher bound must be a multiple of the part size - 1. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadedPart`. + +Chunk has been uploaded successfully. + +## Upload part of file + +Uploads a chunk of a file for an upload session. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) +and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + +This operation is performed by calling function `upload_file_part`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-upload-sessions-id/). + + + +```python +client.chunked_uploads.upload_file_part( + acc.upload_session_id, + generate_byte_stream_from_buffer(chunk_buffer), + digest, + content_range, +) +``` + +### Arguments + +- upload_session_id `str` + - The ID of the upload session. Example: "D5E3F7A" +- request_body `ByteStream` + - Request body of uploadFilePart method +- digest `str` + - The [RFC3230][1] message digest of the chunk uploaded. Only SHA1 is supported. The SHA1 digest must be base64 encoded. The format of this header is as `sha=BASE64_ENCODED_DIGEST`. To get the value for the `SHA` digest, use the openSSL command to encode the file part: `openssl sha1 -binary | base64`. [1]: https://tools.ietf.org/html/rfc3230 +- content_range `str` + - The byte range of the chunk. Must not overlap with the range of a part already uploaded this session. Each part’s size must be exactly equal in size to the part size specified in the upload session that you created. One exception is the last part of the file, as this can be smaller. When providing the value for `content-range`, remember that: _ The lower bound of each part's byte range must be a multiple of the part size. _ The higher bound must be a multiple of the part size - 1. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadedPart`. + +Chunk has been uploaded successfully. + +## Remove upload session by URL + +Abort an upload session and discard all data uploaded. + +This cannot be reversed. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) +and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + +This operation is performed by calling function `delete_file_upload_session_by_url`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-files-upload-sessions-id/). + + + +```python +client.chunked_uploads.delete_file_upload_session_by_url(abort_url) +``` + +### Arguments + +- url `str` + - URL of deleteFileUploadSessionById method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the session was +successfully aborted. + +## Remove upload session + +Abort an upload session and discard all data uploaded. + +This cannot be reversed. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) +and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + +This operation is performed by calling function `delete_file_upload_session_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-files-upload-sessions-id/). + + + +```python +client.chunked_uploads.delete_file_upload_session_by_id(upload_session_id) +``` + +### Arguments + +- upload_session_id `str` + - The ID of the upload session. Example: "D5E3F7A" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the session was +successfully aborted. + +## List parts by URL + +Return a list of the chunks uploaded to the upload session so far. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) +and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + +This operation is performed by calling function `get_file_upload_session_parts_by_url`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-upload-sessions-id-parts/). + + + +```python +client.chunked_uploads.get_file_upload_session_parts_by_url(list_parts_url) +``` + +### Arguments + +- url `str` + - URL of getFileUploadSessionParts method +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadParts`. + +Returns a list of parts that have been uploaded. + +## List parts + +Return a list of the chunks uploaded to the upload session so far. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) +and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + +This operation is performed by calling function `get_file_upload_session_parts`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-upload-sessions-id-parts/). + + + +```python +client.chunked_uploads.get_file_upload_session_parts(upload_session_id) +``` + +### Arguments + +- upload_session_id `str` + - The ID of the upload session. Example: "D5E3F7A" +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadParts`. + +Returns a list of parts that have been uploaded. + +## Commit upload session by URL + +Close an upload session and create a file from the uploaded chunks. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) +and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + +This operation is performed by calling function `create_file_upload_session_commit_by_url`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-upload-sessions-id-commit/). + + + +```python +client.chunked_uploads.create_file_upload_session_commit_by_url( + commit_url, parts, digest +) +``` + +### Arguments + +- url `str` + - URL of createFileUploadSessionCommit method +- parts `List[UploadPart]` + - The list details for the uploaded parts. +- digest `str` + - The [RFC3230][1] message digest of the whole file. Only SHA1 is supported. The SHA1 digest must be Base64 encoded. The format of this header is as `sha=BASE64_ENCODED_DIGEST`. [1]: https://tools.ietf.org/html/rfc3230 +- if_match `Optional[str]` + - Ensures this item hasn't recently changed before making changes. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `412 Precondition Failed` if it has changed since. +- if_none_match `Optional[str]` + - Ensures an item is only returned if it has changed. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `304 Not Modified` if the item has not changed since. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Optional[Files]`. + +Returns the file object in a list.Returns when all chunks have been uploaded but not yet processed. + +Inspect the upload session to get more information about the +progress of processing the chunks, then retry committing the file +when all chunks have processed. + +## Commit upload session + +Close an upload session and create a file from the uploaded chunks. + +The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) +and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + +This operation is performed by calling function `create_file_upload_session_commit`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-upload-sessions-id-commit/). + + + +```python +client.chunked_uploads.create_file_upload_session_commit( + upload_session_id, parts, digest +) +``` + +### Arguments + +- upload_session_id `str` + - The ID of the upload session. Example: "D5E3F7A" +- parts `List[UploadPart]` + - The list details for the uploaded parts. +- digest `str` + - The [RFC3230][1] message digest of the whole file. Only SHA1 is supported. The SHA1 digest must be Base64 encoded. The format of this header is as `sha=BASE64_ENCODED_DIGEST`. [1]: https://tools.ietf.org/html/rfc3230 +- if_match `Optional[str]` + - Ensures this item hasn't recently changed before making changes. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `412 Precondition Failed` if it has changed since. +- if_none_match `Optional[str]` + - Ensures an item is only returned if it has changed. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `304 Not Modified` if the item has not changed since. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Optional[Files]`. + +Returns the file object in a list.Returns when all chunks have been uploaded but not yet processed. + +Inspect the upload session to get more information about the +progress of processing the chunks, then retry committing the file +when all chunks have processed. + +## Upload big file + +Starts the process of chunk uploading a big file. Should return a File object representing uploaded file. + +This operation is performed by calling function `upload_big_file`. + +```python +client.chunked_uploads.upload_big_file( + file_byte_stream, file_name, file_size, parent_folder_id +) +``` + +### Arguments + +- file `ByteStream` + - The stream of the file to upload. +- file_name `str` + - The name of the file, which will be used for storage in Box. +- file_size `int` + - The total size of the file for the chunked upload in bytes. +- parent_folder_id `str` + - The ID of the folder where the file should be uploaded. + +### Returns + +This function returns a value of type `FileFull`. diff --git a/docs/classifications.md b/docs/classifications.md new file mode 100644 index 000000000..dc49bd650 --- /dev/null +++ b/docs/classifications.md @@ -0,0 +1,179 @@ +# ClassificationsManager + +- [List all classifications](#list-all-classifications) +- [Add classification](#add-classification) +- [Update classification](#update-classification) +- [Add initial classifications](#add-initial-classifications) + +## List all classifications + +Retrieves the classification metadata template and lists all the +classifications available to this enterprise. + +This API can also be called by including the enterprise ID in the +URL explicitly, for example +`/metadata_templates/enterprise_12345/securityClassification-6VMVochwUWo/schema`. + +This operation is performed by calling function `get_classification_template`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-templates-enterprise-securityClassification-6VMVochwUWo-schema/). + + + +```python +client.classifications.get_classification_template() +``` + +### Arguments + +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ClassificationTemplate`. + +Returns the `securityClassification` metadata template, which contains +a `Box__Security__Classification__Key` field that lists all the +classifications available to this enterprise. + +## Add classification + +Adds one or more new classifications to the list of classifications +available to the enterprise. + +This API can also be called by including the enterprise ID in the +URL explicitly, for example +`/metadata_templates/enterprise_12345/securityClassification-6VMVochwUWo/schema`. + +This operation is performed by calling function `add_classification`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-metadata-templates-enterprise-securityClassification-6VMVochwUWo-schema--add/). + + + +```python +client.classifications.add_classification( + [ + AddClassificationRequestBody( + data=AddClassificationRequestBodyDataField( + key=get_uuid(), + static_config=AddClassificationRequestBodyDataStaticConfigField( + classification=AddClassificationRequestBodyDataStaticConfigClassificationField( + color_id=4, classification_definition="Other description" + ) + ), + ) + ) + ] +) +``` + +### Arguments + +- request_body `List[AddClassificationRequestBody]` + - Request body of addClassification method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ClassificationTemplate`. + +Returns the updated `securityClassification` metadata template, which +contains a `Box__Security__Classification__Key` field that lists all +the classifications available to this enterprise. + +## Update classification + +Updates the labels and descriptions of one or more classifications +available to the enterprise. + +This API can also be called by including the enterprise ID in the +URL explicitly, for example +`/metadata_templates/enterprise_12345/securityClassification-6VMVochwUWo/schema`. + +This operation is performed by calling function `update_classification`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-metadata-templates-enterprise-securityClassification-6VMVochwUWo-schema--update/). + + + +```python +client.classifications.update_classification( + [ + UpdateClassificationRequestBody( + enum_option_key=classification.key, + data=UpdateClassificationRequestBodyDataField( + key=updated_classification_name, + static_config=UpdateClassificationRequestBodyDataStaticConfigField( + classification=UpdateClassificationRequestBodyDataStaticConfigClassificationField( + color_id=2, + classification_definition=updated_classification_description, + ) + ), + ), + ) + ] +) +``` + +### Arguments + +- request_body `List[UpdateClassificationRequestBody]` + - Request body of updateClassification method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ClassificationTemplate`. + +Returns the updated `securityClassification` metadata template, which +contains a `Box__Security__Classification__Key` field that lists all +the classifications available to this enterprise. + +## Add initial classifications + +When an enterprise does not yet have any classifications, this API call +initializes the classification template with an initial set of +classifications. + +If an enterprise already has a classification, the template will already +exist and instead an API call should be made to add additional +classifications. + +This operation is performed by calling function `create_classification_template`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-metadata-templates-schema--classifications/). + +_Currently we don't have an example for calling `create_classification_template` in integration tests_ + +### Arguments + +- scope `CreateClassificationTemplateScope` + - The scope in which to create the classifications. This should be `enterprise` or `enterprise_{id}` where `id` is the unique ID of the enterprise. +- template_key `CreateClassificationTemplateTemplateKey` + - Defines the list of metadata templates. +- display_name `CreateClassificationTemplateDisplayName` + - The name of the template as shown in web and mobile interfaces. +- hidden `Optional[bool]` + - Determines if the classification template is hidden or available on web and mobile devices. +- copy_instance_on_item_copy `Optional[bool]` + - Determines if classifications are copied along when the file or folder is copied. +- fields `List[CreateClassificationTemplateFields]` + - The classification template requires exactly one field, which holds all the valid classification values. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ClassificationTemplate`. + +Returns a new `securityClassification` metadata template, which +contains a `Box__Security__Classification__Key` field that lists all +the classifications available to this enterprise. diff --git a/docs/client.md b/docs/client.md new file mode 100644 index 000000000..48b11a454 --- /dev/null +++ b/docs/client.md @@ -0,0 +1,166 @@ +# Client + +This is the central entrypoint for all SDK interaction. The BoxClient houses all the API endpoints +divided across resource managers. + + + + +- [Make custom HTTP request](#make-custom-http-request) + - [JSON request](#json-request) + - [Multi-part request](#multi-part-request) + - [Binary response](#binary-response) +- [Additional headers](#additional-headers) + - [As-User header](#as-user-header) + - [Suppress notifications](#suppress-notifications) + - [Custom headers](#custom-headers) +- [Custom Base URLs](#custom-base-urls) +- [Use Proxy for API calls](#use-proxy-for-api-calls) + + + +# Make custom HTTP request + +You can make custom HTTP requests using the `client.make_request()` method. +This method allows you to make any HTTP request to the Box API. It will automatically use authentication and +network configuration settings from the client. +The method accepts a `FetchOptions` object as an argument and returns a `FetchResponse` object. + +## JSON request + +The following example demonstrates how to make a custom POST request to create a new folder in the root folder. + +```python +from box_sdk_gen import FetchResponse, FetchOptions + +response: FetchResponse = client.make_request( + FetchOptions( + method="POST", + url="https://api.box.com/2.0/folders", + data={"name": "new_folder_name", "parent": {"id": "0"}}, + ) +) +print("Received status code: ", response.status) +print("Created folder name: ", response.data["name"]) +``` + +## Multi-part request + +The following example demonstrates how to make a custom multipart request that uploads a file to a folder. + +```python +from box_sdk_gen import FetchResponse, FetchOptions, MultipartItem + +response: FetchResponse = client.make_request( + FetchOptions( + method="POST", + url="https://upload.box.com/api/2.0/files/content", + content_type="multipart/form-data", + multipart_data=[ + MultipartItem( + part_name="attributes", + data={"name": "new_folder_name", "parent": {"id": "0"}}, + ), + MultipartItem(part_name="file", file_stream=open("file.txt", "rb")), + ], + ) +) +print("Received status code: ", response.status) +``` + +## Binary response + +The following example demonstrates how to make a custom request that expects a binary response. +It is required to specify the `response_format` parameter in the `FetchOptions` object to `ResponseFormat.BINARY`. + +```python +from box_sdk_gen import FetchResponse, FetchOptions, ResponseFormat + +file_id = "1234567" +response: FetchResponse = client.make_request( + FetchOptions( + method="GET", + url="".join(["https://api.box.com/2.0/files/", file_id, "/content"]), + response_format=ResponseFormat.BINARY, + ) +) +print("Received status code: ", response.status) +with open("file.txt", "wb") as file: + file.write(response.content) +``` + +# Additional headers + +BoxClient provides a convenient methods, which allow passing additional headers, which will be included +in every API call made by the client. + +## As-User header + +The As-User header is used by enterprise admins to make API calls on behalf of their enterprise's users. +This requires the API request to pass an As-User: USER-ID header. For more details see the [documentation on As-User](https://developer.box.com/en/guides/authentication/oauth2/as-user/). + +The following example assume that the client has been instantiated with an access token belonging to an admin-level user +or Service Account with appropriate privileges to make As-User calls. + +Calling the `client.with_as_user_header()` method creates a new client to impersonate user with the provided ID. +All calls made with the new client will be made in context of the impersonated user, leaving the original client unmodified. + + + +```python +user_client = client.with_as_user_header(user_id="1234567") +``` + +## Suppress notifications + +If you are making administrative API calls (that is, your application has “Manage an Enterprise” +scope, and the user signing in is a co-admin with the correct "Edit settings for your company" +permission) then you can suppress both email and webhook notifications. This can be used, for +example, for a virus-scanning tool to download copies of everyone’s files in an enterprise, +without every collaborator on the file getting an email. All actions will still appear in users' +updates feed and audit logs. + +> **Note:** This functionality is only available for approved applications. + +Calling the `client.with_suppressed_notifications()` method creates a new client. +For all calls made with the new client the notifications will be suppressed. + +```python +new_client = client.with_suppressed_notifications() +``` + +## Custom headers + +You can also specify the custom set of headers, which will be included in every API call made by client. +Calling the `client.with_extra_headers()` method creates a new client, leaving the original client unmodified. + +```python +new_client = client.with_extra_headers(extra_headers={"customHeader": "customValue"}) +``` + +# Custom Base URLs + +You can also specify the custom base URLs, which will be used for API calls made by client. +Calling the `client.with_custom_base_urls()` method creates a new client, leaving the original client unmodified. + +```python +new_client = client.with_custom_base_urls( + base_urls=BaseUrls( + base_url="https://api.box.com", + upload_url="https://upload.box.com/api", + oauth_2_url="https://account.box.com/api/oauth2", + ) +) +``` + +# Use Proxy for API calls + +In order to use a proxy for API calls, calling the `client.with_proxy(proxyConfig)` method creates a new client, leaving the original client unmodified, with the username and password being optional. + +**Note:** We are only supporting http/s proxies with basic authentication. NTLM and other authentication methods are not supported. + +```python +new_client = client.with_proxy( + ProxyConfig(url="http://proxy.com", username="username", password="password") +) +``` diff --git a/docs/collaboration_allowlist_entries.md b/docs/collaboration_allowlist_entries.md new file mode 100644 index 000000000..ff1b297b3 --- /dev/null +++ b/docs/collaboration_allowlist_entries.md @@ -0,0 +1,133 @@ +# CollaborationAllowlistEntriesManager + +- [List allowed collaboration domains](#list-allowed-collaboration-domains) +- [Add domain to list of allowed collaboration domains](#add-domain-to-list-of-allowed-collaboration-domains) +- [Get allowed collaboration domain](#get-allowed-collaboration-domain) +- [Remove domain from list of allowed collaboration domains](#remove-domain-from-list-of-allowed-collaboration-domains) + +## List allowed collaboration domains + +Returns the list domains that have been deemed safe to create collaborations +for within the current enterprise. + +This operation is performed by calling function `get_collaboration_whitelist_entries`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-collaboration-whitelist-entries/). + + + +```python +client.collaboration_allowlist_entries.get_collaboration_whitelist_entries() +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CollaborationAllowlistEntries`. + +Returns a collection of domains that are allowed for collaboration. + +## Add domain to list of allowed collaboration domains + +Creates a new entry in the list of allowed domains to allow +collaboration for. + +This operation is performed by calling function `create_collaboration_whitelist_entry`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-collaboration-whitelist-entries/). + + + +```python +client.collaboration_allowlist_entries.create_collaboration_whitelist_entry( + domain, CreateCollaborationWhitelistEntryDirection.INBOUND +) +``` + +### Arguments + +- domain `str` + - The domain to add to the list of allowed domains. +- direction `CreateCollaborationWhitelistEntryDirection` + - The direction in which to allow collaborations. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CollaborationAllowlistEntry`. + +Returns a new entry on the list of allowed domains. + +## Get allowed collaboration domain + +Returns a domain that has been deemed safe to create collaborations +for within the current enterprise. + +This operation is performed by calling function `get_collaboration_whitelist_entry_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-collaboration-whitelist-entries-id/). + + + +```python +client.collaboration_allowlist_entries.get_collaboration_whitelist_entry_by_id( + new_entry.id +) +``` + +### Arguments + +- collaboration_whitelist_entry_id `str` + - The ID of the entry in the list. Example: "213123" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CollaborationAllowlistEntry`. + +Returns an entry on the list of allowed domains. + +## Remove domain from list of allowed collaboration domains + +Removes a domain from the list of domains that have been deemed safe to create +collaborations for within the current enterprise. + +This operation is performed by calling function `delete_collaboration_whitelist_entry_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-collaboration-whitelist-entries-id/). + + + +```python +client.collaboration_allowlist_entries.delete_collaboration_whitelist_entry_by_id( + entry.id +) +``` + +### Arguments + +- collaboration_whitelist_entry_id `str` + - The ID of the entry in the list. Example: "213123" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the entry was +successfully deleted. diff --git a/docs/collaboration_allowlist_exempt_targets.md b/docs/collaboration_allowlist_exempt_targets.md new file mode 100644 index 000000000..f5e4077fa --- /dev/null +++ b/docs/collaboration_allowlist_exempt_targets.md @@ -0,0 +1,131 @@ +# CollaborationAllowlistExemptTargetsManager + +- [List users exempt from collaboration domain restrictions](#list-users-exempt-from-collaboration-domain-restrictions) +- [Create user exemption from collaboration domain restrictions](#create-user-exemption-from-collaboration-domain-restrictions) +- [Get user exempt from collaboration domain restrictions](#get-user-exempt-from-collaboration-domain-restrictions) +- [Remove user from list of users exempt from domain restrictions](#remove-user-from-list-of-users-exempt-from-domain-restrictions) + +## List users exempt from collaboration domain restrictions + +Returns a list of users who have been exempt from the collaboration +domain restrictions. + +This operation is performed by calling function `get_collaboration_whitelist_exempt_targets`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-collaboration-whitelist-exempt-targets/). + + + +```python +client.collaboration_allowlist_exempt_targets.get_collaboration_whitelist_exempt_targets() +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CollaborationAllowlistExemptTargets`. + +Returns a collection of user exemptions. + +## Create user exemption from collaboration domain restrictions + +Exempts a user from the restrictions set out by the allowed list of domains +for collaborations. + +This operation is performed by calling function `create_collaboration_whitelist_exempt_target`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-collaboration-whitelist-exempt-targets/). + + + +```python +client.collaboration_allowlist_exempt_targets.create_collaboration_whitelist_exempt_target( + CreateCollaborationWhitelistExemptTargetUser(id=user.id) +) +``` + +### Arguments + +- user `CreateCollaborationWhitelistExemptTargetUser` + - The user to exempt. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CollaborationAllowlistExemptTarget`. + +Returns a new exemption entry. + +## Get user exempt from collaboration domain restrictions + +Returns a users who has been exempt from the collaboration +domain restrictions. + +This operation is performed by calling function `get_collaboration_whitelist_exempt_target_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-collaboration-whitelist-exempt-targets-id/). + + + +```python +client.collaboration_allowlist_exempt_targets.get_collaboration_whitelist_exempt_target_by_id( + new_exempt_target.id +) +``` + +### Arguments + +- collaboration_whitelist_exempt_target_id `str` + - The ID of the exemption to the list. Example: "984923" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CollaborationAllowlistExemptTarget`. + +Returns the user's exempted from the list of collaboration domains. + +## Remove user from list of users exempt from domain restrictions + +Removes a user's exemption from the restrictions set out by the allowed list +of domains for collaborations. + +This operation is performed by calling function `delete_collaboration_whitelist_exempt_target_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-collaboration-whitelist-exempt-targets-id/). + + + +```python +client.collaboration_allowlist_exempt_targets.delete_collaboration_whitelist_exempt_target_by_id( + exempt_target.id +) +``` + +### Arguments + +- collaboration_whitelist_exempt_target_id `str` + - The ID of the exemption to the list. Example: "984923" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the exemption was +successfully deleted. diff --git a/docs/collections.md b/docs/collections.md new file mode 100644 index 000000000..7de232627 --- /dev/null +++ b/docs/collections.md @@ -0,0 +1,103 @@ +# CollectionsManager + +- [List all collections](#list-all-collections) +- [List collection items](#list-collection-items) +- [Get collection by ID](#get-collection-by-id) + +## List all collections + +Retrieves all collections for a given user. + +Currently, only the `favorites` collection +is supported. + +This operation is performed by calling function `get_collections`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-collections/). + + + +```python +client.collections.get_collections() +``` + +### Arguments + +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Collections`. + +Returns all collections for the given user. + +## List collection items + +Retrieves the files and/or folders contained within +this collection. + +This operation is performed by calling function `get_collection_items`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-collections-id-items/). + + + +```python +client.collections.get_collection_items(favourite_collection.id) +``` + +### Arguments + +- collection_id `str` + - The ID of the collection. Example: "926489" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ItemsOffsetPaginated`. + +Returns an array of items in the collection. + +## Get collection by ID + +Retrieves a collection by its ID. + +This operation is performed by calling function `get_collection_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-collections-id/). + + + +```python +client.collections.get_collection_by_id(collections.entries[0].id) +``` + +### Arguments + +- collection_id `str` + - The ID of the collection. Example: "926489" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Collection`. + +Returns an array of items in the collection. diff --git a/docs/comments.md b/docs/comments.md new file mode 100644 index 000000000..b5434eee3 --- /dev/null +++ b/docs/comments.md @@ -0,0 +1,174 @@ +# CommentsManager + +- [List file comments](#list-file-comments) +- [Get comment](#get-comment) +- [Update comment](#update-comment) +- [Remove comment](#remove-comment) +- [Create comment](#create-comment) + +## List file comments + +Retrieves a list of comments for a file. + +This operation is performed by calling function `get_file_comments`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-comments/). + + + +```python +client.comments.get_file_comments(file_id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- limit `Optional[int]` + - The maximum number of items to return per page. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Comments`. + +Returns a collection of comment objects. If there are no +comments on this file an empty collection will be returned. + +## Get comment + +Retrieves the message and metadata for a specific comment, as well +as information on the user who created the comment. + +This operation is performed by calling function `get_comment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-comments-id/). + + + +```python +client.comments.get_comment_by_id(new_comment.id) +``` + +### Arguments + +- comment_id `str` + - The ID of the comment. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CommentFull`. + +Returns a full comment object. + +## Update comment + +Update the message of a comment. + +This operation is performed by calling function `update_comment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-comments-id/). + + + +```python +client.comments.update_comment_by_id(new_reply_comment.id, message=new_message) +``` + +### Arguments + +- comment_id `str` + - The ID of the comment. Example: "12345" +- message `Optional[str]` + - The text of the comment to update. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CommentFull`. + +Returns the updated comment object. + +## Remove comment + +Permanently deletes a comment. + +This operation is performed by calling function `delete_comment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-comments-id/). + + + +```python +client.comments.delete_comment_by_id(new_comment.id) +``` + +### Arguments + +- comment_id `str` + - The ID of the comment. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the comment has been deleted. + +## Create comment + +Adds a comment by the user to a specific file, or +as a reply to an other comment. + +This operation is performed by calling function `create_comment`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-comments/). + + + +```python +client.comments.create_comment( + message, CreateCommentItem(id=file_id, type=CreateCommentItemTypeField.FILE) +) +``` + +### Arguments + +- message `str` + - The text of the comment. To mention a user, use the `tagged_message` parameter instead. +- tagged_message `Optional[str]` + - The text of the comment, including `@[user_id:name]` somewhere in the message to mention another user, which will send them an email notification, letting them know they have been mentioned. The `user_id` is the target user's ID, where the `name` can be any custom phrase. In the Box UI this name will link to the user's profile. If you are not mentioning another user, use `message` instead. +- item `CreateCommentItem` + - The item to attach the comment to. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CommentFull`. + +Returns the newly created comment object. + +Not all available fields are returned by default. Use the +[fields](#param-fields) query parameter to explicitly request +any specific fields. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 000000000..4c10d7f8e --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,66 @@ +# Configuration + + + + +- [Max retry attempts](#max-retry-attempts) +- [Custom retry strategy](#custom-retry-strategy) + + + +## Max retry attempts + +The default maximum number of retries in case of failed API call is 5. +To change this number you should initialize `BoxRetryStrategy` with the new value and pass it to `NetworkSession`. + +```python +from box_sdk_gen import ( + BoxClient, + BoxDeveloperTokenAuth, + NetworkSession, + BoxRetryStrategy, +) + +auth = BoxDeveloperTokenAuth(token="DEVELOPER_TOKEN_GOES_HERE") +network_session = NetworkSession(retry_strategy=BoxRetryStrategy(max_attempts=6)) +client = BoxClient(auth=auth, network_session=network_session) +``` + +## Custom retry strategy + +You can also implement your own retry strategy by subclassing `RetryStrategy` and overriding `should_retry` and `retry_after` methods. +This example shows how to set custom strategy that retries on 5xx status codes and waits 1 second between retries. + +```python +from box_sdk_gen import ( + BoxClient, + BoxDeveloperTokenAuth, + NetworkSession, + RetryStrategy, + FetchOptions, + FetchResponse, +) + + +class CustomRetryStrategy(RetryStrategy): + def should_retry( + self, + fetch_options: FetchOptions, + fetch_response: FetchResponse, + attempt_number: int, + ) -> bool: + return fetch_response.status_code >= 500 + + def retry_after( + self, + fetch_options: FetchOptions, + fetch_response: FetchResponse, + attempt_number: int, + ) -> float: + return 1.0 + + +auth = BoxDeveloperTokenAuth(token="DEVELOPER_TOKEN_GOES_HERE") +network_session = NetworkSession(retry_strategy=CustomRetryStrategy()) +client = BoxClient(auth=auth, network_session=network_session) +``` diff --git a/docs/device_pinners.md b/docs/device_pinners.md new file mode 100644 index 000000000..77fd4ce41 --- /dev/null +++ b/docs/device_pinners.md @@ -0,0 +1,98 @@ +# DevicePinnersManager + +- [Get device pin](#get-device-pin) +- [Remove device pin](#remove-device-pin) +- [List enterprise device pins](#list-enterprise-device-pins) + +## Get device pin + +Retrieves information about an individual device pin. + +This operation is performed by calling function `get_device_pinner_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-device-pinners-id/). + + + +```python +client.device_pinners.get_device_pinner_by_id(device_pinner_id) +``` + +### Arguments + +- device_pinner_id `str` + - The ID of the device pin. Example: "2324234" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DevicePinner`. + +Returns information about a single device pin. + +## Remove device pin + +Deletes an individual device pin. + +This operation is performed by calling function `delete_device_pinner_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-device-pinners-id/). + + + +```python +client.device_pinners.delete_device_pinner_by_id(device_pinner_id) +``` + +### Arguments + +- device_pinner_id `str` + - The ID of the device pin. Example: "2324234" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the pin has been deleted. + +## List enterprise device pins + +Retrieves all the device pins within an enterprise. + +The user must have admin privileges, and the application +needs the "manage enterprise" scope to make this call. + +This operation is performed by calling function `get_enterprise_device_pinners`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-enterprises-id-device-pinners/). + + + +```python +client.device_pinners.get_enterprise_device_pinners(enterprise_id) +``` + +### Arguments + +- enterprise_id `str` + - The ID of the enterprise. Example: "3442311" +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- direction `Optional[GetEnterpriseDevicePinnersDirection]` + - The direction to sort results in. This can be either in alphabetical ascending (`ASC`) or descending (`DESC`) order. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DevicePinners`. + +Returns a list of device pins for a given enterprise. diff --git a/docs/docgen.md b/docs/docgen.md new file mode 100644 index 000000000..6e2a00bac --- /dev/null +++ b/docs/docgen.md @@ -0,0 +1,148 @@ +# DocgenManager + +- [Get Box Doc Gen job by ID](#get-box-doc-gen-job-by-id) +- [List all Box Doc Gen jobs](#list-all-box-doc-gen-jobs) +- [Get Box Doc Gen jobs by batch ID](#get-box-doc-gen-jobs-by-batch-id) +- [Generate document using Box Doc Gen template](#generate-document-using-box-doc-gen-template) + +## Get Box Doc Gen job by ID + +Get details of the Box Doc Gen job. + +This operation is performed by calling function `get_docgen_job_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-docgen-jobs-id/). + + + +```python +client.docgen.get_docgen_job_by_id_v2025_r0(docgen_job_item_from_list.id) +``` + +### Arguments + +- job_id `str` + - Box Doc Gen job ID. Example: 123 +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DocGenJobV2025R0`. + +Details of the Box Doc Gen job. + +## List all Box Doc Gen jobs + +Lists all Box Doc Gen jobs for a user. + +This operation is performed by calling function `get_docgen_jobs_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-docgen-jobs/). + + + +```python +client.docgen.get_docgen_jobs_v2025_r0(limit=500) +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DocGenJobsFullV2025R0`. + +A list of Box Doc Gen jobs. + +## Get Box Doc Gen jobs by batch ID + +Lists Box Doc Gen jobs in a batch. + +This operation is performed by calling function `get_docgen_batch_job_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-docgen-batch-jobs-id/). + + + +```python +client.docgen.get_docgen_batch_job_by_id_v2025_r0(docgen_batch.id) +``` + +### Arguments + +- batch_id `str` + - Box Doc Gen batch ID. Example: 123 +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DocGenJobsV2025R0`. + +Returns a list of Box Doc Gen jobs in a Box Doc Gen batch. + +## Generate document using Box Doc Gen template + +Generates a document using a Box Doc Gen template. + +This operation is performed by calling function `create_docgen_batch_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/post-docgen-batches/). + + + +```python +client.docgen.create_docgen_batch_v2025_r0( + FileReferenceV2025R0(id=uploaded_file.id), + "api", + CreateDocgenBatchV2025R0DestinationFolder(id=folder.id), + "pdf", + [ + DocGenDocumentGenerationDataV2025R0( + generated_file_name="test", user_input={"abc": "xyz"} + ) + ], +) +``` + +### Arguments + +- file `FileReferenceV2025R0` +- file_version `Optional[FileVersionBaseV2025R0]` +- input_source `str` + - Source of input. The value has to be `api` for all the API-based document generation requests. +- destination_folder `CreateDocgenBatchV2025R0DestinationFolder` +- output_type `str` + - Type of the output file. +- document_generation_data `List[DocGenDocumentGenerationDataV2025R0]` +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DocGenBatchBaseV2025R0`. + +The created Batch ID. diff --git a/docs/docgen_template.md b/docs/docgen_template.md new file mode 100644 index 000000000..e9e979a55 --- /dev/null +++ b/docs/docgen_template.md @@ -0,0 +1,207 @@ +# DocgenTemplateManager + +- [Create Box Doc Gen template](#create-box-doc-gen-template) +- [List Box Doc Gen templates](#list-box-doc-gen-templates) +- [Delete Box Doc Gen template](#delete-box-doc-gen-template) +- [Get Box Doc Gen template by ID](#get-box-doc-gen-template-by-id) +- [List all Box Doc Gen template tags in template](#list-all-box-doc-gen-template-tags-in-template) +- [Get list of all Box Doc Gen jobs for template](#get-list-of-all-box-doc-gen-jobs-for-template) + +## Create Box Doc Gen template + +Marks a file as a Box Doc Gen template. + +This operation is performed by calling function `create_docgen_template_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/post-docgen-templates/). + + + +```python +client.docgen_template.create_docgen_template_v2025_r0(FileReferenceV2025R0(id=file.id)) +``` + +### Arguments + +- file `FileReferenceV2025R0` +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DocGenTemplateBaseV2025R0`. + +The file which has now been marked as a Box Doc Gen template. + +## List Box Doc Gen templates + +Lists Box Doc Gen templates on which the user is a collaborator. + +This operation is performed by calling function `get_docgen_templates_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-docgen-templates/). + + + +```python +client.docgen_template.get_docgen_templates_v2025_r0() +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DocGenTemplatesV2025R0`. + +Returns a collection of templates. + +## Delete Box Doc Gen template + +Unmarks file as Box Doc Gen template. + +This operation is performed by calling function `delete_docgen_template_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/delete-docgen-templates-id/). + + + +```python +client.docgen_template.delete_docgen_template_by_id_v2025_r0( + created_docgen_template.file.id +) +``` + +### Arguments + +- template_id `str` + - ID of the file which will no longer be marked as a Box Doc Gen template. Example: "123" +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when a file is no longer marked as a Box Doc Gen template. + +## Get Box Doc Gen template by ID + +Lists details of a specific Box Doc Gen template. + +This operation is performed by calling function `get_docgen_template_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-docgen-templates-id/). + + + +```python +client.docgen_template.get_docgen_template_by_id_v2025_r0( + created_docgen_template.file.id +) +``` + +### Arguments + +- template_id `str` + - The ID of a Box Doc Gen template. Example: 123 +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DocGenTemplateV2025R0`. + +Returns a template. + +## List all Box Doc Gen template tags in template + +Lists all tags in a Box Doc Gen template. + +This operation is performed by calling function `get_docgen_template_tags_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-docgen-templates-id-tags/). + + + +```python +client.docgen_template.get_docgen_template_tags_v2025_r0( + fetched_docgen_template.file.id +) +``` + +### Arguments + +- template_id `str` + - ID of template. Example: 123 +- template_version_id `Optional[str]` + - Id of template version. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DocGenTagsV2025R0`. + +A list of document generation template tags.Processing tags for the file. + +## Get list of all Box Doc Gen jobs for template + +Lists the users jobs which use this template. + +This operation is performed by calling function `get_docgen_template_job_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-docgen-template-jobs-id/). + + + +```python +client.docgen_template.get_docgen_template_job_by_id_v2025_r0( + fetched_docgen_template.file.id +) +``` + +### Arguments + +- template_id `str` + - Id of template to fetch jobs for. Example: 123 +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `DocGenJobsV2025R0`. + +A single Box Doc Gen template. diff --git a/docs/downloads.md b/docs/downloads.md new file mode 100644 index 000000000..46ea036eb --- /dev/null +++ b/docs/downloads.md @@ -0,0 +1,128 @@ +# DownloadsManager + +- [Download file URL](#download-file-url) +- [Download file](#download-file) +- [Download file](#download-file) + +## Download file URL + +Get the download URL without downloading the content. + +This operation is performed by calling function `get_download_file_url`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-content/). + + + +```python +client.downloads.get_download_file_url(uploaded_file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- version `Optional[str]` + - The file version to download. +- access_token `Optional[str]` + - An optional access token that can be used to pre-authenticate this request, which means that a download link can be shared with a browser or a third party service without them needing to know how to handle the authentication. When using this parameter, please make sure that the access token is sufficiently scoped down to only allow read access to that file and no other files or folders. +- range `Optional[str]` + - The byte range of the content to download. The format `bytes={start_byte}-{end_byte}` can be used to specify what section of the file to download. +- boxapi `Optional[str]` + - The URL, and optional password, for the shared link of this item. This header can be used to access items that have not been explicitly shared with a user. Use the format `shared_link=[link]` or if a password is required then use `shared_link=[link]&shared_link_password=[password]`. This header can be used on the file or folder shared, as well as on any files or folders nested within the item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `str`. + +Returns the requested file if the client has the **follow +redirects** setting enabled to automatically +follow HTTP `3xx` responses as redirects. If not, the request +will return `302` instead. +For details, see +the [download file guide](g://downloads/file#download-url).If the file is not ready to be downloaded yet `Retry-After` header will +be returned indicating the time in seconds after which the file will +be available for the client to download. + +This response can occur when the file was uploaded immediately before the +download request. + +## Download file + +Returns the contents of a file in binary format. + +This operation is performed by calling function `download_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-content/). + + + +```python +client.downloads.download_file(uploaded_file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- version `Optional[str]` + - The file version to download. +- access_token `Optional[str]` + - An optional access token that can be used to pre-authenticate this request, which means that a download link can be shared with a browser or a third party service without them needing to know how to handle the authentication. When using this parameter, please make sure that the access token is sufficiently scoped down to only allow read access to that file and no other files or folders. +- range `Optional[str]` + - The byte range of the content to download. The format `bytes={start_byte}-{end_byte}` can be used to specify what section of the file to download. +- boxapi `Optional[str]` + - The URL, and optional password, for the shared link of this item. This header can be used to access items that have not been explicitly shared with a user. Use the format `shared_link=[link]` or if a password is required then use `shared_link=[link]&shared_link_password=[password]`. This header can be used on the file or folder shared, as well as on any files or folders nested within the item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Optional[ByteStream]`. + +Returns the requested file if the client has the **follow +redirects** setting enabled to automatically +follow HTTP `3xx` responses as redirects. If not, the request +will return `302` instead. +For details, see +the [download file guide](g://downloads/file#download-url).If the file is not ready to be downloaded yet `Retry-After` header will +be returned indicating the time in seconds after which the file will +be available for the client to download. + +This response can occur when the file was uploaded immediately before the +download request. + +## Download file + +Download file to a given output stream + +This operation is performed by calling function `download_file_to_output_stream`. + +```python +client.downloads.download_file_to_output_stream(uploaded_file.id, file_output_stream) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- output_stream `OutputStream` + - Download file to a given output stream +- version `Optional[str]` + - The file version to download. +- access_token `Optional[str]` + - An optional access token that can be used to pre-authenticate this request, which means that a download link can be shared with a browser or a third party service without them needing to know how to handle the authentication. When using this parameter, please make sure that the access token is sufficiently scoped down to only allow read access to that file and no other files or folders. +- range `Optional[str]` + - The byte range of the content to download. The format `bytes={start_byte}-{end_byte}` can be used to specify what section of the file to download. +- boxapi `Optional[str]` + - The URL, and optional password, for the shared link of this item. This header can be used to access items that have not been explicitly shared with a user. Use the format `shared_link=[link]` or if a password is required then use `shared_link=[link]&shared_link_password=[password]`. This header can be used on the file or folder shared, as well as on any files or folders nested within the item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. diff --git a/docs/email_aliases.md b/docs/email_aliases.md new file mode 100644 index 000000000..538ee43bf --- /dev/null +++ b/docs/email_aliases.md @@ -0,0 +1,94 @@ +# EmailAliasesManager + +- [List user's email aliases](#list-users-email-aliases) +- [Create email alias](#create-email-alias) +- [Remove email alias](#remove-email-alias) + +## List user's email aliases + +Retrieves all email aliases for a user. The collection +does not include the primary login for the user. + +This operation is performed by calling function `get_user_email_aliases`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-users-id-email-aliases/). + + + +```python +client.email_aliases.get_user_email_aliases(new_user.id) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `EmailAliases`. + +Returns a collection of email aliases. + +## Create email alias + +Adds a new email alias to a user account.. + +This operation is performed by calling function `create_user_email_alias`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-users-id-email-aliases/). + + + +```python +client.email_aliases.create_user_email_alias(new_user.id, new_alias_email) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- email `str` + - The email address to add to the account as an alias. Note: The domain of the email alias needs to be registered to your enterprise. See the [domain verification guide](https://support.box.com/hc/en-us/articles/4408619650579-Domain-Verification) for steps to add a new domain. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `EmailAlias`. + +Returns the newly created email alias object. + +## Remove email alias + +Removes an email alias from a user. + +This operation is performed by calling function `delete_user_email_alias_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-users-id-email-aliases-id/). + + + +```python +client.email_aliases.delete_user_email_alias_by_id(new_user.id, new_alias.id) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- email_alias_id `str` + - The ID of the email alias. Example: "23432" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Removes the alias and returns an empty response. diff --git a/docs/events.md b/docs/events.md new file mode 100644 index 000000000..4dcf3c644 --- /dev/null +++ b/docs/events.md @@ -0,0 +1,115 @@ +# EventsManager + +- [Get events long poll endpoint](#get-events-long-poll-endpoint) +- [List user and enterprise events](#list-user-and-enterprise-events) + +## Get events long poll endpoint + +Returns a list of real-time servers that can be used for long-polling updates +to the [event stream](#get-events). + +Long polling is the concept where a HTTP request is kept open until the +server sends a response, then repeating the process over and over to receive +updated responses. + +Long polling the event stream can only be used for user events, not for +enterprise events. + +To use long polling, first use this endpoint to retrieve a list of long poll +URLs. Next, make a long poll request to any of the provided URLs. + +When an event occurs in monitored account a response with the value +`new_change` will be sent. The response contains no other details as +it only serves as a prompt to take further action such as sending a +request to the [events endpoint](#get-events) with the last known +`stream_position`. + +After the server sends this response it closes the connection. You must now +repeat the long poll process to begin listening for events again. + +If no events occur for a while and the connection times out you will +receive a response with the value `reconnect`. When you receive this response +you’ll make another call to this endpoint to restart the process. + +If you receive no events in `retry_timeout` seconds then you will need to +make another request to the real-time server (one of the URLs in the response +for this endpoint). This might be necessary due to network errors. + +Finally, if you receive a `max_retries` error when making a request to the +real-time server, you should start over by making a call to this endpoint +first. + +This operation is performed by calling function `get_events_with_long_polling`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/options-events/). + + + +```python +client.events.get_events_with_long_polling() +``` + +### Arguments + +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `RealtimeServers`. + +Returns a paginated array of servers that can be used +instead of the regular endpoints for long-polling events. + +## List user and enterprise events + +Returns up to a year of past events for a given user +or for the entire enterprise. + +By default this returns events for the authenticated user. To retrieve events +for the entire enterprise, set the `stream_type` to `admin_logs_streaming` +for live monitoring of new events, or `admin_logs` for querying across +historical events. The user making the API call will +need to have admin privileges, and the application will need to have the +scope `manage enterprise properties` checked. + +This operation is performed by calling function `get_events`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-events/). + + + +```python +client.events.get_events() +``` + +### Arguments + +- stream_type `Optional[GetEventsStreamType]` + - Defines the type of events that are returned _ `all` returns everything for a user and is the default _ `changes` returns events that may cause file tree changes such as file updates or collaborations. _ `sync` is similar to `changes` but only applies to synced folders _ `admin_logs` returns all events for an entire enterprise and requires the user making the API call to have admin permissions. This stream type is for programmatically pulling from a 1 year history of events across all users within the enterprise and within a `created_after` and `created_before` time frame. The complete history of events will be returned in chronological order based on the event time, but latency will be much higher than `admin_logs_streaming`. \* `admin_logs_streaming` returns all events for an entire enterprise and requires the user making the API call to have admin permissions. This stream type is for polling for recent events across all users within the enterprise. Latency will be much lower than `admin_logs`, but events will not be returned in chronological order and may contain duplicates. +- stream_position `Optional[str]` + - The location in the event stream to start receiving events from. _ `now` will return an empty list events and the latest stream position for initialization. _ `0` or `null` will return all events. +- limit `Optional[int]` + - Limits the number of events returned. Note: Sometimes, the events less than the limit requested can be returned even when there may be more events remaining. This is primarily done in the case where a number of events have already been retrieved and these retrieved events are returned rather than delaying for an unknown amount of time to see if there are any more results. +- event_type `Optional[List[GetEventsEventType]]` + - A comma-separated list of events to filter by. This can only be used when requesting the events with a `stream_type` of `admin_logs` or `adming_logs_streaming`. For any other `stream_type` this value will be ignored. +- created_after `Optional[DateTime]` + - The lower bound date and time to return events for. This can only be used when requesting the events with a `stream_type` of `admin_logs`. For any other `stream_type` this value will be ignored. +- created_before `Optional[DateTime]` + - The upper bound date and time to return events for. This can only be used when requesting the events with a `stream_type` of `admin_logs`. For any other `stream_type` this value will be ignored. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Events`. + +Returns a list of event objects. + +Events objects are returned in pages, with each page (chunk) +including a list of event objects. The response includes a +`chunk_size` parameter indicating how many events were returned in this +chunk, as well as the next `stream_position` that can be +queried. diff --git a/docs/file_classifications.md b/docs/file_classifications.md new file mode 100644 index 000000000..113e50d7d --- /dev/null +++ b/docs/file_classifications.md @@ -0,0 +1,149 @@ +# FileClassificationsManager + +- [Get classification on file](#get-classification-on-file) +- [Add classification to file](#add-classification-to-file) +- [Update classification on file](#update-classification-on-file) +- [Remove classification from file](#remove-classification-from-file) + +## Get classification on file + +Retrieves the classification metadata instance that +has been applied to a file. + +This API can also be called by including the enterprise ID in the +URL explicitly, for example +`/files/:id//enterprise_12345/securityClassification-6VMVochwUWo`. + +This operation is performed by calling function `get_classification_on_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-metadata-enterprise-securityClassification-6VMVochwUWo/). + + + +```python +client.file_classifications.get_classification_on_file(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Classification`. + +Returns an instance of the `securityClassification` metadata +template, which contains a `Box__Security__Classification__Key` +field that lists all the classifications available to this +enterprise. + +## Add classification to file + +Adds a classification to a file by specifying the label of the +classification to add. + +This API can also be called by including the enterprise ID in the +URL explicitly, for example +`/files/:id//enterprise_12345/securityClassification-6VMVochwUWo`. + +This operation is performed by calling function `add_classification_to_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-id-metadata-enterprise-securityClassification-6VMVochwUWo/). + + + +```python +client.file_classifications.add_classification_to_file( + file.id, box_security_classification_key=classification.key +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- box_security_classification_key `Optional[str]` + - The name of the classification to apply to this file. To list the available classifications in an enterprise, use the classification API to retrieve the [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) which lists all available classification keys. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Classification`. + +Returns the classification template instance +that was applied to the file. + +## Update classification on file + +Updates a classification on a file. + +The classification can only be updated if a classification has already been +applied to the file before. When editing classifications, only values are +defined for the enterprise will be accepted. + +This operation is performed by calling function `update_classification_on_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-id-metadata-enterprise-securityClassification-6VMVochwUWo/). + + + +```python +client.file_classifications.update_classification_on_file( + file.id, [UpdateClassificationOnFileRequestBody(value=second_classification.key)] +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- request_body `List[UpdateClassificationOnFileRequestBody]` + - Request body of updateClassificationOnFile method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Classification`. + +Returns the updated classification metadata template instance. + +## Remove classification from file + +Removes any classifications from a file. + +This API can also be called by including the enterprise ID in the +URL explicitly, for example +`/files/:id//enterprise_12345/securityClassification-6VMVochwUWo`. + +This operation is performed by calling function `delete_classification_from_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-files-id-metadata-enterprise-securityClassification-6VMVochwUWo/). + + + +```python +client.file_classifications.delete_classification_from_file(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the classification is +successfully deleted. diff --git a/docs/file_metadata.md b/docs/file_metadata.md new file mode 100644 index 000000000..5f611f001 --- /dev/null +++ b/docs/file_metadata.md @@ -0,0 +1,231 @@ +# FileMetadataManager + +- [List metadata instances on file](#list-metadata-instances-on-file) +- [Get metadata instance on file](#get-metadata-instance-on-file) +- [Create metadata instance on file](#create-metadata-instance-on-file) +- [Update metadata instance on file](#update-metadata-instance-on-file) +- [Remove metadata instance from file](#remove-metadata-instance-from-file) + +## List metadata instances on file + +Retrieves all metadata for a given file. + +This operation is performed by calling function `get_file_metadata`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-metadata/). + + + +```python +client.file_metadata.get_file_metadata(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Metadatas`. + +Returns all the metadata associated with a file. + +This API does not support pagination and will therefore always return +all of the metadata associated to the file. + +## Get metadata instance on file + +Retrieves the instance of a metadata template that has been applied to a +file. + +This operation is performed by calling function `get_file_metadata_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-metadata-id-id/). + + + +```python +client.file_metadata.get_file_metadata_by_id( + file.id, GetFileMetadataByIdScope.GLOBAL, "properties" +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- scope `GetFileMetadataByIdScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataFull`. + +An instance of the metadata template that includes +additional "key:value" pairs defined by the user or +an application. + +## Create metadata instance on file + +Applies an instance of a metadata template to a file. + +In most cases only values that are present in the metadata template +will be accepted, except for the `global.properties` template which accepts +any key-value pair. + +This operation is performed by calling function `create_file_metadata_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-id-metadata-id-id/). + + + +```python +client.file_metadata.create_file_metadata_by_id( + file.id, + CreateFileMetadataByIdScope.ENTERPRISE, + template_key, + { + "name": "John", + "age": 23, + "birthDate": "2001-01-03T02:20:50.520Z", + "countryCode": "US", + "sports": ["basketball", "tennis"], + }, +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- scope `CreateFileMetadataByIdScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- request_body `Dict` + - Request body of createFileMetadataById method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataFull`. + +Returns the instance of the template that was applied to the file, +including the data that was applied to the template. + +## Update metadata instance on file + +Updates a piece of metadata on a file. + +The metadata instance can only be updated if the template has already been +applied to the file before. When editing metadata, only values that match +the metadata template schema will be accepted. + +The update is applied atomically. If any errors occur during the +application of the operations, the metadata instance will not be changed. + +This operation is performed by calling function `update_file_metadata_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-id-metadata-id-id/). + + + +```python +client.file_metadata.update_file_metadata_by_id( + file.id, + UpdateFileMetadataByIdScope.ENTERPRISE, + template_key, + [ + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, + path="/name", + value="Jack", + ), + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, path="/age", value=24 + ), + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, + path="/birthDate", + value="2000-01-03T02:20:50.520Z", + ), + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, + path="/countryCode", + value="CA", + ), + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, + path="/sports", + value=["football"], + ), + ], +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- scope `UpdateFileMetadataByIdScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- request_body `List[UpdateFileMetadataByIdRequestBody]` + - Request body of updateFileMetadataById method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataFull`. + +Returns the updated metadata template instance, with the +custom template data included. + +## Remove metadata instance from file + +Deletes a piece of file metadata. + +This operation is performed by calling function `delete_file_metadata_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-files-id-metadata-id-id/). + + + +```python +client.file_metadata.delete_file_metadata_by_id( + file.id, DeleteFileMetadataByIdScope.ENTERPRISE, template_key +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- scope `DeleteFileMetadataByIdScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the metadata is +successfully deleted. diff --git a/docs/file_requests.md b/docs/file_requests.md new file mode 100644 index 000000000..3c77125f2 --- /dev/null +++ b/docs/file_requests.md @@ -0,0 +1,156 @@ +# FileRequestsManager + +- [Get file request](#get-file-request) +- [Update file request](#update-file-request) +- [Delete file request](#delete-file-request) +- [Copy file request](#copy-file-request) + +## Get file request + +Retrieves the information about a file request. + +This operation is performed by calling function `get_file_request_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-file-requests-id/). + + + +```python +client.file_requests.get_file_request_by_id(file_request_id) +``` + +### Arguments + +- file_request_id `str` + - The unique identifier that represent a file request. The ID for any file request can be determined by visiting a file request builder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/filerequest/123` the `file_request_id` is `123`. Example: "123" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileRequest`. + +Returns a file request object. + +## Update file request + +Updates a file request. This can be used to activate or +deactivate a file request. + +This operation is performed by calling function `update_file_request_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-file-requests-id/). + + + +```python +client.file_requests.update_file_request_by_id( + copied_file_request.id, title="updated title", description="updated description" +) +``` + +### Arguments + +- file_request_id `str` + - The unique identifier that represent a file request. The ID for any file request can be determined by visiting a file request builder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/filerequest/123` the `file_request_id` is `123`. Example: "123" +- title `Optional[str]` + - An optional new title for the file request. This can be used to change the title of the file request. This will default to the value on the existing file request. +- description `Optional[str]` + - An optional new description for the file request. This can be used to change the description of the file request. This will default to the value on the existing file request. +- status `Optional[UpdateFileRequestByIdStatus]` + - An optional new status of the file request. When the status is set to `inactive`, the file request will no longer accept new submissions, and any visitor to the file request URL will receive a `HTTP 404` status code. This will default to the value on the existing file request. +- is_email_required `Optional[bool]` + - Whether a file request submitter is required to provide their email address. When this setting is set to true, the Box UI will show an email field on the file request form. This will default to the value on the existing file request. +- is_description_required `Optional[bool]` + - Whether a file request submitter is required to provide a description of the files they are submitting. When this setting is set to true, the Box UI will show a description field on the file request form. This will default to the value on the existing file request. +- expires_at `Optional[DateTime]` + - The date after which a file request will no longer accept new submissions. After this date, the `status` will automatically be set to `inactive`. This will default to the value on the existing file request. +- if_match `Optional[str]` + - Ensures this item hasn't recently changed before making changes. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `412 Precondition Failed` if it has changed since. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileRequest`. + +Returns the updated file request object. + +## Delete file request + +Deletes a file request permanently. + +This operation is performed by calling function `delete_file_request_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-file-requests-id/). + + + +```python +client.file_requests.delete_file_request_by_id(updated_file_request.id) +``` + +### Arguments + +- file_request_id `str` + - The unique identifier that represent a file request. The ID for any file request can be determined by visiting a file request builder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/filerequest/123` the `file_request_id` is `123`. Example: "123" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the file request has been successfully +deleted. + +## Copy file request + +Copies an existing file request that is already present on one folder, +and applies it to another folder. + +This operation is performed by calling function `create_file_request_copy`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-file-requests-id-copy/). + + + +```python +client.file_requests.create_file_request_copy( + file_request_id, + CreateFileRequestCopyFolder( + id=file_request.folder.id, type=CreateFileRequestCopyFolderTypeField.FOLDER + ), +) +``` + +### Arguments + +- file_request_id `str` + - The unique identifier that represent a file request. The ID for any file request can be determined by visiting a file request builder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/filerequest/123` the `file_request_id` is `123`. Example: "123" +- folder `CreateFileRequestCopyFolder` + - The folder to associate the new file request to. +- title `Optional[str]` + - An optional new title for the file request. This can be used to change the title of the file request. This will default to the value on the existing file request. +- description `Optional[str]` + - An optional new description for the file request. This can be used to change the description of the file request. This will default to the value on the existing file request. +- status `Optional[CreateFileRequestCopyStatus]` + - An optional new status of the file request. When the status is set to `inactive`, the file request will no longer accept new submissions, and any visitor to the file request URL will receive a `HTTP 404` status code. This will default to the value on the existing file request. +- is_email_required `Optional[bool]` + - Whether a file request submitter is required to provide their email address. When this setting is set to true, the Box UI will show an email field on the file request form. This will default to the value on the existing file request. +- is_description_required `Optional[bool]` + - Whether a file request submitter is required to provide a description of the files they are submitting. When this setting is set to true, the Box UI will show a description field on the file request form. This will default to the value on the existing file request. +- expires_at `Optional[DateTime]` + - The date after which a file request will no longer accept new submissions. After this date, the `status` will automatically be set to `inactive`. This will default to the value on the existing file request. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileRequest`. + +Returns updated file request object. diff --git a/docs/file_version_legal_holds.md b/docs/file_version_legal_holds.md new file mode 100644 index 000000000..7026d2234 --- /dev/null +++ b/docs/file_version_legal_holds.md @@ -0,0 +1,87 @@ +# FileVersionLegalHoldsManager + +- [Get file version legal hold](#get-file-version-legal-hold) +- [List file version legal holds](#list-file-version-legal-holds) + +## Get file version legal hold + +Retrieves information about the legal hold policies +assigned to a file version. + +This operation is performed by calling function `get_file_version_legal_hold_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-file-version-legal-holds-id/). + + + +```python +client.file_version_legal_holds.get_file_version_legal_hold_by_id( + file_version_legal_hold_id +) +``` + +### Arguments + +- file_version_legal_hold_id `str` + - The ID of the file version legal hold. Example: "2348213" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileVersionLegalHold`. + +Returns the legal hold policy assignments for the file version. + +## List file version legal holds + +Get a list of file versions on legal hold for a legal hold +assignment. + +Due to ongoing re-architecture efforts this API might not return all file +versions for this policy ID. + +Instead, this API will only return file versions held in the legacy +architecture. Two new endpoints will available to request any file versions +held in the new architecture. + +For file versions held in the new architecture, the `GET +/legal_hold_policy_assignments/:id/file_versions_on_hold` API can be used to +return all past file versions available for this policy assignment, and the +`GET /legal_hold_policy_assignments/:id/files_on_hold` API can be used to +return any current (latest) versions of a file under legal hold. + +The `GET /legal_hold_policy_assignments?policy_id={id}` API can be used to +find a list of policy assignments for a given policy ID. + +Once the re-architecture is completed this API will be deprecated. + +This operation is performed by calling function `get_file_version_legal_holds`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-file-version-legal-holds/). + + + +```python +client.file_version_legal_holds.get_file_version_legal_holds(policy_id) +``` + +### Arguments + +- policy_id `str` + - The ID of the legal hold policy to get the file version legal holds for. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileVersionLegalHolds`. + +Returns the list of file version legal holds for a specific legal +hold policy. diff --git a/docs/file_version_retentions.md b/docs/file_version_retentions.md new file mode 100644 index 000000000..890fac999 --- /dev/null +++ b/docs/file_version_retentions.md @@ -0,0 +1,86 @@ +# FileVersionRetentionsManager + +- [List file version retentions](#list-file-version-retentions) +- [Get retention on file](#get-retention-on-file) + +## List file version retentions + +Retrieves all file version retentions for the given enterprise. + +**Note**: +File retention API is now **deprecated**. +To get information about files and file versions under retention, +see [files under retention](e://get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](e://get-retention-policy-assignments-id-file-versions-under-retention) endpoints. + +This operation is performed by calling function `get_file_version_retentions`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-file-version-retentions/). + + + +```python +client.file_version_retentions.get_file_version_retentions() +``` + +### Arguments + +- file_id `Optional[str]` + - Filters results by files with this ID. +- file_version_id `Optional[str]` + - Filters results by file versions with this ID. +- policy_id `Optional[str]` + - Filters results by the retention policy with this ID. +- disposition_action `Optional[GetFileVersionRetentionsDispositionAction]` + - Filters results by the retention policy with this disposition action. +- disposition_before `Optional[str]` + - Filters results by files that will have their disposition come into effect before this date. +- disposition_after `Optional[str]` + - Filters results by files that will have their disposition come into effect after this date. +- limit `Optional[int]` + - The maximum number of items to return per page. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileVersionRetentions`. + +Returns a list of all file version retentions for the enterprise. + +## Get retention on file + +Returns information about a file version retention. + +**Note**: +File retention API is now **deprecated**. +To get information about files and file versions under retention, +see [files under retention](e://get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](e://get-retention-policy-assignments-id-file-versions-under-retention) endpoints. + +This operation is performed by calling function `get_file_version_retention_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-file-version-retentions-id/). + + + +```python +client.file_version_retentions.get_file_version_retention_by_id( + file_version_retention.id +) +``` + +### Arguments + +- file_version_retention_id `str` + - The ID of the file version retention. Example: "3424234" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileVersionRetention`. + +Returns a file version retention object. diff --git a/docs/file_versions.md b/docs/file_versions.md new file mode 100644 index 000000000..dbd919f82 --- /dev/null +++ b/docs/file_versions.md @@ -0,0 +1,205 @@ +# FileVersionsManager + +- [List all file versions](#list-all-file-versions) +- [Get file version](#get-file-version) +- [Remove file version](#remove-file-version) +- [Restore file version](#restore-file-version) +- [Promote file version](#promote-file-version) + +## List all file versions + +Retrieve a list of the past versions for a file. + +Versions are only tracked by Box users with premium accounts. To fetch the ID +of the current version of a file, use the `GET /file/:id` API. + +This operation is performed by calling function `get_file_versions`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-versions/). + + + +```python +client.file_versions.get_file_versions(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- limit `Optional[int]` + - The maximum number of items to return per page. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileVersions`. + +Returns an array of past versions for this file. + +## Get file version + +Retrieve a specific version of a file. + +Versions are only tracked for Box users with premium accounts. + +This operation is performed by calling function `get_file_version_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-versions-id/). + + + +```python +client.file_versions.get_file_version_by_id(file.id, file_versions.entries[0].id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- file_version_id `str` + - The ID of the file version. Example: "1234" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileVersionFull`. + +Returns a specific version of a file. + +Not all available fields are returned by default. Use the +[fields](#param-fields) query parameter to explicitly request +any specific fields. + +## Remove file version + +Move a file version to the trash. + +Versions are only tracked for Box users with premium accounts. + +This operation is performed by calling function `delete_file_version_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-files-id-versions-id/). + + + +```python +client.file_versions.delete_file_version_by_id(file.id, file_version.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- file_version_id `str` + - The ID of the file version. Example: "1234" +- if_match `Optional[str]` + - Ensures this item hasn't recently changed before making changes. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `412 Precondition Failed` if it has changed since. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the file has been successfully +deleted. + +## Restore file version + +Restores a specific version of a file after it was deleted. +Don't use this endpoint to restore Box Notes, +as it works with file formats such as PDF, DOC, +PPTX or similar. + +This operation is performed by calling function `update_file_version_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-id-versions-id/). + + + +```python +client.file_versions.update_file_version_by_id( + file.id, file_version.id, trashed_at=create_null() +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- file_version_id `str` + - The ID of the file version. Example: "1234" +- trashed_at `Optional[str]` + - Set this to `null` to clear the date and restore the file. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileVersionFull`. + +Returns a restored file version object. + +## Promote file version + +Promote a specific version of a file. + +If previous versions exist, this method can be used to +promote one of the older versions to the top of the version history. + +This creates a new copy of the old version and puts it at the +top of the versions history. The file will have the exact same contents +as the older version, with the the same hash digest, `etag`, and +name as the original. + +Other properties such as comments do not get updated to their +former values. + +Don't use this endpoint to restore Box Notes, +as it works with file formats such as PDF, DOC, +PPTX or similar. + +This operation is performed by calling function `promote_file_version`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-id-versions-current/). + + + +```python +client.file_versions.promote_file_version( + file.id, id=file_versions.entries[0].id, type=PromoteFileVersionType.FILE_VERSION +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- id `Optional[str]` + - The file version ID. +- type `Optional[PromoteFileVersionType]` + - The type to promote. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileVersionFull`. + +Returns a newly created file version object. diff --git a/docs/file_watermarks.md b/docs/file_watermarks.md new file mode 100644 index 000000000..3ef0f6d54 --- /dev/null +++ b/docs/file_watermarks.md @@ -0,0 +1,99 @@ +# FileWatermarksManager + +- [Get watermark on file](#get-watermark-on-file) +- [Apply watermark to file](#apply-watermark-to-file) +- [Remove watermark from file](#remove-watermark-from-file) + +## Get watermark on file + +Retrieve the watermark for a file. + +This operation is performed by calling function `get_file_watermark`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-watermark/). + + + +```python +client.file_watermarks.get_file_watermark(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Watermark`. + +Returns an object containing information about the +watermark associated for to this file. + +## Apply watermark to file + +Applies or update a watermark on a file. + +This operation is performed by calling function `update_file_watermark`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-id-watermark/). + + + +```python +client.file_watermarks.update_file_watermark( + file.id, + UpdateFileWatermarkWatermark( + imprint=UpdateFileWatermarkWatermarkImprintField.DEFAULT + ), +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- watermark `UpdateFileWatermarkWatermark` + - The watermark to imprint on the file. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Watermark`. + +Returns an updated watermark if a watermark already +existed on this file.Returns a new watermark if no watermark existed on +this file yet. + +## Remove watermark from file + +Removes the watermark from a file. + +This operation is performed by calling function `delete_file_watermark`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-files-id-watermark/). + + + +```python +client.file_watermarks.delete_file_watermark(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Removes the watermark and returns an empty response. diff --git a/docs/files.md b/docs/files.md new file mode 100644 index 000000000..9ce0de627 --- /dev/null +++ b/docs/files.md @@ -0,0 +1,283 @@ +# FilesManager + +- [Get file information](#get-file-information) +- [Update file](#update-file) +- [Delete file](#delete-file) +- [Copy file](#copy-file) +- [Get file thumbnail URL](#get-file-thumbnail-url) +- [Get file thumbnail](#get-file-thumbnail) + +## Get file information + +Retrieves the details about a file. + +This operation is performed by calling function `get_file_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id/). + + + +```python +client.files.get_file_by_id( + uploaded_file.id, fields=["is_externally_owned", "has_collaborations"] +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. Additionally this field can be used to query any metadata applied to the file by specifying the `metadata` field as well as the scope and key of the template to retrieve, for example `?fields=metadata.enterprise_12345.contractTemplate`. +- if_none_match `Optional[str]` + - Ensures an item is only returned if it has changed. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `304 Not Modified` if the item has not changed since. +- boxapi `Optional[str]` + - The URL, and optional password, for the shared link of this item. This header can be used to access items that have not been explicitly shared with a user. Use the format `shared_link=[link]` or if a password is required then use `shared_link=[link]&shared_link_password=[password]`. This header can be used on the file or folder shared, as well as on any files or folders nested within the item. +- x_rep_hints `Optional[str]` + - A header required to request specific `representations` of a file. Use this in combination with the `fields` query parameter to request a specific file representation. The general format for these representations is `X-Rep-Hints: [...]` where `[...]` is one or many hints in the format `[fileType?query]`. For example, to request a `png` representation in `32x32` as well as `64x64` pixel dimensions provide the following hints. `x-rep-hints: [jpg?dimensions=32x32][jpg?dimensions=64x64]` Additionally, a `text` representation is available for all document file types in Box using the `[extracted_text]` representation. `x-rep-hints: [extracted_text]`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileFull`. + +Returns a file object. + +Not all available fields are returned by default. Use the +[fields](#param-fields) query parameter to explicitly request +any specific fields. + +## Update file + +Updates a file. This can be used to rename or move a file, +create a shared link, or lock a file. + +This operation is performed by calling function `update_file_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-id/). + + + +```python +client.files.update_file_by_id( + file_to_update.id, name=updated_name, description="Updated description" +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- name `Optional[str]` + - An optional different name for the file. This can be used to rename the file. File names must be unique within their parent folder. The name check is case-insensitive, so a file named `New File` cannot be created in a parent folder that already contains a folder named `new file`. +- description `Optional[str]` + - The description for a file. This can be seen in the right-hand sidebar panel when viewing a file in the Box web app. Additionally, this index is used in the search index of the file, allowing users to find the file by the content in the description. +- parent `Optional[UpdateFileByIdParent]` +- shared_link `Optional[UpdateFileByIdSharedLink]` +- lock `Optional[UpdateFileByIdLock]` + - Defines a lock on an item. This prevents the item from being moved, renamed, or otherwise changed by anyone other than the user who created the lock. Set this to `null` to remove the lock. +- disposition_at `Optional[DateTime]` + - The retention expiration timestamp for the given file. This date cannot be shortened once set on a file. +- permissions `Optional[UpdateFileByIdPermissions]` + - Defines who can download a file. +- collections `Optional[List[UpdateFileByIdCollections]]` + - An array of collections to make this file a member of. Currently we only support the `favorites` collection. To get the ID for a collection, use the [List all collections][1] endpoint. Passing an empty array `[]` or `null` will remove the file from all collections. [1]: e://get-collections +- tags `Optional[List[str]]` + - The tags for this item. These tags are shown in the Box web app and mobile apps next to an item. To add or remove a tag, retrieve the item's current tags, modify them, and then update this field. There is a limit of 100 tags per item, and 10,000 unique tags per enterprise. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- if_match `Optional[str]` + - Ensures this item hasn't recently changed before making changes. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `412 Precondition Failed` if it has changed since. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileFull`. + +Returns a file object. + +Not all available fields are returned by default. Use the +[fields](#param-fields) query parameter to explicitly request +any specific fields. + +## Delete file + +Deletes a file, either permanently or by moving it to +the trash. + +The the enterprise settings determine whether the item will +be permanently deleted from Box or moved to the trash. + +This operation is performed by calling function `delete_file_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-files-id/). + + + +```python +client.files.delete_file_by_id(thumbnail_file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- if_match `Optional[str]` + - Ensures this item hasn't recently changed before making changes. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `412 Precondition Failed` if it has changed since. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the file has been successfully +deleted. + +## Copy file + +Creates a copy of a file. + +This operation is performed by calling function `copy_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-id-copy/). + + + +```python +client.files.copy_file(file_origin.id, CopyFileParent(id="0"), name=copied_file_name) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- name `Optional[str]` + - An optional new name for the copied file. There are some restrictions to the file name. Names containing non-printable ASCII characters, forward and backward slashes (`/`, `\`), and protected names like `.` and `..` are automatically sanitized by removing the non-allowed characters. +- version `Optional[str]` + - An optional ID of the specific file version to copy. +- parent `CopyFileParent` + - The destination folder to copy the file to. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileFull`. + +Returns a new file object representing the copied file. + +Not all available fields are returned by default. Use the +[fields](#param-fields) query parameter to explicitly request +any specific fields. + +## Get file thumbnail URL + +Get the download URL without downloading the content. + +This operation is performed by calling function `get_file_thumbnail_url`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-thumbnail-id/). + + + +```python +client.files.get_file_thumbnail_url(thumbnail_file.id, GetFileThumbnailUrlExtension.PNG) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extension `GetFileThumbnailUrlExtension` + - The file format for the thumbnail. Example: "png" +- min_height `Optional[int]` + - The minimum height of the thumbnail. +- min_width `Optional[int]` + - The minimum width of the thumbnail. +- max_height `Optional[int]` + - The maximum height of the thumbnail. +- max_width `Optional[int]` + - The maximum width of the thumbnail. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `str`. + +When a thumbnail can be created the thumbnail data will be +returned in the body of the response.Sometimes generating a thumbnail can take a few seconds. In these +situations the API returns a `Location`-header pointing to a +placeholder graphic for this file type. + +The placeholder graphic can be used in a user interface until the +thumbnail generation has completed. The `Retry-After`-header indicates +when to the thumbnail will be ready. At that time, retry this endpoint +to retrieve the thumbnail. + +## Get file thumbnail + +Retrieves a thumbnail, or smaller image representation, of a file. + +Sizes of `32x32`,`64x64`, `128x128`, and `256x256` can be returned in +the `.png` format and sizes of `32x32`, `160x160`, and `320x320` +can be returned in the `.jpg` format. + +Thumbnails can be generated for the image and video file formats listed +[found on our community site][1]. + +[1]: https://community.box.com/t5/Migrating-and-Previewing-Content/File-Types-and-Fonts-Supported-in-Box-Content-Preview/ta-p/327 + +This operation is performed by calling function `get_file_thumbnail_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-thumbnail-id/). + + + +```python +client.files.get_file_thumbnail_by_id( + thumbnail_file.id, GetFileThumbnailByIdExtension.PNG +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extension `GetFileThumbnailByIdExtension` + - The file format for the thumbnail. Example: "png" +- min_height `Optional[int]` + - The minimum height of the thumbnail. +- min_width `Optional[int]` + - The minimum width of the thumbnail. +- max_height `Optional[int]` + - The maximum height of the thumbnail. +- max_width `Optional[int]` + - The maximum width of the thumbnail. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Optional[ByteStream]`. + +When a thumbnail can be created the thumbnail data will be +returned in the body of the response.Sometimes generating a thumbnail can take a few seconds. In these +situations the API returns a `Location`-header pointing to a +placeholder graphic for this file type. + +The placeholder graphic can be used in a user interface until the +thumbnail generation has completed. The `Retry-After`-header indicates +when to the thumbnail will be ready. At that time, retry this endpoint +to retrieve the thumbnail. diff --git a/docs/folder_classifications.md b/docs/folder_classifications.md new file mode 100644 index 000000000..a10765c44 --- /dev/null +++ b/docs/folder_classifications.md @@ -0,0 +1,150 @@ +# FolderClassificationsManager + +- [Get classification on folder](#get-classification-on-folder) +- [Add classification to folder](#add-classification-to-folder) +- [Update classification on folder](#update-classification-on-folder) +- [Remove classification from folder](#remove-classification-from-folder) + +## Get classification on folder + +Retrieves the classification metadata instance that +has been applied to a folder. + +This API can also be called by including the enterprise ID in the +URL explicitly, for example +`/folders/:id/enterprise_12345/securityClassification-6VMVochwUWo`. + +This operation is performed by calling function `get_classification_on_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-id-metadata-enterprise-securityClassification-6VMVochwUWo/). + + + +```python +client.folder_classifications.get_classification_on_folder(folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Classification`. + +Returns an instance of the `securityClassification` metadata +template, which contains a `Box__Security__Classification__Key` +field that lists all the classifications available to this +enterprise. + +## Add classification to folder + +Adds a classification to a folder by specifying the label of the +classification to add. + +This API can also be called by including the enterprise ID in the +URL explicitly, for example +`/folders/:id/enterprise_12345/securityClassification-6VMVochwUWo`. + +This operation is performed by calling function `add_classification_to_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-folders-id-metadata-enterprise-securityClassification-6VMVochwUWo/). + + + +```python +client.folder_classifications.add_classification_to_folder( + folder.id, box_security_classification_key=classification.key +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- box_security_classification_key `Optional[str]` + - The name of the classification to apply to this folder. To list the available classifications in an enterprise, use the classification API to retrieve the [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) which lists all available classification keys. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Classification`. + +Returns the classification template instance +that was applied to the folder. + +## Update classification on folder + +Updates a classification on a folder. + +The classification can only be updated if a classification has already been +applied to the folder before. When editing classifications, only values are +defined for the enterprise will be accepted. + +This operation is performed by calling function `update_classification_on_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-folders-id-metadata-enterprise-securityClassification-6VMVochwUWo/). + + + +```python +client.folder_classifications.update_classification_on_folder( + folder.id, + [UpdateClassificationOnFolderRequestBody(value=second_classification.key)], +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- request_body `List[UpdateClassificationOnFolderRequestBody]` + - Request body of updateClassificationOnFolder method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Classification`. + +Returns the updated classification metadata template instance. + +## Remove classification from folder + +Removes any classifications from a folder. + +This API can also be called by including the enterprise ID in the +URL explicitly, for example +`/folders/:id/enterprise_12345/securityClassification-6VMVochwUWo`. + +This operation is performed by calling function `delete_classification_from_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-folders-id-metadata-enterprise-securityClassification-6VMVochwUWo/). + + + +```python +client.folder_classifications.delete_classification_from_folder(folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the classification is +successfully deleted. diff --git a/docs/folder_locks.md b/docs/folder_locks.md new file mode 100644 index 000000000..85fa46614 --- /dev/null +++ b/docs/folder_locks.md @@ -0,0 +1,106 @@ +# FolderLocksManager + +- [List folder locks](#list-folder-locks) +- [Create folder lock](#create-folder-lock) +- [Delete folder lock](#delete-folder-lock) + +## List folder locks + +Retrieves folder lock details for a given folder. + +You must be authenticated as the owner or co-owner of the folder to +use this endpoint. + +This operation is performed by calling function `get_folder_locks`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folder-locks/). + + + +```python +client.folder_locks.get_folder_locks(folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderLocks`. + +Returns details for all folder locks applied to the folder, including the +lock type and user that applied the lock. + +## Create folder lock + +Creates a folder lock on a folder, preventing it from being moved and/or +deleted. + +You must be authenticated as the owner or co-owner of the folder to +use this endpoint. + +This operation is performed by calling function `create_folder_lock`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-folder-locks/). + + + +```python +client.folder_locks.create_folder_lock( + CreateFolderLockFolder(id=folder.id, type="folder"), + locked_operations=CreateFolderLockLockedOperations(move=True, delete=True), +) +``` + +### Arguments + +- locked_operations `Optional[CreateFolderLockLockedOperations]` + - The operations to lock for the folder. If `locked_operations` is included in the request, both `move` and `delete` must also be included and both set to `true`. +- folder `CreateFolderLockFolder` + - The folder to apply the lock to. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderLock`. + +Returns the instance of the folder lock that was applied to the folder, +including the user that applied the lock and the operations set. + +## Delete folder lock + +Deletes a folder lock on a given folder. + +You must be authenticated as the owner or co-owner of the folder to +use this endpoint. + +This operation is performed by calling function `delete_folder_lock_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-folder-locks-id/). + + + +```python +client.folder_locks.delete_folder_lock_by_id(folder_lock.id) +``` + +### Arguments + +- folder_lock_id `str` + - The ID of the folder lock. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the folder lock is successfully deleted. diff --git a/docs/folder_metadata.md b/docs/folder_metadata.md new file mode 100644 index 000000000..0f6b729cb --- /dev/null +++ b/docs/folder_metadata.md @@ -0,0 +1,236 @@ +# FolderMetadataManager + +- [List metadata instances on folder](#list-metadata-instances-on-folder) +- [Get metadata instance on folder](#get-metadata-instance-on-folder) +- [Create metadata instance on folder](#create-metadata-instance-on-folder) +- [Update metadata instance on folder](#update-metadata-instance-on-folder) +- [Remove metadata instance from folder](#remove-metadata-instance-from-folder) + +## List metadata instances on folder + +Retrieves all metadata for a given folder. This can not be used on the root +folder with ID `0`. + +This operation is performed by calling function `get_folder_metadata`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-id-metadata/). + + + +```python +client.folder_metadata.get_folder_metadata(folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Metadatas`. + +Returns all the metadata associated with a folder. + +This API does not support pagination and will therefore always return +all of the metadata associated to the folder. + +## Get metadata instance on folder + +Retrieves the instance of a metadata template that has been applied to a +folder. This can not be used on the root folder with ID `0`. + +This operation is performed by calling function `get_folder_metadata_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-id-metadata-id-id/). + + + +```python +client.folder_metadata.get_folder_metadata_by_id( + folder.id, GetFolderMetadataByIdScope.GLOBAL, "properties" +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- scope `GetFolderMetadataByIdScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataFull`. + +An instance of the metadata template that includes +additional "key:value" pairs defined by the user or +an application. + +## Create metadata instance on folder + +Applies an instance of a metadata template to a folder. + +In most cases only values that are present in the metadata template +will be accepted, except for the `global.properties` template which accepts +any key-value pair. + +To display the metadata template in the Box web app the enterprise needs to be +configured to enable **Cascading Folder Level Metadata** for the user in the +admin console. + +This operation is performed by calling function `create_folder_metadata_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-folders-id-metadata-id-id/). + + + +```python +client.folder_metadata.create_folder_metadata_by_id( + folder.id, + CreateFolderMetadataByIdScope.ENTERPRISE, + template_key, + { + "name": "John", + "age": 23, + "birthDate": "2001-01-03T02:20:50.520Z", + "countryCode": "US", + "sports": ["basketball", "tennis"], + }, +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- scope `CreateFolderMetadataByIdScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- request_body `Dict` + - Request body of createFolderMetadataById method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataFull`. + +Returns the instance of the template that was applied to the folder, +including the data that was applied to the template. + +## Update metadata instance on folder + +Updates a piece of metadata on a folder. + +The metadata instance can only be updated if the template has already been +applied to the folder before. When editing metadata, only values that match +the metadata template schema will be accepted. + +The update is applied atomically. If any errors occur during the +application of the operations, the metadata instance will not be changed. + +This operation is performed by calling function `update_folder_metadata_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-folders-id-metadata-id-id/). + + + +```python +client.folder_metadata.update_folder_metadata_by_id( + folder.id, + UpdateFolderMetadataByIdScope.ENTERPRISE, + template_key, + [ + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, + path="/name", + value="Jack", + ), + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, path="/age", value=24 + ), + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, + path="/birthDate", + value="2000-01-03T02:20:50.520Z", + ), + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, + path="/countryCode", + value="CA", + ), + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, + path="/sports", + value=["football"], + ), + ], +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- scope `UpdateFolderMetadataByIdScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- request_body `List[UpdateFolderMetadataByIdRequestBody]` + - Request body of updateFolderMetadataById method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataFull`. + +Returns the updated metadata template instance, with the +custom template data included. + +## Remove metadata instance from folder + +Deletes a piece of folder metadata. + +This operation is performed by calling function `delete_folder_metadata_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-folders-id-metadata-id-id/). + + + +```python +client.folder_metadata.delete_folder_metadata_by_id( + folder.id, DeleteFolderMetadataByIdScope.ENTERPRISE, template_key +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- scope `DeleteFolderMetadataByIdScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the metadata is +successfully deleted. diff --git a/docs/folder_watermarks.md b/docs/folder_watermarks.md new file mode 100644 index 000000000..dc23285e4 --- /dev/null +++ b/docs/folder_watermarks.md @@ -0,0 +1,100 @@ +# FolderWatermarksManager + +- [Get watermark for folder](#get-watermark-for-folder) +- [Apply watermark to folder](#apply-watermark-to-folder) +- [Remove watermark from folder](#remove-watermark-from-folder) + +## Get watermark for folder + +Retrieve the watermark for a folder. + +This operation is performed by calling function `get_folder_watermark`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-id-watermark/). + + + +```python +client.folder_watermarks.get_folder_watermark(folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Watermark`. + +Returns an object containing information about the +watermark associated for to this folder. + +## Apply watermark to folder + +Applies or update a watermark on a folder. + +This operation is performed by calling function `update_folder_watermark`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-folders-id-watermark/). + + + +```python +client.folder_watermarks.update_folder_watermark( + folder.id, + UpdateFolderWatermarkWatermark( + imprint=UpdateFolderWatermarkWatermarkImprintField.DEFAULT + ), +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- watermark `UpdateFolderWatermarkWatermark` + - The watermark to imprint on the folder. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Watermark`. + +Returns an updated watermark if a watermark already +existed on this folder.Returns a new watermark if no watermark existed on +this folder yet. + +## Remove watermark from folder + +Removes the watermark from a folder. + +This operation is performed by calling function `delete_folder_watermark`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-folders-id-watermark/). + + + +```python +client.folder_watermarks.delete_folder_watermark(folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +An empty response will be returned when the watermark +was successfully deleted. diff --git a/docs/folders.md b/docs/folders.md new file mode 100644 index 000000000..fa2d33321 --- /dev/null +++ b/docs/folders.md @@ -0,0 +1,295 @@ +# FoldersManager + +- [Get folder information](#get-folder-information) +- [Update folder](#update-folder) +- [Delete folder](#delete-folder) +- [List items in folder](#list-items-in-folder) +- [Create folder](#create-folder) +- [Copy folder](#copy-folder) + +## Get folder information + +Retrieves details for a folder, including the first 100 entries +in the folder. + +Passing `sort`, `direction`, `offset`, and `limit` +parameters in query allows you to manage the +list of returned +[folder items](r://folder--full#param-item-collection). + +To fetch more items within the folder, use the +[Get items in a folder](e://get-folders-id-items) endpoint. + +This operation is performed by calling function `get_folder_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-id/). + + + +```python +client.folders.get_folder_by_id("0") +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. Additionally this field can be used to query any metadata applied to the file by specifying the `metadata` field as well as the scope and key of the template to retrieve, for example `?fields=metadata.enterprise_12345.contractTemplate`. +- sort `Optional[GetFolderByIdSort]` + - Defines the **second** attribute by which items are sorted. The folder type affects the way the items are sorted: _ **Standard folder**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. _ **Root folder**: This parameter is not supported for marker-based pagination on the root folder (the folder with an `id` of `0`). \* **Shared folder with parent path to the associated folder visible to the collaborator**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. +- direction `Optional[GetFolderByIdDirection]` + - The direction to sort results in. This can be either in alphabetical ascending (`ASC`) or descending (`DESC`) order. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- limit `Optional[int]` + - The maximum number of items to return per page. +- if_none_match `Optional[str]` + - Ensures an item is only returned if it has changed. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `304 Not Modified` if the item has not changed since. +- boxapi `Optional[str]` + - The URL, and optional password, for the shared link of this item. This header can be used to access items that have not been explicitly shared with a user. Use the format `shared_link=[link]` or if a password is required then use `shared_link=[link]&shared_link_password=[password]`. This header can be used on the file or folder shared, as well as on any files or folders nested within the item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderFull`. + +Returns a folder, including the first 100 entries in the folder. +If you used query parameters like +`sort`, `direction`, `offset`, or `limit` +the _folder items list_ will be affected accordingly. + +To fetch more items within the folder, use the +[Get items in a folder](e://get-folders-id-items)) endpoint. + +Not all available fields are returned by default. Use the +[fields](#param-fields) query parameter to explicitly request +any specific fields. + +## Update folder + +Updates a folder. This can be also be used to move the folder, +create shared links, update collaborations, and more. + +This operation is performed by calling function `update_folder_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-folders-id/). + + + +```python +client.folders.update_folder_by_id( + folder_to_update.id, name=updated_name, description="Updated description" +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- name `Optional[str]` + - The optional new name for this folder. The following restrictions to folder names apply: names containing non-printable ASCII characters, forward and backward slashes (`/`, `\`), names with trailing spaces, and names `.` and `..` are not allowed. Folder names must be unique within their parent folder. The name check is case-insensitive, so a folder named `New Folder` cannot be created in a parent folder that already contains a folder named `new folder`. +- description `Optional[str]` + - The optional description of this folder. +- sync_state `Optional[UpdateFolderByIdSyncState]` + - Specifies whether a folder should be synced to a user's device or not. This is used by Box Sync (discontinued) and is not used by Box Drive. +- can_non_owners_invite `Optional[bool]` + - Specifies if users who are not the owner of the folder can invite new collaborators to the folder. +- parent `Optional[UpdateFolderByIdParent]` +- shared_link `Optional[UpdateFolderByIdSharedLink]` +- folder_upload_email `Optional[UpdateFolderByIdFolderUploadEmail]` +- tags `Optional[List[str]]` + - The tags for this item. These tags are shown in the Box web app and mobile apps next to an item. To add or remove a tag, retrieve the item's current tags, modify them, and then update this field. There is a limit of 100 tags per item, and 10,000 unique tags per enterprise. +- is_collaboration_restricted_to_enterprise `Optional[bool]` + - Specifies if new invites to this folder are restricted to users within the enterprise. This does not affect existing collaborations. +- collections `Optional[List[UpdateFolderByIdCollections]]` + - An array of collections to make this folder a member of. Currently we only support the `favorites` collection. To get the ID for a collection, use the [List all collections][1] endpoint. Passing an empty array `[]` or `null` will remove the folder from all collections. [1]: e://get-collections +- can_non_owners_view_collaborators `Optional[bool]` + - Restricts collaborators who are not the owner of this folder from viewing other collaborations on this folder. It also restricts non-owners from inviting new collaborators. When setting this field to `false`, it is required to also set `can_non_owners_invite_collaborators` to `false` if it has not already been set. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- if_match `Optional[str]` + - Ensures this item hasn't recently changed before making changes. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `412 Precondition Failed` if it has changed since. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderFull`. + +Returns a folder object for the updated folder + +Not all available fields are returned by default. Use the +[fields](#param-fields) query parameter to explicitly request +any specific fields. + +If the user is moving folders with a large number of items in all of +their descendants, the call will be run asynchronously. If the +operation is not completed within 10 minutes, the user will receive +a 200 OK response, and the operation will continue running. + +## Delete folder + +Deletes a folder, either permanently or by moving it to +the trash. + +This operation is performed by calling function `delete_folder_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-folders-id/). + + + +```python +client.folders.delete_folder_by_id(new_folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- recursive `Optional[bool]` + - Delete a folder that is not empty by recursively deleting the folder and all of its content. +- if_match `Optional[str]` + - Ensures this item hasn't recently changed before making changes. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `412 Precondition Failed` if it has changed since. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the folder is successfully deleted +or moved to the trash. + +## List items in folder + +Retrieves a page of items in a folder. These items can be files, +folders, and web links. + +To request more information about the folder itself, like its size, +use the [Get a folder](#get-folders-id) endpoint instead. + +This operation is performed by calling function `get_folder_items`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-id-items/). + + + +```python +client.folders.get_folder_items(folder_origin.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. Additionally this field can be used to query any metadata applied to the file by specifying the `metadata` field as well as the scope and key of the template to retrieve, for example `?fields=metadata.enterprise_12345.contractTemplate`. +- usemarker `Optional[bool]` + - Specifies whether to use marker-based pagination instead of offset-based pagination. Only one pagination method can be used at a time. By setting this value to true, the API will return a `marker` field that can be passed as a parameter to this endpoint to get the next page of the response. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- limit `Optional[int]` + - The maximum number of items to return per page. +- sort `Optional[GetFolderItemsSort]` + - Defines the **second** attribute by which items are sorted. The folder type affects the way the items are sorted: _ **Standard folder**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. _ **Root folder**: This parameter is not supported for marker-based pagination on the root folder (the folder with an `id` of `0`). \* **Shared folder with parent path to the associated folder visible to the collaborator**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. +- direction `Optional[GetFolderItemsDirection]` + - The direction to sort results in. This can be either in alphabetical ascending (`ASC`) or descending (`DESC`) order. +- boxapi `Optional[str]` + - The URL, and optional password, for the shared link of this item. This header can be used to access items that have not been explicitly shared with a user. Use the format `shared_link=[link]` or if a password is required then use `shared_link=[link]&shared_link_password=[password]`. This header can be used on the file or folder shared, as well as on any files or folders nested within the item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Items`. + +Returns a collection of files, folders, and web links contained in a folder. + +## Create folder + +Creates a new empty folder within the specified parent folder. + +This operation is performed by calling function `create_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-folders/). + + + +```python +client.folders.create_folder(new_folder_name, CreateFolderParent(id="0")) +``` + +### Arguments + +- name `str` + - The name for the new folder. The following restrictions to folder names apply: names containing non-printable ASCII characters, forward and backward slashes (`/`, `\`), names with trailing spaces, and names `.` and `..` are not allowed. Folder names must be unique within their parent folder. The name check is case-insensitive, so a folder named `New Folder` cannot be created in a parent folder that already contains a folder named `new folder`. +- parent `CreateFolderParent` + - The parent folder to create the new folder within. +- folder_upload_email `Optional[CreateFolderFolderUploadEmail]` +- sync_state `Optional[CreateFolderSyncState]` + - Specifies whether a folder should be synced to a user's device or not. This is used by Box Sync (discontinued) and is not used by Box Drive. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderFull`. + +Returns a folder object. + +Not all available fields are returned by default. Use the +[fields](#param-fields) query parameter to explicitly request +any specific fields. + +## Copy folder + +Creates a copy of a folder within a destination folder. + +The original folder will not be changed. + +This operation is performed by calling function `copy_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-folders-id-copy/). + + + +```python +client.folders.copy_folder( + folder_origin.id, CopyFolderParent(id="0"), name=copied_folder_name +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier of the folder to copy. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder with the ID `0` can not be copied. Example: "0" +- name `Optional[str]` + - An optional new name for the copied folder. There are some restrictions to the file name. Names containing non-printable ASCII characters, forward and backward slashes (`/`, `\`), as well as names with trailing spaces are prohibited. Additionally, the names `.` and `..` are not allowed either. +- parent `CopyFolderParent` + - The destination folder to copy the folder to. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderFull`. + +Returns a new folder object representing the copied folder. + +Not all available fields are returned by default. Use the +[fields](#param-fields) query parameter to explicitly request +any specific fields. diff --git a/docs/groups.md b/docs/groups.md new file mode 100644 index 000000000..6f1143c33 --- /dev/null +++ b/docs/groups.md @@ -0,0 +1,192 @@ +# GroupsManager + +- [List groups for enterprise](#list-groups-for-enterprise) +- [Create group](#create-group) +- [Get group](#get-group) +- [Update group](#update-group) +- [Remove group](#remove-group) + +## List groups for enterprise + +Retrieves all of the groups for a given enterprise. The user +must have admin permissions to inspect enterprise's groups. + +This operation is performed by calling function `get_groups`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-groups/). + + + +```python +client.groups.get_groups() +``` + +### Arguments + +- filter_term `Optional[str]` + - Limits the results to only groups whose `name` starts with the search term. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- limit `Optional[int]` + - The maximum number of items to return per page. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Groups`. + +Returns a collection of group objects. If there are no groups, an +empty collection will be returned. + +## Create group + +Creates a new group of users in an enterprise. Only users with admin +permissions can create new groups. + +This operation is performed by calling function `create_group`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-groups/). + + + +```python +client.groups.create_group(group_name, description=group_description) +``` + +### Arguments + +- name `str` + - The name of the new group to be created. This name must be unique within the enterprise. +- provenance `Optional[str]` + - Keeps track of which external source this group is coming, for example `Active Directory`, or `Okta`. Setting this will also prevent Box admins from editing the group name and its members directly via the Box web application. This is desirable for one-way syncing of groups. +- external_sync_identifier `Optional[str]` + - An arbitrary identifier that can be used by external group sync tools to link this Box Group to an external group. Example values of this field could be an **Active Directory Object ID** or a **Google Group ID**. We recommend you use of this field in order to avoid issues when group names are updated in either Box or external systems. +- description `Optional[str]` + - A human readable description of the group. +- invitability_level `Optional[CreateGroupInvitabilityLevel]` + - Specifies who can invite the group to collaborate on folders. When set to `admins_only` the enterprise admin, co-admins, and the group's admin can invite the group. When set to `admins_and_members` all the admins listed above and group members can invite the group. When set to `all_managed_users` all managed users in the enterprise can invite the group. +- member_viewability_level `Optional[CreateGroupMemberViewabilityLevel]` + - Specifies who can see the members of the group. _ `admins_only` - the enterprise admin, co-admins, group's group admin. _ `admins_and_members` - all admins and group members. \* `all_managed_users` - all managed users in the enterprise. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `GroupFull`. + +Returns the new group object. + +## Get group + +Retrieves information about a group. Only members of this +group or users with admin-level permissions will be able to +use this API. + +This operation is performed by calling function `get_group_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-groups-id/). + + + +```python +client.groups.get_group_by_id( + group.id, fields=["id", "name", "description", "group_type"] +) +``` + +### Arguments + +- group_id `str` + - The ID of the group. Example: "57645" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `GroupFull`. + +Returns the group object. + +## Update group + +Updates a specific group. Only admins of this +group or users with admin-level permissions will be able to +use this API. + +This operation is performed by calling function `update_group_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-groups-id/). + + + +```python +client.groups.update_group_by_id(group.id, name=updated_group_name) +``` + +### Arguments + +- group_id `str` + - The ID of the group. Example: "57645" +- name `Optional[str]` + - The name of the new group to be created. Must be unique within the enterprise. +- provenance `Optional[str]` + - Keeps track of which external source this group is coming, for example `Active Directory`, or `Okta`. Setting this will also prevent Box admins from editing the group name and its members directly via the Box web application. This is desirable for one-way syncing of groups. +- external_sync_identifier `Optional[str]` + - An arbitrary identifier that can be used by external group sync tools to link this Box Group to an external group. Example values of this field could be an **Active Directory Object ID** or a **Google Group ID**. We recommend you use of this field in order to avoid issues when group names are updated in either Box or external systems. +- description `Optional[str]` + - A human readable description of the group. +- invitability_level `Optional[UpdateGroupByIdInvitabilityLevel]` + - Specifies who can invite the group to collaborate on folders. When set to `admins_only` the enterprise admin, co-admins, and the group's admin can invite the group. When set to `admins_and_members` all the admins listed above and group members can invite the group. When set to `all_managed_users` all managed users in the enterprise can invite the group. +- member_viewability_level `Optional[UpdateGroupByIdMemberViewabilityLevel]` + - Specifies who can see the members of the group. _ `admins_only` - the enterprise admin, co-admins, group's group admin. _ `admins_and_members` - all admins and group members. \* `all_managed_users` - all managed users in the enterprise. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `GroupFull`. + +Returns the updated group object. + +## Remove group + +Permanently deletes a group. Only users with +admin-level permissions will be able to use this API. + +This operation is performed by calling function `delete_group_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-groups-id/). + + + +```python +client.groups.delete_group_by_id(group.id) +``` + +### Arguments + +- group_id `str` + - The ID of the group. Example: "57645" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the group was +successfully deleted. diff --git a/docs/hub_collaborations.md b/docs/hub_collaborations.md new file mode 100644 index 000000000..5859f6a7e --- /dev/null +++ b/docs/hub_collaborations.md @@ -0,0 +1,179 @@ +# HubCollaborationsManager + +- [Get hub collaborations](#get-hub-collaborations) +- [Create hub collaboration](#create-hub-collaboration) +- [Get hub collaboration by collaboration ID](#get-hub-collaboration-by-collaboration-id) +- [Update hub collaboration](#update-hub-collaboration) +- [Remove hub collaboration](#remove-hub-collaboration) + +## Get hub collaborations + +Retrieves all collaborations for a hub. + +This operation is performed by calling function `get_hub_collaborations_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-hub-collaborations/). + + + +```python +client.hub_collaborations.get_hub_collaborations_v2025_r0(hub.id) +``` + +### Arguments + +- hub_id `str` + - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubCollaborationsV2025R0`. + +Retrieves the collaborations associated with the specified hub. + +## Create hub collaboration + +Adds a collaboration for a single user or a single group to a hub. + +Collaborations can be created using email address, user IDs, or group IDs. + +This operation is performed by calling function `create_hub_collaboration_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/post-hub-collaborations/). + + + +```python +client.hub_collaborations.create_hub_collaboration_v2025_r0( + CreateHubCollaborationV2025R0Hub(id=hub.id), + CreateHubCollaborationV2025R0AccessibleBy(type="user", id=user.id), + "viewer", +) +``` + +### Arguments + +- hub `CreateHubCollaborationV2025R0Hub` + - Hubs reference. +- accessible_by `CreateHubCollaborationV2025R0AccessibleBy` + - The user or group who gets access to the item. +- role `str` + - The level of access granted to hub. Possible values are `editor`, `viewer`, and `co-owner`. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubCollaborationV2025R0`. + +Returns a new hub collaboration object. + +## Get hub collaboration by collaboration ID + +Retrieves details for a hub collaboration by collaboration ID. + +This operation is performed by calling function `get_hub_collaboration_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-hub-collaborations-id/). + + + +```python +client.hub_collaborations.get_hub_collaboration_by_id_v2025_r0(created_collaboration.id) +``` + +### Arguments + +- hub_collaboration_id `str` + - The ID of the hub collaboration. Example: "1234" +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubCollaborationV2025R0`. + +Returns a hub collaboration object. + +## Update hub collaboration + +Updates a hub collaboration. +Can be used to change the hub role. + +This operation is performed by calling function `update_hub_collaboration_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/put-hub-collaborations-id/). + + + +```python +client.hub_collaborations.update_hub_collaboration_by_id_v2025_r0( + created_collaboration.id, role="editor" +) +``` + +### Arguments + +- hub_collaboration_id `str` + - The ID of the hub collaboration. Example: "1234" +- role `Optional[str]` + - The level of access granted to hub. Possible values are `editor`, `viewer`, and `co-owner`. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubCollaborationV2025R0`. + +Returns an updated hub collaboration object. + +## Remove hub collaboration + +Deletes a single hub collaboration. + +This operation is performed by calling function `delete_hub_collaboration_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/delete-hub-collaborations-id/). + + + +```python +client.hub_collaborations.delete_hub_collaboration_by_id_v2025_r0( + created_collaboration.id +) +``` + +### Arguments + +- hub_collaboration_id `str` + - The ID of the hub collaboration. Example: "1234" +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the hub collaboration was +successfully deleted. diff --git a/docs/hub_items.md b/docs/hub_items.md new file mode 100644 index 000000000..cca06abca --- /dev/null +++ b/docs/hub_items.md @@ -0,0 +1,76 @@ +# HubItemsManager + +- [Get hub items](#get-hub-items) +- [Manage hub items](#manage-hub-items) + +## Get hub items + +Retrieves all items associated with a Hub. + +This operation is performed by calling function `get_hub_items_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-hub-items/). + + + +```python +client.hub_items.get_hub_items_v2025_r0(created_hub.id) +``` + +### Arguments + +- hub_id `str` + - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubItemsV2025R0`. + +Retrieves the items associated with the specified Hub. + +## Manage hub items + +Adds and/or removes Hub items from a Hub. + +This operation is performed by calling function `manage_hub_items_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/post-hubs-id-manage-items/). + + + +```python +client.hub_items.manage_hub_items_v2025_r0( + created_hub.id, + operations=[ + HubItemOperationV2025R0( + action=HubItemOperationV2025R0ActionField.ADD, + item=FolderReferenceV2025R0(id=folder.id), + ) + ], +) +``` + +### Arguments + +- hub_id `str` + - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. Example: "12345" +- operations `Optional[List[HubItemOperationV2025R0]]` + - List of operations to perform on Hub items. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubItemsManageResponseV2025R0`. diff --git a/docs/hubs.md b/docs/hubs.md new file mode 100644 index 000000000..c31652d30 --- /dev/null +++ b/docs/hubs.md @@ -0,0 +1,269 @@ +# HubsManager + +- [List all hubs](#list-all-hubs) +- [Create hub](#create-hub) +- [List all hubs for requesting enterprise](#list-all-hubs-for-requesting-enterprise) +- [Get hub information by ID](#get-hub-information-by-id) +- [Update hub information by ID](#update-hub-information-by-id) +- [Delete hub](#delete-hub) +- [Copy hub](#copy-hub) + +## List all hubs + +Retrieves all hubs for requesting user. + +This operation is performed by calling function `get_hubs_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-hubs/). + + + +```python +client.hubs.get_hubs_v2025_r0( + scope="all", sort="name", direction=GetHubsV2025R0Direction.ASC +) +``` + +### Arguments + +- query `Optional[str]` + - The query string to search for hubs. +- scope `Optional[str]` + - The scope of the hubs to retrieve. Possible values include `editable`, `view_only`, and `all`. Default is `all`. +- sort `Optional[str]` + - The field to sort results by. Possible values include `name`, `updated_at`, `last_accessed_at`, `view_count`, and `relevance`. Default is `relevance`. +- direction `Optional[GetHubsV2025R0Direction]` + - The direction to sort results in. This can be either in alphabetical ascending (`ASC`) or descending (`DESC`) order. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubsV2025R0`. + +Returns all hubs for the given user or enterprise. + +## Create hub + +Creates a new Hub. + +This operation is performed by calling function `create_hub_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/post-hubs/). + + + +```python +client.hubs.create_hub_v2025_r0(hub_title, description=hub_description) +``` + +### Arguments + +- title `str` + - Title of the Hub. It cannot be empty and should be less than 50 characters. +- description `Optional[str]` + - Description of the Hub. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubV2025R0`. + +Returns a new Hub object. + +## List all hubs for requesting enterprise + +Retrieves all hubs for a given enterprise. + +Admins or Hub Co-admins of an enterprise +with GCM scope can make this call. + +This operation is performed by calling function `get_enterprise_hubs_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-enterprise-hubs/). + + + +```python +client.hubs.get_enterprise_hubs_v2025_r0( + sort="name", direction=GetEnterpriseHubsV2025R0Direction.ASC +) +``` + +### Arguments + +- query `Optional[str]` + - The query string to search for hubs. +- sort `Optional[str]` + - The field to sort results by. Possible values include `name`, `updated_at`, `last_accessed_at`, `view_count`, and `relevance`. Default is `relevance`. +- direction `Optional[GetEnterpriseHubsV2025R0Direction]` + - The direction to sort results in. This can be either in alphabetical ascending (`ASC`) or descending (`DESC`) order. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubsV2025R0`. + +Returns all hubs for the given user or enterprise. + +## Get hub information by ID + +Retrieves details for a hub by its ID. + +This operation is performed by calling function `get_hub_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-hubs-id/). + + + +```python +client.hubs.get_hub_by_id_v2025_r0(hub_id) +``` + +### Arguments + +- hub_id `str` + - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. Example: "12345" +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubV2025R0`. + +Returns a hub object. + +## Update hub information by ID + +Updates a Hub. Can be used to change title, description, or Hub settings. + +This operation is performed by calling function `update_hub_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/put-hubs-id/). + + + +```python +client.hubs.update_hub_by_id_v2025_r0( + hub_id, title=new_hub_title, description=new_hub_description +) +``` + +### Arguments + +- hub_id `str` + - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. Example: "12345" +- title `Optional[str]` + - Title of the Hub. It cannot be empty and should be less than 50 characters. +- description `Optional[str]` + - Description of the Hub. +- is_ai_enabled `Optional[bool]` + - Indicates if AI features are enabled for the Hub. +- is_collaboration_restricted_to_enterprise `Optional[bool]` + - Indicates if collaboration is restricted to the enterprise. +- can_non_owners_invite `Optional[bool]` + - Indicates if non-owners can invite others to the Hub. +- can_shared_link_be_created `Optional[bool]` + - Indicates if a shared link can be created for the Hub. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubV2025R0`. + +Returns a Hub object. + +## Delete hub + +Deletes a single hub. + +This operation is performed by calling function `delete_hub_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/delete-hubs-id/). + + + +```python +client.hubs.delete_hub_by_id_v2025_r0(hub_id) +``` + +### Arguments + +- hub_id `str` + - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. Example: "12345" +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the hub was +successfully deleted. + +## Copy hub + +Creates a copy of a Hub. + +The original Hub will not be modified. + +This operation is performed by calling function `copy_hub_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/post-hubs-id-copy/). + + + +```python +client.hubs.copy_hub_v2025_r0( + created_hub.id, title=copied_hub_title, description=copied_hub_description +) +``` + +### Arguments + +- hub_id `str` + - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. Example: "12345" +- title `Optional[str]` + - Title of the Hub. It cannot be empty and should be less than 50 characters. +- description `Optional[str]` + - Description of the Hub. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubV2025R0`. + +Returns a new Hub object. diff --git a/docs/integration_mappings.md b/docs/integration_mappings.md new file mode 100644 index 000000000..1b48ead7e --- /dev/null +++ b/docs/integration_mappings.md @@ -0,0 +1,301 @@ +# IntegrationMappingsManager + +- [List Slack integration mappings](#list-slack-integration-mappings) +- [Create Slack integration mapping](#create-slack-integration-mapping) +- [Update Slack integration mapping](#update-slack-integration-mapping) +- [Delete Slack integration mapping](#delete-slack-integration-mapping) +- [List Teams integration mappings](#list-teams-integration-mappings) +- [Create Teams integration mapping](#create-teams-integration-mapping) +- [Update Teams integration mapping](#update-teams-integration-mapping) +- [Delete Teams integration mapping](#delete-teams-integration-mapping) + +## List Slack integration mappings + +Lists [Slack integration mappings](https://support.box.com/hc/en-us/articles/4415585987859-Box-as-the-Content-Layer-for-Slack) in a users' enterprise. + +You need Admin or Co-Admin role to +use this endpoint. + +This operation is performed by calling function `get_slack_integration_mapping`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-integration-mappings-slack/). + + + +```python +user_client.integration_mappings.get_slack_integration_mapping() +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- partner_item_type `Optional[GetSlackIntegrationMappingPartnerItemType]` + - Mapped item type, for which the mapping should be returned. +- partner_item_id `Optional[str]` + - ID of the mapped item, for which the mapping should be returned. +- box_item_id `Optional[str]` + - Box item ID, for which the mappings should be returned. +- box_item_type `Optional[GetSlackIntegrationMappingBoxItemType]` + - Box item type, for which the mappings should be returned. +- is_manually_created `Optional[bool]` + - Whether the mapping has been manually created. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `IntegrationMappings`. + +Returns a collection of integration mappings. + +## Create Slack integration mapping + +Creates a [Slack integration mapping](https://support.box.com/hc/en-us/articles/4415585987859-Box-as-the-Content-Layer-for-Slack) +by mapping a Slack channel to a Box item. + +You need Admin or Co-Admin role to +use this endpoint. + +This operation is performed by calling function `create_slack_integration_mapping`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-integration-mappings-slack/). + + + +```python +user_client.integration_mappings.create_slack_integration_mapping( + IntegrationMappingPartnerItemSlack( + id=slack_partner_item_id, slack_org_id=slack_org_id + ), + IntegrationMappingBoxItemSlack(id=folder.id), +) +``` + +### Arguments + +- partner_item `IntegrationMappingPartnerItemSlack` +- box_item `IntegrationMappingBoxItemSlack` +- options `Optional[IntegrationMappingSlackOptions]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `IntegrationMapping`. + +Returns the created integration mapping. + +## Update Slack integration mapping + +Updates a [Slack integration mapping](https://support.box.com/hc/en-us/articles/4415585987859-Box-as-the-Content-Layer-for-Slack). +Supports updating the Box folder ID and options. + +You need Admin or Co-Admin role to +use this endpoint. + +This operation is performed by calling function `update_slack_integration_mapping_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-integration-mappings-slack-id/). + + + +```python +user_client.integration_mappings.update_slack_integration_mapping_by_id( + slack_integration_mapping.id, box_item=IntegrationMappingBoxItemSlack(id=folder.id) +) +``` + +### Arguments + +- integration_mapping_id `str` + - An ID of an integration mapping. Example: "11235432" +- box_item `Optional[IntegrationMappingBoxItemSlack]` +- options `Optional[IntegrationMappingSlackOptions]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `IntegrationMapping`. + +Returns the updated integration mapping object. + +## Delete Slack integration mapping + +Deletes a [Slack integration mapping](https://support.box.com/hc/en-us/articles/4415585987859-Box-as-the-Content-Layer-for-Slack). + +You need Admin or Co-Admin role to +use this endpoint. + +This operation is performed by calling function `delete_slack_integration_mapping_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-integration-mappings-slack-id/). + + + +```python +user_client.integration_mappings.delete_slack_integration_mapping_by_id( + slack_integration_mapping.id +) +``` + +### Arguments + +- integration_mapping_id `str` + - An ID of an integration mapping. Example: "11235432" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Empty body in response. + +## List Teams integration mappings + +Lists [Teams integration mappings](https://support.box.com/hc/en-us/articles/360044681474-Using-Box-for-Teams) in a users' enterprise. +You need Admin or Co-Admin role to +use this endpoint. + +This operation is performed by calling function `get_teams_integration_mapping`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-integration-mappings-teams/). + + + +```python +user_client.integration_mappings.get_teams_integration_mapping() +``` + +### Arguments + +- partner_item_type `Optional[GetTeamsIntegrationMappingPartnerItemType]` + - Mapped item type, for which the mapping should be returned. +- partner_item_id `Optional[str]` + - ID of the mapped item, for which the mapping should be returned. +- box_item_id `Optional[str]` + - Box item ID, for which the mappings should be returned. +- box_item_type `Optional[GetTeamsIntegrationMappingBoxItemType]` + - Box item type, for which the mappings should be returned. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `IntegrationMappingsTeams`. + +Returns a collection of integration mappings. + +## Create Teams integration mapping + +Creates a [Teams integration mapping](https://support.box.com/hc/en-us/articles/360044681474-Using-Box-for-Teams) +by mapping a Teams channel to a Box item. +You need Admin or Co-Admin role to +use this endpoint. + +This operation is performed by calling function `create_teams_integration_mapping`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-integration-mappings-teams/). + + + +```python +user_client.integration_mappings.create_teams_integration_mapping( + IntegrationMappingPartnerItemTeamsCreateRequest( + type=IntegrationMappingPartnerItemTeamsCreateRequestTypeField.CHANNEL, + id=partner_item_id, + tenant_id=tenant_id, + team_id=team_id, + ), + FolderReference(id=folder.id), +) +``` + +### Arguments + +- partner_item `IntegrationMappingPartnerItemTeamsCreateRequest` +- box_item `FolderReference` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `IntegrationMappingTeams`. + +Returns the created integration mapping. + +## Update Teams integration mapping + +Updates a [Teams integration mapping](https://support.box.com/hc/en-us/articles/360044681474-Using-Box-for-Teams). +Supports updating the Box folder ID and options. +You need Admin or Co-Admin role to +use this endpoint. + +This operation is performed by calling function `update_teams_integration_mapping_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-integration-mappings-teams-id/). + + + +```python +user_client.integration_mappings.update_teams_integration_mapping_by_id( + integration_mapping_id, box_item=FolderReference(id="1234567") +) +``` + +### Arguments + +- integration_mapping_id `str` + - An ID of an integration mapping. Example: "11235432" +- box_item `Optional[FolderReference]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `IntegrationMappingTeams`. + +Returns the updated integration mapping object. + +## Delete Teams integration mapping + +Deletes a [Teams integration mapping](https://support.box.com/hc/en-us/articles/360044681474-Using-Box-for-Teams). +You need Admin or Co-Admin role to +use this endpoint. + +This operation is performed by calling function `delete_teams_integration_mapping_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-integration-mappings-teams-id/). + + + +```python +user_client.integration_mappings.delete_teams_integration_mapping_by_id( + integration_mapping_id +) +``` + +### Arguments + +- integration_mapping_id `str` + - An ID of an integration mapping. Example: "11235432" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Empty body in response. diff --git a/docs/invites.md b/docs/invites.md new file mode 100644 index 000000000..5cfa00488 --- /dev/null +++ b/docs/invites.md @@ -0,0 +1,77 @@ +# InvitesManager + +- [Create user invite](#create-user-invite) +- [Get user invite status](#get-user-invite-status) + +## Create user invite + +Invites an existing external user to join an enterprise. + +The existing user can not be part of another enterprise and +must already have a Box account. Once invited, the user will receive an +email and are prompted to accept the invitation within the +Box web application. + +This method requires the "Manage An Enterprise" scope enabled for +the application, which can be enabled within the developer console. + +This operation is performed by calling function `create_invite`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-invites/). + + + +```python +client.invites.create_invite( + CreateInviteEnterprise(id=current_user.enterprise.id), + CreateInviteActionableBy(login=email), +) +``` + +### Arguments + +- enterprise `CreateInviteEnterprise` + - The enterprise to invite the user to. +- actionable_by `CreateInviteActionableBy` + - The user to invite. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Invite`. + +Returns a new invite object. + +## Get user invite status + +Returns the status of a user invite. + +This operation is performed by calling function `get_invite_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-invites-id/). + + + +```python +client.invites.get_invite_by_id(invitation.id) +``` + +### Arguments + +- invite_id `str` + - The ID of an invite. Example: "213723" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Invite`. + +Returns an invite object. diff --git a/docs/legal_hold_policies.md b/docs/legal_hold_policies.md new file mode 100644 index 000000000..361ab5b38 --- /dev/null +++ b/docs/legal_hold_policies.md @@ -0,0 +1,180 @@ +# LegalHoldPoliciesManager + +- [List all legal hold policies](#list-all-legal-hold-policies) +- [Create legal hold policy](#create-legal-hold-policy) +- [Get legal hold policy](#get-legal-hold-policy) +- [Update legal hold policy](#update-legal-hold-policy) +- [Remove legal hold policy](#remove-legal-hold-policy) + +## List all legal hold policies + +Retrieves a list of legal hold policies that belong to +an enterprise. + +This operation is performed by calling function `get_legal_hold_policies`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-legal-hold-policies/). + + + +```python +client.legal_hold_policies.get_legal_hold_policies() +``` + +### Arguments + +- policy_name `Optional[str]` + - Limits results to policies for which the names start with this search term. This is a case-insensitive prefix. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `LegalHoldPolicies`. + +Returns a list of legal hold policies. + +## Create legal hold policy + +Create a new legal hold policy. + +This operation is performed by calling function `create_legal_hold_policy`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-legal-hold-policies/). + + + +```python +client.legal_hold_policies.create_legal_hold_policy( + legal_hold_policy_name, + description=legal_hold_description, + filter_started_at=filter_started_at, + filter_ended_at=filter_ended_at, + is_ongoing=False, +) +``` + +### Arguments + +- policy_name `str` + - The name of the policy. +- description `Optional[str]` + - A description for the policy. +- filter_started_at `Optional[DateTime]` + - The filter start date. When this policy is applied using a `custodian` legal hold assignments, it will only apply to file versions created or uploaded inside of the date range. Other assignment types, such as folders and files, will ignore the date filter. Required if `is_ongoing` is set to `false`. +- filter_ended_at `Optional[DateTime]` + - The filter end date. When this policy is applied using a `custodian` legal hold assignments, it will only apply to file versions created or uploaded inside of the date range. Other assignment types, such as folders and files, will ignore the date filter. Required if `is_ongoing` is set to `false`. +- is_ongoing `Optional[bool]` + - Whether new assignments under this policy should continue applying to files even after initialization. When this policy is applied using a legal hold assignment, it will continue applying the policy to any new file versions even after it has been applied. For example, if a legal hold assignment is placed on a user today, and that user uploads a file tomorrow, that file will get held. This will continue until the policy is retired. Required if no filter dates are set. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `LegalHoldPolicy`. + +Returns a new legal hold policy object. + +## Get legal hold policy + +Retrieve a legal hold policy. + +This operation is performed by calling function `get_legal_hold_policy_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-legal-hold-policies-id/). + + + +```python +client.legal_hold_policies.get_legal_hold_policy_by_id(legal_hold_policy_id) +``` + +### Arguments + +- legal_hold_policy_id `str` + - The ID of the legal hold policy. Example: "324432" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `LegalHoldPolicy`. + +Returns a legal hold policy object. + +## Update legal hold policy + +Update legal hold policy. + +This operation is performed by calling function `update_legal_hold_policy_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-legal-hold-policies-id/). + + + +```python +client.legal_hold_policies.update_legal_hold_policy_by_id( + legal_hold_policy_id, policy_name=updated_legal_hold_policy_name +) +``` + +### Arguments + +- legal_hold_policy_id `str` + - The ID of the legal hold policy. Example: "324432" +- policy_name `Optional[str]` + - The name of the policy. +- description `Optional[str]` + - A description for the policy. +- release_notes `Optional[str]` + - Notes around why the policy was released. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `LegalHoldPolicy`. + +Returns a new legal hold policy object. + +## Remove legal hold policy + +Delete an existing legal hold policy. + +This is an asynchronous process. The policy will not be +fully deleted yet when the response returns. + +This operation is performed by calling function `delete_legal_hold_policy_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-legal-hold-policies-id/). + + + +```python +client.legal_hold_policies.delete_legal_hold_policy_by_id(legal_hold_policy.id) +``` + +### Arguments + +- legal_hold_policy_id `str` + - The ID of the legal hold policy. Example: "324432" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the policy was +successfully deleted. diff --git a/docs/legal_hold_policy_assignments.md b/docs/legal_hold_policy_assignments.md new file mode 100644 index 000000000..cd14c2aef --- /dev/null +++ b/docs/legal_hold_policy_assignments.md @@ -0,0 +1,201 @@ +# LegalHoldPolicyAssignmentsManager + +- [List legal hold policy assignments](#list-legal-hold-policy-assignments) +- [Assign legal hold policy](#assign-legal-hold-policy) +- [Get legal hold policy assignment](#get-legal-hold-policy-assignment) +- [Unassign legal hold policy](#unassign-legal-hold-policy) +- [List files with current file versions for legal hold policy assignment](#list-files-with-current-file-versions-for-legal-hold-policy-assignment) + +## List legal hold policy assignments + +Retrieves a list of items a legal hold policy has been assigned to. + +This operation is performed by calling function `get_legal_hold_policy_assignments`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-legal-hold-policy-assignments/). + + + +```python +client.legal_hold_policy_assignments.get_legal_hold_policy_assignments( + legal_hold_policy_id +) +``` + +### Arguments + +- policy_id `str` + - The ID of the legal hold policy. +- assign_to_type `Optional[GetLegalHoldPolicyAssignmentsAssignToType]` + - Filters the results by the type of item the policy was applied to. +- assign_to_id `Optional[str]` + - Filters the results by the ID of item the policy was applied to. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `LegalHoldPolicyAssignments`. + +Returns a list of legal hold policy assignments. + +## Assign legal hold policy + +Assign a legal hold to a file, file version, folder, or user. + +This operation is performed by calling function `create_legal_hold_policy_assignment`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-legal-hold-policy-assignments/). + + + +```python +client.legal_hold_policy_assignments.create_legal_hold_policy_assignment( + legal_hold_policy_id, + CreateLegalHoldPolicyAssignmentAssignTo( + type=CreateLegalHoldPolicyAssignmentAssignToTypeField.FILE, id=file_id + ), +) +``` + +### Arguments + +- policy_id `str` + - The ID of the policy to assign. +- assign_to `CreateLegalHoldPolicyAssignmentAssignTo` + - The item to assign the policy to. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `LegalHoldPolicyAssignment`. + +Returns a new legal hold policy assignment. + +## Get legal hold policy assignment + +Retrieve a legal hold policy assignment. + +This operation is performed by calling function `get_legal_hold_policy_assignment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-legal-hold-policy-assignments-id/). + + + +```python +client.legal_hold_policy_assignments.get_legal_hold_policy_assignment_by_id( + legal_hold_policy_assignment_id +) +``` + +### Arguments + +- legal_hold_policy_assignment_id `str` + - The ID of the legal hold policy assignment. Example: "753465" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `LegalHoldPolicyAssignment`. + +Returns a legal hold policy object. + +## Unassign legal hold policy + +Remove a legal hold from an item. + +This is an asynchronous process. The policy will not be +fully removed yet when the response returns. + +This operation is performed by calling function `delete_legal_hold_policy_assignment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-legal-hold-policy-assignments-id/). + + + +```python +client.legal_hold_policy_assignments.delete_legal_hold_policy_assignment_by_id( + legal_hold_policy_assignment_id +) +``` + +### Arguments + +- legal_hold_policy_assignment_id `str` + - The ID of the legal hold policy assignment. Example: "753465" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the assignment was +successfully deleted. + +## List files with current file versions for legal hold policy assignment + +Get a list of files with current file versions for a legal hold +assignment. + +In some cases you may want to get previous file versions instead. In these +cases, use the `GET /legal_hold_policy_assignments/:id/file_versions_on_hold` +API instead to return any previous versions of a file for this legal hold +policy assignment. + +Due to ongoing re-architecture efforts this API might not return all file +versions held for this policy ID. Instead, this API will only return the +latest file version held in the newly developed architecture. The `GET +/file_version_legal_holds` API can be used to fetch current and past versions +of files held within the legacy architecture. + +This endpoint does not support returning any content that is on hold due to +a Custodian collaborating on a Hub. + +The `GET /legal_hold_policy_assignments?policy_id={id}` API can be used to +find a list of policy assignments for a given policy ID. + +This operation is performed by calling function `get_legal_hold_policy_assignment_file_on_hold`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-legal-hold-policy-assignments-id-files-on-hold/). + + + +```python +client.legal_hold_policy_assignments.get_legal_hold_policy_assignment_file_on_hold( + legal_hold_policy_assignment_id +) +``` + +### Arguments + +- legal_hold_policy_assignment_id `str` + - The ID of the legal hold policy assignment. Example: "753465" +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FilesOnHold`. + +Returns the list of current file versions held under legal hold for a +specific legal hold policy assignment. diff --git a/docs/list_collaborations.md b/docs/list_collaborations.md new file mode 100644 index 000000000..e27227955 --- /dev/null +++ b/docs/list_collaborations.md @@ -0,0 +1,162 @@ +# ListCollaborationsManager + +- [List file collaborations](#list-file-collaborations) +- [List folder collaborations](#list-folder-collaborations) +- [List pending collaborations](#list-pending-collaborations) +- [List group collaborations](#list-group-collaborations) + +## List file collaborations + +Retrieves a list of pending and active collaborations for a +file. This returns all the users that have access to the file +or have been invited to the file. + +This operation is performed by calling function `get_file_collaborations`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-collaborations/). + + + +```python +client.list_collaborations.get_file_collaborations(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- limit `Optional[int]` + - The maximum number of items to return per page. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Collaborations`. + +Returns a collection of collaboration objects. If there are no +collaborations on this file an empty collection will be returned. + +This list includes pending collaborations, for which the `status` +is set to `pending`, indicating invitations that have been sent but not +yet accepted. + +## List folder collaborations + +Retrieves a list of pending and active collaborations for a +folder. This returns all the users that have access to the folder +or have been invited to the folder. + +This operation is performed by calling function `get_folder_collaborations`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-id-collaborations/). + + + +```python +client.list_collaborations.get_folder_collaborations(folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- limit `Optional[int]` + - The maximum number of items to return per page. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Collaborations`. + +Returns a collection of collaboration objects. If there are no +collaborations on this folder an empty collection will be returned. + +This list includes pending collaborations, for which the `status` +is set to `pending`, indicating invitations that have been sent but not +yet accepted. + +## List pending collaborations + +Retrieves all pending collaboration invites for this user. + +This operation is performed by calling function `get_collaborations`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-collaborations/). + + + +```python +client.list_collaborations.get_collaborations(GetCollaborationsStatus.PENDING) +``` + +### Arguments + +- status `GetCollaborationsStatus` + - The status of the collaborations to retrieve. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CollaborationsOffsetPaginated`. + +Returns a collection of pending collaboration objects. + +If the user has no pending collaborations, the collection +will be empty. + +## List group collaborations + +Retrieves all the collaborations for a group. The user +must have admin permissions to inspect enterprise's groups. + +Each collaboration object has details on which files or +folders the group has access to and with what role. + +This operation is performed by calling function `get_group_collaborations`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-groups-id-collaborations/). + + + +```python +client.list_collaborations.get_group_collaborations(group.id) +``` + +### Arguments + +- group_id `str` + - The ID of the group. Example: "57645" +- limit `Optional[int]` + - The maximum number of items to return per page. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `CollaborationsOffsetPaginated`. + +Returns a collection of collaboration objects. If there are no +collaborations, an empty collection will be returned. diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 8e916f75a..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\boxsdk.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\boxsdk.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/docs/memberships.md b/docs/memberships.md new file mode 100644 index 000000000..2bb9c8ea3 --- /dev/null +++ b/docs/memberships.md @@ -0,0 +1,218 @@ +# MembershipsManager + +- [List user's groups](#list-users-groups) +- [List members of group](#list-members-of-group) +- [Add user to group](#add-user-to-group) +- [Get group membership](#get-group-membership) +- [Update group membership](#update-group-membership) +- [Remove user from group](#remove-user-from-group) + +## List user's groups + +Retrieves all the groups for a user. Only members of this +group or users with admin-level permissions will be able to +use this API. + +This operation is performed by calling function `get_user_memberships`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-users-id-memberships/). + + + +```python +client.memberships.get_user_memberships(user.id) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- limit `Optional[int]` + - The maximum number of items to return per page. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `GroupMemberships`. + +Returns a collection of membership objects. If there are no +memberships, an empty collection will be returned. + +## List members of group + +Retrieves all the members for a group. Only members of this +group or users with admin-level permissions will be able to +use this API. + +This operation is performed by calling function `get_group_memberships`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-groups-id-memberships/). + + + +```python +client.memberships.get_group_memberships(group.id) +``` + +### Arguments + +- group_id `str` + - The ID of the group. Example: "57645" +- limit `Optional[int]` + - The maximum number of items to return per page. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `GroupMemberships`. + +Returns a collection of membership objects. If there are no +memberships, an empty collection will be returned. + +## Add user to group + +Creates a group membership. Only users with +admin-level permissions will be able to use this API. + +This operation is performed by calling function `create_group_membership`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-group-memberships/). + + + +```python +client.memberships.create_group_membership( + CreateGroupMembershipUser(id=user.id), CreateGroupMembershipGroup(id=group.id) +) +``` + +### Arguments + +- user `CreateGroupMembershipUser` + - The user to add to the group. +- group `CreateGroupMembershipGroup` + - The group to add the user to. +- role `Optional[CreateGroupMembershipRole]` + - The role of the user in the group. +- configurable_permissions `Optional[Dict[str, bool]]` + - Custom configuration for the permissions an admin if a group will receive. This option has no effect on members with a role of `member`. Setting these permissions overwrites the default access levels of an admin. Specifying a value of `null` for this object will disable all configurable permissions. Specifying permissions will set them accordingly, omitted permissions will be enabled by default. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `GroupMembership`. + +Returns a new group membership object. + +## Get group membership + +Retrieves a specific group membership. Only admins of this +group or users with admin-level permissions will be able to +use this API. + +This operation is performed by calling function `get_group_membership_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-group-memberships-id/). + + + +```python +client.memberships.get_group_membership_by_id(group_membership.id) +``` + +### Arguments + +- group_membership_id `str` + - The ID of the group membership. Example: "434534" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `GroupMembership`. + +Returns the group membership object. + +## Update group membership + +Updates a user's group membership. Only admins of this +group or users with admin-level permissions will be able to +use this API. + +This operation is performed by calling function `update_group_membership_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-group-memberships-id/). + + + +```python +client.memberships.update_group_membership_by_id( + group_membership.id, role=UpdateGroupMembershipByIdRole.ADMIN +) +``` + +### Arguments + +- group_membership_id `str` + - The ID of the group membership. Example: "434534" +- role `Optional[UpdateGroupMembershipByIdRole]` + - The role of the user in the group. +- configurable_permissions `Optional[Dict[str, bool]]` + - Custom configuration for the permissions an admin if a group will receive. This option has no effect on members with a role of `member`. Setting these permissions overwrites the default access levels of an admin. Specifying a value of `null` for this object will disable all configurable permissions. Specifying permissions will set them accordingly, omitted permissions will be enabled by default. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `GroupMembership`. + +Returns a new group membership object. + +## Remove user from group + +Deletes a specific group membership. Only admins of this +group or users with admin-level permissions will be able to +use this API. + +This operation is performed by calling function `delete_group_membership_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-group-memberships-id/). + + + +```python +client.memberships.delete_group_membership_by_id(group_membership.id) +``` + +### Arguments + +- group_membership_id `str` + - The ID of the group membership. Example: "434534" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the membership was +successfully deleted. diff --git a/docs/metadata_cascade_policies.md b/docs/metadata_cascade_policies.md new file mode 100644 index 000000000..0f7604fe2 --- /dev/null +++ b/docs/metadata_cascade_policies.md @@ -0,0 +1,179 @@ +# MetadataCascadePoliciesManager + +- [List metadata cascade policies](#list-metadata-cascade-policies) +- [Create metadata cascade policy](#create-metadata-cascade-policy) +- [Get metadata cascade policy](#get-metadata-cascade-policy) +- [Remove metadata cascade policy](#remove-metadata-cascade-policy) +- [Force-apply metadata cascade policy to folder](#force-apply-metadata-cascade-policy-to-folder) + +## List metadata cascade policies + +Retrieves a list of all the metadata cascade policies +that are applied to a given folder. This can not be used on the root +folder with ID `0`. + +This operation is performed by calling function `get_metadata_cascade_policies`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-cascade-policies/). + + + +```python +client.metadata_cascade_policies.get_metadata_cascade_policies(folder.id) +``` + +### Arguments + +- folder_id `str` + - Specifies which folder to return policies for. This can not be used on the root folder with ID `0`. +- owner_enterprise_id `Optional[str]` + - The ID of the enterprise ID for which to find metadata cascade policies. If not specified, it defaults to the current enterprise. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataCascadePolicies`. + +Returns a list of metadata cascade policies. + +## Create metadata cascade policy + +Creates a new metadata cascade policy that applies a given +metadata template to a given folder and automatically +cascades it down to any files within that folder. + +In order for the policy to be applied a metadata instance must first +be applied to the folder the policy is to be applied to. + +This operation is performed by calling function `create_metadata_cascade_policy`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-metadata-cascade-policies/). + + + +```python +client.metadata_cascade_policies.create_metadata_cascade_policy( + folder.id, CreateMetadataCascadePolicyScope.ENTERPRISE, template_key +) +``` + +### Arguments + +- folder_id `str` + - The ID of the folder to apply the policy to. This folder will need to already have an instance of the targeted metadata template applied to it. +- scope `CreateMetadataCascadePolicyScope` + - The scope of the targeted metadata template. This template will need to already have an instance applied to the targeted folder. +- template_key `str` + - The key of the targeted metadata template. This template will need to already have an instance applied to the targeted folder. In many cases the template key is automatically derived of its display name, for example `Contract Template` would become `contractTemplate`. In some cases the creator of the template will have provided its own template key. Please [list the templates for an enterprise][list], or get all instances on a [file][file] or [folder][folder] to inspect a template's key. [list]: e://get-metadata-templates-enterprise [file]: e://get-files-id-metadata [folder]: e://get-folders-id-metadata +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataCascadePolicy`. + +Returns a new of metadata cascade policy. + +## Get metadata cascade policy + +Retrieve a specific metadata cascade policy assigned to a folder. + +This operation is performed by calling function `get_metadata_cascade_policy_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-cascade-policies-id/). + + + +```python +client.metadata_cascade_policies.get_metadata_cascade_policy_by_id(cascade_policy_id) +``` + +### Arguments + +- metadata_cascade_policy_id `str` + - The ID of the metadata cascade policy. Example: "6fd4ff89-8fc1-42cf-8b29-1890dedd26d7" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataCascadePolicy`. + +Returns a metadata cascade policy. + +## Remove metadata cascade policy + +Deletes a metadata cascade policy. + +This operation is performed by calling function `delete_metadata_cascade_policy_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-metadata-cascade-policies-id/). + + + +```python +client.metadata_cascade_policies.delete_metadata_cascade_policy_by_id(cascade_policy_id) +``` + +### Arguments + +- metadata_cascade_policy_id `str` + - The ID of the metadata cascade policy. Example: "6fd4ff89-8fc1-42cf-8b29-1890dedd26d7" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the policy +is successfully deleted. + +## Force-apply metadata cascade policy to folder + +Force the metadata on a folder with a metadata cascade policy to be applied to +all of its children. This can be used after creating a new cascade policy to +enforce the metadata to be cascaded down to all existing files within that +folder. + +This operation is performed by calling function `apply_metadata_cascade_policy`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-metadata-cascade-policies-id-apply/). + + + +```python +client.metadata_cascade_policies.apply_metadata_cascade_policy( + cascade_policy_id, ApplyMetadataCascadePolicyConflictResolution.OVERWRITE +) +``` + +### Arguments + +- metadata_cascade_policy_id `str` + - The ID of the cascade policy to force-apply. Example: "6fd4ff89-8fc1-42cf-8b29-1890dedd26d7" +- conflict_resolution `ApplyMetadataCascadePolicyConflictResolution` + - Describes the desired behavior when dealing with the conflict where a metadata template already has an instance applied to a child. _ `none` will preserve the existing value on the file _ `overwrite` will force-apply the templates values over any existing values. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the API call was successful. The metadata +cascade operation will be performed asynchronously. + +The API call will return directly, before the cascade operation +is complete. There is currently no API to check for the status of this +operation. diff --git a/docs/metadata_templates.md b/docs/metadata_templates.md new file mode 100644 index 000000000..56b848c47 --- /dev/null +++ b/docs/metadata_templates.md @@ -0,0 +1,336 @@ +# MetadataTemplatesManager + +- [Find metadata template by instance ID](#find-metadata-template-by-instance-id) +- [Get metadata template by name](#get-metadata-template-by-name) +- [Update metadata template](#update-metadata-template) +- [Remove metadata template](#remove-metadata-template) +- [Get metadata template by ID](#get-metadata-template-by-id) +- [List all global metadata templates](#list-all-global-metadata-templates) +- [List all metadata templates for enterprise](#list-all-metadata-templates-for-enterprise) +- [Create metadata template](#create-metadata-template) + +## Find metadata template by instance ID + +Finds a metadata template by searching for the ID of an instance of the +template. + +This operation is performed by calling function `get_metadata_templates_by_instance_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-templates/). + + + +```python +client.metadata_templates.get_metadata_templates_by_instance_id( + created_metadata_instance.id +) +``` + +### Arguments + +- metadata_instance_id `str` + - The ID of an instance of the metadata template to find. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTemplates`. + +Returns a list containing the 1 metadata template that matches the +instance ID. + +## Get metadata template by name + +Retrieves a metadata template by its `scope` and `templateKey` values. + +To find the `scope` and `templateKey` for a template, list all templates for +an enterprise or globally, or list all templates applied to a file or folder. + +This operation is performed by calling function `get_metadata_template`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-templates-id-id-schema/). + + + +```python +client.metadata_templates.get_metadata_template( + GetMetadataTemplateScope.ENTERPRISE, template.template_key +) +``` + +### Arguments + +- scope `GetMetadataTemplateScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTemplate`. + +Returns the metadata template matching the `scope` +and `template` name. + +## Update metadata template + +Updates a metadata template. + +The metadata template can only be updated if the template +already exists. + +The update is applied atomically. If any errors occur during the +application of the operations, the metadata template will not be changed. + +This operation is performed by calling function `update_metadata_template`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-metadata-templates-id-id-schema/). + + + +```python +client.metadata_templates.update_metadata_template( + UpdateMetadataTemplateScope.ENTERPRISE, + template_key, + [ + UpdateMetadataTemplateRequestBody( + op=UpdateMetadataTemplateRequestBodyOpField.ADDFIELD, + field_key="newfieldname", + data={"type": "string", "displayName": "newFieldName"}, + ) + ], +) +``` + +### Arguments + +- scope `UpdateMetadataTemplateScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- request_body `List[UpdateMetadataTemplateRequestBody]` + - Request body of updateMetadataTemplate method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTemplate`. + +Returns the updated metadata template, with the +custom template data included. + +## Remove metadata template + +Delete a metadata template and its instances. +This deletion is permanent and can not be reversed. + +This operation is performed by calling function `delete_metadata_template`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-metadata-templates-id-id-schema/). + + + +```python +client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template.template_key +) +``` + +### Arguments + +- scope `DeleteMetadataTemplateScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the metadata +template is successfully deleted. + +## Get metadata template by ID + +Retrieves a metadata template by its ID. + +This operation is performed by calling function `get_metadata_template_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-templates-id/). + + + +```python +client.metadata_templates.get_metadata_template_by_id(template.id) +``` + +### Arguments + +- template_id `str` + - The ID of the template. Example: "f7a9891f" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTemplate`. + +Returns the metadata template that matches the ID. + +## List all global metadata templates + +Used to retrieve all generic, global metadata templates available to all +enterprises using Box. + +This operation is performed by calling function `get_global_metadata_templates`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-templates-global/). + + + +```python +client.metadata_templates.get_global_metadata_templates() +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTemplates`. + +Returns all of the metadata templates available to all enterprises +and their corresponding schema. + +## List all metadata templates for enterprise + +Used to retrieve all metadata templates created to be used specifically within +the user's enterprise. + +This operation is performed by calling function `get_enterprise_metadata_templates`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-templates-enterprise/). + + + +```python +client.metadata_templates.get_enterprise_metadata_templates() +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTemplates`. + +Returns all of the metadata templates within an enterprise +and their corresponding schema. + +## Create metadata template + +Creates a new metadata template that can be applied to +files and folders. + +This operation is performed by calling function `create_metadata_template`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-metadata-templates-schema/). + + + +```python +client.metadata_templates.create_metadata_template( + "enterprise", + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key="testName", + display_name="testName", + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + key="age", + display_name="age", + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.DATE, + key="birthDate", + display_name="birthDate", + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.ENUM, + key="countryCode", + display_name="countryCode", + options=[ + CreateMetadataTemplateFieldsOptionsField(key="US"), + CreateMetadataTemplateFieldsOptionsField(key="CA"), + ], + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.MULTISELECT, + key="sports", + display_name="sports", + options=[ + CreateMetadataTemplateFieldsOptionsField(key="basketball"), + CreateMetadataTemplateFieldsOptionsField(key="football"), + CreateMetadataTemplateFieldsOptionsField(key="tennis"), + ], + ), + ], +) +``` + +### Arguments + +- scope `str` + - The scope of the metadata template to create. Applications can only create templates for use within the authenticated user's enterprise. This value needs to be set to `enterprise`, as `global` scopes can not be created by applications. +- template_key `Optional[str]` + - A unique identifier for the template. This identifier needs to be unique across the enterprise for which the metadata template is being created. When not provided, the API will create a unique `templateKey` based on the value of the `displayName`. +- display_name `str` + - The display name of the template. +- hidden `Optional[bool]` + - Defines if this template is visible in the Box web app UI, or if it is purely intended for usage through the API. +- fields `Optional[List[CreateMetadataTemplateFields]]` + - An ordered list of template fields which are part of the template. Each field can be a regular text field, date field, number field, as well as a single or multi-select list. +- copy_instance_on_item_copy `Optional[bool]` + - Whether or not to copy any metadata attached to a file or folder when it is copied. By default, metadata is not copied along with a file or folder when it is copied. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTemplate`. + +The schema representing the metadata template created. diff --git a/docs/recent_items.md b/docs/recent_items.md new file mode 100644 index 000000000..4f1c86562 --- /dev/null +++ b/docs/recent_items.md @@ -0,0 +1,37 @@ +# RecentItemsManager + +- [List recently accessed items](#list-recently-accessed-items) + +## List recently accessed items + +Returns information about the recent items accessed +by a user, either in the last 90 days or up to the last +1000 items accessed. + +This operation is performed by calling function `get_recent_items`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-recent-items/). + + + +```python +client.recent_items.get_recent_items() +``` + +### Arguments + +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- limit `Optional[int]` + - The maximum number of items to return per page. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `RecentItems`. + +Returns a list recent items access by a user. diff --git a/docs/retention_policies.md b/docs/retention_policies.md new file mode 100644 index 000000000..d92ba85bb --- /dev/null +++ b/docs/retention_policies.md @@ -0,0 +1,204 @@ +# RetentionPoliciesManager + +- [List retention policies](#list-retention-policies) +- [Create retention policy](#create-retention-policy) +- [Get retention policy](#get-retention-policy) +- [Update retention policy](#update-retention-policy) +- [Delete retention policy](#delete-retention-policy) + +## List retention policies + +Retrieves all of the retention policies for an enterprise. + +This operation is performed by calling function `get_retention_policies`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-retention-policies/). + + + +```python +client.retention_policies.get_retention_policies() +``` + +### Arguments + +- policy_name `Optional[str]` + - Filters results by a case sensitive prefix of the name of retention policies. +- policy_type `Optional[GetRetentionPoliciesPolicyType]` + - Filters results by the type of retention policy. +- created_by_user_id `Optional[str]` + - Filters results by the ID of the user who created policy. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- limit `Optional[int]` + - The maximum number of items to return per page. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `RetentionPolicies`. + +Returns a list retention policies in the enterprise. + +## Create retention policy + +Creates a retention policy. + +This operation is performed by calling function `create_retention_policy`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-retention-policies/). + + + +```python +client.retention_policies.create_retention_policy( + retention_policy_name, + CreateRetentionPolicyPolicyType.FINITE, + CreateRetentionPolicyDispositionAction.REMOVE_RETENTION, + description=retention_description, + retention_length="1", + retention_type=CreateRetentionPolicyRetentionType.MODIFIABLE, + can_owner_extend_retention=True, + are_owners_notified=True, +) +``` + +### Arguments + +- policy_name `str` + - The name for the retention policy. +- description `Optional[str]` + - The additional text description of the retention policy. +- policy_type `CreateRetentionPolicyPolicyType` + - The type of the retention policy. A retention policy type can either be `finite`, where a specific amount of time to retain the content is known upfront, or `indefinite`, where the amount of time to retain the content is still unknown. +- disposition_action `CreateRetentionPolicyDispositionAction` + - The disposition action of the retention policy. `permanently_delete` deletes the content retained by the policy permanently. `remove_retention` lifts retention policy from the content, allowing it to be deleted by users once the retention policy has expired. +- retention_length `Optional[str]` + - The length of the retention policy. This value specifies the duration in days that the retention policy will be active for after being assigned to content. If the policy has a `policy_type` of `indefinite`, the `retention_length` will also be `indefinite`. +- retention_type `Optional[CreateRetentionPolicyRetentionType]` + - Specifies the retention type: _ `modifiable`: You can modify the retention policy. For example, you can add or remove folders, shorten or lengthen the policy duration, or delete the assignment. Use this type if your retention policy is not related to any regulatory purposes. _ `non_modifiable`: You can modify the retention policy only in a limited way: add a folder, lengthen the duration, retire the policy, change the disposition action or notification settings. You cannot perform other actions, such as deleting the assignment or shortening the policy duration. Use this type to ensure compliance with regulatory retention policies. +- can_owner_extend_retention `Optional[bool]` + - Whether the owner of a file will be allowed to extend the retention. +- are_owners_notified `Optional[bool]` + - Whether owner and co-owners of a file are notified when the policy nears expiration. +- custom_notification_recipients `Optional[List[UserMini]]` + - A list of users notified when the retention policy duration is about to end. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `RetentionPolicy`. + +Returns a new retention policy object. + +## Get retention policy + +Retrieves a retention policy. + +This operation is performed by calling function `get_retention_policy_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-retention-policies-id/). + + + +```python +client.retention_policies.get_retention_policy_by_id(retention_policy.id) +``` + +### Arguments + +- retention_policy_id `str` + - The ID of the retention policy. Example: "982312" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `RetentionPolicy`. + +Returns the retention policy object. + +## Update retention policy + +Updates a retention policy. + +This operation is performed by calling function `update_retention_policy_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-retention-policies-id/). + + + +```python +client.retention_policies.update_retention_policy_by_id( + retention_policy.id, policy_name=updated_retention_policy_name +) +``` + +### Arguments + +- retention_policy_id `str` + - The ID of the retention policy. Example: "982312" +- policy_name `Optional[str]` + - The name for the retention policy. +- description `Optional[str]` + - The additional text description of the retention policy. +- disposition_action `Optional[str]` + - The disposition action of the retention policy. This action can be `permanently_delete`, which will cause the content retained by the policy to be permanently deleted, or `remove_retention`, which will lift the retention policy from the content, allowing it to be deleted by users, once the retention policy has expired. You can use `null` if you don't want to change `disposition_action`. +- retention_type `Optional[str]` + - Specifies the retention type: _ `modifiable`: You can modify the retention policy. For example, you can add or remove folders, shorten or lengthen the policy duration, or delete the assignment. Use this type if your retention policy is not related to any regulatory purposes. _ `non-modifiable`: You can modify the retention policy only in a limited way: add a folder, lengthen the duration, retire the policy, change the disposition action or notification settings. You cannot perform other actions, such as deleting the assignment or shortening the policy duration. Use this type to ensure compliance with regulatory retention policies. When updating a retention policy, you can use `non-modifiable` type only. You can convert a `modifiable` policy to `non-modifiable`, but not the other way around. +- retention_length `Optional[str]` + - The length of the retention policy. This value specifies the duration in days that the retention policy will be active for after being assigned to content. If the policy has a `policy_type` of `indefinite`, the `retention_length` will also be `indefinite`. +- status `Optional[str]` + - Used to retire a retention policy. If not retiring a policy, do not include this parameter or set it to `null`. +- can_owner_extend_retention `Optional[bool]` + - Determines if the owner of items under the policy can extend the retention when the original retention duration is about to end. +- are_owners_notified `Optional[bool]` + - Determines if owners and co-owners of items under the policy are notified when the retention duration is about to end. +- custom_notification_recipients `Optional[List[UserBase]]` + - A list of users notified when the retention duration is about to end. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `RetentionPolicy`. + +Returns the updated retention policy object. + +## Delete retention policy + +Permanently deletes a retention policy. + +This operation is performed by calling function `delete_retention_policy_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-retention-policies-id/). + + + +```python +client.retention_policies.delete_retention_policy_by_id(retention_policy.id) +``` + +### Arguments + +- retention_policy_id `str` + - The ID of the retention policy. Example: "982312" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the policy has been deleted. diff --git a/docs/retention_policy_assignments.md b/docs/retention_policy_assignments.md new file mode 100644 index 000000000..d1249371b --- /dev/null +++ b/docs/retention_policy_assignments.md @@ -0,0 +1,185 @@ +# RetentionPolicyAssignmentsManager + +- [List retention policy assignments](#list-retention-policy-assignments) +- [Assign retention policy](#assign-retention-policy) +- [Get retention policy assignment](#get-retention-policy-assignment) +- [Remove retention policy assignment](#remove-retention-policy-assignment) +- [Get files under retention](#get-files-under-retention) + +## List retention policy assignments + +Returns a list of all retention policy assignments associated with a specified +retention policy. + +This operation is performed by calling function `get_retention_policy_assignments`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-retention-policies-id-assignments/). + + + +```python +client.retention_policy_assignments.get_retention_policy_assignments( + retention_policy.id +) +``` + +### Arguments + +- retention_policy_id `str` + - The ID of the retention policy. Example: "982312" +- type `Optional[GetRetentionPolicyAssignmentsType]` + - The type of the retention policy assignment to retrieve. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `RetentionPolicyAssignments`. + +Returns a list of the retention policy assignments associated with the +specified retention policy. + +## Assign retention policy + +Assigns a retention policy to an item. + +This operation is performed by calling function `create_retention_policy_assignment`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-retention-policy-assignments/). + + + +```python +client.retention_policy_assignments.create_retention_policy_assignment( + retention_policy.id, + CreateRetentionPolicyAssignmentAssignTo( + type=CreateRetentionPolicyAssignmentAssignToTypeField.FOLDER, id=folder.id + ), +) +``` + +### Arguments + +- policy_id `str` + - The ID of the retention policy to assign. +- assign_to `CreateRetentionPolicyAssignmentAssignTo` + - The item to assign the policy to. +- filter_fields `Optional[List[CreateRetentionPolicyAssignmentFilterFields]]` + - If the `assign_to` type is `metadata_template`, then optionally add the `filter_fields` parameter which will require an array of objects with a field entry and a value entry. Currently only one object of `field` and `value` is supported. +- start_date_field `Optional[str]` + - The date the retention policy assignment begins. If the `assigned_to` type is `metadata_template`, this field can be a date field's metadata attribute key id. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `RetentionPolicyAssignment`. + +Returns a new retention policy assignment object. + +## Get retention policy assignment + +Retrieves a retention policy assignment. + +This operation is performed by calling function `get_retention_policy_assignment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-retention-policy-assignments-id/). + + + +```python +client.retention_policy_assignments.get_retention_policy_assignment_by_id( + retention_policy_assignment.id +) +``` + +### Arguments + +- retention_policy_assignment_id `str` + - The ID of the retention policy assignment. Example: "1233123" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `RetentionPolicyAssignment`. + +Returns the retention policy assignment object. + +## Remove retention policy assignment + +Removes a retention policy assignment +applied to content. + +This operation is performed by calling function `delete_retention_policy_assignment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-retention-policy-assignments-id/). + + + +```python +client.retention_policy_assignments.delete_retention_policy_assignment_by_id( + retention_policy_assignment.id +) +``` + +### Arguments + +- retention_policy_assignment_id `str` + - The ID of the retention policy assignment. Example: "1233123" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the policy assignment +is successfully deleted. + +## Get files under retention + +Returns a list of files under retention for a retention policy assignment. + +This operation is performed by calling function `get_files_under_retention_policy_assignment`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-retention-policy-assignments-id-files-under-retention/). + + + +```python +client.retention_policy_assignments.get_files_under_retention_policy_assignment( + retention_policy_assignment.id +) +``` + +### Arguments + +- retention_policy_assignment_id `str` + - The ID of the retention policy assignment. Example: "1233123" +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FilesUnderRetention`. + +Returns a list of files under retention that are associated with the +specified retention policy assignment. diff --git a/docs/search.md b/docs/search.md new file mode 100644 index 000000000..580dcab9e --- /dev/null +++ b/docs/search.md @@ -0,0 +1,141 @@ +# SearchManager + +- [Query files/folders by metadata](#query-files-folders-by-metadata) +- [Search for content](#search-for-content) + +## Query files/folders by metadata + +Create a search using SQL-like syntax to return items that match specific +metadata. + +By default, this endpoint returns only the most basic info about the items for +which the query matches. To get additional fields for each item, including any +of the metadata, use the `fields` attribute in the query. + +This operation is performed by calling function `search_by_metadata_query`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-metadata-queries-execute-read/). + + + +```python +client.search.search_by_metadata_query( + search_from, + "0", + query="name = :name AND age < :age AND birthDate >= :birthDate AND countryCode = :countryCode AND sports = :sports", + query_params={ + "name": "John", + "age": 50, + "birthDate": "2001-01-01T02:20:10.120Z", + "countryCode": "US", + "sports": ["basketball", "tennis"], + }, +) +``` + +### Arguments + +- from\_ `str` + - Specifies the template used in the query. Must be in the form `scope.templateKey`. Not all templates can be used in this field, most notably the built-in, Box-provided classification templates can not be used in a query. +- query `Optional[str]` + - The query to perform. A query is a logical expression that is very similar to a SQL `SELECT` statement. Values in the search query can be turned into parameters specified in the `query_param` arguments list to prevent having to manually insert search values into the query string. For example, a value of `:amount` would represent the `amount` value in `query_params` object. +- query_params `Optional[Dict]` + - Set of arguments corresponding to the parameters specified in the `query`. The type of each parameter used in the `query_params` must match the type of the corresponding metadata template field. +- ancestor_folder_id `str` + - The ID of the folder that you are restricting the query to. A value of zero will return results from all folders you have access to. A non-zero value will only return results found in the folder corresponding to the ID or in any of its subfolders. +- order_by `Optional[List[SearchByMetadataQueryOrderBy]]` + - A list of template fields and directions to sort the metadata query results by. The ordering `direction` must be the same for each item in the array. +- limit `Optional[int]` + - A value between 0 and 100 that indicates the maximum number of results to return for a single request. This only specifies a maximum boundary and will not guarantee the minimum number of results returned. +- marker `Optional[str]` + - Marker to use for requesting the next page. +- fields `Optional[List[str]]` + - By default, this endpoint returns only the most basic info about the items for which the query matches. This attribute can be used to specify a list of additional attributes to return for any item, including its metadata. This attribute takes a list of item fields, metadata template identifiers, or metadata template field identifiers. For example: _ `created_by` will add the details of the user who created the item to the response. _ `metadata..` will return the mini-representation of the metadata instance identified by the `scope` and `templateKey`. \* `metadata...` will return all the mini-representation of the metadata instance identified by the `scope` and `templateKey` plus the field specified by the `field` name. Multiple fields for the same `scope` and `templateKey` can be defined. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataQueryResults`. + +Returns a list of files and folders that match this metadata query. + +## Search for content + +Searches for files, folders, web links, and shared files across the +users content or across the entire enterprise. + +This operation is performed by calling function `search_for_content`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-search/). + + + +```python +client.search.search_for_content( + ancestor_folder_ids=["0"], + mdfilters=[ + MetadataFilter( + filters=search_filters, + scope=MetadataFilterScopeField.ENTERPRISE, + template_key=template_key, + ) + ], +) +``` + +### Arguments + +- query `Optional[str]` + - The string to search for. This query is matched against item names, descriptions, text content of files, and various other fields of the different item types. This parameter supports a variety of operators to further refine the results returns. _ `""` - by wrapping a query in double quotes only exact matches are returned by the API. Exact searches do not return search matches based on specific character sequences. Instead, they return matches based on phrases, that is, word sequences. For example: A search for `"Blue-Box"` may return search results including the sequence `"blue.box"`, `"Blue Box"`, and `"Blue-Box"`; any item containing the words `Blue` and `Box` consecutively, in the order specified. _ `AND` - returns items that contain both the search terms. For example, a search for `marketing AND BoxWorks` returns items that have both `marketing` and `BoxWorks` within its text in any order. It does not return a result that only has `BoxWorks` in its text. _ `OR` - returns items that contain either of the search terms. For example, a search for `marketing OR BoxWorks` returns a result that has either `marketing` or `BoxWorks` within its text. Using this operator is not necessary as we implicitly interpret multi-word queries as `OR` unless another supported boolean term is used. _ `NOT` - returns items that do not contain the search term provided. For example, a search for `marketing AND NOT BoxWorks` returns a result that has only `marketing` within its text. Results containing `BoxWorks` are omitted. We do not support lower case (that is, `and`, `or`, and `not`) or mixed case (that is, `And`, `Or`, and `Not`) operators. This field is required unless the `mdfilters` parameter is defined. +- scope `Optional[SearchForContentScope]` + - Limits the search results to either the files that the user has access to, or to files available to the entire enterprise. The scope defaults to `user_content`, which limits the search results to content that is available to the currently authenticated user. The `enterprise_content` can be requested by an admin through our support channels. Once this scope has been enabled for a user, it will allow that use to query for content across the entire enterprise and not only the content that they have access to. +- file_extensions `Optional[List[str]]` + - Limits the search results to any files that match any of the provided file extensions. This list is a comma-separated list of file extensions without the dots. +- created_at_range `Optional[List[str]]` + - Limits the search results to any items created within a given date range. Date ranges are defined as comma separated RFC3339 timestamps. If the the start date is omitted (`,2014-05-17T13:35:01-07:00`) anything created before the end date will be returned. If the end date is omitted (`2014-05-15T13:35:01-07:00,`) the current date will be used as the end date instead. +- updated_at_range `Optional[List[str]]` + - Limits the search results to any items updated within a given date range. Date ranges are defined as comma separated RFC3339 timestamps. If the start date is omitted (`,2014-05-17T13:35:01-07:00`) anything updated before the end date will be returned. If the end date is omitted (`2014-05-15T13:35:01-07:00,`) the current date will be used as the end date instead. +- size_range `Optional[List[int]]` + - Limits the search results to any items with a size within a given file size range. This applied to files and folders. Size ranges are defined as comma separated list of a lower and upper byte size limit (inclusive). The upper and lower bound can be omitted to create open ranges. +- owner_user_ids `Optional[List[str]]` + - Limits the search results to any items that are owned by the given list of owners, defined as a list of comma separated user IDs. The items still need to be owned or shared with the currently authenticated user for them to show up in the search results. If the user does not have access to any files owned by any of the users an empty result set will be returned. To search across an entire enterprise, we recommend using the `enterprise_content` scope parameter which can be requested with our support team. +- recent_updater_user_ids `Optional[List[str]]` + - Limits the search results to any items that have been updated by the given list of users, defined as a list of comma separated user IDs. The items still need to be owned or shared with the currently authenticated user for them to show up in the search results. If the user does not have access to any files owned by any of the users an empty result set will be returned. This feature only searches back to the last 10 versions of an item. +- ancestor_folder_ids `Optional[List[str]]` + - Limits the search results to items within the given list of folders, defined as a comma separated lists of folder IDs. Search results will also include items within any subfolders of those ancestor folders. The folders still need to be owned or shared with the currently authenticated user. If the folder is not accessible by this user, or it does not exist, a `HTTP 404` error code will be returned instead. To search across an entire enterprise, we recommend using the `enterprise_content` scope parameter which can be requested with our support team. +- content_types `Optional[List[SearchForContentContentTypes]]` + - Limits the search results to any items that match the search query for a specific part of the file, for example the file description. Content types are defined as a comma separated lists of Box recognized content types. The allowed content types are as follows. _ `name` - The name of the item, as defined by its `name` field. _ `description` - The description of the item, as defined by its `description` field. _ `file_content` - The actual content of the file. _ `comments` - The content of any of the comments on a file or folder. \* `tags` - Any tags that are applied to an item, as defined by its `tags` field. +- type `Optional[SearchForContentType]` + - Limits the search results to any items of this type. This parameter only takes one value. By default the API returns items that match any of these types. _ `file` - Limits the search results to files, _ `folder` - Limits the search results to folders, \* `web_link` - Limits the search results to web links, also known as bookmarks. +- 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 is a list that specifies exactly **one** metadata template used to filter the search results. The parameter 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]` + - Defines the direction in which search results are ordered. This API defaults to returning items in descending (`DESC`) order unless this parameter is explicitly specified. When results are sorted by `relevance` the ordering is locked to returning items in descending order of relevance, and this parameter is ignored. +- limit `Optional[int]` + - Defines the maximum number of items to return as part of a page of results. +- include_recent_shared_links `Optional[bool]` + - Defines whether the search results should include any items that the user recently accessed through a shared link. When this parameter has been set to true, the format of the response of this API changes to return a list of [Search Results with Shared Links](r://search_results_with_shared_links). +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- deleted_user_ids `Optional[List[str]]` + - Limits the search results to items that were deleted by the given list of users, defined as a list of comma separated user IDs. The `trash_content` parameter needs to be set to `trashed_only`. If searching in trash is not performed, an empty result set is returned. The items need to be owned or shared with the currently authenticated user for them to show up in the search results. If the user does not have access to any files owned by any of the users, an empty result set is returned. Data available from 2023-02-01 onwards. +- deleted_at_range `Optional[List[str]]` + - Limits the search results to any items deleted within a given date range. Date ranges are defined as comma separated RFC3339 timestamps. If the the start date is omitted (`2014-05-17T13:35:01-07:00`), anything deleted before the end date will be returned. If the end date is omitted (`2014-05-15T13:35:01-07:00`), the current date will be used as the end date instead. The `trash_content` parameter needs to be set to `trashed_only`. If searching in trash is not performed, then an empty result is returned. Data available from 2023-02-01 onwards. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Union[SearchResults, SearchResultsWithSharedLinks]`. + +Returns a collection of search results. If there are no matching +search results, the `entries` array will be empty. diff --git a/docs/session_termination.md b/docs/session_termination.md new file mode 100644 index 000000000..5e05aed07 --- /dev/null +++ b/docs/session_termination.md @@ -0,0 +1,70 @@ +# SessionTerminationManager + +- [Create jobs to terminate users session](#create-jobs-to-terminate-users-session) +- [Create jobs to terminate user group session](#create-jobs-to-terminate-user-group-session) + +## Create jobs to terminate users session + +Validates the roles and permissions of the user, +and creates asynchronous jobs +to terminate the user's sessions. +Returns the status for the POST request. + +This operation is performed by calling function `terminate_users_sessions`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-users-terminate-sessions/). + + + +```python +client.session_termination.terminate_users_sessions( + [get_env_var("USER_ID")], [user.login] +) +``` + +### Arguments + +- user_ids `List[str]` + - A list of user IDs. +- user_logins `List[str]` + - A list of user logins. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SessionTerminationMessage`. + +Returns a message about the request status. + +## Create jobs to terminate user group session + +Validates the roles and permissions of the group, +and creates asynchronous jobs +to terminate the group's sessions. +Returns the status for the POST request. + +This operation is performed by calling function `terminate_groups_sessions`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-groups-terminate-sessions/). + + + +```python +client.session_termination.terminate_groups_sessions([group.id]) +``` + +### Arguments + +- group_ids `List[str]` + - A list of group IDs. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SessionTerminationMessage`. + +Returns a message about the request status. diff --git a/docs/shared_links_app_items.md b/docs/shared_links_app_items.md new file mode 100644 index 000000000..740ab9ad7 --- /dev/null +++ b/docs/shared_links_app_items.md @@ -0,0 +1,36 @@ +# SharedLinksAppItemsManager + +- [Find app item for shared link](#find-app-item-for-shared-link) + +## Find app item for shared link + +Returns the app item represented by a shared link. + +The link can originate from the current enterprise or another. + +This operation is performed by calling function `find_app_item_for_shared_link`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shared-items--app-items/). + + + +```python +client.shared_links_app_items.find_app_item_for_shared_link( + "".join(["shared_link=", app_item_shared_link]) +) +``` + +### Arguments + +- boxapi `str` + - A header containing the shared link and optional password for the shared link. The format for this header is `shared_link=[link]&shared_link_password=[password]`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AppItem`. + +Returns a full app item resource if the shared link is valid and +the user has access to it. diff --git a/docs/shared_links_files.md b/docs/shared_links_files.md new file mode 100644 index 000000000..4d9bb23f9 --- /dev/null +++ b/docs/shared_links_files.md @@ -0,0 +1,200 @@ +# SharedLinksFilesManager + +- [Find file for shared link](#find-file-for-shared-link) +- [Get shared link for file](#get-shared-link-for-file) +- [Add shared link to file](#add-shared-link-to-file) +- [Update shared link on file](#update-shared-link-on-file) +- [Remove shared link from file](#remove-shared-link-from-file) + +## Find file for shared link + +Returns the file represented by a shared link. + +A shared file can be represented by a shared link, +which can originate within the current enterprise or within another. + +This endpoint allows an application to retrieve information about a +shared file when only given a shared link. + +The `shared_link_permission_options` array field can be returned +by requesting it in the `fields` query parameter. + +This operation is performed by calling function `find_file_for_shared_link`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shared-items/). + + + +```python +user_client.shared_links_files.find_file_for_shared_link( + "".join( + [ + "shared_link=", + file_from_api.shared_link.url, + "&shared_link_password=Secret123@", + ] + ) +) +``` + +### Arguments + +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- if_none_match `Optional[str]` + - Ensures an item is only returned if it has changed. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `304 Not Modified` if the item has not changed since. +- boxapi `str` + - A header containing the shared link and optional password for the shared link. The format for this header is as follows: `shared_link=[link]&shared_link_password=[password]`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileFull`. + +Returns a full file resource if the shared link is valid and +the user has access to it. + +## Get shared link for file + +Gets the information for a shared link on a file. + +This operation is performed by calling function `get_shared_link_for_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id--get-shared-link/). + + + +```python +client.shared_links_files.get_shared_link_for_file(file_id, "shared_link") +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileFull`. + +Returns the base representation of a file with the +additional shared link information. + +## Add shared link to file + +Adds a shared link to a file. + +This operation is performed by calling function `add_share_link_to_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-id--add-shared-link/). + + + +```python +client.shared_links_files.add_share_link_to_file( + file_id, + "shared_link", + shared_link=AddShareLinkToFileSharedLink( + access=AddShareLinkToFileSharedLinkAccessField.OPEN, password="Secret123@" + ), +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- shared_link `Optional[AddShareLinkToFileSharedLink]` + - The settings for the shared link to create on the file. Use an empty object (`{}`) to use the default settings for shared links. +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileFull`. + +Returns the base representation of a file with a new shared +link attached. + +## Update shared link on file + +Updates a shared link on a file. + +This operation is performed by calling function `update_shared_link_on_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-id--update-shared-link/). + + + +```python +client.shared_links_files.update_shared_link_on_file( + file_id, + "shared_link", + shared_link=UpdateSharedLinkOnFileSharedLink( + access=UpdateSharedLinkOnFileSharedLinkAccessField.COLLABORATORS + ), +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- shared_link `Optional[UpdateSharedLinkOnFileSharedLink]` + - The settings for the shared link to update. +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileFull`. + +Returns a basic representation of the file, with the updated shared +link attached. + +## Remove shared link from file + +Removes a shared link from a file. + +This operation is performed by calling function `remove_shared_link_from_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-id--remove-shared-link/). + + + +```python +client.shared_links_files.remove_shared_link_from_file( + file_id, "shared_link", shared_link=create_null() +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- shared_link `Optional[RemoveSharedLinkFromFileSharedLink]` + - By setting this value to `null`, the shared link is removed from the file. +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FileFull`. + +Returns a basic representation of a file, with the shared link removed. diff --git a/docs/shared_links_folders.md b/docs/shared_links_folders.md new file mode 100644 index 000000000..487793219 --- /dev/null +++ b/docs/shared_links_folders.md @@ -0,0 +1,197 @@ +# SharedLinksFoldersManager + +- [Find folder for shared link](#find-folder-for-shared-link) +- [Get shared link for folder](#get-shared-link-for-folder) +- [Add shared link to folder](#add-shared-link-to-folder) +- [Update shared link on folder](#update-shared-link-on-folder) +- [Remove shared link from folder](#remove-shared-link-from-folder) + +## Find folder for shared link + +Return the folder represented by a shared link. + +A shared folder can be represented by a shared link, +which can originate within the current enterprise or within another. + +This endpoint allows an application to retrieve information about a +shared folder when only given a shared link. + +This operation is performed by calling function `find_folder_for_shared_link`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shared-items--folders/). + + + +```python +user_client.shared_links_folders.find_folder_for_shared_link( + "".join( + [ + "shared_link=", + folder_from_api.shared_link.url, + "&shared_link_password=Secret123@", + ] + ) +) +``` + +### Arguments + +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- if_none_match `Optional[str]` + - Ensures an item is only returned if it has changed. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `304 Not Modified` if the item has not changed since. +- boxapi `str` + - A header containing the shared link and optional password for the shared link. The format for this header is as follows: `shared_link=[link]&shared_link_password=[password]`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderFull`. + +Returns a full folder resource if the shared link is valid and +the user has access to it. + +## Get shared link for folder + +Gets the information for a shared link on a folder. + +This operation is performed by calling function `get_shared_link_for_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-id--get-shared-link/). + + + +```python +client.shared_links_folders.get_shared_link_for_folder(folder.id, "shared_link") +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderFull`. + +Returns the base representation of a folder with the +additional shared link information. + +## Add shared link to folder + +Adds a shared link to a folder. + +This operation is performed by calling function `add_share_link_to_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-folders-id--add-shared-link/). + + + +```python +client.shared_links_folders.add_share_link_to_folder( + folder.id, + "shared_link", + shared_link=AddShareLinkToFolderSharedLink( + access=AddShareLinkToFolderSharedLinkAccessField.OPEN, password="Secret123@" + ), +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- shared_link `Optional[AddShareLinkToFolderSharedLink]` + - The settings for the shared link to create on the folder. Use an empty object (`{}`) to use the default settings for shared links. +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderFull`. + +Returns the base representation of a folder with a new shared +link attached. + +## Update shared link on folder + +Updates a shared link on a folder. + +This operation is performed by calling function `update_shared_link_on_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-folders-id--update-shared-link/). + + + +```python +client.shared_links_folders.update_shared_link_on_folder( + folder.id, + "shared_link", + shared_link=UpdateSharedLinkOnFolderSharedLink( + access=UpdateSharedLinkOnFolderSharedLinkAccessField.COLLABORATORS + ), +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- shared_link `Optional[UpdateSharedLinkOnFolderSharedLink]` + - The settings for the shared link to update. +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderFull`. + +Returns a basic representation of the folder, with the updated shared +link attached. + +## Remove shared link from folder + +Removes a shared link from a folder. + +This operation is performed by calling function `remove_shared_link_from_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-folders-id--remove-shared-link/). + + + +```python +client.shared_links_folders.remove_shared_link_from_folder( + folder.id, "shared_link", shared_link=create_null() +) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- shared_link `Optional[RemoveSharedLinkFromFolderSharedLink]` + - By setting this value to `null`, the shared link is removed from the folder. +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderFull`. + +Returns a basic representation of a folder, with the shared link removed. diff --git a/docs/shared_links_web_links.md b/docs/shared_links_web_links.md new file mode 100644 index 000000000..e9d58dce4 --- /dev/null +++ b/docs/shared_links_web_links.md @@ -0,0 +1,198 @@ +# SharedLinksWebLinksManager + +- [Find web link for shared link](#find-web-link-for-shared-link) +- [Get shared link for web link](#get-shared-link-for-web-link) +- [Add shared link to web link](#add-shared-link-to-web-link) +- [Update shared link on web link](#update-shared-link-on-web-link) +- [Remove shared link from web link](#remove-shared-link-from-web-link) + +## Find web link for shared link + +Returns the web link represented by a shared link. + +A shared web link can be represented by a shared link, +which can originate within the current enterprise or within another. + +This endpoint allows an application to retrieve information about a +shared web link when only given a shared link. + +This operation is performed by calling function `find_web_link_for_shared_link`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shared-items--web-links/). + + + +```python +user_client.shared_links_web_links.find_web_link_for_shared_link( + "".join( + [ + "shared_link=", + web_link_from_api.shared_link.url, + "&shared_link_password=Secret123@", + ] + ) +) +``` + +### Arguments + +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- if_none_match `Optional[str]` + - Ensures an item is only returned if it has changed. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `304 Not Modified` if the item has not changed since. +- boxapi `str` + - A header containing the shared link and optional password for the shared link. The format for this header is as follows: `shared_link=[link]&shared_link_password=[password]`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `WebLink`. + +Returns a full web link resource if the shared link is valid and +the user has access to it. + +## Get shared link for web link + +Gets the information for a shared link on a web link. + +This operation is performed by calling function `get_shared_link_for_web_link`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-web-links-id--get-shared-link/). + + + +```python +client.shared_links_web_links.get_shared_link_for_web_link(web_link_id, "shared_link") +``` + +### Arguments + +- web_link_id `str` + - The ID of the web link. Example: "12345" +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `WebLink`. + +Returns the base representation of a web link with the +additional shared link information. + +## Add shared link to web link + +Adds a shared link to a web link. + +This operation is performed by calling function `add_share_link_to_web_link`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-web-links-id--add-shared-link/). + + + +```python +client.shared_links_web_links.add_share_link_to_web_link( + web_link_id, + "shared_link", + shared_link=AddShareLinkToWebLinkSharedLink( + access=AddShareLinkToWebLinkSharedLinkAccessField.OPEN, password="Secret123@" + ), +) +``` + +### Arguments + +- web_link_id `str` + - The ID of the web link. Example: "12345" +- shared_link `Optional[AddShareLinkToWebLinkSharedLink]` + - The settings for the shared link to create on the web link. Use an empty object (`{}`) to use the default settings for shared links. +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `WebLink`. + +Returns the base representation of a web link with a new shared +link attached. + +## Update shared link on web link + +Updates a shared link on a web link. + +This operation is performed by calling function `update_shared_link_on_web_link`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-web-links-id--update-shared-link/). + + + +```python +client.shared_links_web_links.update_shared_link_on_web_link( + web_link_id, + "shared_link", + shared_link=UpdateSharedLinkOnWebLinkSharedLink( + access=UpdateSharedLinkOnWebLinkSharedLinkAccessField.COLLABORATORS + ), +) +``` + +### Arguments + +- web_link_id `str` + - The ID of the web link. Example: "12345" +- shared_link `Optional[UpdateSharedLinkOnWebLinkSharedLink]` + - The settings for the shared link to update. +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `WebLink`. + +Returns a basic representation of the web link, with the updated shared +link attached. + +## Remove shared link from web link + +Removes a shared link from a web link. + +This operation is performed by calling function `remove_shared_link_from_web_link`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-web-links-id--remove-shared-link/). + + + +```python +client.shared_links_web_links.remove_shared_link_from_web_link( + web_link_id, "shared_link", shared_link=create_null() +) +``` + +### Arguments + +- web_link_id `str` + - The ID of the web link. Example: "12345" +- shared_link `Optional[RemoveSharedLinkFromWebLinkSharedLink]` + - By setting this value to `null`, the shared link is removed from the web link. +- fields `str` + - Explicitly request the `shared_link` fields to be returned for this item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `WebLink`. + +Returns a basic representation of a web link, with the +shared link removed. diff --git a/docs/shield_information_barrier_reports.md b/docs/shield_information_barrier_reports.md new file mode 100644 index 000000000..f9b7990ed --- /dev/null +++ b/docs/shield_information_barrier_reports.md @@ -0,0 +1,101 @@ +# ShieldInformationBarrierReportsManager + +- [List shield information barrier reports](#list-shield-information-barrier-reports) +- [Create shield information barrier report](#create-shield-information-barrier-report) +- [Get shield information barrier report by ID](#get-shield-information-barrier-report-by-id) + +## List shield information barrier reports + +Lists shield information barrier reports. + +This operation is performed by calling function `get_shield_information_barrier_reports`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shield-information-barrier-reports/). + + + +```python +client.shield_information_barrier_reports.get_shield_information_barrier_reports( + barrier_id +) +``` + +### Arguments + +- shield_information_barrier_id `str` + - The ID of the shield information barrier. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierReports`. + +Returns a paginated list of shield information barrier report objects. + +## Create shield information barrier report + +Creates a shield information barrier report for a given barrier. + +This operation is performed by calling function `create_shield_information_barrier_report`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-shield-information-barrier-reports/). + + + +```python +client.shield_information_barrier_reports.create_shield_information_barrier_report( + shield_information_barrier=ShieldInformationBarrierBase( + id=barrier_id, + type=ShieldInformationBarrierBaseTypeField.SHIELD_INFORMATION_BARRIER, + ) +) +``` + +### Arguments + +- shield_information_barrier `Optional[ShieldInformationBarrierBase]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierReport`. + +Returns the shield information barrier report information object. + +## Get shield information barrier report by ID + +Retrieves a shield information barrier report by its ID. + +This operation is performed by calling function `get_shield_information_barrier_report_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shield-information-barrier-reports-id/). + + + +```python +client.shield_information_barrier_reports.get_shield_information_barrier_report_by_id( + created_report.id +) +``` + +### Arguments + +- shield_information_barrier_report_id `str` + - The ID of the shield information barrier Report. Example: "3423" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierReport`. + +Returns the shield information barrier report object. diff --git a/docs/shield_information_barrier_segment_members.md b/docs/shield_information_barrier_segment_members.md new file mode 100644 index 000000000..13fa7510b --- /dev/null +++ b/docs/shield_information_barrier_segment_members.md @@ -0,0 +1,144 @@ +# ShieldInformationBarrierSegmentMembersManager + +- [Get shield information barrier segment member by ID](#get-shield-information-barrier-segment-member-by-id) +- [Delete shield information barrier segment member by ID](#delete-shield-information-barrier-segment-member-by-id) +- [List shield information barrier segment members](#list-shield-information-barrier-segment-members) +- [Create shield information barrier segment member](#create-shield-information-barrier-segment-member) + +## Get shield information barrier segment member by ID + +Retrieves a shield information barrier +segment member by its ID. + +This operation is performed by calling function `get_shield_information_barrier_segment_member_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shield-information-barrier-segment-members-id/). + + + +```python +client.shield_information_barrier_segment_members.get_shield_information_barrier_segment_member_by_id( + segment_member.id +) +``` + +### Arguments + +- shield_information_barrier_segment_member_id `str` + - The ID of the shield information barrier segment Member. Example: "7815" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierSegmentMember`. + +Returns the shield information barrier segment member object. + +## Delete shield information barrier segment member by ID + +Deletes a shield information barrier +segment member based on provided ID. + +This operation is performed by calling function `delete_shield_information_barrier_segment_member_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-shield-information-barrier-segment-members-id/). + + + +```python +client.shield_information_barrier_segment_members.delete_shield_information_barrier_segment_member_by_id( + segment_member.id +) +``` + +### Arguments + +- shield_information_barrier_segment_member_id `str` + - The ID of the shield information barrier segment Member. Example: "7815" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response if the +segment member was deleted successfully. + +## List shield information barrier segment members + +Lists shield information barrier segment members +based on provided segment IDs. + +This operation is performed by calling function `get_shield_information_barrier_segment_members`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shield-information-barrier-segment-members/). + + + +```python +client.shield_information_barrier_segment_members.get_shield_information_barrier_segment_members( + segment.id +) +``` + +### Arguments + +- shield_information_barrier_segment_id `str` + - The ID of the shield information barrier segment. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierSegmentMembers`. + +Returns a paginated list of +shield information barrier segment member objects. + +## Create shield information barrier segment member + +Creates a new shield information barrier segment member. + +This operation is performed by calling function `create_shield_information_barrier_segment_member`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-shield-information-barrier-segment-members/). + + + +```python +client.shield_information_barrier_segment_members.create_shield_information_barrier_segment_member( + CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegment( + id=segment.id, + type=CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentTypeField.SHIELD_INFORMATION_BARRIER_SEGMENT, + ), + UserBase(id=get_env_var("USER_ID")), +) +``` + +### Arguments + +- type `Optional[CreateShieldInformationBarrierSegmentMemberType]` + - A type of the shield barrier segment member. +- shield_information_barrier `Optional[ShieldInformationBarrierBase]` +- shield_information_barrier_segment `CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegment` + - The `type` and `id` of the requested shield information barrier segment. +- user `UserBase` + - User to which restriction will be applied. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierSegmentMember`. + +Returns a new shield information barrier segment member object. diff --git a/docs/shield_information_barrier_segment_restrictions.md b/docs/shield_information_barrier_segment_restrictions.md new file mode 100644 index 000000000..d18dbbe5b --- /dev/null +++ b/docs/shield_information_barrier_segment_restrictions.md @@ -0,0 +1,150 @@ +# ShieldInformationBarrierSegmentRestrictionsManager + +- [Get shield information barrier segment restriction by ID](#get-shield-information-barrier-segment-restriction-by-id) +- [Delete shield information barrier segment restriction by ID](#delete-shield-information-barrier-segment-restriction-by-id) +- [List shield information barrier segment restrictions](#list-shield-information-barrier-segment-restrictions) +- [Create shield information barrier segment restriction](#create-shield-information-barrier-segment-restriction) + +## Get shield information barrier segment restriction by ID + +Retrieves a shield information barrier segment +restriction based on provided ID. + +This operation is performed by calling function `get_shield_information_barrier_segment_restriction_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shield-information-barrier-segment-restrictions-id/). + + + +```python +client.shield_information_barrier_segment_restrictions.get_shield_information_barrier_segment_restriction_by_id( + segment_restriction_id +) +``` + +### Arguments + +- shield_information_barrier_segment_restriction_id `str` + - The ID of the shield information barrier segment Restriction. Example: "4563" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierSegmentRestriction`. + +Returns the shield information barrier segment +restriction object. + +## Delete shield information barrier segment restriction by ID + +Delete shield information barrier segment restriction +based on provided ID. + +This operation is performed by calling function `delete_shield_information_barrier_segment_restriction_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-shield-information-barrier-segment-restrictions-id/). + + + +```python +client.shield_information_barrier_segment_restrictions.delete_shield_information_barrier_segment_restriction_by_id( + segment_restriction_id +) +``` + +### Arguments + +- shield_information_barrier_segment_restriction_id `str` + - The ID of the shield information barrier segment Restriction. Example: "4563" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Empty body in response. + +## List shield information barrier segment restrictions + +Lists shield information barrier segment restrictions +based on provided segment ID. + +This operation is performed by calling function `get_shield_information_barrier_segment_restrictions`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shield-information-barrier-segment-restrictions/). + + + +```python +client.shield_information_barrier_segment_restrictions.get_shield_information_barrier_segment_restrictions( + segment_id +) +``` + +### Arguments + +- shield_information_barrier_segment_id `str` + - The ID of the shield information barrier segment. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierSegmentRestrictions`. + +Returns a paginated list of +shield information barrier segment restriction objects. + +## Create shield information barrier segment restriction + +Creates a shield information barrier +segment restriction object. + +This operation is performed by calling function `create_shield_information_barrier_segment_restriction`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-shield-information-barrier-segment-restrictions/). + + + +```python +client.shield_information_barrier_segment_restrictions.create_shield_information_barrier_segment_restriction( + CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegment( + id=segment_id, + type=CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegmentTypeField.SHIELD_INFORMATION_BARRIER_SEGMENT, + ), + CreateShieldInformationBarrierSegmentRestrictionRestrictedSegment( + id=segment_to_restrict_id, + type=CreateShieldInformationBarrierSegmentRestrictionRestrictedSegmentTypeField.SHIELD_INFORMATION_BARRIER_SEGMENT, + ), + type=CreateShieldInformationBarrierSegmentRestrictionType.SHIELD_INFORMATION_BARRIER_SEGMENT_RESTRICTION, +) +``` + +### Arguments + +- type `CreateShieldInformationBarrierSegmentRestrictionType` + - The type of the shield barrier segment restriction for this member. +- shield_information_barrier `Optional[ShieldInformationBarrierBase]` +- shield_information_barrier_segment `CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegment` + - The `type` and `id` of the requested shield information barrier segment. +- restricted_segment `CreateShieldInformationBarrierSegmentRestrictionRestrictedSegment` + - The `type` and `id` of the restricted shield information barrier segment. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierSegmentRestriction`. + +Returns the newly created Shield +Information Barrier Segment Restriction object. diff --git a/docs/shield_information_barrier_segments.md b/docs/shield_information_barrier_segments.md new file mode 100644 index 000000000..c208101ee --- /dev/null +++ b/docs/shield_information_barrier_segments.md @@ -0,0 +1,175 @@ +# ShieldInformationBarrierSegmentsManager + +- [Get shield information barrier segment with specified ID](#get-shield-information-barrier-segment-with-specified-id) +- [Delete shield information barrier segment](#delete-shield-information-barrier-segment) +- [Update shield information barrier segment with specified ID](#update-shield-information-barrier-segment-with-specified-id) +- [List shield information barrier segments](#list-shield-information-barrier-segments) +- [Create shield information barrier segment](#create-shield-information-barrier-segment) + +## Get shield information barrier segment with specified ID + +Retrieves shield information barrier segment based on provided ID.. + +This operation is performed by calling function `get_shield_information_barrier_segment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shield-information-barrier-segments-id/). + + + +```python +client.shield_information_barrier_segments.get_shield_information_barrier_segment_by_id( + segment_id +) +``` + +### Arguments + +- shield_information_barrier_segment_id `str` + - The ID of the shield information barrier segment. Example: "3423" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierSegment`. + +Returns the shield information barrier segment object. + +## Delete shield information barrier segment + +Deletes the shield information barrier segment +based on provided ID. + +This operation is performed by calling function `delete_shield_information_barrier_segment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-shield-information-barrier-segments-id/). + + + +```python +client.shield_information_barrier_segments.delete_shield_information_barrier_segment_by_id( + segment_id +) +``` + +### Arguments + +- shield_information_barrier_segment_id `str` + - The ID of the shield information barrier segment. Example: "3423" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Empty body in response. + +## Update shield information barrier segment with specified ID + +Updates the shield information barrier segment based on provided ID.. + +This operation is performed by calling function `update_shield_information_barrier_segment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-shield-information-barrier-segments-id/). + + + +```python +client.shield_information_barrier_segments.update_shield_information_barrier_segment_by_id( + segment_id, description=updated_segment_description +) +``` + +### Arguments + +- shield_information_barrier_segment_id `str` + - The ID of the shield information barrier segment. Example: "3423" +- name `Optional[str]` + - The updated name for the shield information barrier segment. +- description `Optional[str]` + - The updated description for the shield information barrier segment. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierSegment`. + +Returns the updated shield information barrier segment object. + +## List shield information barrier segments + +Retrieves a list of shield information barrier segment objects +for the specified Information Barrier ID. + +This operation is performed by calling function `get_shield_information_barrier_segments`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shield-information-barrier-segments/). + + + +```python +client.shield_information_barrier_segments.get_shield_information_barrier_segments( + barrier_id +) +``` + +### Arguments + +- shield_information_barrier_id `str` + - The ID of the shield information barrier. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierSegments`. + +Returns a paginated list of shield information barrier segment objects. + +## Create shield information barrier segment + +Creates a shield information barrier segment. + +This operation is performed by calling function `create_shield_information_barrier_segment`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-shield-information-barrier-segments/). + + + +```python +client.shield_information_barrier_segments.create_shield_information_barrier_segment( + ShieldInformationBarrierBase( + id=barrier_id, + type=ShieldInformationBarrierBaseTypeField.SHIELD_INFORMATION_BARRIER, + ), + segment_name, + description=segment_description, +) +``` + +### Arguments + +- shield_information_barrier `ShieldInformationBarrierBase` +- name `str` + - Name of the shield information barrier segment. +- description `Optional[str]` + - Description of the shield information barrier segment. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrierSegment`. + +Returns a new shield information barrier segment object. diff --git a/docs/shield_information_barriers.md b/docs/shield_information_barriers.md new file mode 100644 index 000000000..50b3efce8 --- /dev/null +++ b/docs/shield_information_barriers.md @@ -0,0 +1,131 @@ +# ShieldInformationBarriersManager + +- [Get shield information barrier with specified ID](#get-shield-information-barrier-with-specified-id) +- [Add changed status of shield information barrier with specified ID](#add-changed-status-of-shield-information-barrier-with-specified-id) +- [List shield information barriers](#list-shield-information-barriers) +- [Create shield information barrier](#create-shield-information-barrier) + +## Get shield information barrier with specified ID + +Get shield information barrier based on provided ID. + +This operation is performed by calling function `get_shield_information_barrier_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shield-information-barriers-id/). + + + +```python +client.shield_information_barriers.get_shield_information_barrier_by_id(barrier_id) +``` + +### Arguments + +- shield_information_barrier_id `str` + - The ID of the shield information barrier. Example: "1910967" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrier`. + +Returns the shield information barrier object. + +## Add changed status of shield information barrier with specified ID + +Change status of shield information barrier with the specified ID. + +This operation is performed by calling function `update_shield_information_barrier_status`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-shield-information-barriers-change-status/). + + + +```python +client.shield_information_barriers.update_shield_information_barrier_status( + barrier_id, UpdateShieldInformationBarrierStatusStatus.DISABLED +) +``` + +### Arguments + +- id `str` + - The ID of the shield information barrier. +- status `UpdateShieldInformationBarrierStatusStatus` + - The desired status for the shield information barrier. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrier`. + +Returns the updated shield information barrier object. + +## List shield information barriers + +Retrieves a list of shield information barrier objects +for the enterprise of JWT. + +This operation is performed by calling function `get_shield_information_barriers`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-shield-information-barriers/). + + + +```python +client.shield_information_barriers.get_shield_information_barriers() +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarriers`. + +Returns a paginated list of +shield information barrier objects, +empty list if currently no barrier. + +## Create shield information barrier + +Creates a shield information barrier to +separate individuals/groups within the same +firm and prevents confidential information passing between them. + +This operation is performed by calling function `create_shield_information_barrier`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-shield-information-barriers/). + + + +```python +client.shield_information_barriers.create_shield_information_barrier( + EnterpriseBase(id=enterprise_id) +) +``` + +### Arguments + +- enterprise `EnterpriseBase` + - The `type` and `id` of enterprise this barrier is under. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldInformationBarrier`. + +Returns a new shield information barrier object. diff --git a/docs/shield_lists.md b/docs/shield_lists.md new file mode 100644 index 000000000..39505cada --- /dev/null +++ b/docs/shield_lists.md @@ -0,0 +1,177 @@ +# ShieldListsManager + +- [Get all shield lists in enterprise](#get-all-shield-lists-in-enterprise) +- [Create shield list](#create-shield-list) +- [Get single shield list by shield list id](#get-single-shield-list-by-shield-list-id) +- [Delete single shield list by shield list id](#delete-single-shield-list-by-shield-list-id) +- [Update shield list](#update-shield-list) + +## Get all shield lists in enterprise + +Retrieves all shield lists in the enterprise. + +This operation is performed by calling function `get_shield_lists_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-shield-lists/). + + + +```python +client.shield_lists.get_shield_lists_v2025_r0() +``` + +### Arguments + +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldListsV2025R0`. + +Returns the list of shield list objects. + +## Create shield list + +Creates a shield list. + +This operation is performed by calling function `create_shield_list_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/post-shield-lists/). + + + +```python +client.shield_lists.create_shield_list_v2025_r0( + shield_list_country_name, + ShieldListContentCountryV2025R0( + type=ShieldListContentCountryV2025R0TypeField.COUNTRY, + country_codes=["US", "PL"], + ), + description="A list of things that are shielded", +) +``` + +### Arguments + +- name `str` + - The name of the shield list. +- description `Optional[str]` + - Optional description of Shield List. +- content `ShieldListContentRequestV2025R0` +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldListV2025R0`. + +Returns the shield list object. + +## Get single shield list by shield list id + +Retrieves a single shield list by its ID. + +This operation is performed by calling function `get_shield_list_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-shield-lists-id/). + + + +```python +client.shield_lists.get_shield_list_by_id_v2025_r0(shield_list_country.id) +``` + +### Arguments + +- shield_list_id `str` + - The unique identifier that represents a shield list. The ID for any Shield List can be determined by the response from the endpoint fetching all shield lists for the enterprise. Example: "90fb0e17-c332-40ed-b4f9-fa8908fbbb24 " +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldListV2025R0`. + +Returns the shield list object. + +## Delete single shield list by shield list id + +Delete a single shield list by its ID. + +This operation is performed by calling function `delete_shield_list_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/delete-shield-lists-id/). + + + +```python +client.shield_lists.delete_shield_list_by_id_v2025_r0(shield_list_country.id) +``` + +### Arguments + +- shield_list_id `str` + - The unique identifier that represents a shield list. The ID for any Shield List can be determined by the response from the endpoint fetching all shield lists for the enterprise. Example: "90fb0e17-c332-40ed-b4f9-fa8908fbbb24 " +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Shield List correctly removed. No content in response. + +## Update shield list + +Updates a shield list. + +This operation is performed by calling function `update_shield_list_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/put-shield-lists-id/). + + + +```python +client.shield_lists.update_shield_list_by_id_v2025_r0( + shield_list_country.id, + shield_list_country_name, + ShieldListContentCountryV2025R0( + type=ShieldListContentCountryV2025R0TypeField.COUNTRY, country_codes=["US"] + ), + description="Updated description", +) +``` + +### Arguments + +- shield_list_id `str` + - The unique identifier that represents a shield list. The ID for any Shield List can be determined by the response from the endpoint fetching all shield lists for the enterprise. Example: "90fb0e17-c332-40ed-b4f9-fa8908fbbb24 " +- name `str` + - The name of the shield list. +- description `Optional[str]` + - Optional description of Shield List. +- content `ShieldListContentRequestV2025R0` +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ShieldListV2025R0`. + +Returns the shield list object. diff --git a/docs/sign_requests.md b/docs/sign_requests.md new file mode 100644 index 000000000..bee346654 --- /dev/null +++ b/docs/sign_requests.md @@ -0,0 +1,214 @@ +# SignRequestsManager + +- [Cancel Box Sign request](#cancel-box-sign-request) +- [Resend Box Sign request](#resend-box-sign-request) +- [Get Box Sign request by ID](#get-box-sign-request-by-id) +- [List Box Sign requests](#list-box-sign-requests) +- [Create Box Sign request](#create-box-sign-request) + +## Cancel Box Sign request + +Cancels a sign request. + +This operation is performed by calling function `cancel_sign_request`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-sign-requests-id-cancel/). + + + +```python +client.sign_requests.cancel_sign_request(created_sign_request.id) +``` + +### Arguments + +- sign_request_id `str` + - The ID of the signature request. Example: "33243242" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SignRequest`. + +Returns a Sign Request object. + +## Resend Box Sign request + +Resends a signature request email to all outstanding signers. + +This operation is performed by calling function `resend_sign_request`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-sign-requests-id-resend/). + +_Currently we don't have an example for calling `resend_sign_request` in integration tests_ + +### Arguments + +- sign_request_id `str` + - The ID of the signature request. Example: "33243242" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the API call was successful. +The email notifications will be sent asynchronously. + +## Get Box Sign request by ID + +Gets a sign request by ID. + +This operation is performed by calling function `get_sign_request_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-sign-requests-id/). + + + +```python +client.sign_requests.get_sign_request_by_id(created_sign_request.id) +``` + +### Arguments + +- sign_request_id `str` + - The ID of the signature request. Example: "33243242" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SignRequest`. + +Returns a signature request. + +## List Box Sign requests + +Gets signature requests created by a user. If the `sign_files` and/or +`parent_folder` are deleted, the signature request will not return in the list. + +This operation is performed by calling function `get_sign_requests`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-sign-requests/). + + + +```python +client.sign_requests.get_sign_requests() +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- senders `Optional[List[str]]` + - A list of sender emails to filter the signature requests by sender. If provided, `shared_requests` must be set to `true`. +- shared_requests `Optional[bool]` + - If set to `true`, only includes requests that user is not an owner, but user is a collaborator. Collaborator access is determined by the user access level of the sign files of the request. Default is `false`. Must be set to `true` if `senders` are provided. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SignRequests`. + +Returns a collection of sign requests. + +## Create Box Sign request + +Creates a signature request. This involves preparing a document for signing and +sending the signature request to signers. + +This operation is performed by calling function `create_sign_request`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-sign-requests/). + + + +```python +client.sign_requests.create_sign_request( + [ + SignRequestCreateSigner( + email=signer_email, + suppress_notifications=True, + declined_redirect_url="https://www.box.com", + embed_url_external_user_id="123", + is_in_person=False, + login_required=False, + password="password", + role=SignRequestCreateSignerRoleField.SIGNER, + ) + ], + source_files=[FileBase(id=file_to_sign.id)], + parent_folder=FolderMini(id=destination_folder.id), + is_document_preparation_needed=False, + redirect_url="https://www.box.com", + declined_redirect_url="https://www.box.com", + are_text_signatures_enabled=True, + email_subject="Sign this document", + email_message="Please sign this document", + are_reminders_enabled=True, + name="Sign Request", + prefill_tags=[ + SignRequestPrefillTag( + date_value=date_from_string("2035-01-01"), document_tag_id="0" + ) + ], + days_valid=30, + external_id="123", + external_system_name="BoxSignIntegration", +) +``` + +### Arguments + +- source_files `Optional[List[FileBase]]` + - List of files to create a signing document from. This is currently limited to ten files. Only the ID and type fields are required for each file. +- signature_color `Optional[CreateSignRequestSignatureColor]` + - Force a specific color for the signature (blue, black, or red). +- signers `List[SignRequestCreateSigner]` + - Array of signers for the signature request. 35 is the max number of signers permitted. **Note**: It may happen that some signers belong to conflicting [segments](r://shield-information-barrier-segment-member) (user groups). This means that due to the security policies, users are assigned to segments to prevent exchanges or communication that could lead to ethical conflicts. In such a case, an attempt to send the sign request will result in an error. Read more about [segments and ethical walls](https://support.box.com/hc/en-us/articles/9920431507603-Understanding-Information-Barriers#h_01GFVJEHQA06N7XEZ4GCZ9GFAQ). +- parent_folder `Optional[FolderMini]` +- is_document_preparation_needed `Optional[bool]` + - Indicates if the sender should receive a `prepare_url` in the response to complete document preparation using the UI. +- redirect_url `Optional[str]` + - When specified, the signature request will be redirected to this url when a document is signed. +- declined_redirect_url `Optional[str]` + - The uri that a signer will be redirected to after declining to sign a document. +- are_text_signatures_enabled `Optional[bool]` + - Disables the usage of signatures generated by typing (text). +- email_subject `Optional[str]` + - Subject of sign request email. This is cleaned by sign request. If this field is not passed, a default subject will be used. +- email_message `Optional[str]` + - Message to include in sign request email. The field is cleaned through sanitization of specific characters. However, some html tags are allowed. Links included in the message are also converted to hyperlinks in the email. The message may contain the following html tags including `a`, `abbr`, `acronym`, `b`, `blockquote`, `code`, `em`, `i`, `ul`, `li`, `ol`, and `strong`. Be aware that when the text to html ratio is too high, the email may end up in spam filters. Custom styles on these tags are not allowed. If this field is not passed, a default message will be used. +- are_reminders_enabled `Optional[bool]` + - Reminds signers to sign a document on day 3, 8, 13 and 18. Reminders are only sent to outstanding signers. +- name `Optional[str]` + - Name of the signature request. +- prefill_tags `Optional[List[SignRequestPrefillTag]]` + - When a document contains sign-related tags in the content, you can prefill them using this `prefill_tags` by referencing the 'id' of the tag as the `external_id` field of the prefill tag. +- days_valid `Optional[int]` + - Set the number of days after which the created signature request will automatically expire if not completed. By default, we do not apply any expiration date on signature requests, and the signature request does not expire. +- external_id `Optional[str]` + - This can be used to reference an ID in an external system that the sign request is related to. +- template_id `Optional[str]` + - When a signature request is created from a template this field will indicate the id of that template. +- external_system_name `Optional[str]` + - Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SignRequest`. + +Returns a Box Sign request object. diff --git a/docs/sign_templates.md b/docs/sign_templates.md new file mode 100644 index 000000000..c9c6bfa19 --- /dev/null +++ b/docs/sign_templates.md @@ -0,0 +1,62 @@ +# SignTemplatesManager + +- [List Box Sign templates](#list-box-sign-templates) +- [Get Box Sign template by ID](#get-box-sign-template-by-id) + +## List Box Sign templates + +Gets Box Sign templates created by a user. + +This operation is performed by calling function `get_sign_templates`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-sign-templates/). + + + +```python +client.sign_templates.get_sign_templates(limit=2) +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SignTemplates`. + +Returns a collection of templates. + +## Get Box Sign template by ID + +Fetches details of a specific Box Sign template. + +This operation is performed by calling function `get_sign_template_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-sign-templates-id/). + + + +```python +client.sign_templates.get_sign_template_by_id(sign_templates.entries[0].id) +``` + +### Arguments + +- template_id `str` + - The ID of a Box Sign template. Example: "123075213-7d117509-8f05-42e4-a5ef-5190a319d41d" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SignTemplate`. + +Returns details of a template. diff --git a/docs/skills.md b/docs/skills.md new file mode 100644 index 000000000..1c15dbe9b --- /dev/null +++ b/docs/skills.md @@ -0,0 +1,173 @@ +# SkillsManager + +- [List Box Skill cards on file](#list-box-skill-cards-on-file) +- [Create Box Skill cards on file](#create-box-skill-cards-on-file) +- [Update Box Skill cards on file](#update-box-skill-cards-on-file) +- [Remove Box Skill cards from file](#remove-box-skill-cards-from-file) +- [Update all Box Skill cards on file](#update-all-box-skill-cards-on-file) + +## List Box Skill cards on file + +List the Box Skills metadata cards that are attached to a file. + +This operation is performed by calling function `get_box_skill_cards_on_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-metadata-global-boxSkillsCards/). + + + +```python +client.skills.get_box_skill_cards_on_file(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SkillCardsMetadata`. + +Returns all the metadata associated with a file. + +This API does not support pagination and will therefore always return +all of the metadata associated to the file. + +## Create Box Skill cards on file + +Applies one or more Box Skills metadata cards to a file. + +This operation is performed by calling function `create_box_skill_cards_on_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-id-metadata-global-boxSkillsCards/). + + + +```python +client.skills.create_box_skill_cards_on_file(file.id, cards_to_create) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- cards `List[Union[KeywordSkillCard, TimelineSkillCard, TranscriptSkillCard, StatusSkillCard]]` + - A list of Box Skill cards to apply to this file. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SkillCardsMetadata`. + +Returns the instance of the template that was applied to the file, +including the data that was applied to the template. + +## Update Box Skill cards on file + +Updates one or more Box Skills metadata cards to a file. + +This operation is performed by calling function `update_box_skill_cards_on_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-files-id-metadata-global-boxSkillsCards/). + + + +```python +client.skills.update_box_skill_cards_on_file( + file.id, + [ + UpdateBoxSkillCardsOnFileRequestBody( + op=UpdateBoxSkillCardsOnFileRequestBodyOpField.REPLACE, + path="/cards/0", + value=card_to_update, + ) + ], +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- request_body `List[UpdateBoxSkillCardsOnFileRequestBody]` + - Request body of updateBoxSkillCardsOnFile method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `SkillCardsMetadata`. + +Returns the updated metadata template, with the +custom template data included. + +## Remove Box Skill cards from file + +Removes any Box Skills cards metadata from a file. + +This operation is performed by calling function `delete_box_skill_cards_from_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-files-id-metadata-global-boxSkillsCards/). + + + +```python +client.skills.delete_box_skill_cards_from_file(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the cards are +successfully deleted. + +## Update all Box Skill cards on file + +An alternative method that can be used to overwrite and update all Box Skill +metadata cards on a file. + +This operation is performed by calling function `update_all_skill_cards_on_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-skill-invocations-id/). + +_Currently we don't have an example for calling `update_all_skill_cards_on_file` in integration tests_ + +### Arguments + +- skill_id `str` + - The ID of the skill to apply this metadata for. Example: "33243242" +- status `UpdateAllSkillCardsOnFileStatus` + - Defines the status of this invocation. Set this to `success` when setting Skill cards. +- metadata `UpdateAllSkillCardsOnFileMetadata` + - The metadata to set for this skill. This is a list of Box Skills cards. These cards will overwrite any existing Box skill cards on the file. +- file `UpdateAllSkillCardsOnFileFile` + - The file to assign the cards to. +- file_version `Optional[UpdateAllSkillCardsOnFileFileVersion]` + - The optional file version to assign the cards to. +- usage `Optional[UpdateAllSkillCardsOnFileUsage]` + - A descriptor that defines what items are affected by this call. Set this to the default values when setting a card to a `success` state, and leave it out in most other situations. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the card has been successfully updated. diff --git a/docs/source/boxsdk.auth.rst b/docs/source/boxsdk.auth.rst deleted file mode 100644 index bba00b889..000000000 --- a/docs/source/boxsdk.auth.rst +++ /dev/null @@ -1,85 +0,0 @@ -boxsdk.auth package -=================== - -Submodules ----------- - -boxsdk.auth.ccg\_auth module ----------------------------- - -.. automodule:: boxsdk.auth.ccg_auth - :members: - :undoc-members: - :show-inheritance: - -boxsdk.auth.cooperatively\_managed\_oauth2 module -------------------------------------------------- - -.. automodule:: boxsdk.auth.cooperatively_managed_oauth2 - :members: - :undoc-members: - :show-inheritance: - -boxsdk.auth.developer\_token\_auth module ------------------------------------------ - -.. automodule:: boxsdk.auth.developer_token_auth - :members: - :undoc-members: - :show-inheritance: - -boxsdk.auth.jwt\_auth module ----------------------------- - -.. automodule:: boxsdk.auth.jwt_auth - :members: - :undoc-members: - :show-inheritance: - -boxsdk.auth.oauth2 module -------------------------- - -.. automodule:: boxsdk.auth.oauth2 - :members: - :undoc-members: - :show-inheritance: - -boxsdk.auth.redis\_managed\_jwt\_auth module --------------------------------------------- - -.. automodule:: boxsdk.auth.redis_managed_jwt_auth - :members: - :undoc-members: - :show-inheritance: - -boxsdk.auth.redis\_managed\_oauth2 module ------------------------------------------ - -.. automodule:: boxsdk.auth.redis_managed_oauth2 - :members: - :undoc-members: - :show-inheritance: - -boxsdk.auth.remote\_managed\_oauth2 module ------------------------------------------- - -.. automodule:: boxsdk.auth.remote_managed_oauth2 - :members: - :undoc-members: - :show-inheritance: - -boxsdk.auth.server\_auth module -------------------------------- - -.. automodule:: boxsdk.auth.server_auth - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: boxsdk.auth - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/boxsdk.client.rst b/docs/source/boxsdk.client.rst deleted file mode 100644 index f4028ab6d..000000000 --- a/docs/source/boxsdk.client.rst +++ /dev/null @@ -1,45 +0,0 @@ -boxsdk.client package -===================== - -Submodules ----------- - -boxsdk.client.client module ---------------------------- - -.. automodule:: boxsdk.client.client - :members: - :undoc-members: - :show-inheritance: - -boxsdk.client.developer\_token\_client module ---------------------------------------------- - -.. automodule:: boxsdk.client.developer_token_client - :members: - :undoc-members: - :show-inheritance: - -boxsdk.client.development\_client module ----------------------------------------- - -.. automodule:: boxsdk.client.development_client - :members: - :undoc-members: - :show-inheritance: - -boxsdk.client.logging\_client module ------------------------------------- - -.. automodule:: boxsdk.client.logging_client - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: boxsdk.client - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/boxsdk.network.rst b/docs/source/boxsdk.network.rst deleted file mode 100644 index bb38e8b7a..000000000 --- a/docs/source/boxsdk.network.rst +++ /dev/null @@ -1,29 +0,0 @@ -boxsdk.network package -====================== - -Submodules ----------- - -boxsdk.network.default\_network module --------------------------------------- - -.. automodule:: boxsdk.network.default_network - :members: - :undoc-members: - :show-inheritance: - -boxsdk.network.network\_interface module ----------------------------------------- - -.. automodule:: boxsdk.network.network_interface - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: boxsdk.network - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/boxsdk.object.rst b/docs/source/boxsdk.object.rst deleted file mode 100644 index 142a6994f..000000000 --- a/docs/source/boxsdk.object.rst +++ /dev/null @@ -1,421 +0,0 @@ -boxsdk.object package -===================== - -Submodules ----------- - -boxsdk.object.api\_json\_object module --------------------------------------- - -.. automodule:: boxsdk.object.api_json_object - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.base\_api\_json\_object module --------------------------------------------- - -.. automodule:: boxsdk.object.base_api_json_object - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.base\_endpoint module ------------------------------------ - -.. automodule:: boxsdk.object.base_endpoint - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.base\_item module -------------------------------- - -.. automodule:: boxsdk.object.base_item - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.base\_object module ---------------------------------- - -.. automodule:: boxsdk.object.base_object - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.cloneable module ------------------------------- - -.. automodule:: boxsdk.object.cloneable - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.collaboration module ----------------------------------- - -.. automodule:: boxsdk.object.collaboration - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.collaboration\_allowlist module ---------------------------------------------- - -.. automodule:: boxsdk.object.collaboration_allowlist - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.collaboration\_allowlist\_entry module ----------------------------------------------------- - -.. automodule:: boxsdk.object.collaboration_allowlist_entry - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.collaboration\_allowlist\_exempt\_target module -------------------------------------------------------------- - -.. automodule:: boxsdk.object.collaboration_allowlist_exempt_target - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.collection module -------------------------------- - -.. automodule:: boxsdk.object.collection - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.comment module ----------------------------- - -.. automodule:: boxsdk.object.comment - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.device\_pinner module ------------------------------------ - -.. automodule:: boxsdk.object.device_pinner - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.email\_alias module ---------------------------------- - -.. automodule:: boxsdk.object.email_alias - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.enterprise module -------------------------------- - -.. automodule:: boxsdk.object.enterprise - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.event module --------------------------- - -.. automodule:: boxsdk.object.event - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.events module ---------------------------- - -.. automodule:: boxsdk.object.events - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.file module -------------------------- - -.. automodule:: boxsdk.object.file - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.file\_request module ----------------------------------- - -.. automodule:: boxsdk.object.file_request - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.file\_version module ----------------------------------- - -.. automodule:: boxsdk.object.file_version - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.file\_version\_retention module ---------------------------------------------- - -.. automodule:: boxsdk.object.file_version_retention - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.folder module ---------------------------- - -.. automodule:: boxsdk.object.folder - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.folder\_lock module ---------------------------------- - -.. automodule:: boxsdk.object.folder_lock - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.group module --------------------------- - -.. automodule:: boxsdk.object.group - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.group\_membership module --------------------------------------- - -.. automodule:: boxsdk.object.group_membership - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.invite module ---------------------------- - -.. automodule:: boxsdk.object.invite - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.item module -------------------------- - -.. automodule:: boxsdk.object.item - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.legal\_hold module --------------------------------- - -.. automodule:: boxsdk.object.legal_hold - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.legal\_hold\_policy module ----------------------------------------- - -.. automodule:: boxsdk.object.legal_hold_policy - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.legal\_hold\_policy\_assignment module ----------------------------------------------------- - -.. automodule:: boxsdk.object.legal_hold_policy_assignment - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.metadata module ------------------------------ - -.. automodule:: boxsdk.object.metadata - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.metadata\_cascade\_policy module ----------------------------------------------- - -.. automodule:: boxsdk.object.metadata_cascade_policy - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.metadata\_template module ---------------------------------------- - -.. automodule:: boxsdk.object.metadata_template - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.recent\_item module ---------------------------------- - -.. automodule:: boxsdk.object.recent_item - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.retention\_policy module --------------------------------------- - -.. automodule:: boxsdk.object.retention_policy - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.retention\_policy\_assignment module --------------------------------------------------- - -.. automodule:: boxsdk.object.retention_policy_assignment - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.search module ---------------------------- - -.. automodule:: boxsdk.object.search - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.sign\_request module ----------------------------------- - -.. automodule:: boxsdk.object.sign_request - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.sign\_template module ------------------------------------ - -.. automodule:: boxsdk.object.sign_template - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.storage\_policy module ------------------------------------- - -.. automodule:: boxsdk.object.storage_policy - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.storage\_policy\_assignment module ------------------------------------------------- - -.. automodule:: boxsdk.object.storage_policy_assignment - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.task module -------------------------- - -.. automodule:: boxsdk.object.task - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.task\_assignment module -------------------------------------- - -.. automodule:: boxsdk.object.task_assignment - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.terms\_of\_service module ---------------------------------------- - -.. automodule:: boxsdk.object.terms_of_service - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.terms\_of\_service\_user\_status module ------------------------------------------------------ - -.. automodule:: boxsdk.object.terms_of_service_user_status - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.trash module --------------------------- - -.. automodule:: boxsdk.object.trash - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.upload\_session module ------------------------------------- - -.. automodule:: boxsdk.object.upload_session - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.user module -------------------------- - -.. automodule:: boxsdk.object.user - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.watermark module ------------------------------- - -.. automodule:: boxsdk.object.watermark - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.web\_link module ------------------------------- - -.. automodule:: boxsdk.object.web_link - :members: - :undoc-members: - :show-inheritance: - -boxsdk.object.webhook module ----------------------------- - -.. automodule:: boxsdk.object.webhook - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: boxsdk.object - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/boxsdk.pagination.rst b/docs/source/boxsdk.pagination.rst deleted file mode 100644 index 342ff3837..000000000 --- a/docs/source/boxsdk.pagination.rst +++ /dev/null @@ -1,69 +0,0 @@ -boxsdk.pagination package -========================= - -Submodules ----------- - -boxsdk.pagination.box\_object\_collection module ------------------------------------------------- - -.. automodule:: boxsdk.pagination.box_object_collection - :members: - :undoc-members: - :show-inheritance: - -boxsdk.pagination.dict\_page module ------------------------------------ - -.. automodule:: boxsdk.pagination.dict_page - :members: - :undoc-members: - :show-inheritance: - -boxsdk.pagination.limit\_offset\_based\_dict\_collection module ---------------------------------------------------------------- - -.. automodule:: boxsdk.pagination.limit_offset_based_dict_collection - :members: - :undoc-members: - :show-inheritance: - -boxsdk.pagination.limit\_offset\_based\_object\_collection module ------------------------------------------------------------------ - -.. automodule:: boxsdk.pagination.limit_offset_based_object_collection - :members: - :undoc-members: - :show-inheritance: - -boxsdk.pagination.marker\_based\_dict\_collection module --------------------------------------------------------- - -.. automodule:: boxsdk.pagination.marker_based_dict_collection - :members: - :undoc-members: - :show-inheritance: - -boxsdk.pagination.marker\_based\_object\_collection module ----------------------------------------------------------- - -.. automodule:: boxsdk.pagination.marker_based_object_collection - :members: - :undoc-members: - :show-inheritance: - -boxsdk.pagination.page module ------------------------------ - -.. automodule:: boxsdk.pagination.page - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: boxsdk.pagination - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/boxsdk.rst b/docs/source/boxsdk.rst deleted file mode 100644 index ba153803a..000000000 --- a/docs/source/boxsdk.rst +++ /dev/null @@ -1,51 +0,0 @@ -boxsdk package -============== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - boxsdk.auth - boxsdk.client - boxsdk.network - boxsdk.object - boxsdk.pagination - boxsdk.session - boxsdk.util - -Submodules ----------- - -boxsdk.config module --------------------- - -.. automodule:: boxsdk.config - :members: - :undoc-members: - :show-inheritance: - -boxsdk.exception module ------------------------ - -.. automodule:: boxsdk.exception - :members: - :undoc-members: - :show-inheritance: - -boxsdk.version module ---------------------- - -.. automodule:: boxsdk.version - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: boxsdk - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/boxsdk.session.rst b/docs/source/boxsdk.session.rst deleted file mode 100644 index 798187638..000000000 --- a/docs/source/boxsdk.session.rst +++ /dev/null @@ -1,37 +0,0 @@ -boxsdk.session package -====================== - -Submodules ----------- - -boxsdk.session.box\_request module ----------------------------------- - -.. automodule:: boxsdk.session.box_request - :members: - :undoc-members: - :show-inheritance: - -boxsdk.session.box\_response module ------------------------------------ - -.. automodule:: boxsdk.session.box_response - :members: - :undoc-members: - :show-inheritance: - -boxsdk.session.session module ------------------------------ - -.. automodule:: boxsdk.session.session - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: boxsdk.session - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/boxsdk.util.rst b/docs/source/boxsdk.util.rst deleted file mode 100644 index 46c84a538..000000000 --- a/docs/source/boxsdk.util.rst +++ /dev/null @@ -1,117 +0,0 @@ -boxsdk.util package -=================== - -Submodules ----------- - -boxsdk.util.api\_call\_decorator module ---------------------------------------- - -.. automodule:: boxsdk.util.api_call_decorator - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.chunked\_uploader module ------------------------------------- - -.. automodule:: boxsdk.util.chunked_uploader - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.datetime\_formatter module --------------------------------------- - -.. automodule:: boxsdk.util.datetime_formatter - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.default\_arg\_value module --------------------------------------- - -.. automodule:: boxsdk.util.default_arg_value - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.deprecation\_decorator module ------------------------------------------ - -.. automodule:: boxsdk.util.deprecation_decorator - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.enum module ------------------------ - -.. automodule:: boxsdk.util.enum - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.json module ------------------------ - -.. automodule:: boxsdk.util.json - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.log module ----------------------- - -.. automodule:: boxsdk.util.log - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.lru\_cache module ------------------------------ - -.. automodule:: boxsdk.util.lru_cache - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.multipart\_stream module ------------------------------------- - -.. automodule:: boxsdk.util.multipart_stream - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.shared\_link module -------------------------------- - -.. automodule:: boxsdk.util.shared_link - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.text\_enum module ------------------------------ - -.. automodule:: boxsdk.util.text_enum - :members: - :undoc-members: - :show-inheritance: - -boxsdk.util.translator module ------------------------------ - -.. automodule:: boxsdk.util.translator - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: boxsdk.util - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index c2db4faae..000000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,353 +0,0 @@ -# -# boxsdk documentation build configuration file, created by -# sphinx-quickstart on Tue Dec 16 01:10:30 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The main toctree document. -main_doc = 'index' - -# General information about the project. -project = 'boxsdk' -copyright = '2014, Box' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -import pkg_resources -try: - release = pkg_resources.get_distribution('boxsdk').version -except pkg_resources.DistributionNotFound: - print('To build the documentation, The distribution information of boxsdk') - print('Has to be available. Either install the package into your') - print('development environment or run "setup.py develop" to setup the') - print('metadata. A virtualenv is recommended!') - sys.exit(1) -del pkg_resources - -version = '.'.join(release.split('.')[:2]) - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = 'en' - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'nature' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'boxsdkdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'boxsdk.tex', 'boxsdk Documentation', - 'Author', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'boxsdk', 'boxsdk Documentation', - ['Author'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'boxsdk', 'boxsdk Documentation', - 'Author', 'boxsdk', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - - -# -- Options for Epub output ---------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = 'boxsdk' -epub_author = 'Author' -epub_publisher = 'Author' -epub_copyright = '2014, Author' - -# The basename for the epub file. It defaults to the project name. -#epub_basename = u'boxsdk' - -# The HTML theme for the epub output. Since the default themes are not optimized -# for small screen space, using the same theme for HTML and epub output is -# usually not wise. This defaults to 'epub', a theme designed to save visual -# space. -#epub_theme = 'epub' - -# The language of the text. It defaults to the language option -# or 'en' if the language is not set. -#epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -#epub_identifier = '' - -# A unique identification for the text. -#epub_uid = '' - -# A tuple containing the cover image and cover page html template filenames. -#epub_cover = () - -# A sequence of (type, uri, title) tuples for the guide element of content.opf. -#epub_guide = () - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_pre_files = [] - -# HTML files shat should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_post_files = [] - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - -# The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 - -# Allow duplicate toc entries. -#epub_tocdup = True - -# Choose between 'default' and 'includehidden'. -#epub_tocscope = 'default' - -# Fix unsupported image types using the Pillow. -#epub_fix_images = False - -# Scale large images. -#epub_max_image_width = 0 - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#epub_show_urls = 'inline' - -# If false, no index is generated. -#epub_use_index = True diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 987777f91..000000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,106 +0,0 @@ -.. boxsdk documentation main file, created by - sphinx-quickstart on Tue Dec 16 01:10:30 2014. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Box Python SDK -============== - - -Installing ----------- - -.. code-block:: console - - pip install boxsdk - - -Source Code ------------ - -https://github.com/box/box-python-sdk - - -Quickstart ----------- - -Create a developer token from your app's configuration page (https://app.box.com/developers/services). - -You'll be prompted for it on the command line. - -.. code-block:: pycon - - $ from boxsdk import DevelopmentClient - $ client = DevelopmentClient() - >>> Enter developer token: - $ me = client.user().get() - - GET https://api.box.com/2.0/users/me {'headers': {u'Authorization': u'Bearer ----KkeV', - u'User-Agent': u'box-python-sdk-1.4.3'}, - 'params': None} - - {"type":"user","id":"----6009","name":"Jeffrey Meadows","login":"jmeadows@box.com",...} - - $ me.name - >>> Jeffrey Meadows - -The ``DevelopmentClient`` uses Box developer tokens for auth (and will prompt you for a new token upon -expiration), and logs API requests and responses, making it really easy to get started learning the SDK and Box API. - - -Creating an App for Users -------------------------- - -Authorization -~~~~~~~~~~~~~ - -If you'd like other users to use your app, you need to set up a way for them to authorize your app and -grant it access to their Box account. The ``auth`` module contains several classes to help you do that. - -The simplest class is the ``OAuth2`` class. To use it, instantiate it with your ``client_id`` and ``client_secret``. - -Follow the `tutorial on GitHub `_ for -instructions on how to get an authorized client for a user. Using the ``store_tokens`` callback, you may persist -the user's auth and refresh tokens for the next time they use your app. Once they return to your app, you can -create an authorized client like so: - -.. code-block:: python - - from boxsdk import OAuth2, Client - - oauth = OAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - store_tokens=your_store_tokens_callback_method, - access_token=persisted_access_token, - refresh_token=persisted_refresh_token, - ) - client = Client(oauth) - - -Making requests to Box -~~~~~~~~~~~~~~~~~~~~~~ - -Once you have an authorized client, you can use it to make requests to Box on your user's behalf. The client -has several methods to help you get started, many of which return Box objects, which, in turn, have methods that -correspond to Box API endpoints. - -The module documentation below describes each of these methods and which parameters they require. Some API endpoints -do not have corresponding SDK methods; for those, you can use the generic ``make_request`` method of the client. - -Module Documentation --------------------- - -.. toctree:: - :maxdepth: 4 - - boxsdk - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/docs/source/modules.rst b/docs/source/modules.rst deleted file mode 100644 index 989c0d560..000000000 --- a/docs/source/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -boxsdk -====== - -.. toctree:: - :maxdepth: 4 - - boxsdk diff --git a/docs/storage_policies.md b/docs/storage_policies.md new file mode 100644 index 000000000..f28f7d1b2 --- /dev/null +++ b/docs/storage_policies.md @@ -0,0 +1,64 @@ +# StoragePoliciesManager + +- [List storage policies](#list-storage-policies) +- [Get storage policy](#get-storage-policy) + +## List storage policies + +Fetches all the storage policies in the enterprise. + +This operation is performed by calling function `get_storage_policies`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-storage-policies/). + + + +```python +client.storage_policies.get_storage_policies() +``` + +### Arguments + +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `StoragePolicies`. + +Returns a collection of storage policies. + +## Get storage policy + +Fetches a specific storage policy. + +This operation is performed by calling function `get_storage_policy_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-storage-policies-id/). + + + +```python +client.storage_policies.get_storage_policy_by_id(storage_policy.id) +``` + +### Arguments + +- storage_policy_id `str` + - The ID of the storage policy. Example: "34342" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `StoragePolicy`. + +Returns a storage policy object. diff --git a/docs/storage_policy_assignments.md b/docs/storage_policy_assignments.md new file mode 100644 index 000000000..187dd3c59 --- /dev/null +++ b/docs/storage_policy_assignments.md @@ -0,0 +1,178 @@ +# StoragePolicyAssignmentsManager + +- [List storage policy assignments](#list-storage-policy-assignments) +- [Assign storage policy](#assign-storage-policy) +- [Get storage policy assignment](#get-storage-policy-assignment) +- [Update storage policy assignment](#update-storage-policy-assignment) +- [Unassign storage policy](#unassign-storage-policy) + +## List storage policy assignments + +Fetches all the storage policy assignment for an enterprise or user. + +This operation is performed by calling function `get_storage_policy_assignments`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-storage-policy-assignments/). + + + +```python +client.storage_policy_assignments.get_storage_policy_assignments( + GetStoragePolicyAssignmentsResolvedForType.USER, user_id +) +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- resolved_for_type `GetStoragePolicyAssignmentsResolvedForType` + - The target type to return assignments for. +- resolved_for_id `str` + - The ID of the user or enterprise to return assignments for. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `StoragePolicyAssignments`. + +Returns a collection of storage policies for +the enterprise or user. + +## Assign storage policy + +Creates a storage policy assignment for an enterprise or user. + +This operation is performed by calling function `create_storage_policy_assignment`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-storage-policy-assignments/). + + + +```python +client.storage_policy_assignments.create_storage_policy_assignment( + CreateStoragePolicyAssignmentStoragePolicy(id=policy_id), + CreateStoragePolicyAssignmentAssignedTo( + id=user_id, type=CreateStoragePolicyAssignmentAssignedToTypeField.USER + ), +) +``` + +### Arguments + +- storage_policy `CreateStoragePolicyAssignmentStoragePolicy` + - The storage policy to assign to the user or enterprise. +- assigned_to `CreateStoragePolicyAssignmentAssignedTo` + - The user or enterprise to assign the storage policy to. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `StoragePolicyAssignment`. + +Returns the new storage policy assignment created. + +## Get storage policy assignment + +Fetches a specific storage policy assignment. + +This operation is performed by calling function `get_storage_policy_assignment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-storage-policy-assignments-id/). + + + +```python +client.storage_policy_assignments.get_storage_policy_assignment_by_id( + storage_policy_assignment.id +) +``` + +### Arguments + +- storage_policy_assignment_id `str` + - The ID of the storage policy assignment. Example: "932483" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `StoragePolicyAssignment`. + +Returns a storage policy assignment object. + +## Update storage policy assignment + +Updates a specific storage policy assignment. + +This operation is performed by calling function `update_storage_policy_assignment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-storage-policy-assignments-id/). + + + +```python +client.storage_policy_assignments.update_storage_policy_assignment_by_id( + storage_policy_assignment.id, + UpdateStoragePolicyAssignmentByIdStoragePolicy(id=storage_policy_2.id), +) +``` + +### Arguments + +- storage_policy_assignment_id `str` + - The ID of the storage policy assignment. Example: "932483" +- storage_policy `UpdateStoragePolicyAssignmentByIdStoragePolicy` + - The storage policy to assign to the user or enterprise. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `StoragePolicyAssignment`. + +Returns an updated storage policy assignment object. + +## Unassign storage policy + +Delete a storage policy assignment. + +Deleting a storage policy assignment on a user +will have the user inherit the enterprise's default +storage policy. + +There is a rate limit for calling this endpoint of only +twice per user in a 24 hour time frame. + +This operation is performed by calling function `delete_storage_policy_assignment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-storage-policy-assignments-id/). + + + +```python +client.storage_policy_assignments.delete_storage_policy_assignment_by_id( + storage_policy_assignment.id +) +``` + +### Arguments + +- storage_policy_assignment_id `str` + - The ID of the storage policy assignment. Example: "932483" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the storage policy +assignment is successfully deleted. diff --git a/docs/task_assignments.md b/docs/task_assignments.md new file mode 100644 index 000000000..c0fcbefbd --- /dev/null +++ b/docs/task_assignments.md @@ -0,0 +1,167 @@ +# TaskAssignmentsManager + +- [List task assignments](#list-task-assignments) +- [Assign task](#assign-task) +- [Get task assignment](#get-task-assignment) +- [Update task assignment](#update-task-assignment) +- [Unassign task](#unassign-task) + +## List task assignments + +Lists all of the assignments for a given task. + +This operation is performed by calling function `get_task_assignments`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-tasks-id-assignments/). + + + +```python +client.task_assignments.get_task_assignments(task.id) +``` + +### Arguments + +- task_id `str` + - The ID of the task. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TaskAssignments`. + +Returns a collection of task assignment defining what task on +a file has been assigned to which users and by who. + +## Assign task + +Assigns a task to a user. + +A task can be assigned to more than one user by creating multiple +assignments. + +This operation is performed by calling function `create_task_assignment`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-task-assignments/). + + + +```python +client.task_assignments.create_task_assignment( + CreateTaskAssignmentTask(type=CreateTaskAssignmentTaskTypeField.TASK, id=task.id), + CreateTaskAssignmentAssignTo(id=current_user.id), +) +``` + +### Arguments + +- task `CreateTaskAssignmentTask` + - The task to assign to a user. +- assign_to `CreateTaskAssignmentAssignTo` + - The user to assign the task to. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TaskAssignment`. + +Returns a new task assignment object. + +## Get task assignment + +Retrieves information about a task assignment. + +This operation is performed by calling function `get_task_assignment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-task-assignments-id/). + + + +```python +client.task_assignments.get_task_assignment_by_id(task_assignment.id) +``` + +### Arguments + +- task_assignment_id `str` + - The ID of the task assignment. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TaskAssignment`. + +Returns a task assignment, specifying who the task has been assigned to +and by whom. + +## Update task assignment + +Updates a task assignment. This endpoint can be +used to update the state of a task assigned to a user. + +This operation is performed by calling function `update_task_assignment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-task-assignments-id/). + + + +```python +client.task_assignments.update_task_assignment_by_id( + task_assignment.id, + message="updated message", + resolution_state=UpdateTaskAssignmentByIdResolutionState.APPROVED, +) +``` + +### Arguments + +- task_assignment_id `str` + - The ID of the task assignment. Example: "12345" +- message `Optional[str]` + - An optional message by the assignee that can be added to the task. +- resolution_state `Optional[UpdateTaskAssignmentByIdResolutionState]` + - The state of the task assigned to the user. _ For a task with an `action` value of `complete` this can be `incomplete` or `completed`. _ For a task with an `action` of `review` this can be `incomplete`, `approved`, or `rejected`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TaskAssignment`. + +Returns the updated task assignment object. + +## Unassign task + +Deletes a specific task assignment. + +This operation is performed by calling function `delete_task_assignment_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-task-assignments-id/). + + + +```python +client.task_assignments.delete_task_assignment_by_id(task_assignment.id) +``` + +### Arguments + +- task_assignment_id `str` + - The ID of the task assignment. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the task +assignment was successfully deleted. diff --git a/docs/tasks.md b/docs/tasks.md new file mode 100644 index 000000000..755487964 --- /dev/null +++ b/docs/tasks.md @@ -0,0 +1,175 @@ +# TasksManager + +- [List tasks on file](#list-tasks-on-file) +- [Create task](#create-task) +- [Get task](#get-task) +- [Update task](#update-task) +- [Remove task](#remove-task) + +## List tasks on file + +Retrieves a list of all the tasks for a file. This +endpoint does not support pagination. + +This operation is performed by calling function `get_file_tasks`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-tasks/). + + + +```python +client.tasks.get_file_tasks(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Tasks`. + +Returns a list of tasks on a file. + +If there are no tasks on this file an empty collection is returned +instead. + +## Create task + +Creates a single task on a file. This task is not assigned to any user and +will need to be assigned separately. + +This operation is performed by calling function `create_task`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-tasks/). + + + +```python +client.tasks.create_task( + CreateTaskItem(type=CreateTaskItemTypeField.FILE, id=file.id), + action=CreateTaskAction.REVIEW, + message="test message", + due_at=date_time, + completion_rule=CreateTaskCompletionRule.ALL_ASSIGNEES, +) +``` + +### Arguments + +- item `CreateTaskItem` + - The file to attach the task to. +- action `Optional[CreateTaskAction]` + - The action the task assignee will be prompted to do. Must be _ `review` defines an approval task that can be approved or, rejected _ `complete` defines a general task which can be completed. +- message `Optional[str]` + - An optional message to include with the task. +- due_at `Optional[DateTime]` + - Defines when the task is due. Defaults to `null` if not provided. +- completion_rule `Optional[CreateTaskCompletionRule]` + - Defines which assignees need to complete this task before the task is considered completed. _ `all_assignees` (default) requires all assignees to review or approve the the task in order for it to be considered completed. _ `any_assignee` accepts any one assignee to review or approve the the task in order for it to be considered completed. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Task`. + +Returns the newly created task. + +## Get task + +Retrieves information about a specific task. + +This operation is performed by calling function `get_task_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-tasks-id/). + + + +```python +client.tasks.get_task_by_id(task.id) +``` + +### Arguments + +- task_id `str` + - The ID of the task. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Task`. + +Returns a task object. + +## Update task + +Updates a task. This can be used to update a task's configuration, or to +update its completion state. + +This operation is performed by calling function `update_task_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-tasks-id/). + + + +```python +client.tasks.update_task_by_id(task.id, message="updated message") +``` + +### Arguments + +- task_id `str` + - The ID of the task. Example: "12345" +- action `Optional[UpdateTaskByIdAction]` + - The action the task assignee will be prompted to do. Must be _ `review` defines an approval task that can be approved or rejected, _ `complete` defines a general task which can be completed. +- message `Optional[str]` + - The message included with the task. +- due_at `Optional[DateTime]` + - When the task is due at. +- completion_rule `Optional[UpdateTaskByIdCompletionRule]` + - Defines which assignees need to complete this task before the task is considered completed. _ `all_assignees` (default) requires all assignees to review or approve the the task in order for it to be considered completed. _ `any_assignee` accepts any one assignee to review or approve the the task in order for it to be considered completed. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Task`. + +Returns the updated task object. + +## Remove task + +Removes a task from a file. + +This operation is performed by calling function `delete_task_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-tasks-id/). + + + +```python +client.tasks.delete_task_by_id(task.id) +``` + +### Arguments + +- task_id `str` + - The ID of the task. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the task was successfully deleted. diff --git a/docs/terms_of_service_user_statuses.md b/docs/terms_of_service_user_statuses.md new file mode 100644 index 000000000..24c9e9e57 --- /dev/null +++ b/docs/terms_of_service_user_statuses.md @@ -0,0 +1,107 @@ +# TermsOfServiceUserStatusesManager + +- [List terms of service user statuses](#list-terms-of-service-user-statuses) +- [Create terms of service status for new user](#create-terms-of-service-status-for-new-user) +- [Update terms of service status for existing user](#update-terms-of-service-status-for-existing-user) + +## List terms of service user statuses + +Retrieves an overview of users and their status for a +terms of service, including Whether they have accepted +the terms and when. + +This operation is performed by calling function `get_terms_of_service_user_statuses`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-terms-of-service-user-statuses/). + + + +```python +client.terms_of_service_user_statuses.get_terms_of_service_user_statuses( + tos.id, user_id=user.id +) +``` + +### Arguments + +- tos_id `str` + - The ID of the terms of service. +- user_id `Optional[str]` + - Limits results to the given user ID. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TermsOfServiceUserStatuses`. + +Returns a list of terms of service statuses. + +## Create terms of service status for new user + +Sets the status for a terms of service for a user. + +This operation is performed by calling function `create_terms_of_service_status_for_user`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-terms-of-service-user-statuses/). + + + +```python +client.terms_of_service_user_statuses.create_terms_of_service_status_for_user( + CreateTermsOfServiceStatusForUserTos(id=tos.id), + CreateTermsOfServiceStatusForUserUser(id=user.id), + False, +) +``` + +### Arguments + +- tos `CreateTermsOfServiceStatusForUserTos` + - The terms of service to set the status for. +- user `CreateTermsOfServiceStatusForUserUser` + - The user to set the status for. +- is_accepted `bool` + - Whether the user has accepted the terms. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TermsOfServiceUserStatus`. + +Returns a terms of service status object. + +## Update terms of service status for existing user + +Updates the status for a terms of service for a user. + +This operation is performed by calling function `update_terms_of_service_status_for_user_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-terms-of-service-user-statuses-id/). + + + +```python +client.terms_of_service_user_statuses.update_terms_of_service_status_for_user_by_id( + created_tos_user_status.id, True +) +``` + +### Arguments + +- terms_of_service_user_status_id `str` + - The ID of the terms of service status. Example: "324234" +- is_accepted `bool` + - Whether the user has accepted the terms. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TermsOfServiceUserStatus`. + +Returns the updated terms of service status object. diff --git a/docs/terms_of_services.md b/docs/terms_of_services.md new file mode 100644 index 000000000..68be19616 --- /dev/null +++ b/docs/terms_of_services.md @@ -0,0 +1,131 @@ +# TermsOfServicesManager + +- [List terms of services](#list-terms-of-services) +- [Create terms of service](#create-terms-of-service) +- [Get terms of service](#get-terms-of-service) +- [Update terms of service](#update-terms-of-service) + +## List terms of services + +Returns the current terms of service text and settings +for the enterprise. + +This operation is performed by calling function `get_terms_of_service`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-terms-of-services/). + + + +```python +client.terms_of_services.get_terms_of_service() +``` + +### Arguments + +- tos_type `Optional[GetTermsOfServiceTosType]` + - Limits the results to the terms of service of the given type. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TermsOfServices`. + +Returns a collection of terms of service text and settings for the +enterprise. + +## Create terms of service + +Creates a terms of service for a given enterprise +and type of user. + +This operation is performed by calling function `create_terms_of_service`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-terms-of-services/). + + + +```python +client.terms_of_services.create_terms_of_service( + CreateTermsOfServiceStatus.DISABLED, + "Test TOS", + tos_type=CreateTermsOfServiceTosType.MANAGED, +) +``` + +### Arguments + +- status `CreateTermsOfServiceStatus` + - Whether this terms of service is active. +- tos_type `Optional[CreateTermsOfServiceTosType]` + - The type of user to set the terms of service for. +- text `str` + - The terms of service text to display to users. The text can be set to empty if the `status` is set to `disabled`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TermsOfService`. + +Returns a new task object. + +## Get terms of service + +Fetches a specific terms of service. + +This operation is performed by calling function `get_terms_of_service_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-terms-of-services-id/). + +_Currently we don't have an example for calling `get_terms_of_service_by_id` in integration tests_ + +### Arguments + +- terms_of_service_id `str` + - The ID of the terms of service. Example: "324234" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TermsOfService`. + +Returns a terms of service object. + +## Update terms of service + +Updates a specific terms of service. + +This operation is performed by calling function `update_terms_of_service_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-terms-of-services-id/). + + + +```python +client.terms_of_services.update_terms_of_service_by_id( + tos.id, UpdateTermsOfServiceByIdStatus.DISABLED, "TOS" +) +``` + +### Arguments + +- terms_of_service_id `str` + - The ID of the terms of service. Example: "324234" +- status `UpdateTermsOfServiceByIdStatus` + - Whether this terms of service is active. +- text `str` + - The terms of service text to display to users. The text can be set to empty if the `status` is set to `disabled`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TermsOfService`. + +Returns an updated terms of service object. diff --git a/docs/transfer.md b/docs/transfer.md new file mode 100644 index 000000000..d3e6b3c5c --- /dev/null +++ b/docs/transfer.md @@ -0,0 +1,73 @@ +# TransferManager + +- [Transfer owned folders](#transfer-owned-folders) + +## Transfer owned folders + +Move all of the items (files, folders and workflows) owned by a user into +another user's account + +Only the root folder (`0`) can be transferred. + +Folders can only be moved across users by users with administrative +permissions. + +All existing shared links and folder-level collaborations are transferred +during the operation. Please note that while collaborations at the individual +file-level are transferred during the operation, the collaborations are +deleted when the original user is deleted. + +If the user has a large number of items across all folders, the call will +be run asynchronously. If the operation is not completed within 10 minutes, +the user will receive a 200 OK response, and the operation will continue running. + +If the destination path has a metadata cascade policy attached to any of +the parent folders, a metadata cascade operation will be kicked off +asynchronously. + +There is currently no way to check for when this operation is finished. + +The destination folder's name will be in the format `{User}'s Files and +Folders`, where `{User}` is the display name of the user. + +To make this API call your application will need to have the "Read and write +all files and folders stored in Box" scope enabled. + +Please make sure the destination user has access to `Relay` or `Relay Lite`, +and has access to the files and folders involved in the workflows being +transferred. + +Admins will receive an email when the operation is completed. + +This operation is performed by calling function `transfer_owned_folder`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-users-id-folders-0/). + + + +```python +client.transfer.transfer_owned_folder( + source_user.id, TransferOwnedFolderOwnedBy(id=target_user.id), notify=False +) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- owned_by `TransferOwnedFolderOwnedBy` + - The user who the folder will be transferred to. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- notify `Optional[bool]` + - Determines if users should receive email notification for the action performed. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `FolderFull`. + +Returns the information for the newly created +destination folder. diff --git a/docs/trashed_files.md b/docs/trashed_files.md new file mode 100644 index 000000000..3b77196f2 --- /dev/null +++ b/docs/trashed_files.md @@ -0,0 +1,113 @@ +# TrashedFilesManager + +- [Restore file](#restore-file) +- [Get trashed file](#get-trashed-file) +- [Permanently remove file](#permanently-remove-file) + +## Restore file + +Restores a file that has been moved to the trash. + +An optional new parent ID can be provided to restore the file to in case the +original folder has been deleted. + +This operation is performed by calling function `restore_file_from_trash`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-id/). + + + +```python +client.trashed_files.restore_file_from_trash(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- name `Optional[str]` + - An optional new name for the file. +- parent `Optional[RestoreFileFromTrashParent]` +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TrashFileRestored`. + +Returns a file object when the file has been restored. + +## Get trashed file + +Retrieves a file that has been moved to the trash. + +Please note that only if the file itself has been moved to the +trash can it be retrieved with this API call. If instead one of +its parent folders was moved to the trash, only that folder +can be inspected using the +[`GET /folders/:id/trash`](e://get_folders_id_trash) API. + +To list all items that have been moved to the trash, please +use the [`GET /folders/trash/items`](e://get-folders-trash-items/) +API. + +This operation is performed by calling function `get_trashed_file_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-files-id-trash/). + + + +```python +client.trashed_files.get_trashed_file_by_id(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TrashFile`. + +Returns the file that was trashed, +including information about when the it +was moved to the trash. + +## Permanently remove file + +Permanently deletes a file that is in the trash. +This action cannot be undone. + +This operation is performed by calling function `delete_trashed_file_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-files-id-trash/). + + + +```python +client.trashed_files.delete_trashed_file_by_id(file.id) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the file was +permanently deleted. diff --git a/docs/trashed_folders.md b/docs/trashed_folders.md new file mode 100644 index 000000000..66a0f27fc --- /dev/null +++ b/docs/trashed_folders.md @@ -0,0 +1,120 @@ +# TrashedFoldersManager + +- [Restore folder](#restore-folder) +- [Get trashed folder](#get-trashed-folder) +- [Permanently remove folder](#permanently-remove-folder) + +## Restore folder + +Restores a folder that has been moved to the trash. + +An optional new parent ID can be provided to restore the folder to in case the +original folder has been deleted. + +During this operation, part of the file tree will be locked, mainly +the source folder and all of its descendants, as well as the destination +folder. + +For the duration of the operation, no other move, copy, delete, or restore +operation can performed on any of the locked folders. + +This operation is performed by calling function `restore_folder_from_trash`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-folders-id/). + + + +```python +client.trashed_folders.restore_folder_from_trash(folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- name `Optional[str]` + - An optional new name for the folder. +- parent `Optional[RestoreFolderFromTrashParent]` +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TrashFolderRestored`. + +Returns a folder object when the folder has been restored. + +## Get trashed folder + +Retrieves a folder that has been moved to the trash. + +Please note that only if the folder itself has been moved to the +trash can it be retrieved with this API call. If instead one of +its parent folders was moved to the trash, only that folder +can be inspected using the +[`GET /folders/:id/trash`](e://get_folders_id_trash) API. + +To list all items that have been moved to the trash, please +use the [`GET /folders/trash/items`](e://get-folders-trash-items/) +API. + +This operation is performed by calling function `get_trashed_folder_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-id-trash/). + + + +```python +client.trashed_folders.get_trashed_folder_by_id(folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TrashFolder`. + +Returns the folder that was trashed, +including information about when the it +was moved to the trash. + +## Permanently remove folder + +Permanently deletes a folder that is in the trash. +This action cannot be undone. + +This operation is performed by calling function `delete_trashed_folder_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-folders-id-trash/). + + + +```python +client.trashed_folders.delete_trashed_folder_by_id(folder.id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the folder was +permanently deleted. diff --git a/docs/trashed_items.md b/docs/trashed_items.md new file mode 100644 index 000000000..22b41a270 --- /dev/null +++ b/docs/trashed_items.md @@ -0,0 +1,51 @@ +# TrashedItemsManager + +- [List trashed items](#list-trashed-items) + +## List trashed items + +Retrieves the files and folders that have been moved +to the trash. + +Any attribute in the full files or folders objects can be passed +in with the `fields` parameter to retrieve those specific +attributes that are not returned by default. + +This endpoint defaults to use offset-based pagination, yet also supports +marker-based pagination using the `marker` parameter. + +This operation is performed by calling function `get_trashed_items`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-folders-trash-items/). + + + +```python +client.trashed_items.get_trashed_items() +``` + +### Arguments + +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- limit `Optional[int]` + - The maximum number of items to return per page. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- usemarker `Optional[bool]` + - Specifies whether to use marker-based pagination instead of offset-based pagination. Only one pagination method can be used at a time. By setting this value to true, the API will return a `marker` field that can be passed as a parameter to this endpoint to get the next page of the response. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- direction `Optional[GetTrashedItemsDirection]` + - The direction to sort results in. This can be either in alphabetical ascending (`ASC`) or descending (`DESC`) order. +- sort `Optional[GetTrashedItemsSort]` + - Defines the **second** attribute by which items are sorted. Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. This parameter is not supported when using marker-based pagination. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Items`. + +Returns a list of items that have been deleted. diff --git a/docs/trashed_web_links.md b/docs/trashed_web_links.md new file mode 100644 index 000000000..b1d31d080 --- /dev/null +++ b/docs/trashed_web_links.md @@ -0,0 +1,103 @@ +# TrashedWebLinksManager + +- [Restore web link](#restore-web-link) +- [Get trashed web link](#get-trashed-web-link) +- [Permanently remove web link](#permanently-remove-web-link) + +## Restore web link + +Restores a web link that has been moved to the trash. + +An optional new parent ID can be provided to restore the web link to in case +the original folder has been deleted. + +This operation is performed by calling function `restore_weblink_from_trash`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-web-links-id/). + + + +```python +client.trashed_web_links.restore_weblink_from_trash(weblink.id) +``` + +### Arguments + +- web_link_id `str` + - The ID of the web link. Example: "12345" +- name `Optional[str]` + - An optional new name for the web link. +- parent `Optional[RestoreWeblinkFromTrashParent]` +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TrashWebLinkRestored`. + +Returns a web link object when it has been restored. + +## Get trashed web link + +Retrieves a web link that has been moved to the trash. + +This operation is performed by calling function `get_trashed_web_link_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-web-links-id-trash/). + + + +```python +client.trashed_web_links.get_trashed_web_link_by_id(weblink.id) +``` + +### Arguments + +- web_link_id `str` + - The ID of the web link. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `TrashWebLink`. + +Returns the web link that was trashed, +including information about when the it +was moved to the trash. + +## Permanently remove web link + +Permanently deletes a web link that is in the trash. +This action cannot be undone. + +This operation is performed by calling function `delete_trashed_web_link_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-web-links-id-trash/). + + + +```python +client.trashed_web_links.delete_trashed_web_link_by_id(weblink.id) +``` + +### Arguments + +- web_link_id `str` + - The ID of the web link. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the web link was +permanently deleted. diff --git a/docs/uploads.md b/docs/uploads.md new file mode 100644 index 000000000..8d4095301 --- /dev/null +++ b/docs/uploads.md @@ -0,0 +1,173 @@ +# UploadsManager + +- [Upload file version](#upload-file-version) +- [Preflight check before upload](#preflight-check-before-upload) +- [Upload file](#upload-file) +- [Upload a file with a preflight check](#upload-a-file-with-a-preflight-check) + +## Upload file version + +Update a file's content. For file sizes over 50MB we recommend +using the Chunk Upload APIs. + +The `attributes` part of the body must come **before** the +`file` part. Requests that do not follow this format when +uploading the file will receive a HTTP `400` error with a +`metadata_after_file_contents` error code. + +This operation is performed by calling function `upload_file_version`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-id-content/). + + + +```python +client.uploads.upload_file_version( + uploaded_file.id, + UploadFileVersionAttributes(name=new_file_version_name), + new_file_content_stream, +) +``` + +### Arguments + +- file_id `str` + - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- attributes `UploadFileVersionAttributes` + - The additional attributes of the file being uploaded. Mainly the name and the parent folder. These attributes are part of the multi part request body and are in JSON format. The `attributes` part of the body must come **before** the `file` part. Requests that do not follow this format when uploading the file will receive a HTTP `400` error with a `metadata_after_file_contents` error code. +- file `ByteStream` + - The content of the file to upload to Box. The `attributes` part of the body must come **before** the `file` part. Requests that do not follow this format when uploading the file will receive a HTTP `400` error with a `metadata_after_file_contents` error code. +- file_file_name `Optional[str]` +- file_content_type `Optional[str]` +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- if_match `Optional[str]` + - Ensures this item hasn't recently changed before making changes. Pass in the item's last observed `etag` value into this header and the endpoint will fail with a `412 Precondition Failed` if it has changed since. +- content_md_5 `Optional[str]` + - An optional header containing the SHA1 hash of the file to ensure that the file was not corrupted in transit. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Files`. + +Returns the new file object in a list. + +## Preflight check before upload + +Performs a check to verify that a file will be accepted by Box +before you upload the entire file. + +This operation is performed by calling function `preflight_file_upload_check`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/options-files-content/). + + + +```python +client.uploads.preflight_file_upload_check( + name=new_file_name, size=1024 * 1024, parent=PreflightFileUploadCheckParent(id="0") +) +``` + +### Arguments + +- name `Optional[str]` + - The name for the file. +- size `Optional[int]` + - The size of the file in bytes. +- parent `Optional[PreflightFileUploadCheckParent]` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UploadUrl`. + +If the check passed, the response will include a session URL that +can be used to upload the file to. + +## Upload file + +Uploads a small file to Box. For file sizes over 50MB we recommend +using the Chunk Upload APIs. + +The `attributes` part of the body must come **before** the +`file` part. Requests that do not follow this format when +uploading the file will receive a HTTP `400` error with a +`metadata_after_file_contents` error code. + +This operation is performed by calling function `upload_file`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-files-content/). + + + +```python +client.uploads.upload_file( + UploadFileAttributes( + name=new_file_name, parent=UploadFileAttributesParentField(id="0") + ), + file_content_stream, +) +``` + +### Arguments + +- attributes `UploadFileAttributes` + - The additional attributes of the file being uploaded. Mainly the name and the parent folder. These attributes are part of the multi part request body and are in JSON format. The `attributes` part of the body must come **before** the `file` part. Requests that do not follow this format when uploading the file will receive a HTTP `400` error with a `metadata_after_file_contents` error code. +- file `ByteStream` + - The content of the file to upload to Box. The `attributes` part of the body must come **before** the `file` part. Requests that do not follow this format when uploading the file will receive a HTTP `400` error with a `metadata_after_file_contents` error code. +- file_file_name `Optional[str]` +- file_content_type `Optional[str]` +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- content_md_5 `Optional[str]` + - An optional header containing the SHA1 hash of the file to ensure that the file was not corrupted in transit. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Files`. + +Returns the new file object in a list. + +## Upload a file with a preflight check + +Upload a file with a preflight check + +This operation is performed by calling function `upload_with_preflight_check`. + +```python +client.uploads.upload_with_preflight_check( + UploadWithPreflightCheckAttributes( + name=new_file_name, + size=-1, + parent=UploadWithPreflightCheckAttributesParentField(id="0"), + ), + file_content_stream, +) +``` + +### Arguments + +- attributes `UploadWithPreflightCheckAttributes` +- file `ByteStream` + - The content of the file to upload to Box. The `attributes` part of the body must come **before** the `file` part. Requests that do not follow this format when uploading the file will receive a HTTP `400` error with a `metadata_after_file_contents` error code. +- file_file_name `Optional[str]` +- file_content_type `Optional[str]` +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- content_md_5 `Optional[str]` + - An optional header containing the SHA1 hash of the file to ensure that the file was not corrupted in transit. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Files`. diff --git a/docs/usage/README.md b/docs/usage/README.md deleted file mode 100644 index f7fd6f5d7..000000000 --- a/docs/usage/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Getting raw json response - -The response returned by an API endpoint is being automatically translated to the corresponding -object basing on the value of the `type` field. If you want to get the raw response object, -you can access response dict using `response_object` property. Example below will print the -raw json response returned by the Get file information endpoint. - -``` python -file = client.file('1122334455').get() -print(json.dumps(file.response_object)) -``` diff --git a/docs/usage/ai.md b/docs/usage/ai.md deleted file mode 100644 index 0a9d4da51..000000000 --- a/docs/usage/ai.md +++ /dev/null @@ -1,103 +0,0 @@ -AI -== - -AI allows to send an intelligence request to supported large language models and returns an answer based on the provided prompt and items. - - - - -- [Send AI request](#send-ai-request) -- [Send AI text generation request](#send-ai-text-generation-request) -- [Get AI agent default configuration](#get-ai-agent-default-configuration) - - - -Send AI request ------------------------- - -Calling the [`client.send_ai_question(items, prompt, mode, ai_agent)`][send-ai-question] method will send an AI request to the supported large language models. The `items` parameter is a list of items to be processed by the LLM, often files. The `prompt` provided by the client to be answered by the LLM. The prompt's length is limited to 10000 characters. The `mode` specifies if this request is for a single or multiple items. If you select `single_item_qa` the items array can have one element only. Selecting `multiple_item_qa` allows you to provide up to 25 items. The `ai_agent` specifies the AI agent which will be used to handle queries. - - - - -```python -items = [{ - "id": "1582915952443", - "type": "file", - "content": "More information about public APIs" -}] -ai_agent = { - 'type': 'ai_agent_ask', - 'basic_text_multi': { - 'model': 'openai__gpt_3_5_turbo' - } -} -answer = client.send_ai_question( - items=items, - prompt="What is this file?", - mode="single_item_qa", - ai_agent=ai_agent -) -print(answer) -``` - -NOTE: The AI endpoint may return a 412 status code if you use for your request a file which has just been updated to the box. -It usually takes a few seconds for the file to be indexed and available for the AI endpoint. - -[send-ai-question]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.send_ai_question - -Send AI text generation request ------------------------- - -Calling the [`client.send_ai_text_gen(dialogue_history, items, prompt, ai_agent)`][send-ai-text-gen] method will send an AI text generation request to the supported large language models. The `dialogue_history` parameter is history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response. The `items` parameter is a list of items to be processed by the LLM, often files. The `prompt` provided by the client to be answered by the LLM. The prompt's length is limited to 10000 characters. The `ai_agent` specifies the AI agent which will be used for generating text. - - -```python -items = [{ - "id": "1582915952443", - "type": "file", - "content": "More information about public APIs" -}] -dialogue_history = [{ - "prompt": "Make my email about public APIs sound more professional", - "answer": "Here is the first draft of your professional email about public APIs", - "created_at": "2013-12-12T10:53:43-08:00" - }, - { - "prompt": "Can you add some more information?", - "answer": "Public API schemas provide necessary information to integrate with APIs...", - "created_at": "2013-12-12T11:20:43-08:00" -}] -ai_agent = { - 'type': 'ai_agent_text_gen', - 'basic_gen': { - 'model': 'openai__gpt_3_5_turbo_16k' - } -} -answer = client.send_ai_text_gen( - dialogue_history=dialogue_history, - items=items, - prompt="Write an email to a client about the importance of public APIs.", - ai_agent=ai_agent -) -print(answer) -``` - -[send-ai-text-gen]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.send_ai_text_gen - -Get AI agent default configuration ------------------------- - -To get an AI agent default configuration call the [`client.get_ai_agent_default_config(mode, language, model)`][get-ai-agent-default] method. The `mode` parameter filters the agent configuration to be returned. It can be either `ask` or `text_gen`. The `language` parameter specifies the ISO language code to return the agent config for. If the language is not supported, the default agent configuration is returned. The `model` parameter specifies the model for which the default agent configuration should be returned. - - -```python -config = client.get_ai_agent_default_config( - mode='text_gen', - language='en', - model='openai__gpt_3_5_turbo' -) -print(config) -``` - -[get-ai-agent-default]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_ai_agent_default_config diff --git a/docs/usage/authentication.md b/docs/usage/authentication.md deleted file mode 100644 index bce264e8c..000000000 --- a/docs/usage/authentication.md +++ /dev/null @@ -1,417 +0,0 @@ -Authentication -============== - -The Box API uses OAuth2 for authentication, which can be difficult to implement. -The SDK makes it easier by providing classes that handle obtaining tokens and -automatically refreshing them when possible. See the -[OAuth 2 overview](https://developer.box.com/en/guides/authentication/) for a detailed -overview of how the Box API handles authentication. - - - - -- [Ways to Authenticate](#ways-to-authenticate) - - [Developer Token](#developer-token) - - [Server Auth with JWT](#server-auth-with-jwt) - - [Client Credentials Grant](#client-credentials-grant) - - [Obtaining Service Account token](#obtaining-service-account-token) - - [Obtaining User token](#obtaining-user-token) - - [Traditional 3-Legged OAuth2](#traditional-3-legged-oauth2) - - [Redirect to Authorization URL](#redirect-to-authorization-url) - - [Authenticate (Get Token Pair)](#authenticate-get-token-pair) - - [Initialize a Client Given Access and Refresh Token](#initialize-a-client-given-access-and-refresh-token) - - [Box View Authentication with App Tokens](#box-view-authentication-with-app-tokens) -- [As-User](#as-user) -- [Downscoping token](#downscoping-token) -- [Revoking Tokens](#revoking-tokens) - - - -Ways to Authenticate --------------------- - -### Developer Token - -The fastest way to get started using the API is with developer tokens. A -developer token is simply a short-lived access token that cannot be refreshed -and can only be used with your own account. Therefore, they're only useful for -testing an app and aren't suitable for production. You can obtain a developer -token from your application's [developer console][dev_console] page. - -For manual testing in a Python REPL, you can interactively create a [`DevelopmentClient`][dev_client]. -This client will prompt for a new developer token any time the current one expires, and will automatically -log API requests and responses for testing and debugging. - -```python ->>> from boxsdk import DevelopmentClient ->>> client = DevelopmentClient() -Enter developer token: ->>> me = client.user().get() -GET https://api.box.com/2.0/users/me {'headers': {'Authorization': '---wXyZ', - 'User-Agent': 'box-python-sdk-2.0.0', - 'X-Box-UA': 'agent=box-python-sdk/2.0.0; env=python/3.6.5'}, - 'params': None} -"GET https://api.box.com/2.0/users/me" 200 454 -{'Date': 'Tue, 30 Oct 2018 20:57:36 GMT', 'Content-Type': 'application/json', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Strict-Transport-Security': 'max-age=31536000', 'Cache-Control': 'no-cache, no-store', 'Content-Encoding': 'gzip', 'Vary': 'Accept-Encoding', 'BOX-REQUEST-ID': '0dnjcjpu1krfunto6s7mrpal2ba', 'Age': '0'} -{'address': '', - 'avatar_url': 'https://cloud.app.box.com/api/avatar/large/33333', - 'created_at': '2012-06-07T11:14:50-07:00', - 'id': '33333', - 'job_title': '', - 'language': 'en', - 'login': 'user@example.com', - 'max_upload_size': 16106127360, - 'modified_at': '2018-10-29T12:13:57-07:00', - 'name': 'Example User', - 'phone': '', - 'space_amount': 1000000000000000.0, - 'space_used': 14330011102, - 'status': 'active', - 'timezone': 'America/Los_Angeles', - 'type': 'user'} ->>> -``` - -To create a [`Client`][client_class] non-interactively with a developer token, construct an [`OAuth2`][oauth2_class] -object with the `access_token` set to the developer token and construct the client with that. - - -```python -from boxsdk import Client, OAuth2 - -auth = OAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - access_token='DEVELOPER_TOKEN_GOES_HERE', -) -client = Client(auth) -me = client.user().get() -print(f'My user ID is {me.id}') -``` - -[dev_console]: https://app.box.com/developers/console -[dev_client]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#module-boxsdk.client.development_client -[client_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client -[oauth2_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.auth.html#boxsdk.auth.oauth2.OAuth2 - -### Server Auth with JWT - -Authenticating with a JWT requires some extra dependencies. To get them, simply -``` -pip install "boxsdk[jwt]" -``` - -Server auth allows your application to authenticate itself with the Box API -for a given enterprise. By default, your application has a -[Service Account](https://developer.box.com/en/guides/authentication/user-types/app-users/) -that represents it and can perform API calls. The Service Account is separate -from the Box accounts of the application developer and the enterprise admin of -any enterprise that has authorized the app — files stored in that account are -not accessible in any other account by default, and vice versa. - -If you generated your public and private keys automatically through the -[Box Developer Console][dev_console], you can use the JSON file created there -to configure your SDK instance and create a client to make calls as the -Service Account by calling the appropriate static [`JWTAuth`][jwt_auth_class] method: - - -```python -from boxsdk import JWTAuth, Client - -auth = JWTAuth.from_settings_file('/path/to/settings.json') -client = Client(auth) -service_account = client.user().get() -print(f'Service Account user ID is {service_account.id}') -``` - -Otherwise, you'll need to provide the necessary configuration fields directly -to the [`JWTAuth`][jwt_auth_class] constructor: - - -```python -from boxsdk import JWTAuth, Client - -service_account_auth = JWTAuth( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - enterprise_id='YOUR_ENTERPRISE_ID', - jwt_key_id='YOUR_JWT_KEY_ID', - rsa_private_key_file_sys_path='CERT.PEM', - rsa_private_key_passphrase='PASSPHRASE', - store_tokens=your_store_tokens_callback_method, -) - -access_token = auth.authenticate_instance() - -service_account_client = Client(auth) -``` - -App auth applications also often have associated App Users, which are -[created and managed directly by the application](https://developer.box.com/en/guides/authentication/user-types/app-users/) -— they do not have normal login credentials, and can only be accessed through -the Box API by the application that created them. You may authenticate as the -Service Account to provision and manage users, or as an individual app user to -make calls as that user. See the [API documentation](https://developer.box.com/) -for detailed instructions on how to use app auth. - -Clients for making calls as an App User can be created with the same [`JWTAuth`][jwt_auth_class] -constructor as in the above examples, similarly to creating a Service Account client. Simply pass the -[`User`][user_class] object for the app user instead of an `enterprise_id` when constructing the auth instance: - - -```python -app_user = service_account_client.user(user_id='APP_USER_ID') - -app_user_auth = JWTAuth( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - user=app_user, - jwt_key_id='YOUR_JWT_KEY_ID', - rsa_private_key_file_sys_path='CERT.PEM', - rsa_private_key_passphrase='PASSPHRASE', - store_tokens=your_store_tokens_callback_method, -) -app_user_auth.authenticate_user() -app_user_client = Client(app_user_auth) -``` - -[jwt_auth_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.auth.html#boxsdk.auth.jwt_auth.JWTAuth -[user_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User - - -### Client Credentials Grant - -Allows you to obtain an access token by having client credentials and secret with enterprise or user ID, -which allows you to work using a service or user account. - -You can use `CCGAuth` to initialize a client object the same way as for other authentication types: - -```python -auth = CCGAuth( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - user="YOUR_USER_ID" -) - -client = Client(auth) -print(f'Id of the authenticated user is: {client.user().get().id}') -``` - -Obtained token is valid for specified amount of time, it will be refreshed automatically by default. - -#### Obtaining Service Account token - -The [Service Account](https://developer.box.com/guides/getting-started/user-types/service-account//) -is separate from the Box accounts of the application developer and the -enterprise admin of any enterprise that has authorized the app — files stored in that account -are not accessible in any other account by default, and vice versa. -To obtain service account you will have to provide enterprise ID with client id and secret: - - -```python -auth = CCGAuth( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - enterprise_id="YOUR_ENETRPRISE_ID" -) -``` - -Remember that you can still make calls on behalf of managed users, which are part of your enterprise, by using -[As-User](#as-user) bahaviour. - -#### Obtaining User token - -To obtain user account you will have to provide user ID with client id and secret - -```python -auth = CCGAuth( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - user="YOUR_USER_ID" -) -``` -In order to enable obtaining user token you have to go to your application configuration that can be found -[here](https://app.box.com/developers/console). In`Configuration` tab, in section `Advanced Features` -select `Generate user access tokens`. Do not forget to re-authorize application if it was already authorized. - - -### Traditional 3-Legged OAuth2 - -If your application needs to integrate with existing Box users who will provide -their login credentials to grant your application access to their account, you -will need to go through the standard OAuth2 login flow. A detailed guide for -this process is available in the -[Authentication with OAuth API documentation](https://developer.box.com/en/guides/authentication/oauth2/). - -Using an auth code is the most common way of authenticating with the Box API for -existing Box users, to integrate with their accounts. -Your application must provide a way for the user to login to Box (usually with a -browser or web view) in order to obtain an auth code. - -After a user logs in and grants your application access to their Box account, -they will be redirected to your application's `redirect_uri` which will contain -an auth code. This auth code can then be used along with your client ID and -client secret to establish an API connection. - -#### Redirect to Authorization URL - -The first step in the process is to redirect the user to the Box Authorize URL, which you can generate -(along with a CSRF token) by calling [`oauth.get_authorization_url(redirect_url)`][get_authorization_url] with -your application's redirect URL. - - -```python -from boxsdk import OAuth2 - -oauth = OAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - store_tokens=your_store_tokens_callback_method, -) - -auth_url, csrf_token = oauth.get_authorization_url('http://YOUR_REDIRECT_URL') - -# Redirect user to auth_url, where they will enter their Box credentials -``` - -The SDK will keep the tokens in memory for the duration of the Python script run, so you don't always need to pass -`store_tokens`. - -[get_authorization_url]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.auth.html#boxsdk.auth.oauth2.OAuth2.get_authorization_url - -#### Authenticate (Get Token Pair) - -If you navigate the user to the auth_url, the user will be redirected to -`https://YOUR_REDIRECT_URL?code=YOUR_AUTH_CODE&state=CSRF_TOKEN` after they log in to Box. After getting the auth code, -you will be able to exchange it for an access token and refresh token. - -The SDK handles all the work for you; all you need to do is call [`oauth.authenticate(auth_code)`][authenticate] with -the auth code pulled from the query parameters of the incoming URL: - - -```python -from boxsdk import Client - -# Make sure that the csrf token you get from the `state` parameter -# in the final redirect URI is the same token you get from the -# get_authorization_url method to protect against CSRF vulnerabilities. -assert 'THE_CSRF_TOKEN_YOU_GOT' == csrf_token -access_token, refresh_token = oauth.authenticate('YOUR_AUTH_CODE') -client = Client(oauth) -``` - -[authenticate]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.auth.html#boxsdk.auth.oauth2.OAuth2.authenticate - -#### Initialize a Client Given Access and Refresh Token - -You can also instantiate a client given the access and refresh token. You first need to construct an -[OAuth2][oauth2_class] object with the access and refresh token passed in. Once you have created the -oauth object you then pass it into your [Client][client_class] object to instantiate your client. Finally, you can begin making calls with your client. - - -```python -from boxsdk import Client, OAuth2 - -oauth = OAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - access_token='ACCESS_TOKEN', - refresh_token='REFRESH_TOKEN', -) - -client = Client(oauth) - -user = client.user().get() -print(f'User ID is {user.id}') -``` - -### Box View Authentication with App Tokens - -[Box View](https://developer.box.com/en/guides/embed/box-view/) -uses a long-lived access token that is generated from the [Box Developer Console][dev_console] to make API calls. -These access tokens cannot be automatically refreshed from the SDK, and must be manually changed in -your application code. - -To use the primary or secondary access token generated in the Developer Console, -simply create a [`Client`][client_class] with that token: - - -```python -from boxsdk import Client, OAuth2 - -auth = OAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='', - access_token='APP_ACCESS_TOKEN_GOES_HERE' -) -client = Client(auth) -``` - -As-User -------- - -The As-User header is used by enterprise admins to make API calls on behalf of -their enterprise's users. This requires the API request to pass an -`As-User: USER-ID` header. For more details see the -[documentation on As-User](https://developer.box.com/en/guides/authentication/oauth2/as-user/). - -The following examples assume that the `client` has been instantiated with an -access token belonging to an admin-level user or Service Account with appropriate -privileges to make As-User calls. - -Calling the [`client.as_user(user)`][as_user] method with the [`User`][user_class] creates a new client to impersonate -the provided user. All calls made with the new client will be made in context of the impersonated user, leaving the -original client unmodified. - - -```python -user_to_impersonate = client.user(user_id='USER_ID_GOES_HERE') -user_client = client.as_user(user_to_impersonate) -``` - -[as_user]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.cloneable.Cloneable.as_user - -Downscoping token --------------- - -You can downscope a client's access token for one with a lower scope, in order -to restrict the permissions for a child client or to pass to a less secure -location (e.g. a browser-based app). This is useful if you want to use the -[Box UI Elements](https://developer.box.com/en/guides/embed/ui-elements/), since they generally -do not need full read/write permissions to run. - -To exchange the token held by a client for a new token with only `item_preview` -scope, restricted to a single file, suitable for the -[Content Preview UI Element](https://developer.box.com/en/guides/embed/ui-elements/preview/), call -[`client.downscope_token(scopes, item=None, additional_data=None)`][downscope_token] with the scope(s) needed. -This method returns a [`TokenResponse`][token_response] object with the downscoped token information. - - -```python -target_file = client.file(file_id='FILE_ID_HERE') -token_info = client.downscope_token(['item_preview'], target_file) -downscoped_client = Client( - OAuth2( - client_id=None, - client_secret=None, - access_token=token_info.access_token - ) -) -``` -But bear in mind that there is no way of refreshing this token, and you will need to add you own logic to do that. - -[downscope_token]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.downscope_token -[token_response]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.auth.html#boxsdk.auth.oauth2.TokenResponse - -Revoking Tokens ---------------- - -To revoke the tokens contained in an [`OAuth2`][oauth2_class] instance, removing the ability to call the Box API, -call [`oauth.revoke()`][revoke]. - - -```python -oauth.revoke() -``` - -[revoke]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.auth.html#boxsdk.auth.oauth2.OAuth2.revoke diff --git a/docs/usage/classifications.md b/docs/usage/classifications.md deleted file mode 100644 index d0c77023f..000000000 --- a/docs/usage/classifications.md +++ /dev/null @@ -1,217 +0,0 @@ -Classifications -=============== - -Classifications are a type of metadata that allows users and applications -to define and assign a content classification to files and folders. - -Classifications use the metadata APIs to add and remove classifications, and -assign them to files. For more details on metadata templates please see the -[metadata documentation](./metadata.md). - - - - - -- [Add initial classifications](#add-initial-classifications) -- [List all classifications](#list-all-classifications) -- [Add another classification](#add-another-classification) -- [Update a classification](#update-a-classification) -- [Delete a classification](#delete-a-classification) -- [Delete all classifications](#delete-all-classifications) -- [Add classification to file](#add-classification-to-file) -- [Update classification on file](#update-classification-on-file) -- [Get classification on file](#get-classification-on-file) -- [Remove classification from file](#remove-classification-from-file) -- [Add classification to folder](#add-classification-to-folder) -- [Update classification on folder](#update-classification-on-folder) -- [Get classification on folder](#get-classification-on-folder) -- [Remove classification from folder](#remove-classification-from-folder) - - - -Add initial classifications ---------------------------- - -If an enterprise does not already have a classification defined, the first classification(s) -can be added with the -`client.create_metadata_template(display_name, fields, template_key=None, hidden=False, scope='enterprise')`](https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_metadata_template) -method. - - -```python -from boxsdk.object.metadata_template import MetadataField, MetadataFieldType - -fields = [ - MetadataField(MetadataFieldType.ENUM, 'Classification', key='Box__Security__Classification__Key', options=['Top Secret']) -] - -template = client.create_metadata_template('Classification', fields, template_key='securityClassification-6VMVochwUWo') -``` - -List all classifications ------------------------- - -To retrieve a list of all the classifications in an enterprise call the -[`client.metadata_template(scope, template_key)`](https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.metadata_template) -method to get the classifications template, which will contain a list of all the -classifications. - - -```python -template = client.metadata_template('enterprise', 'securityClassification-6VMVochwUWo').get() -``` - -Add another classification --------------------------- - -To add another classification, call the -[`template.start_update()`][start_update] API to start making changes to the -template, and then call the [`template.update_info(updates=new_updates)`][update_info] -with the changes to apply to the template. - - -```python -template = client.metadata_template('enterprise', 'securityClassification-6VMVochwUWo') -new_updates = template.start_update() -new_updates.add_enum_option('Box__Security__Classification__Key', 'Sensitive') -updated_template = template.update_info(updates=new_updates) -``` - -[start_update]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata_template.MetadataTemplate.start_update -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata_template.MetadataTemplate.update_info - -Update a classification ------------------------ - -To update a classification, call the -[`template.start_update()`][start_update] API to start making changes to the -template, and then call the [`template.update_info(updates=new_updates)`][update_info] -with the classification to change on the template. - - -```python -template = client.metadata_template('enterprise', 'securityClassification-6VMVochwUWo') -new_updates = template.start_update() -new_updates.edit_enum_option('Box__Security__Classification__Key', 'Sensitive', 'Very Sensitive') -updated_template = template.update_info(updates=new_updates) -``` - -Add classification to file --------------------------- - -To add a classification to a file, call -[`file.metadata(scope='global', template='properties')`][set-metadata] -with the name of the classification template, as well as the details of the classification -to add to the file. - - -```python -classification = { - 'Box__Security__Classification__Key': 'Sensitive', -} -applied_metadata = client.file(file_id='11111').metadata(scope='enterprise', template='securityClassification-6VMVochwUWo').set(classification) -``` - -[set-metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.metadata - -Update classification on file ------------------------------ - -To update a classification on a file, call -[`file.metadata(scope='global', template='properties')`][update-metadata] -with the name of the classification template, as well as the details of the classification -to add to the file. - - -```python -classification = { - 'Box__Security__Classification__Key': 'Sensitive', -} -applied_metadata = client.file(file_id='11111').metadata(scope='enterprise', template='securityClassification-6VMVochwUWo').set(classification) -``` - -[update-metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.metadata - -Get classification on file --------------------------- - -Retrieve the classification on a file by calling -[`file.metadata(scope='global', template='properties').get()`](https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.get) -on a file. - - -```python -metadata = client.file(file_id='11111').metadata(scope='enterprise', template='securityClassification-6VMVochwUWo').get() -``` - -Remove classification from file -------------------------------- - -A classification can be removed from a file by calling -[`file.metadata(scope='global', template='properties').delete()`](https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.delete). - - -```python -client.file(file_id='11111').metadata(scope='securityClassification-6VMVochwUWo', template='myMetadata').delete() -``` - - - -Add classification to folder --------------------------- - -To add a classification to a folder, call -[`folder.metadata(scope='global', template='properties')`][set-metadata] -with the name of the classification template, as well as the details of the classification -to add to the folder. - - -```python -classification = { - 'Box__Security__Classification__Key': 'Sensitive', -} -applied_metadata = client.folder(folder_id='11111').metadata(scope='enterprise', template='securityClassification-6VMVochwUWo').set(classification) -``` - -[set-metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.metadata - -Update classification on folder ------------------------------ - -To update a classification on a folder, call -[`folder.metadata(scope='global', template='properties')`][update-metadata] -with the name of the classification template, as well as the details of the classification -to add to the folder. - - -```python -classification = { - 'Box__Security__Classification__Key': 'Sensitive', -} -applied_metadata = client.folder(folder_id='11111').metadata(scope='enterprise', template='securityClassification-6VMVochwUWo').set(classification) -``` - -[update-metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.metadata - -Get classification on folder --------------------------- - -Retrieve the classification on a folder by calling -[`folder.metadata(scope='global', template='properties').get()`](https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.get) -on a folder. - - -```python -metadata = client.folder(folder_id='11111').metadata(scope='enterprise', template='securityClassification-6VMVochwUWo').get() -``` - -Remove classification from folder -------------------------------- - -A classification can be removed from a folder by calling -[`folder.metadata(scope='global', template='properties').delete()`](https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.delete). - - -```python -client.folder(folder_id='11111').metadata(scope='securityClassification-6VMVochwUWo', template='myMetadata').delete() -``` diff --git a/docs/usage/collaboration.md b/docs/usage/collaboration.md deleted file mode 100644 index 871a7ea20..000000000 --- a/docs/usage/collaboration.md +++ /dev/null @@ -1,180 +0,0 @@ -Collaborations -============== - -Collaborations are used to share folders between users or groups. They also -define what permissions a user has for a folder. - - - - - -- [Add a Collaboration](#add-a-collaboration) -- [Edit a Collaboration](#edit-a-collaboration) -- [Remove a Collaboration](#remove-a-collaboration) -- [Get a Collaboration's Information](#get-a-collaborations-information) -- [List Collaborations on a Folder or File](#list-collaborations-on-a-folder-or-file) -- [List Pending Collaborations](#list-pending-collaborations) -- [Accept or Reject a Pending Collaboration](#accept-or-reject-a-pending-collaboration) - - - -Add a Collaboration -------------------- - -You can add a collaboration on a folder or a file by calling -[`item.collaborate(accessible_by, role, can_view_path=None, notify=None, fields=None)`][collaborate]. Pass the -[`User`][user_class] or [`Group`][group_class] to collaborate the item with as the `accessible_by` parameter. The -`role` parameter determines what permissions the collaborator will have on the folder. This method returns a -[`Collaboration`][collaboration_class] object representing the new collaboration on the item. - - -```python -from boxsdk.object.collaboration import CollaborationRole - -user = client.user(user_id='11111') -collaboration = client.folder(folder_id='22222').collaborate(user, CollaborationRole.VIEWER) - -collaborator = collaboration.accessible_by -item = collaboration.item -has_accepted = 'has' if collaboration.status == 'accepted' else 'has not' -print(f'{collaborator.name} {has_accepted} accepted the collaboration to folder "{item.name}"') -``` - -Alternatively, you can also invite a user with their email address. - -```python -from boxsdk.object.collaboration import CollaborationRole - -email_of_invitee = 'testuser@example.com' -collaboration = client.folder(folder_id='22222').collaborate_with_login(email_of_invitee, CollaborationRole.VIEWER) -``` - -Or, you can invite a group using the group id - - -```python -from boxsdk.object.collaboration import CollaborationRole - -group = client.group(group_id='11111') -collaboration = client.folder(folder_id='22222').collaborate(group, CollaborationRole.VIEWER) - -collaborator = collaboration.accessible_by -item = collaboration.item -has_accepted = 'has' if collaboration.status == 'accepted' else 'has not' -print(f'{collaborator.name} {has_accepted} accepted the collaboration to folder "{item.name}"') -``` - -> __Note:__ The `can_view_path` parameter is currently only available for collaborations on folders. - -[collaborate]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.collaborate -[user_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User -[group_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.group.Group -[collaboration_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration.Collaboration - -Edit a Collaboration --------------------- - -A collaboration can be edited by calling [`collaboration.update_info(*, data=None, role=None, status=None, **kwargs)`][update_info]. -Note that `role` fields is always required when updating a collaboration. This method returns an updated -[`Collaboration`][collaboration_class] object, leaving the original unmodified. - - -```python -from boxsdk.object.collaboration import CollaborationRole - -collaboration_update = {'role': CollaborationRole.EDITOR, 'can_view_path': False} -collaboration = client.collaboration(collab_id='12345') -updated_collaboration = collaboration.update_info(data=collaboration_update) -``` - -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration.Collaboration.update_info - -Remove a Collaboration ----------------------- - -A collaboration can be removed by calling [`collaboration.delete()`][delete]. This will generally cause the user or -group associated with the collaboration to lose access to the item. This method returns `True` to indicate that removal -succeeded. - - -```python -collaboration_id = '1111' -client.collaboration(collaboration_id).delete() -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete - -Get a Collaboration's Information ---------------------------------- - -To get information about a specific collaboration, call [`collaboration.get()`][get]. This method returns a new -[`Collaboration`][collaboration_class] with fields populated by data from the API. - - -```python -collaboration = client.collaboration(collab_id='12345').get() -``` - -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -List Collaborations on a Folder or File ----------------------------------------- - -To retrieve all collaborations on a specified [`Folder`][folder_class] or [`File`][file_class], call -[`item.get_collaborations(limit=None, marker=None, fields=None)`][get_collaborations]. This method returns a -`BoxObjectCollection` that you can use to iterate over all -[`Collaboration`][collaboration_class] objects in the collection. - - -```python -collaborations = client.folder(folder_id='22222').get_collaborations() -for collab in collaborations: - target = collab.accessible_by - print(f'{target.type.capitalize()} {target.name} is collaborated on the folder') -``` - - -```python -collaborations = client.file(file_id='11111').get_collaborations() -for collab in collaborations - target = collab.accessible_by - print(f'{target.type.capitalize()} {target.name} is collaborated on the file') -``` - -[folder_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder -[file_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File -[get_collaborations]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.get_collaborations - -List Pending Collaborations ---------------------------- - -To retrieve all pending collaborations for the current user, call -[`client.get_pending_collaborations(limit=None, offset=None, fields=None)`][get_pending_collaborations]. The user can -accept or reject these collaborations. This method returns a `BoxObjectCollection` that you -can use to iterate over all pending [`Collaboration`][collaboration_class] objects in the collection. - - -```python -pending_collaborations = client.get_pending_collaborations() -for pending_collaboration in pending_collaborations: - print(f'Collaboration {pending_collaboration.id} is pending') -``` - -[get_pending_collaborations]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_pending_collaborations - -Accept or Reject a Pending Collaboration ------------------------------------------ - -To accept or reject a pending collaboration, call [`collaboration.accept()`][accept] or -[`collaboration.reject()`][reject]. These methods both return the updated [`Collaboration`][collaboration_class] -object, leaving the original unmodified. - - -```python -accepted_collab = client.collaboration(collab_id='12345').accept() - -rejected_collab = client.collaboration(collab_id='98765').reject() -``` - -[accept]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration.Collaboration.accept -[reject]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration.Collaboration.reject diff --git a/docs/usage/collaboration_allowlist.md b/docs/usage/collaboration_allowlist.md deleted file mode 100644 index 069418c5e..000000000 --- a/docs/usage/collaboration_allowlist.md +++ /dev/null @@ -1,148 +0,0 @@ -Collaboration Allowlist -======================= - -The Collaboration Allowlist API allows you to manage a set of approved domains (i.e. a allowlist) that can collaborate -with your enterprise. You can also manage whether the allowlisted domains are approved for outbound or inbound -collaboration. - -It is important to note that the collaboration allowlist functionality is only available to customers with Box Governance. - - - - - -- [List Collaboration Allowlist Entries](#list-collaboration-allowlist-entries) -- [Get Information for Collaboration Allowlist Entry](#get-information-for-collaboration-allowlist-entry) -- [Allowlist a Domain for Collaboration](#allowlist-a-domain-for-collaboration) -- [Remove a Domain from Allowlist](#remove-a-domain-from-allowlist) -- [List Exempt Users](#list-exempt-users) -- [Get Exempt User Information](#get-exempt-user-information) -- [Exempt User from Allowlist](#exempt-user-from-allowlist) -- [Remove User Exemption](#remove-user-exemption) - - - -List Collaboration Allowlist Entries ------------------------------------- - -To retrieve a list of collaboration allowlist entries, call -[`collaboration_allowlist.get_entries(limit=None, marker=None, fields=None)`][get_entries]. This method returns a -`BoxObjectCollection` which can iterate over the [`CollaborationAllowlistEntry`][entry_class] -objects in the collection. - - -```python -allowlist_entries = client.collaboration_allowlist().get_entries() -for entry in allowlist_entries: - direction = entry.direction if entry.direction != 'both' else 'bidirectional' - print(f'Domain {entry.domain} is allowlisted for {direction} collaboration') -``` - -[get_entries]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration_allowlist.CollaborationAllowlist.get_entries -[entry_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration_allowlist_entry.CollaborationAllowlistEntry - -Get Information for Collaboration Allowlist Entry -------------------------------------------------- - -To get information about a collaboration allowlist entry, use [`collaboration_allowlist_entry.get(*, fields=None, headers=None, **kwargs)`][get]. -This method returns a [`CollaborationAllowlistEntry`][entry_class] object with fields populated by data form the API. - - -```python -allowlist_entry = client.collaboration_allowlist_entry(entry_id='11111').get() -``` - -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -Allowlist a Domain for Collaboration ------------------------------------- - -To allowlist a domain for collaboration, call [`collaboration_allowlist.add_domain(domain, direction)`][add_domain] with -the domain to allowlist and the direction(s) collaboration should be allowed in. This method returns a -[`CollaborationAllowlistEntry`][entry_class] object representing the newly-allowlisted domain. - -You can determine the direction of the allowlist by passing in 'outbound', 'inbound', or 'both'. Outbound collaboration -is defined as a user in your enterprise collaborating on content owned by someone outside your enterprise. Inbound -collaboration is defined as a user outside of your enterprise collaborating on content owned by your enterprise. - - -```python -from boxsdk.object.collaboration_allowlist import AllowlistDirection -domain = 'example.com' -allowlist_entry = client.collaboration_allowlist().add_domain(domain, direction=AllowlistDirection.INBOUND) -``` - -[add_domain]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration_allowlist.CollaborationAllowlist.add_domain - -Remove a Domain from Allowlist ------------------------------- - -To remove a collaboration allowlisted domain, call [`collaboration_allowlist_entry.delete()`][delete]. This will remove -the domain from the allowlist, restricting collaboration to and from users in that domain. This method returns `True` -to indicate that deletion was successful. - - -```python -client.collaboration_allowlist_entry(entry_id='11111').delete() -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete - -List Exempt Users ------------------ - -To get all exempt users from the collaboration allowlist, call -[`collaboration_allowlist.get_exemptions(limit=None, marker=None, fields=None)`][get_exemptions]. This method returns -a `BoxObjectCollection` that allows you to iterate over each -[`CollaborationAllowlistExemptTarget`][exemption_class] in the collection. - - -```python -exemptions = client.collaboration_allowlist().get_exemptions() -for exemption in exemptions: - print(f'{exemption.user.name} (ID: {exemption.user.id}) is exempt from the collaboration allowlist') -``` - -[get_exemptions]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration_allowlist.CollaborationAllowlist.get_exemptions -[exemption_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration_allowlist_exempt_target.CollaborationAllowlistExemptTarget - -Get Exempt User Information ---------------------------- - -To get information about an exempted user, call [`collaboration_allowlist_exempt_target.get(*, fields=None, headers=None, **kwargs)`][get]. -This method will return a [`CollaborationAllowlistExemptTarget][exemption_class] with fields populated by data from the API. - - -```python -exemption_id = '11111' -exemption = client.collaboration_allowlist_exempt_target(exemption_id).get() -``` - -Exempt User from Allowlist --------------------------- - -To exempt a user from the collaboration allowlist, call [`collaboration_allowlist.add_exemption(user)`][add_exemption] -with the [`User`][user_class] to exempt from the allowlist. This user will no longer be subject to the collaboration -allowlist, and will be permitted to collaborate with users from any other domain. This method returns a -[`CollaborationAllowlistExemptTarget`][exemption_class] object representing the exempted user. - - -```python -user = client.user(user_id='11111') -exemption = client.collaboration_allowlist().add_exemption(user) -``` - -[add_exemption]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration_allowlist.CollaborationAllowlist.add_exemption -[user_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User - -Remove User Exemption ---------------------- - -To remove a user exemption from the collaboration allowlist, call -[`collaboration_allowlist_exempt_target.delete()`][delete]. This will remove the exemption and make the user subject to -the collaboration allowlist again. This method returns `True` to indicate that deletion was successful. - - -```python -client.collaboration_allowlist_exempt_target(exemption_id='22222').delete() -``` diff --git a/docs/usage/collections.md b/docs/usage/collections.md deleted file mode 100644 index 1e42cf932..000000000 --- a/docs/usage/collections.md +++ /dev/null @@ -1,81 +0,0 @@ -Collections -=========== - -Collections allow users to mark specific files, folders and web links to make it easier to find them. - - - - -- [Get a User's Collections](#get-a-users-collections) -- [Get the Items in a Collection](#get-the-items-in-a-collection) -- [Add an Item to a Collection](#add-an-item-to-a-collection) -- [Remove an Item from a Collection](#remove-an-item-from-a-collection) - - - -Get a User's Collections ------------------------- - -To get all collections belonging to a user, call [`client.collections(limit=None, offset=0, fields=None)`][collections]. -This method returns a `BoxObjectCollection` that you can use to iterate over all the -[`Collection`][collection_class] objects in the set. - - -```python -collections = client.collections() -for collection in collections: - print(f'Collection "{collection.name}" has ID {collection.id}') -``` - -[collections]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.collections -[collection_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collection.Collection - -Get the Items in a Collection ------------------------------ - -To retrieve a list of items contained in a collection, call -[`collection.get_items(limit=None, offset=0, fields=None)`][get_items]. This method returns a -`BoxObjectCollection` that you can use to iterate over all the [`BaseItem`][base_item_class] objects in -the collection. [`BaseItem`][base_item_class] is a super class for files, folders and web links. - - -```python -items = client.collection(collection_id='12345').get_items() -for item in items: - print(f'{item.type.capitalize()} "{item.name}" is in the collection') -``` - -[get_items]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collection.Collection.get_items -[base_item_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem - -Add an Item to a Collection ----------------------- - -To add an [`BaseItem`][base_item_class] to a collection, call [`item.add_to_collection(collection)`][add_to_collection] with the -[`Collection`][collection_class] you want to add the item to. This method returns the updated [`BaseItem`][base_item_class] -object. - - -```python -collection = client.collection(collection_id='12345') -updated_file = client.file(file_id='11111').add_to_collection(collection) -print(f'File "{updated_file.name}" added to collection!') -``` - -[add_to_collection]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.add_to_collection - -Remove an Item from a Collection ---------------------------- - -To remove an [`BaseItem`][base_item_class] from a collection, call -[`item.remove_from_collection(collection)`][remove_from_collection] with the [`Collection`][collection_class] you want -to remove the item from. This method returns the updated [`BaseItem`][base_item_class] object. - - -```python -collection = client.collection(collection_id='12345') -updated_file = client.file(file_id='11111').remove_from_collection(collection) -print(f'File "{updated_file.name}" removed from collection!') -``` - -[remove_from_collection]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.remove_from_collection diff --git a/docs/usage/comments.md b/docs/usage/comments.md deleted file mode 100644 index 04ef71499..000000000 --- a/docs/usage/comments.md +++ /dev/null @@ -1,123 +0,0 @@ -Comments -======== - -Comment objects represent a user-created comment on a file. They can be added directly to a file. - - - - - -- [Get Information About a Comment](#get-information-about-a-comment) -- [Get the Comments on a File](#get-the-comments-on-a-file) -- [Add a Comment to a File](#add-a-comment-to-a-file) -- [Reply to a Comment](#reply-to-a-comment) -- [Edit a Comment](#edit-a-comment) -- [Delete a Comment](#delete-a-comment) - - - -Get Information About a Comment -------------------------------- - -To get a specific comment object, first call `[client.comment(comment_id)`][comment] to construct the appropriate -[`Comment`][comment_class] object, and then call [`comment.get(*, fields=None, headers=None, **kwargs)`][get] to -retrieve the data about the comment. The latter method returns a new [`Comment`][comment_class] object with fields -populated by data from the API, leaving the original unmodified. - - -```python -comment = client.comment(comment_id='55555').get() -print(f'The comment says "{comment.message}"') -``` - -[comment]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.comment -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get -[comment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.comment.Comment - -Get the Comments on a File --------------------------- - -To retrieve the comment left on a file, call [`file.get_comments(limit=None, offset=0, fields=None)`][get_comments]. -This method returns a `BoxObjectCollection` that you can use to iterate over all the -[`Comment`][comment_class] objects in the set. - - -```python -comments = client.file(file_id='11111').get_comments() -for comment in comments: - print(f'Comment was left by {comment.created_by.name} at {comment.created_at}') -``` - -[get_comments]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_comments - -Add a Comment to a File ------------------------ - -To leave a comment on a file, call [`file.add_comment(message)`][add_comment] with the message to leave in the comment. - - -```python -comment = client.file(file_id='11111').add_comment('When should I have this done by?') -``` - -You can at-mention other users by adding special tags within the message, in the format `@[USER_ID:USER_NAME]`. For -example, to at-mention John Doe, whose user ID is `"33333"`: `@[33333:John Doe]`. - - -```python -comment = client.file(file_id='11111').add_comment('Hey @[44444:boss], when should I have this done by?') -``` - -This method returns a [`Comment`][comment_class] object representing the newly-created comment. - -[add_comment]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.add_comment - -Reply to a Comment ------------------- - -To reply to a comment, call [`comment.reply(message)`][reply] with the message to leave in the comment. - - -```python -reply_comment = client.comment(comment_id='12345').reply('If possible, please finish this by the end of the week!') -``` - -You can at-mention other users by adding special tags within the message, in the format `@[USER_ID:USER_NAME]`. For -example, to at-mention John Doe, whose user ID is `"33333"`: `@[33333:John Doe]`. - - -```python -reply_comment = client.comment(comment_id='12345').reply('@[33333:John Doe], if possible, please finish this by the end of the week!') -``` - -This method returns a [`Comment`][comment_class] object representing the newly-created comment. - -[reply]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.comment.Comment.reply - -Edit a Comment --------------- - -To edit a comment and change its message, call [`comment.edit(message)`][edit] with the message to leave in the comment. -You can at-mention other users by adding special tags within the message, in the format `@[USER_ID:USER_NAME]`. For -example, to at-mention John Doe, whose user ID is `"33333"`: `@[33333:John Doe]`. This method returns an updated -[`Comment`][comment_class] object, leaving the original unmodified. - - -```python -edited_comment = client.comment(comment_id='98765').edit('If possible, please finish this by Friday!') -``` - -[edit]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.comment.Comment.edit - -Delete a Comment ----------------- - -To delete a comment, call [`comment.delete()`][delete]. This will remove the comment from the file. This method -returns `True` to indicate that the deletion succeeded. - - -```python -client.comment(comment_id='12345').delete() -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete diff --git a/docs/usage/configuration.md b/docs/usage/configuration.md deleted file mode 100644 index c12848d14..000000000 --- a/docs/usage/configuration.md +++ /dev/null @@ -1,83 +0,0 @@ -Configuration -============= - -The Python SDK has helpful custom config that you can set for a variety of use cases. - - - - -- [Proxy](#proxy) - - [Unauthenticated Proxy](#unauthenticated-proxy) - - [Basic Authentication Proxy](#basic-authentication-proxy) -- [Configure URLs](#configure-urls) - - [Base URL](#base-url) - - [OAUTH2 URLs](#oauth2-urls) - - [Upload URL](#upload-url) -- [Max retry attmepts](#max-retry-attmepts) - - - -Proxy ------ - -### Unauthenticated Proxy - -In order to set up configuration for basic proxy with the Python SDK, simply specify the proxy address for the `Proxy.URL` field. - -```python -from boxsdk.config import Proxy -Proxy.URL = 'http://example-proxy-address.com' -``` - -### Basic Authentication Proxy - -The Python SDK also lets you set an authenticated proxy. To do this, specify the `user` and `password` fields and pass set that on the `Proxy.AUTH` field. - -```python -from boxsdk.config import Proxy -Proxy.AUTH = { - 'user': 'test_user', - 'password': 'test_password', -} -``` - -URLs configuration ------------------- - -### Base URL -The default base URL used for making API calls to Box can be changed by setting the value of the `API.BASE_API_URL` field. - -```python -from boxsdk.config import API -API.BASE_API_URL = 'https://new-base-url.com' -``` - -### OAUTH2 URLs -The default URLs used to authorize a user and obtain OAuth2 authorization tokens can be modified by overwriting -`API.OAUTH2_API_URL` and `API.OAUTH2_AUTHORIZE_URL` constants. - -```python -from boxsdk.config import API -API.OAUTH2_API_URL = 'https://my-company.com/oauth2' -API.OAUTH2_AUTHORIZE_URL = 'https://my-company.com/authorize' -``` - -### Upload URL -The default URL used when uploading files to Box can be changed by assigning a new value to the `API.UPLOAD_URL` field. -If this variable is ever changed from default value, the SDK will alwayse use this URL to upload files to Box, -even if `use_upload_session_urls` is set to `True` while creating an upload session for a chunked upload. - -```python -from boxsdk.config import API -API.UPLOAD_URL = 'https://my-company-upload-url.com' -``` - -Max retry attmepts ------------------- - -The default maximum number of retries in case of failed API call is 5 (usually 202, 429 and >= 500 response codes are retried). -To change this number you can set `API.MAX_RETRY_ATTEMPTS` field. -```python -from boxsdk.config import API -API.MAX_RETRY_ATTEMPTS = 6 -``` diff --git a/docs/usage/device_pin.md b/docs/usage/device_pin.md deleted file mode 100644 index 08628b5e2..000000000 --- a/docs/usage/device_pin.md +++ /dev/null @@ -1,63 +0,0 @@ -Device Pins -=========== - -Device pinning is a feature that allows enterprise admins to pin their user’s -corporate-managed Box account to a particular mobile device or Box Sync client. - - - - - -- [List Enterprise Device Pins](#list-enterprise-device-pins) -- [Get Device Pin Information](#get-device-pin-information) -- [Delete Device Pin](#delete-device-pin) - - - -List Enterprise Device Pins ---------------------------- - -To retrieve all device pins for an enterprise, call -[`client.device_pinners(enterprise=None, limit=None, marker=None, direction=None, fields=None)`][device_pinners]. -If an `enterprise` is not specified, this defaults to the current enterprise. This method returns a -`BoxObjectCollection` that allows you to iterate over the [`DevicePinner`][device_pin_class] objects in the collection. - - -```python -device_pins = client.device_pinners() -for pin in device_pins: - print(f'Pinned {pin.product_name} device for {pin.owned_by.name}') -``` - -[device_pinners]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.device_pinners -[device_pin_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.device_pinner.DevicePinner - -Get Device Pin Information --------------------------- - -To get information about a specific device pin, call [`device_pinner.get(*, fields=None, headers=None, **kwargs)`][get]. -This method returns a new [`DevicePinner`][device_pin_class] object with fields populated by data from the API. - - -```python -device_pin_id = '1111' -device_pin = client.device_pinner(device_pin_id).get() -print(f'{pin.product_name} device for {pin.owned_by.name} is pinned') -``` - -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -Delete Device Pin ------------------ - -To delete a specific device pin, call [`device_pinner.delete()`][delete]. This method returns `True` to indicate that -the deletion was successful. - - -```python -device_pin_id = '1111' -client.device_pin(device_pin_id).delete() -print('Device pin deleted!') -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete diff --git a/docs/usage/events.md b/docs/usage/events.md deleted file mode 100644 index 34946205c..000000000 --- a/docs/usage/events.md +++ /dev/null @@ -1,151 +0,0 @@ -Events -====== - -It is possible to poll the Box API for events, in order to get information about activity within Box as it happens. - -The Box API supports two types of event streams: one for the events specific to a particular user and one for all of -the events in an enterprise. - - - - - -- [User Events](#user-events) - - [Listening to the Event Stream](#listening-to-the-event-stream) - - [Get the Current Stream Position](#get-the-current-stream-position) - - [Get Events Manually](#get-events-manually) -- [Enterprise Events](#enterprise-events) - - [Get Events Manually](#get-events-manually-1) - - - -User Events ------------ - -The Box API provides an events endpoint that utilizes long-polling to send events in real-time. The SDK provides a -generator that automatically handles long-polling and deduplicating events. - -### Listening to the Event Stream - -To automatically receive events as they happen, call -[`events.generate_events_with_long_polling(stream_position=None, stream_type=UserEventsStreamType.ALL)`][generator] and iterate over -the results. By default, this will start listening for events from the current time onward; to get all available events, -pass a `stream_position` of `0`. The generator yields [`Event`][event_class] objects representing each event. - - -```python -events = client.events().generate_events_with_long_polling() -for event in events: - print(f'Got {event.event_type} event') -``` - -[generator]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.events.Events.generate_events_with_long_polling -[event_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.event.Event - -### Get the Current Stream Position - -It is possible to get the current stream position, which can later be used to fetch events from that point in time -forward, by calling [`events.get_latest_stream_position(stream_type=UserEventsStreamType.ALL)`][get_stream_position]. -This method returns the current stream position value as an `int`. - -```python -stream_position = client.events().get_latest_stream_position() -print(f'The current stream position is {stream_position}') -``` - -[get_stream_position]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.events.Events.get_latest_stream_position - -### Get Events Manually - -To manually retrieve a set of events, call -[`events.get_events(limit=100, stream_position=0, stream_type=UserEventsStreamType.ALL)`][get_events]. By default, this -will fetch the first available events chronologically; you can pass a specific `stream_position` to get events from a -particular time. This method returns a `dict` with the relevant [`Event`][event_class] objects in a `list` under the -`entries` key and the next stream position value under the `next_stream_position` key. - - -```python -stream_position = 0 -events = client.events().get_events(stream_position=stream_position) -stream_position = events['next_stream_position'] -for event in events['entries']: - print(f'Got {event.event_type} event that occurred at {event.created_at}') -``` - -[get_events]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.events.Events.get_events - -Enterprise Events ------------------ - -Currently, the SDK only provides a manual interface for retrieving Enterprise (or Admin) Events. - -### Get Events Manually - -To manually retrieve a set of admin events, call -[`events.get_events(limit=100, stream_position=0, stream_type=UserEventsStreamType.ALL)`][get_events] with `stream_type` -set to `EnterpriseEventsStreamType.ADMIN_LOGS`. By default, this will fetch the first available events chronologically; -you can pass a specific `stream_position` to get events from a particular time. This method returns a `dict` with the -relevant [`Event`][event_class] objects in a `list` under the `entries` key and the next stream position value under the -`next_stream_position` key. - -```python -from boxsdk.object.events import EnterpriseEventsStreamType - -stream_position = 0 -events = client.events().get_events(stream_type=EnterpriseEventsStreamType.ADMIN_LOGS, stream_position=stream_position) -stream_position = events['next_stream_position'] -for event in events['entries']: - print(f'Got {event.event_type} event that occurred at {event.created_at}') -``` - -### Get Admin Events - -The SDK also allows you to retrieve enterprise events. Use [`events.get_admin_events_streaming(self, limit=None, stream_position=0, event_types=None)`] for live monitoring (events up to two weeks, low latency) and [`events.get_admin_events(self, limit=None, stream_position=0, created_after=None, created_before=None, event_types=None)`] for historical querying (events up to one year, higher latency). -If `limit` param is set to None, then default API value (limit=100) will be used. -Live monitoring example - - -```python - events = client.events() - .get_admin_events_streaming() - for event in events['entries']: - print(f'Got {event.event_type} event that occurred at {event.created_at}') -``` - -Addditionally, a list of event types can be passed along to filter down the returned events. - - -```python - events = client.events() - .get_admin_events_streaming(event_types=['ITEM_CREATE']) - for event in events['entries']: - print(f'Got {event.event_type} event that occurred at {event.created_at}') -``` - -When using historical querying you can specify before and after a certain datetime and the types of events to retrieve with the `event_type` by calling -[`events.get_admin_events(self, limit=None, stream_position=0, created_after=None, created_before=None, event_types=None)`][admin_events_details]. -If `limit` param is set to None, then default API value (limit=100) will be used. -The format for the `created_after` and `created_before` fields are supported by [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) and look -something like this: 2019-08-12T09:12:36-00:00. For more information on the date format please see [here](https://developer.box.com/en/guides/api-calls/types-and-formats/#date-and-times). -This method returns a `dict` with the relevant [`Event`][event_class] objects in a `list` under the -`entries` key and the next stream position value under the `next_stream_position` key. - - -```python - events = client.events() - .get_admin_events(created_after='2019-07-01T22:02:24-07:00') - for event in events['entries']: - print(f'Got {event.event_type} event that occurred at {event.created_at}') -``` - -Addditionally, a list of event types can be passed along to filter down the returned events. - - -```python - events = client.events() - .get_admin_events(created_after='2019-07-01T22:02:24-07:00', event_types=['ITEM_CREATE']) - for event in events['entries']: - print(f'Got {event.event_type} event that occurred at {event.created_at}') -``` - -[admin_events_details]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.events.Events.get_admin_events diff --git a/docs/usage/file_request.md b/docs/usage/file_request.md deleted file mode 100644 index 8ecdb0cc3..000000000 --- a/docs/usage/file_request.md +++ /dev/null @@ -1,79 +0,0 @@ -File Requests -============= - -File request objects represent a file request associated with a folder. - - - - -- [Get a File Request's Information](#get-a-file-requests-information) -- [Copy a File Request's Information](#copy-a-file-requests-information) -- [Update a File Request's Information](#update-a-file-requests-information) -- [Delete a File Request](#delete-a-file-request) - - - -Get a File Request's Information ------------------------- - -To get a file request object, first call [`client.file_request(file_request_id)`][file_request] to construct the appropriate [`FileRequest`][file_request_class] object, and then calling [`file_request.get(*, fields=None, headers=None, **kwargs)`][get] will return the [`FileRequest`][file_request_class] object populated with data -from the API, leaving the original object unmodified. - - -```python -file_request = client.file_request(file_request_id='123456').get() -print(f'File request {file_request.id} on folder {file_request.folder.name}') -``` - -[file_request]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.file_request -[file_request_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file_request.FileRequest -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - - -Copy a File Request's Information ---------------------------- - -To copy a file request, first call [`client.file_request(file_request_id)`][file_request] to construct the appropriate [`FileRequest`][file_request_class] object, and then calling [`file_request.copy(folder, description=None, title=None, expires_at=None, require_description=None, require_email=None, status=None)`][copy]. It will return the [`FileRequest`][file_request_class] object populated with data new created file request from the API. - - -```python -file_request = client.file_request(file_request_id='123456') -folder = client.folder(folder_id='123456789') -new_file_request = file_request.copy(folder=folder, title="Copied file request") -``` - -[file_request_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file_request.FileRequest -[copy]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file_request.FileRequest.copy - -Update a File Request's Information ---------------------------- - -To update a file request object, call [`file_request.update_info(data=file_request_update)`][update_info] with a `dict` of properties to -update on the file request. This method returns a newly updated [`FileRequest`][file_request_class] object, leaving the original object unmodified. - - -```python -from boxsdk.object.file_request import StatusState -update_data = { - "description": 'Updated description', - "is_email_required": True, - "status": StatusState.ACTIVE -} -file_request.update_info(data=update_data) -``` - -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info -[file_request_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file_request.FileRequest - -Delete a File Request -------------- - -To delete a file request, call [`file_request.delete()`][delete], it deletes a file request permanently. - - -```python -file_request = client.file_request(file_request_id='123456') -file_request.delete() -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete \ No newline at end of file diff --git a/docs/usage/files.md b/docs/usage/files.md deleted file mode 100644 index a4f67de87..000000000 --- a/docs/usage/files.md +++ /dev/null @@ -1,1060 +0,0 @@ -Files -===== - -File objects represent individual files in Box. They can be used to download a -file's contents, upload new versions, and perform other common file operations -(move, copy, delete, etc.). - - - - -- [Files](#files) - - [Get a File's Information](#get-a-files-information) - - [Update a File's Information](#update-a-files-information) - - [Download a File](#download-a-file) - - [Get Download URL](#get-download-url) - - [Upload a File](#upload-a-file) - - [Chunked Upload](#chunked-upload) - - [Automatic Uploader](#automatic-uploader) - - [Upload new file](#upload-new-file) - - [Upload new file version](#upload-new-file-version) - - [Preflight check before upload](#preflight-check-before-upload) - - [Resume Upload](#resume-upload) - - [Abort Chunked Upload](#abort-chunked-upload) - - [Manual Process](#manual-process) - - [Create Upload Session for File Version](#create-upload-session-for-file-version) - - [Create Upload Session for File](#create-upload-session-for-file) - - [Upload Part](#upload-part) - - [Commit Upload Session](#commit-upload-session) - - [Abort Upload Session](#abort-upload-session) - - [List Upload Parts](#list-upload-parts) - - [Move a File](#move-a-file) - - [Copy a File](#copy-a-file) - - [Rename a File](#rename-a-file) - - [Delete a File](#delete-a-file) - - [Get Previous Versions of a File](#get-previous-versions-of-a-file) - - [Upload a New Version of a File](#upload-a-new-version-of-a-file) - - [Promote a Previous Version of a File](#promote-a-previous-version-of-a-file) - - [Delete a Previous Version of a File](#delete-a-previous-version-of-a-file) - - [Lock a File](#lock-a-file) - - [Unlock a File](#unlock-a-file) - - [Create a Shared Link Download URL](#create-a-shared-link-download-url) - - [Find a File for a Shared Link](#find-a-file-for-a-shared-link) - - [Create or update a Shared Link](#create-or-update-a-shared-link) - - [Get a Shared Link](#get-a-shared-link) - - [Remove a Shared Link](#remove-a-shared-link) - - [Get an Embed Link](#get-an-embed-link) - - [Get File Representations](#get-file-representations) - - [Get Thumbnail (Deprecated)](#get-thumbnail-deprecated) - - [Get Thumbnail](#get-thumbnail) - - [Set Metadata](#set-metadata) - - [Get Metadata](#get-metadata) - - [Remove Metadata](#remove-metadata) - - [Get All Metadata](#get-all-metadata) - - [Set a Classification](#set-a-classification) - - [Retrieve a Classification](#retrieve-a-classification) - - [Remove a Classification](#remove-a-classification) - - [Set retention policy expiration date](#set-retention-policy-expiration-date) - - - -Get a File's Information ------------------------- - -Calling [`file.get(*, fields=None, etag=None, **kwargs)`][get_info] on a [`File`][file_class] retrieves information -about the file from the API. This method returns a new [`File`][file_class] object populated with the information retrieved. - -You can specify an `Iterable` of fields to retrieve from the API in the `fields` parameter. - - -```python -file_id = '11111' -file_info = client.file(file_id).get() -print(f'File "{file_info.name}" has a size of {file_info.size} bytes') -``` - -[get_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get -[file_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File - -Update a File's Information ---------------------------- - -To update fields on the [`File`][file_class] object, call [`file.update_info(data=data_to_update)`][update_info] with -a `dict` of fields to update. This method returns the updated [`File`][file_class] object, leaving the original it -was called on unmodified. - - -```python -file_id = '11111' -updated_file = client.file(file_id).update_info(data={'description': 'My file'}) -``` - -Download a File ---------------- - -A file can be downloaded in two ways: by returning the entire contents of the file as `bytes` or by providing an output -stream to which the contents of the file will be written. For both methods, you can optionally download a specific -version of the file by passing the desired [`FileVersion`][file_version_class] in the `file_version` parameter. You may -also wish to download only a certain chunk of the file by passing a tuple of byte offsets via the `byte_range` -parameter — the lower and upper bounds you wish to download. - -To get the entire contents of the file as `bytes`, call [`file.content(file_version=None, byte_range=None)`][content]. - - -```python -file_id = '11111' -file_content = client.file(file_id).content() -``` - -For users with premium accounts, previous versions of a file can be downloaded. - - -```python -file_id = '11111' -file_version = client.file_version('12345') -version_content = client.file(file_id).content(file_version=file_version) -``` - -Additonally, only a part of the file can be downloaded by specifying a byte range. - -```python -file_id = '11111' -beginning_of_file_content = client.file(file_id).content(byte_range=(0,99)) -``` - -To download the file contents to an output stream, call -[`file.download_to(writeable_stream, file_version=None, byte_range=None)`][download_to] with the stream. - -```python -file_id = '11111' - -# Write the Box file contents to disk -with open('file.pdf', 'wb') as output_file: - client.file(file_id).download_to(output_file) -``` - -[file_version_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file_version.FileVersion -[content]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.content -[download_to]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.download_to - -Get Download URL ----------------- - -To get a download URL suitable for passing to a web browser or other application, which will allow someone to download -the file, call [`file.get_download_url(file_version=None)`][get_download_url]. The will return a `unicode` string -containing the file's download URL. You can optionally pass a [`FileVersion`][file_version_class] via the -`file_version` parameter to get a download URL for a specific version of the file. - - -```python -file_id = '11111' -download_url = client.file(file_id).get_download_url() -print(f'The file\'s download URL is: {download_url}') -``` - -[get_download_url]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_download_url - -Upload a File -------------- - -Files are uploaded to a folder in one of two ways: by providing a path to a file on disk, or via a readable stream -containing the file contents. - -To upload a file from a path on disk, call the -[`folder.upload(file_path, file_name=None, file_description=None,preflight_check=False, preflight_expected_size=0)`][upload] method -on the [`Folder`][folder_class] you want to upload the file into. By default, the file uploaded to Box will have the -same file name as the one on disk; you can override this by passing a different name in the `file_name` parameter. You can, optionally, also choose to set a file description upon upload by using the `file_description` parameter. -This method returns a [`File`][file_class] object representing the newly-uploaded file. - - -```python -folder_id = '22222' -new_file = client.folder(folder_id).upload('/home/me/document.pdf') -print(f'File "{new_file.name}" uploaded to Box with file ID {new_file.id}') -``` - -To upload a file from a readable stream, call -[`folder.upload_stream(file_stream, file_name, file_description=None, preflight_check=False, preflight_expected_size=0)`][upload_stream] -with the stream and a name for the file. This method returns a [`File`][file_class] object representing the -newly-uploaded file. - -```python -file_name = 'file.pdf' -stream = open('/path/to/file.pdf', 'rb') - -folder_id = '22222' -new_file = client.folder(folder_id).upload_stream(stream, file_name) -print(f'File "{new_file.name}" uploaded to Box with file ID {new_file.id}') -``` - ----- -**NOTE:** - -Both methods `folder.upload()` and `folder.upload_stream()` include the `stream_file_content` parameter, -which controls how the file content is uploaded. - -If you are uploading a large file, you may want to stream the request to avoid excessive memory usage. -According to `requests'` library [docs][request_docs], by default, the `requests` library does not support streaming uploads, -and all the data must be read into memory before being sent to the server. -However, the `requests-toolbelt` package includes a `MultipartEncoder` class, which enables file uploads without -loading the entire file into memory. This approach is the default in the Box Python SDK. - -That said, handling 307 Temporary Redirects presents a challenge with streamed file uploads. -307 redirect requires that both the request method and body remain unchanged. -This can be problematic when uploading a file stream because the stream will already be exhausted when the redirect occurs. - -To address this issue, the `stream_file_content` parameter has been introduced in upload methods. This allows you to choose between: - - Streaming the file (`stream_file_content=True`): Optimizes memory usage but may cause issues with redirects. - - - Using the default `requests'` library behavior (`stream_file_content=False`): Ensures the file can be re-read if a - redirect occurs but may consume more memory. This is especially important when working with proxy servers. - -[folder_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder -[upload]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.upload -[upload_stream]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.upload_stream -[request_docs]: https://docs.python-requests.org/en/latest/user/quickstart/#post-a-multipart-encoded-file - -Chunked Upload --------------- - -For large files or in cases where the network connection is less reliable, -you may want to upload the file in parts. This allows a single part to fail -without aborting the entire upload, and failed parts can then be retried. - -Since box-python-sdk 3.11.0 release, by default the SDK uses upload urls provided in response -when creating a new upload session. This allowes to always upload your content to the closest Box data center and -can significantly improve upload speed. You can always disable this feature and always use base upload url by -setting `use_upload_session_urls` flag to `False` when creating upload session. - -### Automatic Uploader - -Since box-python-sdk 3.7.0 release, automatic uploader uses multiple threads, which significantly speeds up the upload process. -By default, automatic chunked uploader will use 5 threads. -You can change this number by setting `API.CHUNK_UPLOAD_THREADS` to a new number. - -```python -from boxsdk.config import API -API.CHUNK_UPLOAD_THREADS = 6 -``` - -#### Upload new file - -The SDK provides a method of automatically handling a chunked upload. First get a folder you want to upload the file to. -Then call [`folder.get_chunked_uploader(file_path, rename_file=False, use_upload_session_urls=True)`][get_chunked_uploader_for_file] -to retrieve a [`ChunkedUploader`][chunked_uploader_class] object. Setting `use_upload_session_urls` to `True` inilializes -the uploader that utlizies urls returned by the `Create Upload Session` endpoint response unless a custom -API.UPLOAD_URL was set in the config. Setting `use_upload_session_urls` to `False` inilializes the uploader that uses always base upload urls. -Calling the method [`chunked_upload.start()`][start] will kick off the chunked upload process and return the [File][file_class] -object that was uploaded. - - -```python -# uploads large file to a root folder -chunked_uploader = client.folder('0').get_chunked_uploader(file_path='/path/to/file.txt', file_name='new_name.txt') -uploaded_file = chunked_uploader.start() -print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}') -``` - -You can also upload file stream by creating a [`UploadSession`][upload_session_class] first. This can be done by calling -[`folder.create_upload_session(file_size, file_name=None, use_upload_session_urls=True)`][create_upload_session] method. -`use_upload_session_urls` flag is used to determine if the upload session should use urls returned by -the `Create Upload Session` endpoint or should it always use base upload urls. Then you can call -method [`upload_session.get_chunked_uploader_for_stream(content_stream, file_size)`][get_chunked_uploader_for_stream]. - -```python -test_file_path = '/path/to/large_file.mp4' -with open(test_file_path, 'rb') as content_stream: - total_size = os.stat(test_file_path).st_size - upload_session = client.folder('0').create_upload_session(file_size=total_size, file_name='large_file.mp4') - chunked_uploader = upload_session.get_chunked_uploader_for_stream(content_stream=content_stream, file_size=total_size) - uploaded_file = chunked_uploader.start() - print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}') -``` - -#### Upload new file version - -To upload a new file version for a large file, first get a file you want to replace. -Then call [`file.get_chunked_uploader(file_path, rename_file=False, use_upload_session_urls=True)`][get_chunked_uploader_for_version] -to retrieve a [`ChunkedUploader`][chunked_uploader_class] object. Calling the method [`chunked_upload.start()`][start] -will kick off the chunked upload process and return the updated [File][file_class]. - - -```python -# uploads new large file version -chunked_uploader = client.file('existing_big_file_id').get_chunked_uploader(file_path='/path/to/file') -uploaded_file = chunked_uploader.start() -print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}') -# the uploaded_file.id will be the same as 'existing_big_file_id' -``` - -#### Preflight check before upload - -To check if a file can be uploaded with given name to a specific folder call -[`folder.preflight_check(size, name)`][preflight_check]. If the check did not pass, this method will raise an exception -including some details on why it did not pass. - - -```python -file_name = 'large_file.mp4' -test_file_path = '/path/to/large_file.mp4' -total_size = os.stat(test_file_path).st_size -destination_folder_id = '0' -try: - client.folder(destination_folder_id).preflight_check(size=total_size, name=file_name) -except BoxAPIException as e: - print(f'File {file_name} cannot be uploaded to folder with id: {destination_folder_id}. Reason: {e.message}') -``` - -[start]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.chunked_uploader.ChunkedUploader.start -[chunked_uploader_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.chunked_uploader.ChunkedUploader -[get_chunked_uploader_for_version]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_chunked_uploader -[get_chunked_uploader_for_file]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.get_chunked_uploader -[upload_session_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.upload_session.UploadSession -[get_chunked_uploader_for_stream]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.upload_session.UploadSession.get_chunked_uploader_for_stream -[preflight_check]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.preflight_check - -#### Resume Upload - -Sometimes an upload can be interrupted, in order to resume uploading where you last left off, simply call the -[`chunked_uploader.resume()`][resume] method. This will return the [File][file_class] object that was uploaded. - -```python -chunked_uploader = client.file('12345').get_chunked_uploader('/path/to/file') -try: - uploaded_file = chunked_uploader.start() -except: - uploaded_file = chunked_uploader.resume() -print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}') -``` - -[resume]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.chunked_uploader.ChunkedUploader.resume - -#### Abort Chunked Upload - -To abort a running upload, which cancels all currently uploading chunks and aborts the upload session, call the method -[`chunked_uploader.abort()`][abort]. - -```python -from boxsdk.exception import BoxNetworkException - -test_file_path = '/path/to/large_file.mp4' -content_stream = open(test_file_path, 'rb') -total_size = os.stat(test_file_path).st_size -chunked_uploader = client.file('existing_big_file_id').get_chunked_uploader(file_path='/path/to/file') -try: - uploaded_file = chunked_uploader.start() -except BoxNetworkException: - chunked_uploader.abort() -``` - -### Manual Process - -For more complicated upload scenarios, such as those being coordinated across multiple processes or when an unrecoverable error occurs with the automatic uploader, the endpoints for chunked upload operations are also exposed directly. - -For example, this is roughly how a chunked upload is done manually: - -```python -import hashlib -import os - - -test_file_path = '/path/to/large_file.mp4' -total_size = os.stat(test_file_path).st_size -sha1 = hashlib.sha1() -content_stream = open(test_file_path, 'rb') -upload_session = client.folder(folder_id='11111').create_upload_session(file_size=total_size, file_name='test_file_name.mp4') -part_array = [] - -for part_num in range(upload_session.total_parts): - - copied_length = 0 - chunk = b'' - while copied_length < upload_session.part_size: - bytes_read = content_stream.read(upload_session.part_size - copied_length) - if bytes_read is None: - # stream returns none when no bytes are ready currently but there are - # potentially more bytes in the stream to be read. - continue - if len(bytes_read) == 0: - # stream is exhausted. - break - chunk += bytes_read - copied_length += len(bytes_read) - - uploaded_part = upload_session.upload_part_bytes(chunk, part_num*upload_session.part_size, total_size) - part_array.append(uploaded_part) - updated_sha1 = sha1.update(chunk) -content_sha1 = sha1.digest() -uploaded_file = upload_session.commit(content_sha1=content_sha1, parts=part_array) -print(f'File ID: {uploaded_file.id} and File Name: {uploaded_file.name}') -``` - -The individual endpoint methods are detailed below: - -#### Create Upload Session for File Version - -To create an upload session for uploading a large version, call -[`file.create_upload_session(file_size, file_name=None, use_upload_session_urls=True)`][create_version_upload_session] -with the size of the file to be uploaded. You can optionally specify a new `file_name` to rename the file on upload. -`use_upload_session_urls` flag is used to determine if the upload session should use urls returned by -the `Create Upload Session` endpoint or should it always use base upload urls. This method returns an -[`UploadSession`][upload_session_class] object representing the created upload session. - - -```python -file_size = 26000000 -upload_session = client.file('11111').create_upload_session(file_size) -print(f'Created upload session {upload_session.id} with chunk size of {upload_session.part_size} bytes') -``` - -[create_version_upload_session]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.create_upload_session -[upload_session_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.upload_session.UploadSession - -#### Create Upload Session for File - -To create an upload session for uploading a new large file, call -[`folder.create_upload_session(file_size, file_name, use_upload_session_urls=True)`][create_upload_session] with -the size and filename of the file to be uploaded. `use_upload_session_urls` flag is used to determine if the upload -session should use urls returned by the `Create Upload Session` endpoint or should it always use base upload urls. -This method returns an [`UploadSession`][upload_session_class] object representing the created upload session. - - -```python -file_size = 26000000 -file_name = 'test_file.pdf' -upload_session = client.folder('22222').create_upload_session(file_size, file_name) -print(f'Created upload session {upload_session.id} with chunk size of {upload_session.part_size} bytes') -``` - -[create_upload_session]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.create_upload_session - -#### Upload Part - -To upload a part of the file to this session, call -[`upload_session.upload_part_bytes(part_bytes, offset, total_size, part_content_sha1=None)`][upload_part_bytes] with -the `bytes` to be uploaded, the byte offset within the file (which should be a multiple of the upload session -`part_size`), and the total size of the file being uploaded. This method returns a `dict` for the part record; these -records should be kept for the commit operation. - -> __Note:__ The number of bytes uploaded for each part must be exactly `upload_sesion.part_size`, except for the last -> part (which just includes however many bytes are left in the file). - - -```python -upload_session = client.upload_session('11493C07ED3EABB6E59874D3A1EF3581') -offset = upload_session.part_size * 3 -total_size = 26000000 -part_bytes = b'abcdefgh' -part = upload_session.upload_part_bytes(part_bytes, offset, total_size) -print(f'Successfully uploaded part ID {part["part_id"]}') -``` - -[upload_part_bytes]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.upload_session.UploadSession.upload_part_bytes - -#### Commit Upload Session - -After uploading all parts of the file, commit the upload session to Box by calling -[`upload_session.commit(content_sha1, parts=None, file_attributes=None, etag=None)`][commit] with the SHA1 hash of the -entire file. For best consistency guarantees, you should also pass an `Iterable` of the parts `dict`s via the `parts` -parameter; otherwise, the list of parts will be retrieved from the API. You may also pass a `dict` of `file_attributes` -to set on the new file. - - -```python -import hashlib - -sha1 = hashlib.sha1() -# sha1 should have been updated with all the bytes of the file - -file_atributes = { - 'description': 'A file uploaded via Chunked Upload', -} - -upload_session = client.upload_session('11493C07ED3EABB6E59874D3A1EF3581') -uploaded_file = upload_session.commit(sha1.digest(), file_atributes=file_atributes) -print(f'Successfully uploaded file {uploaded_file.id} with description {uploaded_file.description}') -``` - -[commit]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.upload_session.UploadSession.commit - -#### Abort Upload Session - -To abort a chunked upload and lose all uploaded file parts, call [`upload_session.abort()`][abort]. This method returns -`True` to indicate that the deletion succeeded. - - -```python -client.upload_session('11493C07ED3EABB6E59874D3A1EF3581').abort() -print('Upload was successfully canceled') -``` - -[abort]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.upload_session.UploadSession.abort - -#### List Upload Parts - -To return the list of parts uploaded so far, call [`upload_session.get_parts(limit=None, offset=None)`][get_parts]. -This method returns a `BoxObjectCollection` that allows you to iterate over the part `dict`s in the collection. - - -```python -parts = client.upload_session('11493C07ED3EABB6E59874D3A1EF3581').get_parts() -for part in parts: - print(f'Part {part["part_id"]} at offset {part["offset"]} has already been uploaded') -``` - -[get_parts]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.upload_session.UploadSession.get_parts - -Move a File ------------ - -To move a file from one folder into another, call [`file.move(parent_folder, name=None)`][move] with the destination -folder to move the file into. You can optionally provide a `name` parameter to automatically rename the file in case -of a name conflict in the destination folder. This method returns the updated [`File`][file_class] object in the new -folder. - -```python -file_id = '11111' -destination_folder_id = '44444' - -file_to_move = client.file(file_id) -destination_folder = client.folder(destination_folder_id) - -moved_file = file_to_move.move(parent_folder=destination_folder) -print(f'File "{moved_file.name}" has been moved into folder "{moved_file.parent.name}"') -``` - -[move]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.move - -Copy a File ------------ - -A file can be copied to a new folder by calling [`file.copy(*, parent_folder, name=None, file_version=None, **_kwargs)`][copy] -with the destination folder and an optional new name for the file in case there is a name conflict in the destination -folder. This method returns a [`File`][file_class] object representing the copy of the file in the destination folder. - - -```python -file_id = '11111' -destination_folder_id = '44444' - -file_to_copy = client.file(file_id) -destination_folder = client.folder(destination_folder_id) - -file_copy = file_to_copy.copy(parent_folder=destination_folder) -print(f'File "{file_copy.name}" has been copied into folder "{file_copy.parent.name}"') -``` - -[copy]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.copy - -Rename a File ------------ - -A file can be renamed by calling [`file.rename(name)`][rename]. This method returns the updated -[`File`][file_class] object with a new name. Remeber to provide also extension of the file along with the new name. - -```python -file = client.file(file_id='11111') - -renamed_file = file.rename("new-name.pdf") -print(f'File was renamed to "{renamed_file.name}"') -``` - -[rename]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.rename - -Delete a File -------------- - -Calling the [`file.delete()`][delete] method will delete the file. Depending on enterprise settings, this will either move -the file to the user's trash or permanently delete the file. This method returns `True` to signify that the deletion -was successful. - - -```python -client.file(file_id='11111').delete() -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.delete - -Get Previous Versions of a File -------------------------------- - -Previous versions of a file can be retrieved with the -[`file.get_previous_versions(limit=None, offset=None, fields=None)`][get_previous_versions] method. This method returns -a [`BoxObjectCollection`][box_object_collection] that can iterate over the [`FileVersion`][file_version_class] objects -in the collection. - - -```python -file_id = '11111' - -file_versions = client.file(file_id).get_previous_versions() -for version in file_versions: - print(f'File version {version.id} was created at {version.created_at}') -``` - -[get_previous_versions]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_previous_versions -[box_object_collection]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.pagination.html#boxsdk.pagination.box_object_collection.BoxObjectCollection - -Upload a New Version of a File ------------------------------- - -New versions of a file can be uploaded in one of two ways: by providing a path to a file on disk, or via a readable -stream containing the file contents. - -To upload a new file version from a path on disk, call the -[`file.update_contents(file_path, etag=None, preflight_check=False, preflight_expected_size=0)`][update_contents] -method. This method returns a [`File`][file_class] object representing the updated file. - - -```python -file_id = '11111' -file_path = '/path/to/file.pdf' - -updated_file = client.file(file_id).update_contents(file_path) -print(f'File "{updated_file.name}" has been updated') -``` - -To upload a file version from a readable stream, call -[`file.update_contents_with_stream(file_stream, etag=None, preflight_check=False, preflight_expected_size=0)`][update_contents_with_stream] -with the stream. This method returns a [`File`][file_class] object representing the -newly-uploaded file. - -```python -file_id = '11111' -stream = open('/path/to/file.pdf', 'rb') - -updated_file = client.file(file_id).update_contents_with_stream(stream) -print(f'File "{updated_file.name}" has been updated') -``` - -[update_contents]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.update_contents -[update_contents_with_stream]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.update_contents_with_stream - -Promote a Previous Version of a File ------------------------------------- - -A previous version of a file can be promoted by calling the [`file.promote_version(file_version)`][promote_version] -method to become the current version of the file with the [`FileVersion`][file_version_class] to promote. This create a -copy of the old file version and puts it on the top of the versions stack. This method returns the new copy -[`FileVersion`][file_version_class] object. - - -```python -file_id = '11111' -file_version_id = '12345' - -version_to_promote = client.file_version(file_version_id) - -new_version = client.file(file_id).promote_version(version_to_promote) -print(f'Version {file_version_id} promoted; new version {new_version.id} created') -``` - -[promote_version]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.promote_version - -Delete a Previous Version of a File ------------------------------------ - -A version of a file can be deleted and moved to the trash by calling -[`file.delete_version(file_version, etag=None)`][delete_version] with the [`FileVersion`] to delete. - - -```python -file_id = '11111' -version_id = '12345' - -version_to_delete = client.file_version(version_id) -client.file(file_id).delete_version(version_to_delete) -``` - -[delete_version]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.delete_version - -Lock a File ------------ - -A locked file cannot be modified by any other user until it is unlocked. This is useful if you want to "check out" a -file while you're working on it, to ensure that other collaborators do not make changes while your changes are in -progress. - -To lock a file, call [`file.lock(prevent_download=False, expire_time=None)`][lock]. You can optionally prevent other -users from downloading the file while it is locked by passing `True` for the `prevent_download` parameter. You can also -set an expiration time for the lock, which will automatically release the lock at the specified time. The expiration -time is formatted as an [RFC3339 datetime][rfc3339]. - -This method returns the updated [`File`][file_class] object. - -```python -file_id = '11111' - -updated_file = client.file(file_id).lock(expiration_time='2020-01-01T00:00:00-08:00') -print(f'File "{updated_file.name}" has been locked!') -``` - -[lock]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.lock -[rfc3339]: https://tools.ietf.org/html/rfc3339#section-5.8 - -Unlock a File -------------- - -A locked file can be unlocked by calling [`file.unlock()`][unlock]. This method returns the updated -[`File`][file_class] object. - -```python -file_id = '11111' - -updated_file = client.file(file_id).unlock() -print(f'File "{updated_file.name}" has been unlocked!') -``` - -[unlock]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.unlock - -Create a Shared Link Download URL --------------------- - -A shared link for a file can be generated by calling -[`file.get_shared_link_download_url(access=None, etag=None, unshared_at=None, allow_preview=None, password=None, vanity_name=None)`][get_shared_link_download_url]. -This method returns a `unicode` string containing the shared link URL. - - -```python -file_id = '11111' - -url = client.file(file_id).get_shared_link_download_url(access='collaborators', vanity_name="my-unique-vanity-name") -print(f'The file shared link download URL is: {url}') -``` - -[get_shared_link_download_url]: -https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_shared_link_download_url - -Find a File for a Shared Link ------------------------------ - -To find a file given a shared link, use the -[`client.get_shared_item`](https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html?highlight=get_shared_item#boxsdk.client.client.Client.get_shared_item) -method. - - -```python -file = client.get_shared_item('https://app.box.com/s/gjasdasjhasd', password='letmein') -``` - -Create or update a Shared Link --------------------- - -A shared link for a file can be generated or updated by calling -[`file.get_shared_link(*, access=None, etag=None, unshared_at=None, allow_download=None, allow_preview=None, -allow_edit=None, password=None, vanity_name=None, **kwargs)`][get_shared_link]. -This method returns a `unicode` string containing the shared link URL. - - -```python -file_id = '11111' - -url = client.file(file_id).get_shared_link(access='open', allow_download=True, allow_edit=True) -print(f'The file shared link URL is: {url}') -``` - -[get_shared_link]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_shared_link - -Get a Shared Link --------------------- - -To check for an existing shared link on a file, simply call -`file.shared_link` - -This method returns a `unicode` string containing the shared link URL. - - -```python -file_id = '11111' -shared_link = client.file(file_id).get().shared_link -url = shared_link['url'] -``` - -Remove a Shared Link --------------------- - -A shared link for a file can be removed by calling [`file.remove_shared_link(*, etag=None, **kwargs)`][remove_shared_link]. - - -```python -file_id = '11111' -client.file(file_id).remove_shared_link() -``` - -[remove_shared_link]: -https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.remove_shared_link - - -Get an Embed Link ------------------ - -A file embed URL can be generated by calling [`file.get_embed_url()`][get_embed_url]. This method returns a `unicode` -string containing a URL suitable for embedding in an `') -``` - -[get_embed_url]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_embed_url - -Get File Representations ------------------------- - -To get the preview representations of a file, call the -[`file.get_representation_info(rep_hints=None)`][get_representation_info] method with the -[representation hints][rep_hints] to fetch — if no hints are provided, limited information about all available -representations will be returned. This method returns a `list` of `dict`s containing the information about the -requested [representations][rep_api_obj]. - -Note that this method only provides information about a set of available representations; your -application will need to handle checking the status of the representations and downloading them -via the provided content URL template. - -```python -file_id = '11111' -rep_hints = '[pdf][extracted_text]' - -representations = client.file(file_id).get_representation_info(rep_hints) -for rep in representations: - print(f'{rep["representation"]} representation has status {rep["status"]["state"]}') - print(f'Info URL for this representation is: {rep["info"]["url"]}') - print(f'Content URL template is: {rep["content"]["url_template"]}') -``` - -[get_representation_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_representation_info -[rep_hints]: https://developer.box.com/en/reference/get-files-id/#param-X-Rep-Hints -[rep_api_obj]: https://developer.box.com/en/reference/resources/representations - -Get Thumbnail (Deprecated) --------------------------- - -A thumbnail for a file can be retrieved by calling -[`file.get_thumbnail(extension='png', min_width=None, min_height=None, max_width=None, max_height=None)`][get_thumbnail]. -This method returns the `bytes` of the thumbnail image. - - -```python -file_id = '11111' - -thumbnail = client.file(file_id).get_thumbnail(extension='jpg') -``` - -[get_thumbnail]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_thumbnail - -Get Thumbnail -------------- - -A thumbnail for a file can now be retrieved by calling [`file.get_thumbnail_representation(dimensions, extension='png')`][get_thumbnail_representation]. This method returns the `bytes` of the thumbnail image. You must pass in a dimension that is valid for the extension you pass in for this file. To find valid dimensions, you must first make a call with [`file.get_representation_info(rep_hints=None)`]. This will return a `dict` of all available representations with their extensions and dimensions. More details about can be found on our developer docs [here](https://developer.box.com/guides/representations/list-all-representations/). - - -```python -file_id = '11111' - -thumbnail = client.file(file_id).get_thumbnail_representation('92x92', extension='jpg') -``` - -[get_thumbnail_representation]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_thumbnail_representation - -Set Metadata ------------- - -To set metadata on a file call the [`file.metadata(scope='global', template='properties')`][metadata] -to specify the scope and template key of the metadata template to attach. Then, call the [`metadata.set(data)`][metadata_set] method with the key/value pairs to attach. This method returns a `dict` containing the applied metadata instance. - -Note: This method will unconditionally apply the provided metadata, overwriting the existing metadata for the keys provided. -To specifically create or update metadata, see the `create()` or `update()` methods. - -```python -metadata = { - 'foo': 'bar', -} -applied_metadata = client.file(file_id='11111').metadata(scope='enterprise', template='testtemplate').set(metadata) -print(f'Set metadata in instance ID {applied_metadata["$id"]}') -``` - -Metadata can be added to a file either as free-form key/value pairs or from an existing template. To add metadata to -a file, first call [`file.metadata(scope='global', template='properties')`][metadata] to specify the scope and -template key of the metadata template to attach (or use the default values to attach free-form keys and values). Then, -call [`metadata.create(data)`][metadata_create] with the key/value pairs to attach. This method can only be used to -attach a given metadata template to the file for the first time, and returns a `dict` containing the applied metadata -instance. - -Note: This method will only succeed if the provided metadata template is not currently applied to the file, otherwise it will -fail with a Conflict error. - - -```python -metadata = { - 'foo': 'bar', - 'baz': 'quux', -} - -applied_metadata = client.file(file_id='11111').metadata().create(metadata) -print(f'Applied metadata in instance ID {applied_metadata["$id"]}') -``` - -Updating metadata values is performed via a series of discrete operations, which are applied atomically against the -existing file metadata. First, specify which metadata will be updated by calling -[`file.metadata(scope='global', template='properties')`][metadata]. Then, start an update sequence by calling -[`metadata.start_update()`][metadata_start_update] and add update operations to the returned -[`MetadataUpdate`][metadata_update_obj]. Finally, perform the update by calling -[`metadata.update(metadata_update)`][metadata_update]. This final method returns a `dict` of the updated metadata -instance. - -Note: This method will only succeed if the provided metadata template has already been applied to the file; If the file does not -have existing metadata, this method will fail with a Not Found error. This is useful you know the file will already have metadata applied, -since it will save an API call compared to `set()`. - - -```python -file_obj = client.file(file_id='11111') -file_metadata = file_obj.metadata(scope='enterprise', template='myMetadata') - -updates = file_metadata.start_update() -updates.add('/foo', 'bar') -updates.update('/baz', 'murp', old_value='quux') # Ensure the old value was "quux" before updating to "murp" - -updated_metadata = file_metadata.update(updates) -print('Updated metadata on file!') -print(f'foo is now {updated_metadata["foo"]} and baz is now {updated_metadata["baz"]}') -``` - -[set_metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.set -[metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.metadata -[metadata_create]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.create -[metadata_start_update]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.start_update -[metadata_update_obj]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.MetadataUpdate -[metadata_update]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.update - -Get Metadata ------------- - -To retrieve the metadata instance on a file for a specific metadata template, first call -[`file.metadata(scope='global', template='properties')`][metadata] to specify the scope and template key of the -metadata template to retrieve, then call [`metadata.get()`][metadata_get] to retrieve the metadata values attached to -the file. This method returns a `dict` containing the applied metadata instance. - - -```python -metadata = client.file(file_id='11111').metadata(scope='enterprise', template='myMetadata').get() -print(f'Got metadata instance {metadata["$id"]}') -``` - -[metadata_get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.get - -Remove Metadata ---------------- - -To remove a metadata instance from a file, call -[`file.metadata(scope='global', template='properties')`][metadata] to specify the scope and template key of the -metadata template to remove, then call [`metadata.delete()`][metadata_delete] to remove the metadata from the file. -This method returns `True` to indicate that the removal succeeded. - - -```python -client.file(file_id='11111').metadata(scope='enterprise', template='myMetadata').delete() -``` - -[metadata_delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.delete - -Get All Metadata ----------------- - -To retrieve all metadata attached to a file, call [`file.get_all_metadata()`][get_all_metadata]. This method returns a -[`BoxObjectCollection`][box_object_collection] that can be used to iterate over the `dict`s representing each metadata -instance attached to the -file. - - -```python -file_metadata = client.file(file_id='11111').get_all_metadata() -for instance in file_metadata: - if 'foo' in instance: - print(f'Metadata instance {instance["id"]} has value "{instance["foo"]}" for foo') -``` - -[get_all_metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.get_all_metadata - -Set a Classification --------------------- - -It is important to note that this feature is only available if you have Governance. - -To add classification to a [`File`][file_class], call [`file.set_classification(classification)`][set_classification]. -This method returns the classification type on the [`File`][file_class] object. If a classification already exists then -this call will update the existing classification with the new [`ClassificationType`][classification_type_class]. - -```python -from boxsdk.object.item import ClassificationType - -classification = client.file(file_id='11111').set_classification(ClassificationType.PUBLIC) -print(f'Classification Type is: {classification}') -``` - -The set method will always work no matter the state your [`File`][file_class] is in. For cases already where a -classification value already exists [`set_classification(classification)`][set_classification] may make multiple -API calls. - -Alternatively, if you already know you have a classification and you are simple updating it, you can use the -[`update_classification(classification)`][update_classification]. This will ultimately help you save one extra API call. - -```python -classification = client.file(file_id='11111').update_classification(ClassificationType.NONE) -print(f'Classification Type is: {classification}') -``` - -[set_classification]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.set_classification -[update_classification]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.update_classification -[classification_type_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.ClassificationType - -Retrieve a Classification -------------------------- - -To retrieve a classification from a [`File`][file_class], call [`file.get_classification()`][get_classification]. -This method returns the classification type on the [`File`][file_class] object. - -```python -classification = client.file(file_id='11111').get_classification() -print(f'Classification Type is: {classification}') -``` - -[get_classification]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.get_classification - -Remove a Classification ------------------------ - -To remove a classification from a [`File`][file_class], call [`file.remove_classification()`][remove_classification]. - -```python -client.file(file_id='11111').remove_classification() -``` - -[remove_classification]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.remove_classification - -Set retention policy expiration date -------------------------------------- - -To set new retention policy expiration date for the file, call [`set_disposition_at(date_time)`][set_disposition_at]. -This method will only work for files under retention with `permanently_delete` disposition action set. Remember that -disposition date can't be shortened once set on a file. -As the `date_time` parameter you can use either datetime string, e.g. '2035-03-04T10:14:24+14:00' or -`datetime.datetime` object. - -```python -import datetime, pytz - -new_disposition_date = datetime.datetime(year=2029, month=3, day=4, hour=10, minute=14, second=24, tzinfo=pytz.timezone('US/Alaska')) -client.file(file_id='11111').set_disposition_at(date_time=new_disposition_date) -``` - -If `datetime.datetime` object doesn't have timezone specified, the local timezone will be used. -To get the current disposition date you can use the snippet below. - -```python -disposition_date = client.file(file_id='11111').get(fields=('disposition_at',)).disposition_at -``` - -[set_disposition_at]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.set_disposition_at diff --git a/docs/usage/folders.md b/docs/usage/folders.md deleted file mode 100644 index 9c02d0c4e..000000000 --- a/docs/usage/folders.md +++ /dev/null @@ -1,498 +0,0 @@ -Folders -======= - -Folder objects represent a folder from a user's account. They can be used to -iterate through a folder's contents, collaborate a folder with another user or -group, and perform other common folder operations (move, copy, delete, etc.). - - - - -- [Get Information About a Folder](#get-information-about-a-folder) -- [Get the User's Root Folder](#get-the-users-root-folder) -- [Get the Items in a Folder](#get-the-items-in-a-folder) -- [Update a Folder](#update-a-folder) -- [Create a Folder](#create-a-folder) -- [Copy a Folder](#copy-a-folder) -- [Move a Folder](#move-a-folder) -- [Rename a File](#rename-a-file) -- [Delete a Folder](#delete-a-folder) -- [Find a Folder for a Shared Link](#find-a-folder-for-a-shared-link) -- [Create or update a Shared Link](#create-or-update-a-shared-link) -- [Get a Shared Link](#get-a-shared-link) -- [Remove a Shared Link](#remove-a-shared-link) -- [Set Metadata](#set-metadata) -- [Get Metadata](#get-metadata) -- [Remove Metadata](#remove-metadata) -- [Get All Metadata](#get-all-metadata) -- [Get Metadata For Folder Items](#get-metadata-for-folder-items) -- [Set a Classification](#set-a-classification) -- [Retrieve a Classification](#retrieve-a-classification) -- [Remove a Classification](#remove-a-classification) -- [Create a Folder Lock](#create-a-folder-lock) -- [Get Folder Locks](#get-folder-locks) -- [Delete a Folder Lock](#delete-a-folder-lock) - - - -Get Information About a Folder ------------------------------- - -To retrieve information about a folder, first call [`client.folder(folder_id)`][folder] to initialize the -[`Folder`][folder_class] object. Then, call [`folder.get(*, fields=None, etag=None, **kwargs)`][get] to retrieve data about the -folder. This method returns a new [`Folder][folder_class] object with fields populated by data from the API, leaving -the original object unmodified. - -You can pass a list of `fields` to retrieve from the API in order to filter to just the necessary fields or add -ones not returned by default. - - -```python -folder = client.folder(folder_id='22222').get() -print(f'Folder "{folder.name}" has {folder.item_collection["total_count"]} items in it') -``` - -[folder]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.folder -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.get -[folder_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder - -Get the User's Root Folder --------------------------- - -To get the current user's root folder, call [`client.root_folder()`][root_folder] to initialize the appropriate -[`Folder`][folder_class] object. - -```python -root_folder = client.root_folder().get() -``` - -[root_folder]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.root_folder - -Get the Items in a Folder -------------------------- - -To retrieve the items in a folder, call -[`folder.get_items(limit=None, offset=0, marker=None, use_marker=False, sort=None, direction=None, fields=None)`][get_items]. -This method returns a `BoxObjectCollection` that allows you to iterate over all the [`Item`][item_class] objects in -the collection. - - -```python -items = client.folder(folder_id='22222').get_items() -for item in items: - print(f'{item.type.capitalize()} {item.id} is named "{item.name}"') -``` - -[get_items]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.get_items -[item_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item - -Update a Folder ---------------- - -To update a folder's information, call [`folder.update_info(*, data, etag=None, **kwargs)`][update_info] with a `dict` -of properties to update on the folder. This method returns a new updated [`Folder`][folder_class] object, leaving -the original object unmodified. - - -```python -updated_folder = client.folder(folder_id='22222').update_info(data={ - 'name': '[ARCHIVED] Planning documents', - 'description': 'Old planning documents', -}) -print('Folder updated!') -``` - -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.update_info - -Create a Folder ---------------- - -A folder can be created by calling [`folder.create_subfolder(name)`][create_subfolder] on the parent folder with the -name of the subfolder to be created. This method returns a new [`Folder`][folder_class] representing the created -subfolder. - - -```python -subfolder = client.folder('0').create_subfolder('My Stuff') -print(f'Created subfolder with ID {subfolder.id}') -``` - -[create_subfolder]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.create_subfolder - -Copy a Folder -------------- - -A folder can be copied into a new parent folder by calling [`folder.copy(parent_folder, name=None)`][copy] with the -destination folder and an optional new name for the file in case there is a name conflict in the destination folder. -This method returns a new [`Folder`][folder_class] object representing the copy of the folder in the destination folder. - - -```python -folder_id = '22222' -destination_folder_id = '44444' - -folder_to_copy = client.folder(folder_id) -destination_folder = client.folder(destination_folder_id) - -folder_copy = folder_to_copy.copy(parent_folder=destination_folder) -print(f'Folder "{folder_copy.name}" has been copied into folder "{folder_copy.parent.name}"') -``` - -[copy]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.copy - -Move a Folder -------------- - -To move a folder from one parent folder into another, call [`folder.move(parent_folder, name=None)`][move] with the -destination folder to move the folder into. You can optionally provide a `name` parameter to automatically rename the -folder in case of a name conflict in the destination folder. This method returns the updated [`Folder`][folder_class] -object in the new folder. - -```python -folder_id = '11111' -destination_folder_id = '44444' - -folder_to_move = client.folder(folder_id) -destination_folder = client.folder(destination_folder_id) - -moved_folder = folder_to_move.move(parent_folder=destination_folder) -print(f'Folder "{moved_folder.name}" has been moved into folder "{moved_folder.parent.name}"') -``` - -[move]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.move - -Rename a File ------------ - -A folder can be renamed by calling [`folder.rename(name)`][rename]. This method returns the updated -[`Folder`][folder_class] object with a new name. - -```python -folder = client.folder(folder_id='11111') - -renamed_folder = folder.rename("new-name") -print(f'Folder was renamed to "{renamed_folder.name}"') -``` - -[rename]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.rename - -Delete a Folder ---------------- - -Calling the [`folder.delete(recursive=True, etag=None)`][delete] method will delete the folder. Depending on enterprise -settings, this will either move the folder to the user's trash or permanently delete the folder. This method returns -`True` to signify that the deletion was successful. - -By default, the method will delete the folder and all of its contents; to fail the deletion if the folder is not empty, -set the `recursive` parameter to `False`. - - -```python -client.folder(folder_id='22222').delete() -``` - -[delete]: -https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.delete - -Find a Folder for a Shared Link ------------------------------ - -To find a folder given a shared link, use the -[`client.get_shared_item`](https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html?highlight=get_shared_item#boxsdk.client.client.Client.get_shared_item) -method. - - -```python -folder = client.get_shared_item('https://app.box.com/s/gjasdasjhasd', password='letmein') -``` - -Create or update a Shared Link --------------------- - -A shared link for a folder can be generated or updated by calling -[`folder.get_shared_link(access=None, etag=None, unshared_at=None, allow_download=None, allow_preview=None, -password=None, vanity_name=None, **kwargs)`][get_shared_link]. -This method returns a `unicode` string containing the shared link URL. - - -```python -folder_id = '11111' - -url = client.folder(folder_id).get_shared_link(access='open', allow_download=False) -print(f'The folder shared link URL is: {url}') -``` - -[get_shared_link]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.get_shared_link - - -Get a Shared Link --------------------- - -To check for an existing shared link on a folder, simply call -`folder.shared_link` - -This method returns a `unicode` string containing the shared link URL. - - -```python -folder_id = '11111' -shared_link = client.folder(folder_id).get().shared_link -url = shared_link['url'] -``` - -Remove a Shared Link --------------------- - -A shared link for a folder can be removed by calling -[`folder.remove_shared_link(etag=None, **kwargs)`][remove_shared_link]. - - -```python -folder_id = '11111' -client.folder(folder_id).remove_shared_link() -``` - -[remove_shared_link]: -https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.remove_shared_link - - -Set Metadata ------------- - -To set metadata on a folder call the [`folder.metadata(scope='global', template='properties')`][metadata] -to specify the scope and template key of the metadata template to attach. Then, call the [`metadata.set(data)`][metadata_set] method with the key/value pairs to attach. This method returns a `dict` containing the applied metadata instance. - -Note: This method will unconditionally apply the provided metadata, overwriting the existing metadata for the keys provided. -To specifically create or update metadata, see the `create()` or `update()` methods. - -```python -metadata = { - 'foo': 'bar', -} -applied_metadata = client.folder(folder_id='11111').metadata(scope='enterprise', template='testtemplate').set(metadata) -print(f'Set metadata in instance ID {applied_metadata["$id"]}') -``` - -Metadata can be added to a folder either as free-form key/value pairs or from an existing template. To add metadata to -a folder, first call [`folder.metadata(scope='global', template='properties')`][metadata] to specify the scope -and template key of the metadata template to attach (or use the default values to attach free-form keys and values). -Then, call [`metadata.create(data)`][metadata_create] with the key/value pairs to attach. This method can only be used -to attach a given metadata template to the folder for the first time, and returns a `dict` containing the applied -metadata instance. - -Note: This method will only succeed if the provided metadata template is not currently applied to the folder, otherwise it will -fail with a Conflict error. - - -```python -metadata = { - 'foo': 'bar', - 'baz': 'quux', -} - -applied_metadata = client.folder(folder_id='22222').metadata().create(metadata) -print(f'Applied metadata in instance ID {applied_metadata["$id"]}') -``` - -Updating metadata values is performed via a series of discrete operations, which are applied atomically against the -existing folder metadata. First, specify which metadata will be updated by calling -[`folder.metadata(scope='global', template='properties')`][metadata]. Then, start an update sequence by calling -[`metadata.start_update()`][metadata_start_update] and add update operations to the returned -[`MetadataUpdate`][metadata_update_obj]. Finally, perform the update by calling -[`metadata.update(metadata_update)`][metadata_update]. This final method returns a `dict` of the updated metadata -instance. - -Note: This method will only succeed if the provided metadata template has already been applied to the folder; If the folder does not -have existing metadata, this method will fail with a Not Found error. This is useful you know the file will already have metadata applied, -since it will save an API call compared to `set()`. - - -```python -folder = client.folder(folder_id='22222') -folder_metadata = folder.metadata(scope='enterprise', template='myMetadata') - -updates = folder_metadata.start_update() -updates.add('/foo', 'bar') -updates.update('/baz', 'murp', old_value='quux') # Ensure the old value was "quux" before updating to "murp" - -updated_metadata = folder_metadata.update(updates) -print('Updated metadata on folder!') -print(f'foo is now {updated_metadata["foo"]} and baz is now {updated_metadata["baz"]}') -``` - -[set_metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.set -[metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.metadata -[metadata_create]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.create -[metadata_start_update]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.start_update -[metadata_update_obj]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.MetadataUpdate -[metadata_update]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.update - -Get Metadata ------------- - -To retrieve the metadata instance on a folder for a specific metadata template, first call -[`folder.metadata(scope='global', template='properties')`][metadata] to specify the scope and template key of -the metadata template to retrieve, then call [`metadata.get()`][metadata_get] to retrieve the metadata values attached -to the folder. This method returns a `dict` containing the applied metadata instance. - - -```python -metadata = client.folder(folder_id='22222').metadata(scope='enterprise', template='myMetadata').get() -print(f'Got metadata instance {metadata["$id"]}') -``` - -[metadata_get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.get - -Remove Metadata ---------------- - -To remove a metadata instance from a folder, call -[`folder.metadata(scope='global', template='properties')`][metadata] to specify the scope and template key of the -metadata template to remove, then call [`metadata.delete()`][metadata_delete] to remove the metadata from the folder. -This method returns `True` to indicate that the removal succeeded. - - -```python -client.folder(folder_id='11111').metadata(scope='enterprise', template='myMetadata').delete() -``` - -[metadata_delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata.Metadata.delete - -Get All Metadata ----------------- - -To retrieve all metadata attached to a folder, call [`folder.get_all_metadata()`][get_all_metadata]. This method -returns a [`BoxObjectCollection`][box_object_collection] that can be used to iterate over the `dict`s representing each -metadata instance attached to the folder. - - -```python -folder_metadata = client.folder(folder_id='22222').get_all_metadata() -for instance in folder_metadata: - if 'foo' in instance: - print(f'Metadata instance {instance["id"]} has value "{instance["foo"]}" for foo') -``` - -[get_all_metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.get_all_metadata - -Get Metadata For Folder Items ------------------------------ - -When fetching folder items, you may wish to retrieve metadata for the items simultaneously to avoid needing to make -an API call for each item. You can retrieve up to 3 metadata instances per item by passing the special -`metadata..` field to -[`folder.get_items(limit=None, offset=0, marker=None, use_marker=False, sort=None, direction=None, fields=None)`][get_items]. -The metadata is available as a multi-level `dict` on the returned [`Item`][item_class] objects. - -```python -fields = [ - 'type', - 'id', - 'name', - 'metadata.enterprise.vendorContract', -] -items = client.folder(folder_id='22222').get_items(fields=fields) -for item in items: - if item.metadata: - metadata = item.metadata['enterprise']['vendorContract'] - print(f'{item.type.capitalize()} {item.id} is a vendor contract with vendor name {metadata["vendorName"]}') -``` - -Set a Classification --------------------- - -It is important to note that this feature is available only if you have Governance. - -To add classification to a [`Folder`][folder_class], call [`folder.set_classification(classification)`][set_classification]. -This method returns the classification type on the [`Folder`][folder_class] object. If a classification already exists then -this call will update the existing classification with the new [`ClassificationType`][classification_type_class]. - -```python -from boxsdk.object.item import ClassificationType - -classification = client.folder(folder_id='11111').set_classification(ClassificationType.PUBLIC) -print(f'Classification Type is: {classification}') -``` - -The set method will always work no matter the state your [`Folder`][folder_class] is in. For cases already where a -classification value already exists [`set_classification(classification)`][set_classification] may make multiple -API calls. - -Alternatively, if you already know you have a classification and you are simple updating it, you can use the -[`update_classification(classification)`][update_classification]. This will ultimately help you save one extra API call. - -```python -classification = client.folder(folder_id='11111').update_classification(ClassificationType.NONE) -print(f'Classification Type is: {classification}') -``` - -[set_classification]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.set_classification -[update_classification]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.update_classification -[classification_type_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.ClassificationType - -Retrieve a Classification -------------------------- - -To retrieve a classification from a [`Folder`][folder_class], call [`folder.get_classification()`][get_classification]. -This method returns the classification type on the [`Folder`][folder_class] object. - -```python -classification = client.folder(folder_id='11111').get_classification() -print(f'Classification Type is: {classification}') -``` - -[get_classification]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.get_classification - -Remove a Classification ------------------------ - -To remove a classification from a [`Folder`][folder_class], call [`folder.remove_classification()`][remove_classification]. - -```python -client.folder(folder_id='11111').remove_classification() -``` - -[remove_classification]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item.remove_classification - -Create a Folder Lock -------------- - -To lock a folder, call -[`client.folder(folder_id).create_lock()`][create-folder-lock] -with the ID of the folder. This prevents the folder from being moved and/or deleted. - -```python -lock = client.folder(folder_id).create_lock() -print(f'Created a lock with ID {lock.folder.id}') -``` - -[create_folder_lock]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.create_lock - - -Get Folder Locks -------------------------- - -To retrieve a list of the locks on a folder, call -[`client.folder(folder_id).get_locks()][get-folder-locks] -with the ID of the folder. Currently only one lock can exist per folder. Folder locks define access restrictions placed by folder owners to prevent specific folders from being moved or deleted. - -```python -locks = client.folder(folder_id).get_locks() -lock = locks.next() -print(f'A lock on a folder with ID {lock.folder.id}') -``` - -[get_folder_locks]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.get_locks - -Delete a Folder Lock ------------------- - -To remove a folder lock, call -[`client.folder_lock(folder_lock_id).delete()`][delete-folder-lock] -with the ID of the folder lock. - -```python -client.folder_lock(folder_lock_id='22222').delete() -``` - -[delete_folder_lock]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder_lock.FolderLock diff --git a/docs/usage/group.md b/docs/usage/group.md deleted file mode 100644 index ae0f7a54f..000000000 --- a/docs/usage/group.md +++ /dev/null @@ -1,243 +0,0 @@ -Groups -====== -Groups are sets of users that can be used in collaborations. - - - - - -- [List Groups](#list-groups) -- [Create a Group](#create-a-group) -- [Get Information about a Group](#get-information-about-a-group) -- [Update a Group](#update-a-group) -- [Delete a Group](#delete-a-group) -- [Get a Group's Collaborations](#get-a-groups-collaborations) -- [Add User to Group](#add-user-to-group) -- [Get Information about a Group Membership](#get-information-about-a-group-membership) -- [Update Group Membership](#update-group-membership) -- [Remove User from Group](#remove-user-from-group) -- [List Group Members](#list-group-members) -- [List Memberships for User](#list-memberships-for-user) - - - -List Groups ------------ - -Calling [`client.get_groups(name=None, limit=None, offset=None, fields=None)`][get_groups] will return a -`BoxObjectCollection` that allows you to iterate over the [`Group`][group_class] objects representing groups in the -enterprise. - - -```python -groups = client.get_groups() -for group in groups: - print(f'Group "{group.name}" has ID "{group.id}"') -``` - -Alternatively, you can set a filter on the name of the groups by passing the `name` parameter: - -```python -group_name = 'Example Group' -groups = client.get_groups(group_name) -for group in groups: - print(f'Group {group.id} has a name matching {group_name}') -``` - -[get_groups]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_groups -[group_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.group.Group - -Create a Group --------------- - -To create a new group, call -[`client.create_group(name, provenance=None, external_sync_identifier=None, description=None, invitability_level=None, member_viewability_level=None, fields=None)`][create_group] with the name of the group and any optional group properties you want to set. This method -returns a [`Group`][group_class] object representing the created group. - -You can read more about the optional parameters in the -[Create Group API documentation](https://developer.box.com/en/reference/post-groups/). - - -```python -created_group = client.create_group('Example Group') -print(f'Created group with ID {created_group.id}') -``` - -[create_group]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_group - -Get Information about a Group ------------------------------ - -To retrieve information about a group, first call [`client.group(group_id)`][group] to initialize a -[`Group`][group_class] object. Then, call [`group.get(*, fields=None, headers=None, **kwargs)`][get] to retrieve the -data about that group. This method returns a new [`Group`][group_class] object with fields populated by data form the API, -leaving the original object unmodified. - - -```python -group = client.group(group_id='11111').get() -print(f'Got group {group.name}') -``` - -You can optionally specify a list of `fields` to retrieve from the API, in order to filter out fields you don't need or -add fields that are not returned from the API by default: - -```python -group = client.group(group_id='11111').get(['name', 'description', 'provenance']) -print(f'The "{group.name}" group ({group.description}) came from {group.provenance}') -``` - -[group]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.group -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - - -Update a Group --------------- - -To update a group, call [`group.update_info(data=group_update)`][update_info] with a `dict` of the group properties -to update. This method returns a new [`Group`][group_class] object with the updates applied, leaving the original -object unmodified. - - -```python -group_update = {'name': 'New Group Name'} -updated_group = client.group(group_id='11111').update_info(data=group_update) -print(f'Changed the name of group {updated_group.id} to "{updated_group.name}"') -``` - -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info - -Delete a Group --------------- - -To delete a group, call [`group.delete()`][delete]. This method returns `True` to indicate that the deletion was -successful. - - -```python -client.group(group_id='11111').delete() -print('The group was deleted!') -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete - -Get a Group's Collaborations ----------------------------- - -To retrieve all collaborations for a group, call -[`group.get_collaborations(limit=None, offset=None, fields=None)`][get_collaborations]. This method returns a -`BoxObjectCollection` that allows you to iterate over the [`Collaboration`][collaboration_class] objects in the -collection. - - -```python -collaborations = client.group(group_id='11111').get_collaborations() -for collaboration in collaborations: - print(f'The group is collaborated on {collaboration.item.type} {collaboration.item.id}') -``` - -[get_collaborations]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.group.Group.get_collaborations -[collaboration_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.collaboration.Collaboration - -Add User to Group ------------------ - -To add a new member to the group, call -[`group.add_member(user, role='member', configurable_permissions=None)`][add_member] with the [`User`][user_class] to -add to the group. This method returns a new [`GroupMembership`][membership_class] object representing the presence of -the user in the group. - -You can optionally specify the user's `role` in the group, and for users with an admin role you can configure which -permissions they have in the group by passing a `dict` of [group permissions][permissions] to `configurable_permissions`. - - -```python -user = client.user('1111') -membership = client.group(group_id='11111').add_member(user) -print(f'Added {membership.user.name} to the {membership.group.name} group!') -``` - -[add_member]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.group.Group.add_member -[user_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User -[permissions]: https://developer.box.com/en/reference/resources/group-membership/ - -Get Information about a Group Membership ----------------------------------------- - -To retrieve information about a group membership, first call -[`client.group_membership(group_membership_id)`][group_membership] to initialize the -[`GroupMembership`][membership_class] object. Then, call [`group_membership.get(*, fields=None, headers=None, **kwargs)`][get] -to retrieve data about the group membership from the API. This returns a new [`GroupMembership`][membership_class] -object with fields populated by data from the API, leaving the original object unmodified. - - -```python -membership_id = '11111' -membership = client.group_membership(membership_id).get() -print(f'User "{membership.user.name}" is a member of the {membership.group.name} group') -``` - -[group_membership]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.group_membership -[membership_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.group_membership.GroupMembership - -Update Group Membership ------------------------ - -To update a group membership, call [`membership.update_info(data=membership_update)`][update_info] with a `dict` of -properties to update on the membership object. This method returns a new [`GroupMembership`][membership_class] object -with the changes applied, leaving the original object unmodified. - - -```python -membership_id = '1234' -membership_update = {'role': 'admin'} -updated_membership = client.group_membership(membership_id).update_info(data=membership_update) -print(f'Updated {updated_membership.user.name}\'s group role to {updated_membership.role}') -``` - -Remove User from Group ----------------------- - -To remove a user from a group, delete their associated group membership by calling [`group_membership.delete()`][delete]. -This method returns `True` to indicate that the deletion was successful. - - -```python -membership_id = '1234' -client.group_membership(membership_id).delete() -print('The membership was deleted!') -``` - -List Group Members ------------------- - -To retrieve all of the memberships for a given group, call -[`group.get_memberships(limit=None, offset=0, fields=None)`][get_memberships]. This method returns a -`BoxObjectCollection` that allows you to iterate over all of the [`GroupMembership`][membership_class] objects in the -collection. - - -```python -group_memberships = client.group(group_id='11111').get_memberships() -for membership in group_memberships: - print(f'{membership.user.name} is a {membership.role} of the {membership.group.name} group') -``` - -[get_memberships]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.group.Group.get_memberships - -List Memberships for User -------------------------- - -To retrieve all of the groups a user belongs to, get a list of their associated group memberships by calling -[`user.get_group_memberships(limit=None, offset=0, fields=None)`][get_group_memberships]. This method returns a -`BoxObjectCollection` that allows you to iterate over the [`GroupMembership`][membership_class] objects in the -collection. - - -```python -user_memberships = client.user(user_id='33333').get_group_memberships() -for membership in user_memberships: - print(f'User is in the {membership.group.name} group') -``` - -[get_group_memberships]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.get_group_memberships diff --git a/docs/usage/legal_hold.md b/docs/usage/legal_hold.md deleted file mode 100644 index 96a452d85..000000000 --- a/docs/usage/legal_hold.md +++ /dev/null @@ -1,228 +0,0 @@ -Legal Hold Policies -=================== - - -A legal hold policy blocks permanent deletion of content during ongoing litigation. Admins can create legal hold -policies and then later assign them to specific folders, files, or users. - -Legal Hold Policy information describes the basic characteristics of the Policy, such as name, description, and filter -dates. It is important to note that the legal hold object contains no information about what this policy applies to. - -If an order of discovery is received or the customer is part of an ongoing litigation, a legal hold policy can be -created to keep track of everything that needs to be held. The actual holding is done via Legal Hold Assignments. - - - - - -- [Get Information About a Legal Hold Policy](#get-information-about-a-legal-hold-policy) -- [List Legal Hold Policies](#list-legal-hold-policies) -- [Create New Legal Hold Policy](#create-new-legal-hold-policy) -- [Update Legal Hold Policy](#update-legal-hold-policy) -- [Delete Legal Hold Policy](#delete-legal-hold-policy) -- [Assign Legal Hold Policy](#assign-legal-hold-policy) -- [List Legal Hold Policy Assignments](#list-legal-hold-policy-assignments) -- [Get Information About a Legal Hold Assignment](#get-information-about-a-legal-hold-assignment) -- [Delete Legal Hold Assignment](#delete-legal-hold-assignment) -- [List File Version Legal Holds](#list-file-version-legal-holds) -- [Get Information about a File Version Legal Hold](#get-information-about-a-file-version-legal-hold) - - - -Get Information About a Legal Hold Policy ------------------------------------------ - -To retrieve information about a legal hold policy, first call [`client.legal_hold_policy(policy_id)`][legal_hold_policy] -to initialize the [`LegalHoldPolicy`][policy_class] and then call [`legal_hold_policy.get(*, fields=None, headers=None, **kwargs)`][get] to -retrieve data from the API. This method returns a new [`LegalHoldPolicy`][policy_class] object with fields populated by -data form the API, leaving the original object unmodified. - - -```python -legal_hold_policy = client.legal_hold_policy(policy_id='12345').get() -print(f'The "{legal_hold_policy.policy_name}" policy is {legal_hold_policy.status}') -``` - -[legal_hold_policy]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.legal_hold_policy -[policy_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.legal_hold_policy.LegalHoldPolicy -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -List Legal Hold Policies ------------------------- - -To get the legal hold policies available in the enterprise, call -[`client.get_legal_hold_policies(policy_name=None, limit=None, marker=None, fields=None)`][get_legal_hold_policies]. -You can optionally pass a `policy_name` value to filter the results to include only policies that are a case-insensitive -prefix match by name. This method returns a `BoxObjectCollection` that allows you to iterate over the -[`LegalHoldPolicy`][policy_class] objects in the collection. - - -```python -policies = client.get_legal_hold_policies() -for policy in policies: - print(f'Legal Hold Policy "{policy.name}" has ID {policy.id}') -``` - -[get_legal_hold_policies]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_legal_hold_policies - -Create New Legal Hold Policy ----------------------------- - -To create a new legal hold policy, call -[`client.create_legal_hold_policy(policy_name, description=None, filter_starting_at=None, filter_ending_at=None, is_ongoing=None)`][create_legal_hold_policy] with the name for the policy. You can optionally include a human-readable `description`, as -well as parameters describing which time period the policy applies to. You must specify either `filter_starting_at` -and `filter_ending_at` dates, or `is_ongoing=True`. This method returns a new [`LegalHoldPolicy`][policy_class] object -representing the created policy. - - -```python -new_policy = client.create_legal_hold_policy('New Policy', is_ongoing=True) -print(f'Created legal hold policy with ID {new_policy.id}') -``` - -[create_legal_hold_policy]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_legal_hold_policy - -Update Legal Hold Policy ------------------------- - -To update an existing legal hold policy, call [`legal_hold_policy.update_info(data=policy_update)`][update_info] with -a `dict` of properties to update on the policy. This method returns a new [`LegalHoldPolicy`][policy_class] object -with the updates applied, leaving the original object unmodified. - - -```python -policy_update = {'description': 'New Description', 'release_notes': 'Example Notes'} -updated_policy = client.legal_hold_policy(policy_id='12345').update_info(data=policy_update) -``` - -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info - -Delete Legal Hold Policy ------------------------- - -To delete a legal hold policy, call [`legal_hold_policy.delete()`][delete]. This method returns `True` to indicate that -the deletion request was successful. - -> __Note:__ This is an asynchronous process - the policy assignment may not be fully deleted yet when the -> response comes back. - - -```python -client.legal_hold_policy(policy_id='12345').delete() -print('Legal hold policy was deleted!') -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete - -Assign Legal Hold Policy ------------------------- - -To assign a legal hold policy, call [`legal_hold_policy.assign(assignee)`][assign]. You can assign a legal hold policy -to a [`Folder`][folder_class], [`File`][file_class], [`FileVersion`][file_version_class], or [`User`][user_class]. -This will cause the associated items to be held and unable to be deleted. - - -```python -folder_to_assign = client.folder(folder_id='22222') -assignment = client.legal_hold_policy(policy_id'12345').assign(folder_to_assign) -print(f'Applied policy "{assignment.legal_hold_policy.policy_name}" to {assignment.assigned_to.type} {assignment.assigned_to.id}') -``` - -[assign]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.legal_hold_policy.LegalHoldPolicy.assign -[folder_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder -[file_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File -[file_version_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file_version.FileVersion -[user_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User - -List Legal Hold Policy Assignments ----------------------------------- - -To get the assignments for a specific legal hold policy, call -[`legal_hold_policy.get_assignments(assign_to_type=None, assign_to_id=None, limit=None, marker=None, fields=None)`][get_assignments]. -This method returns a `BoxObjectCollection` that allows you to iterate over the -[`LegalHoldPolicyAssignment`][assignment_class] objects in the collection. - - -```python -assignments = client.legal_hold_policy(policy_id='12345').get_assignments() -for assignment in assignments: - print(f'Found policy assignment with ID {assignment.id}') -``` - -To filter by the assignee `type` and/or `id` you can use pass in the `assign_to_type` and `assign_to_id` filter. - -```python -folder_id = '1111' -assignments = client.legal_hold_policy('1234').get_assignments('folder', folder_id) -for assignment in assignments: - print(f'Found policy assignment with ID {assignment.id}') -``` - -[get_assignments]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.legal_hold_policy.LegalHoldPolicy.get_assignments -[assignment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.legal_hold_policy_assignment.LegalHoldPolicyAssignments - -Get Information About a Legal Hold Assignment ---------------------------------------------- - -To retrieve information about a legal hold policy assignment, first call -[`client.legal_hold_policy_assignment(policy_assignment_id)`][legal_hold_policy_assignment] to initialize the -[`LegalHoldPolicyAssignment`][assignment_class] and then call [`legal_hold_policy_assignment.get(*, fields=None, headers=None, **kwargs)`][get] to -retrieve data about the assignment from the API. This method returns a new -[`LegalHoldPolicyAssignment`][assignment_class] with fields populated by data from the API, leaving the original object -unmodified. - - -```python -assignment_id = '98765' -assignment = client.legal_hold_policy_assignment(assignment_id).get() -print(f'Policy {assignment.legal_hold_policy.id} is assigned to {assignment.assigned_to.type} {assignment.assigned_to.id}') -``` - -[legal_hold_policy_assignment]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.legal_hold_policy_assignment - -Delete Legal Hold Assignment ----------------------------- - -To delete an existing legal hold policy assignment, call [`legal_hold_policy_assignment.delete()`][delete]. This method -returns `True` to indicate that the deletion request was successful. - -> __Note:__ This is an asynchronous process - the policy assignment may not be fully deleted yet when the -> response comes back. - - -```python -assignment_id = '1111' -client.legal_hold_policy_assignment(assignment_id).delete() -``` - -List File Version Legal Holds ------------------------------ - -To get the actual hold records associated with a policy, call -[`legal_hold_policy.get_file_version_legal_holds()`][get_file_version_legal_holds]. This method returns a -`BoxObjectCollection` that allows you to iterate over the [`LegalHold`][hold_class] objects in the -collection. - - -```python -legal_holds = client.legal_hold_policy(policy_id='12345').get_file_version_legal_holds() -for legal_hold in legal_holds: - print(f'Got file version legal hold with ID {legal_hold.id}') -``` - -[get_file_version_legal_holds]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.legal_hold_policy.LegalHoldPolicy.get_file_version_legal_holds -[hold_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.legal_hold.LegalHold - -Get Information about a File Version Legal Hold ------------------------------------------------ - -To retrieve information about a file version legal hold, call [`legal_hold.get(*, fields=None, headers=None, **kwargs)`][get]. This method -returns a new [`LegalHold`][hold_class] with fields populated by data from the API, leaving the original object -unmodified. - - -```python -file_version_legal_hold_id = '55555' -legal_hold = client.legal_hold(file_version_legal_hold_id).get() -print(f'Version {legal_hold.file_version.id} of file {legal_hold.file.id} is held by {len(legal_hold.legal_hold_policy_assignments)} assignment(s)') -``` diff --git a/docs/usage/metadata.md b/docs/usage/metadata.md deleted file mode 100644 index 94150a306..000000000 --- a/docs/usage/metadata.md +++ /dev/null @@ -1,162 +0,0 @@ -Metadata -======== - -Metadata allows users and applications to define and store custom data associated -with their files/folders. Metadata consists of key:value pairs that belong to -files/folders. For example, an important contract may have key:value pairs of -`"clientNumber":"820183"` and `"clientName":"bioMedicalCorp"`. - -Metadata that belongs to a file/folder is grouped by templates. Templates allow -the metadata service to provide a multitude of services, such as pre-defining sets -of key:value pairs or schema enforcement on specific fields. - -Each file/folder can have multiple distinct template instances associated with it, -and templates are also grouped by scopes. Currently, the only scopes support are -`enterprise` and `global`. Enterprise scopes are defined on a per enterprises basis, -whereas global scopes are Box application-wide. - -In addition to `enterprise` scoped templates, every file on Box has access to the -`global` `properties` template. The Properties template is a bucket of free form -key:value string pairs, with no additional schema associated with it. Properties -are ideal for scenarios where applications want to write metadata to file objects -in a flexible way, without pre-defined template structure. - - - - - -- [Create Metadata Template](#create-metadata-template) -- [Get Metadata Template](#get-metadata-template) - - [Get by scope and template key](#get-by-scope-and-template-key) - - [Get by template ID](#get-by-template-id) -- [Update Metadata Template](#update-metadata-template) -- [Get Enterprise Metadata Templates](#get-enterprise-metadata-templates) -- [Delete Metadata Template](#delete-metadata-template) - - - -Create Metadata Template ------------------------- - -To create a new metadata template, call -[`client.create_metadata_template(display_name, fields, template_key=None, hidden=False, scope='enterprise', copy_instance_on_item_copy=False)`][create_template] -with the human-readable name of the template and the [`MetadataField`s][metadata_field_class] the template should have. -You can optionally specify a key for the template, otherwise one will be derived from the display name. At the current -time, only `enterprise` scope templates are supported. This method returns a -[`MetadataTemplate`][metadata_template_class] object representing the created template. - - -```python -from boxsdk.object.metadata_template import MetadataField, MetadataFieldType - -fields = [ - MetadataField(MetadataFieldType.STRING, 'Name'), - MetadataField(MetadataFieldType.DATE, 'Birthday', 'bday'), - MetadataField(MetadataFieldType.ENUM, 'State', options=['CA', 'TX', 'NY']) -] -template = client.create_metadata_template('Employee Record', fields, hidden=True) -print(f'Metadata template ID {template.scope}/{template.templateKey} created!') -``` - -[create_template]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_metadata_template -[metadata_field_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata_template.MetadataField -[metadata_template_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata_template.MetadataTemplate - -Get Metadata Template ---------------------- - -### Get by scope and template key - -To retrieve a specific template by scope and template key, first use -[`client.metadata_template(scope, template_key)`][metadata_template] to construct the appropriate -[`MetadataTemplate`][metadata_template_class] object, and then call [`template.get()`][get] to retrieve data about -the template. This method returns a new [`MetadataTemplate`][metadata_template_class] object with fields populated by -data from the API, leaving the original object unmodified. - - -```python -template = client.metadata_template('enterprise', 'employeeRecord').get() -print(f'The {template.displayName} template has {len(template.fields)} fields') -``` - -[metadata_template]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.metadata_template -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -### Get by template ID - -To retrieve a template by ID, call [`client.get_metadata_template_by_id(template_id)`][get_by_id] with the ID of the -metadata template. This method returns a [`MetadataTemplate`][metadata_template_class] object with fields populated by -data from the API. - - -```python -template = client.metadata_template_by_id(template_id='abcdef-fba434-ace44').get() -print(f'The {template.displayName} template has {len(template.fields)} fields') -``` - -[get_by_id]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_metadata_template_by_id - -Update Metadata Template ------------------------- - -To make changes to a metadata template, first call [`template.start_update()`][start_update] to create a -[`MetadataTemplateUpdate`][template_update_class] to track updates. Call the methods on this object to add the -necessary update operations, and then call [`template.update_info(*, updates, **kwargs)`][update_info] with the updates -object to apply the changes to the metadata template. This method returns an updated -[`MetadataTemplate`][metadata_template_class] object with the changes applied, leaving the original object unmodified. - - -```python -template = client.metadata_template('enterprise', 'employeeRecord') -updates = template.start_update() -updates.add_enum_option('state', 'WI') -updates.edit_template({'hidden': False}) -updates.edit_template({'copyInstanceOnItemCopy': False}) -updated_template = template.update_info(updates=updates) -``` - -[start_update]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata_template.MetadataTemplate.start_update -[template_update_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata_template.MetadataTemplateUpdate -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata_template.MetadataTemplate.update_info - -Get Enterprise Metadata Templates ---------------------------------- - -Get all metadata templates for the current enterprise by calling -[`client.get_metadata_templates(scope='enterprise', limit=None, marker=None, fields=None)`][get_metadata_templates]. -By default, this retrieves all templates scoped to the current enterprise, but you can pass the `scope` parameter to -retrieve templates for a different scope. This method returns a [`BoxObjectCollection`][box_object_collection] that -allows you to iterate over all the [`MetadataTemplate`][metadata_template_class] objects in the collection. - - -```python -templates = client.get_metadata_templates() -for template in templates: - print(f'Metadata template {template.templateKey} is in enterprise scope') -``` - -To return the metadata templates available to all enterprises pass in the -`global` scope. - - -```python -templates = client.get_metadata_templates(scope='global) -for template in templates: - print(f'Metadata template {template.templateKey} is in global scope') -``` - -[get_metadata_templates]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_metadata_templates -[box_object_collection]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.pagination.html#boxsdk.pagination.box_object_collection.BoxObjectCollection - -Delete Metadata Template ------------------------- - -To delete a metadata template, call [`template.delete()`][delete]. This method returns `True` to indicate the deletion -was successful. - - -```python -client.metadata_template('enterprise', 'employeeRecord').delete() -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete diff --git a/docs/usage/metadata_cascade_policies.md b/docs/usage/metadata_cascade_policies.md deleted file mode 100644 index 94fb03743..000000000 --- a/docs/usage/metadata_cascade_policies.md +++ /dev/null @@ -1,115 +0,0 @@ -Metadata Cascade Policies -========================= - -Metadata Cascade Policies allow the metadata values on a folder to be applied to the files within that folder. A -cascade policy associates the folder with a specific metadata template whose instance values on the folder should be -cascaded to files within the folder. - -> __Note:__ The Metadata Cascade Policy endpoints are currently in beta. Please email -> [betas+metadata@box.com](mailto:betas+metadata@box.com) if you would like to enable this beta feature for your -> enterprise. If you do not have this enabled for your enterprise, you will get a 403 error. - - - - - -- [Create a Metadata Cascade Policy](#create-a-metadata-cascade-policy) -- [Get Information About a Metadata Cascade Policy](#get-information-about-a-metadata-cascade-policy) -- [Get Cascade Policies on a Folder](#get-cascade-policies-on-a-folder) -- [Force Apply Cascade Policy](#force-apply-cascade-policy) -- [Remove Cascade Policy](#remove-cascade-policy) - - - -Create a Metadata Cascade Policy --------------------------------- - -To create a metadata cascade policy on a folder, call [`folder.cascade_metadata(metadata_template)`][cascade_metadata] -with the [`MetadataTemplate`][metadata_template_class] whose values should be cascaded within the folder. This -method returns a [`MetadataCascadePolicy`][cascade_policy_class] object representing the newly-created policy. - - -```python -folder = client.folder(folder_id='22222') -metadata_template = client.metadata_template('enterprise', 'securityClassiciation') - -cascade_policy = folder.cascade_metadata(metadata_template) -print(f'Folder {cascade_policy.parent.id} has a metadata cascade policy for {cascade_policy.scope} template "{cascade_policy.templateKey}"') -``` - -[cascade_metadata]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.cascade_metadata -[metadata_template_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata_template.MetadataTemplate -[cascade_policy_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata_cascade_policy.MetadataCascadePolicy - -Get Information About a Metadata Cascade Policy ------------------------------------------------ - -To retrieve information about a metadata cascade policy, first call -[`client.metadata_cascade_policy(policy_id)`][initializer] with the ID of the cascade policy to initialize the -[`MetadataCascadePolicy`][cascade_policy_class] object, then call [`cascade_policy.get()`][get] to retrieve data about -the policy. This method returns a new [`MetadataCascadePolicy`][cascade_policy_class] object with fields populated by -data from the API, leaving the original object unmodified. - - -```python -cascade_policy = client.metadata_cascade_policy('84113349-794d-445c-b93c-d8481b223434').get() -print(f'Cascade policy applies to a template owned by enterprise {cascade_policy.owner_enterprise.id}') -``` - -[initializer]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.metadata_cascade_policy -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -Get Cascade Policies on a Folder --------------------------------- - -To get a list of the cascade policies applied to a folder, call -[`folder.get_metadata_cascade_policies(owner_enterprise=None, limit=None, marker=None, fields=None)`][get_metadata_cascade_policies]. -You can optionally pass an [`Enterprise`][enterprise_class] object via the `owner_enterprise` parameter to retrieve -cascade policies related to templates for a specific enterprise; if not specified, this defaults to the current -enterprise. This method returns a [`BoxObjectCollection`][box_object_collection] that allows you to iterate over the -[`MetadataCascadePolicy`][cascade_policy_class] objects in the collection. - - -```python -cascade_policies = client.folder(folder_id='22222').get_metadata_cascade_policies() -for policy in cascade_policies: - print(f'Metadata template {policy.templateKey} is cascaded') -``` - -[get_metadata_cascade_policies]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.get_metadata_cascade_policies -[enterprise_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.enterprise.Enterprise -[box_object_collection]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.pagination.html#boxsdk.pagination.box_object_collection.BoxObjectCollection - -Force Apply Cascade Policy --------------------------- - -A Cascade Policy can be forced to re-apply to all files in the associated folder by calling -[`cascade_policy.force_apply(conflict_resolution)`][force_apply] with the conflict resolution strategy to use. If -files in the folder already have metadata values that conflict with the ones being force applied from the folder, you -can choose to either preserve the existing values or overwrite them with the folder's values. This method returns -`True` to indicate that the force application was successful. - - -```python -from boxsdk.object.metadata_cascade_policy import CascadePolicyConflictResolution - -cascade_policy = client.metadata_cascade_policy(policy_id='84113349-794d-445c-b93c-d8481b223434') -cascade_policy.force_apply(CascadePolicyConflictResolution.PRESERVE_EXISTING) -print('Cascade policy was force applied!') -``` - -[force_apply]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.metadata_cascade_policy.MetadataCascadePolicy.force_apply - -Remove Cascade Policy ---------------------- - -A metadata cascade policy can be removed from a folder by calling [`cascade_policy.delete()`][delete]. This method -returns `True` to indicate that the deletion was successful. - - -```python -client.metadata_cascade_policy(policy_id='84113349-794d-445c-b93c-d8481b223434').delete() -print('Cascade policy successfully removed') -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete diff --git a/docs/usage/retention_policy.md b/docs/usage/retention_policy.md deleted file mode 100644 index 3a40af603..000000000 --- a/docs/usage/retention_policy.md +++ /dev/null @@ -1,233 +0,0 @@ -Retention Policies -================== - -A retention policy blocks permanent deletion of content for a specified amount of time. Admins can create retention -policies and then later assign them to specific folders or their entire enterprise. - - - - -- [Create Retention Policy](#create-retention-policy) -- [Get Retention Policy](#get-retention-policy) -- [Get Retention Policies](#get-retention-policies) -- [Update Retention Policy](#update-retention-policy) -- [Assign Retention Policy](#assign-retention-policy) -- [Get Retention Policy Assignment](#get-retention-policy-assignment) -- [Get Retention Policy Assignments](#get-retention-policy-assignments) -- [Get File Version Retentions](#get-file-version-retentions) (deprecated, use [Get Files under Retention for a Retention Policy Assignment](#get-files-under-retention-for-an-assignment) and [Get File Versions under Retention for a Retention Policy Assignment](#get-file-versions-under-retention-for-an-assignment) instead) -- [Get Information about a File Version Retention](#get-information-about-a-file-version-retention) -- [Get Files under Retention for a Retention Policy Assignment](#get-files-under-retention-for-a-retention-policy-assignment) -- [Get File Versions under Retention for a Retention Policy Assignment](#get-file-versions-under-retention-for-a-retention-policy-assignment) - - - -Create Retention Policy ------------------------ - -To create a retention policy object, call [`client.create_retention_policy(policy_name, disposition_action, retention_length, can_owner_extend_retention=None, are_owners_notified=None, custom_notification_recipients=None)`][create_retention_policy]. This will let you create a new indefinite -[`RetentionPolicy`][retention_policy_class] object populated with data from the API. - - -```python -policy_name = 'Test Indefinite Policy Name' -disposition_action = 'remove_retention' -indefinite_retention_policy = client.create_retention_policy(policy_name, disposition_action, float('inf')) -print(f'Indefinite Retention Policy ID is {indefinite_retention_policy.id} and the policy name is {indefinite_retention_policy.policy_name}') -``` - -Alternatively, if you want to create a finite retention policy, you can do so by calling -[`client.create_retention_policy(policy_name, disposition_action, retention_length=5)`][create_retention_policy] - -```python -policy_name = 'Test Finite Policy Name' -disposition_action = 'remove_retention' -retention_length = 5 -finite_retention_policy = client.create_retention_policy(policy_name=policy_name, disposition_action=disposition_action, retention_length=retention_length) -print(f'Finite Retention Policy ID is {finite_retention_policy.id} and the policy name is {finite_retention_policy.policy_name}') -``` - -[create_retention_policy]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_retention_policy -[retention_policy_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.retention_policy.RetentionPolicy - -Get Retention Policy --------------------- - -To get a retention policy object, first call [`client.retention_policy(retention_id)`][retention_policy] to construct the -appropriate [`RetentionPolicy`][retention_policy_class] object, and then calling [`retention_policy.get(*, fields=None, headers=None, **kwargs)`][get] -will return the [`RetentionPolicy`][retention_policy_class] object populated with data from the API. - - -```python -retention_policy = client.retention_policy(retention_id='12345').get() -print(f'Retention Policy ID is {retention_policy.id} and the name is {retention_policy.policy_name}') -``` - -[retention_policy]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.retention_policy -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -Get Retention Policies ----------------------- - -To retrieve all retention policies for an enterprise, call [`client.get_retention_policies`][get_retention_policies]. -This method returns a `BoxObjectCollection` that allows you to iterate over the -[`Retention Policy`][retention_policy_class] objects in the collection. - - -```python -retention_policies = client.get_retention_policies() -for policy in retention_policies: - print(f'The policy ID is {policy.id} and the name is {policy.policy_name}') -``` - -[get_retention_policies]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_retention_policies - -Update Retention Policy ------------------------ - -To update a retention policy object, calling [`retention_policy.update_info(data=policy_update)`][update_info] with -a `dict` of properties to update on the retention policy. This method returns a newly updates -[`RetentionPolicy`][retention_policy_class] object, leaving the original object unmodified. - - -```python -policy_update = {'policy_name': 'New Policy Name',} -updated_retention_policy = client.retention_policy(retention_id='12345').update_info(data=policy_update) -print(f'Retention Policy ID is {updated_retention_policy.id} and the new policy name is {updated_retention_policy.policy_name}') -``` - -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info - -Assign Retention Policy ------------------------ - -To assign a retention policy, call [`retention_policy.assign(folder)`][assign] will create a new -[`RetentionPolicyAssignment`][retention_policy_assignment_class] object populated with data from the API. - - -```python -folder = client.folder(folder_id='1111') -assignment = client.retention_policy(retention_id='12345').assign(folder) -print(f'Assignment ID is {assignment.id} and it is assigned by {assignment.assigned_by.name}') -``` - -[retention_policy_assignment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.retention_policy_assignment.RetentionPolicyAssignment -[assign]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.retention_policy.RetentionPolicy.assign - -Get Retention Policy Assignment -------------------------------- - -To get a retention policy object, first call [`client.retention_policy_assignment(assignment_id)`][retention_policy_assignment] -to construct the appropriate [`Retention Policy Assignment`][retention_policy_assignment_class] object, and then calling -[`retention_policy_assignment.get(*, fields=None, headers=None, **kwargs)`][get] will return the -[`Retention Policy Assignment`][retention_policy_assignment_class] object populated with data from the API. - - -```python -assignment = client.retention_policy_assignment('12345').get() -print(f'Assignment id is {assignment.id} and it is assigned by {assignment.assigned_by.name}') -``` - -[retention_policy_assignment]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.client.client.Client.retention_policy_assignment -[retention_policy_assignment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.retention_policy_assignment.RetentionPolicyAssignment -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -Get Retention Policy Assignments --------------------------------- - -To retrieve all retention policy assignments for an enterprise, call -[`retention_policy.assignments(assignment_type=None, limit=None, marker=None, fields=None)`][get_assignments] -will return a `BoxObjectCollection` that allows you to iterate over the -[`RetentionPolicyAssignment`][retention_policy_assignment_class] objects in the collection. - - -```python -assignments = client.retention_policy(retention_id='12345').assignments(limit=10) -for assignment in assignments: - print(f'Assignment ID is {assignment.id} and it is assigned by {assignment.assigned_by.name}') -``` - -Alternatively, you can also specify the `type` of assignment to retrieve with -[`retention_policy.assignments(assignment_type='folder')`][get_assignments]. - -```python -assignments = client.retention_policy(retention_id='12345').assignments(assignment_type='folder', limit=10) -for assignment in assignments: - print(f'Assignment ID is {assignment.id} and it is assigned by {assignment.assigned_by.name}') -``` - -[get_assignments]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.retention_policy.assignments - -Get File Version Retentions ---------------------------- - -To retrieve all file version retentions, call [`client.get_file_version_retentions(target_file=None, file_version=None, policy=None, disposition_action=None, disposition_before=None, disposition_after=None, limit=None, marker=None, fields=None)`][get_file_version_retentions]. This method will return a -`BoxObjectCollection` that allows you to iterate over the [`FileVersionRetention`][file_version_retention_class] -objects in the collection. - - -```python -retentions = client.get_file_version_retentions() -for retention in retentions: - print(f'The file version retention ID is {retention.id} and the data time applied at is {retention.applied_at}') -``` - -[get_file_version_retentions]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client,Client.get_file_version_retentions -[file_version_rention_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file_version_retention.FileVersionRetention - - -Get Information about a File Version Retention ----------------------------------------------- - -To get a file version retention object, first call [`client.file_version_retention(retention_id)`][file_version_retention] -to construct the appropriate [`File Version Retention`][file_version_retention_class] object, and then calling -[`file_version_retention.get(*, fields=None, headers=None, **kwargs)`][get] will return the [`FileVersionRetention`][file_version_retention] -object populated with data from the API. - - -```python -retention_info = client.file_version_retention(retention_id='12345').get() -print(f'The file version retention ID is {retention.id} and the data time applied at is {retention.applied_at}') -``` - -[file_version_retention]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.file_version_retention -[file_version_retention_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file_version_retention.FileVersionRetention -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -Get Files under Retention for a Retention Policy Assignment ---------------------------- - -To retrieve all files under retention for a Retention Policy Assignment, -call [`retention_policy_assignment.get_files_under_retention(limit=None, marker=None)`][get-files-under-retention-for-an-assignment]. -This method will return a `MarkerBasedObjectCollection` that allows you to iterate over the [`File`][file_class] -objects in the collection. - - -```python -retention_policy_assignment = client.retention_policy_assignment(assignment_id='12345').get() -files_under_retention = retention_policy_assignment.get_files_under_retention() -for file in files_under_retention: - print(f'The file with ID {file.object_id} and name {file.name} is under retention for a retention policy assignment with ID {retention_policy_assignment.object_id}') -``` - -[get-files-under-retention-for-an-assignment]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#module-boxsdk.object.retention_policy_assignment -[file_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#module-boxsdk.object.file - - -Get File Versions under Retention for a Retention Policy Assignment ---------------------------- - -To retrieve all file versions under retention for a retention policy assignment, -call [`retention_policy_assignment.get_file_versions_under_retention(limit=None, marker=None)`][get-file-versions-under-retention-for-an-assignment]. -This method will return a `MarkerBasedObjectCollection` that allows you to iterate over the [`FileVersion`][file_version_class] -objects in the collection. - - -```python -retention_policy_assignment = client.retention_policy_assignment(assignment_id='12345').get() -file_versions_under_retention = retention_policy_assignment.get_file_versions_under_retention() -for file_version in file_versions_under_retention: - print(f'The version {file_version.file_version.object_id} of {file_version.name} file is under retention for a retention policy assignment with ID {retention_policy_assignment.object_id}') -``` - -[get-file-versions-under-retention-for-an-assignment]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#module-boxsdk.object.retention_policy_assignment -[file_version_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#module-boxsdk.object.file_version diff --git a/docs/usage/search.md b/docs/usage/search.md deleted file mode 100644 index 0bcc00a20..000000000 --- a/docs/usage/search.md +++ /dev/null @@ -1,104 +0,0 @@ -Search -====== - -Search provides a powerful way of finding items that are accessible by a single user or an entire -enterprise. - -- [Search for Content](#search-for-content) -- [Metadata Query](#metadata-query) - -Search for Content ------------------- - -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. - - -```python -items = client.search().query(query='TEST QUERY', limit=100, file_extensions=['pdf', 'doc']) -for item in items: - print(f'The item ID is {item.id} and the item name is {item.name}') -``` - -[query]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.search.Search.query -[item_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item - -### Metadata Search - -To filter by metadata in your search, first create [`MetadataSearchFilter`][metadata_search_filter] object with the -specified `template_key` and `scope` as well as adding filter, `field_key` and `value` with -[`metadata_filter.add_value_based_filter(field_key, value)`][add_value_based_filter]. Next, create a -[`MetadataSearchFilters`][metadata_search_filters] object and call [`metadata_filters.add_filter(metadata_filter)`][add_filter] -and pass in the [`MetadataSearchFilter`][metadata_search_filter] object created earlier. Finally, call -[`search.query(query, metadata_filters=None, **kwargs)`][query] with [`MetadataSearchFilters`][metadata_search_filters] -object passed in. - -```python -from boxsdk.object.search import MetadataSearchFilter, MetadataSearchFilters - -metadata_search_filter = MetadataSearchFilter(template_key='marketingCollateral', scope='enterprise') -metadata_search_filter.add_value_based_filter(field_key='documentType', value='datasheet') -metadata_search_filter.add_value_based_filter(field_key='clientNumber', value='a123') -metadata_search_filters = MetadataSearchFilters() -metadata_search_filters.add_filter(metadata_search_filter) - -client.search().query(None, limit=100, offset=0, metadata_filters=metadata_search_filters) -``` - -[metadata_search_filter]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.search.MetadataSearchFilter -[metadata_search_filters]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.search.MetadataSearchFilters -[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. - - -```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(f'The item ID is {search_result.item.id} and the item name is {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)` - -**Note:** paramter `use_index` is deprecated and if provided, will be ignored. The current way to create an index is to contact the MDQ team directly via email. MDQ will now use static query analysis to use the index that is most efficient for the call. - - - -By default, this method returns only the most basic info about the items for which the query matches. To get additional fields for each item, including any of the metadata, use the fields parameter. - - -```python -from_template = 'enterprise_12345.someTemplate' -ancestor_folder_id = '5555' -query = 'amount >= :arg' -query_params = {'arg': 100} -order_by = [ - { - 'field_key': 'amount', - 'direction': 'asc' - } -] -fields = ['type', 'id', 'name', 'metadata.enterprise_67890.catalogImages.$parent'] -limit = 2 -marker = 'AAAAAmVYB1FWec8GH6yWu2nwmanfMh07IyYInaa7DZDYjgO1H4KoLW29vPlLY173OKs' -items = client.search().metadata_query( - from_template=from_template, - ancestor_folder_id=ancestor_folder_id, - query=query, - query_params=query_params, - order_by=order_by, - marker=marker, - limit=limit, - fields=fields - ) -for item in items: - print(f'The item ID is {item.id} and the item name is {item.name}') -``` diff --git a/docs/usage/sign_requests.md b/docs/usage/sign_requests.md deleted file mode 100644 index 0c61f3f02..000000000 --- a/docs/usage/sign_requests.md +++ /dev/null @@ -1,120 +0,0 @@ -Sign Requests -================== - -Sign Requests are used to request e-signatures on documents from signers. -A Sign Request can refer to one or more Box Files and can be sent to one or more Box Sign Request Signers. - - - - - -- [Create Sign Request](#create-sign-request) -- [Get all Sign Requests](#get-all-sign-requests) -- [Get Sign Request by ID](#get-sign-request-by-id) -- [Cancel Sign Request](#cancel-sign-request) -- [Resend Sign Request](#resend-sign-request) - - - -Create Sign Request ------------------------- - -The [`client.create_sign_request_v2(signers, files=None, parent_folder_id=None, prefill_tags=None, are_reminders_enabled=None, are_text_signatures_enabled=None, days_valid=None, email_message=None, email_subject=None, external_id=None, is_document_preparation_needed=None, redirect_url=None, declined_redirect_url=None, template_id=None)`][create-sign-request] -method will create a Sign Request. You need to provide at least one file and up to 10 files (from which the signing document will be created) or template_id of the sign request template. You need to include at least one signer that will receive the Sign Request. - -Example with files: - - - -```python -source_file = { - 'id': '12345', - 'type': 'file' -} -files = [source_file] - -signer = { - 'name': 'John Doe', - 'email': 'signer@mail.com' -} -signers = [signer] -parent_folder_id = '123456789' - -new_sign_request = client.create_sign_request_v2(signers, files=files, parent_folder_id=parent_folder_id) -print(f'(Sign Request ID: {new_sign_request.id})') -``` - -Example with sign template - -```python -signer = { - 'name': 'John Doe', - 'email': 'signer@mail.com' -} - -new_sign_request = client.create_sign_request_v2(signers, template_id='12345') -print(f'(Sign Request ID: {new_sign_request.id})') -``` - -If you set ```isDocumentPreparationNeeded``` flag to true, you need to visit ```prepareUrl``` before the Sign Request will be sent. -For more information on ```isDocumentPreparationNeeded``` and the other parameters available, please refer to the [developer documentation](https://developer.box.com/guides/sign-request/). - -[create-sign-request]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_sign_request - -Get All Sign Requests ------------------------- - -Calling the [`client.get_sign_requests()`][get-all-sign-requests] -will return an iterable that will page through all the Sign Requests. This method offers `limit` and `fields` parameters. The `limit` parameter specifies the maximum number of items to be returned in a single response. The `fields` parameter is used to specify what additional properties should be returned on the return object. For more information on what `fields` are available, please refer to the [developer documentation](https://developer.box.com/guides/box-sign/). - - -```python -sign_requests = client.get_sign_requests() -for sign_request in sign_requests: - print(f'(Sign Request ID: {sign_request.id})') -``` - -[get-all-sign-requests]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_sign_requests - -Get Sign Request by ID ------------------------- - -Calling [`client.sign_request(sign_request_id)`][get-sign-request-by-id] will return an object -containing information about the Sign Request. -The `fields` parameter is used to specify what additional properties should be returned in the return object. - - -```python -sign_request = client.sign_request(sign_request_id='12345').get() -print(f'Sign Request ID is {sign_request.id}') -``` - -[get-sign-request-by-id]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.sign_request - -Cancel Sign Request ------------------------- - -Calling [`sign_requests.cancel()`][cancel-sign-request] will cancel a created Sign Request. - - -```python -sign_request = client.sign_request(sign_request_id='12345') -cancelled_sign_request = sign_request.cancel() -print(f'Cancelled Sign Request status is {cancelled_sign_request.status}') -``` - -[cancel-sign-request]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.retention_policy.SignRequest.cancel - -Resend Sign Request ------------------------- - -Calling [`sign_requests.resend()`][resend-sign-request] will resend a Sign Request to all signers that have not signed it yet. -There is an 10-minute cooling-off period between re-sending reminder emails. - - -```python -sign_request = client.sign_request(sign_request_id='12345') -sign_request.resend() -``` - -[resend-sign-request]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.retention_policy.SignRequest.resend diff --git a/docs/usage/sign_templates.md b/docs/usage/sign_templates.md deleted file mode 100644 index 52ee09337..000000000 --- a/docs/usage/sign_templates.md +++ /dev/null @@ -1,39 +0,0 @@ -Sign Templates -============== - -Sign Templates are reusable templates that can be used to create Sign Requests. For now, Sign Templates can only be created through the Box web application. - - - - -- [Get All Sign Templates](#get-all-sign-templates) -- [Get Sign Template by ID](#get-sign-template-by-id) - - - -Get All Sign Templates ----------------------- - -Calling the [`client.get_sign_templates()`][get-all-sign-templates] method will return an iterable that will page through all the Sign Templates. This method offers `limit` parameter. The `limit` parameter specifies the maximum number of items to be returned in a single response. - - -```python -sign_templates = client.get_sign_templates() -for sign_template in sign_templates: - print(f'(Sign Template ID: {sign_template.id})') -``` - -[get-all-sign-templates]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_sign_templates - -Get Sign Template by ID ------------------------ - -Calling the [`client.get_sign_template(template_id)`][get-sign-template] method will return a Sign Template object. - - -```python -sign_template = client.get_sign_template('12345') -print(f'(Sign Template ID: {sign_template.id})') -``` - -[get-sign-template]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_sign_template diff --git a/docs/usage/storage_policy.md b/docs/usage/storage_policy.md deleted file mode 100644 index 9e40911f6..000000000 --- a/docs/usage/storage_policy.md +++ /dev/null @@ -1,139 +0,0 @@ -Storage Policies -================ - -Allows the enterprise admin to manage the Storage Policies for users in their -enterprise. Used for an enterprise to decide storage location for users based on -where they work/reside. - - - - - -- [Get Storage Policy](#get-storage-policy) -- [List Available Storage Policies](#list-available-storage-policies) -- [Assign a Storage Policy to a User](#assign-a-storage-policy-to-a-user) -- [Get Assignment Information about a Storage Policy Assignment](#get-assignment-information-about-a-storage-policy-assignment) -- [Get Assignment for User](#get-assignment-for-user) -- [Delete Assignment](#delete-assignment) - - - -Get Storage Policy ------------------- - -To get a storage policy object, first call [`client.storage_policy(policy_id)`][storage_policy] to construct the -appropriate [`Storage Policy`][storage_policy_class] object, and then calling [`storage_policy.get(*, fields=None, headers=None, **kwargs)`][get] -will return the [`StoragePolicy`][storage_policy_class] object populated with data from the API. - - -```python -storage_policy = client.storage_policy(policy_id='12345').get() -print(f'Storage Policy ID is {storage_policy.id} and name is {storage_policy.name}') -``` - -[storage_policy]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.storage_policy -[storage_policy_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.storage_policy.StoragePolicy -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -List Available Storage Policies -------------------------------- - -To retrieve all storage policies for an enterprise, call [`client.get_storage_policies(limit=None, fields=None)`][get_storage_policies]. -This method returns a `BoxObjectCollection` that allows you to iterate over the [`StoragePolicy`][storage_policy_class] -objects in the collection. - - -```python -storage_policies = client.get_storage_policies(limit=100) -for storage_policy in storage_policies: - print(f'The storage policy id is {storage_policy.id} and name is {storage_policy.name}') -``` - -[get_storage_policies]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_storage_policies - -Assign a Storage Policy to a User ---------------------------------- - -To assign a storage policy to a user, call [`storage_policy.assign(user)`][assign] will create a -[`StoragePolicyAssignment`][storage_policy_assignment_class] object with data populated from the API. - - -```python -user = client.user(user_id='12345') -assignment = client.storage_policy(policy_id='56781').assign(user) -print(f'Assignment ID is {assignment.id} and the assignee id is {assignment.assigned_to.id}') -``` - -If you know the user does not have a storage policy assigned you can directly create a storage policy assignment by calling -[`storage_policy.create_assignment(user)`][create_assignment] will create a [StoragePolicyAssignment][storage_policy_assignment_class] -object with data populated from the API. - -```python -user = client.user('56781') -assignment = client.storage_policy(policy_id='12345').create_assignment(user) -print(f'Storage Policy Assignment ID is {assignment.id} and the assignee ID is {assignment.assigned_to.id}') -``` - -If the user already has an assignment, you can call [storage_policy_assignment.update_info(data=updated_storage_policy)][update_info] -with a `dict` of properties to update on the storage policy assignment. This method returns a newly update -[`StoragePolicyAssignment`][storage_policy_assignment] object with data populated from the API, leaving the original -object unmodified. - - -```python -updated_storage_policy = {'storage_policy': {'type': 'storage_policy', 'id': '12345'}} -updated_assignment = client.storage_policy_assignment(assignment_id='ZW50ZXJwcmldfgeV82MDMwMDQ=').update_info(data=updated_storage_policy) -print(f'Update storage policy ID is {updated_assignment.storage_policy.id}') -``` - -[user]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.user -[create_assignment]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.storage_policy.StoragePolicy.create_assignment -[storage_policy_assignment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.storage_policy_assignment.StoragePolicyAssignment -[assign]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.storage_policy.StoragePolicy.assign - -Get Assignment Information about a Storage Policy Assignment ------------------------------------------------------------- - -To get a storage policy assignment object, first call [`client.storage_policy_assignment(assignment_id)`][storage_policy_assignment] -to construct the appropriate [`Storage Policy Assignment`][storage_policy_assignment_class] object, and then calling -[`storage_policy_assignment.get(*, fields=None, headers=None, **kwargs)`][get] will return the [`StoragePolicyAssignment`][storage_policy_assignment_class] -object populated with data from the API. - - -```python -assignment = client.storage_policy_assignment(assignment_id='12345').get() -print(f'Assignment ID is {assignment.id} and the storage policy ID is {assignment.storage_policy.id}') -``` - -[storage_policy_assignment]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.storage_policy_assignment -[storage_policy_assignment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.storage_policy_assignment.StoragePolicyAssignment -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -Get Assignment for User -------------------------- - -To get a storage policy assignment object for a user, calling [`user.get_storage_policy_assignment()`][user_assignment] will -return the [`StoragePolicyAssignment`][storage_policy_assignment_class] object populated with data from the API. - - -```python -assignment = client.user(user_id='12345').get_storage_policy_assignment() -print(f'Assignment ID is {assignment.id} and the storage policy ID is {assignment.storage_policy.id}') -``` - -[user_assignment]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.get_storage_policy_assignment -[storage_policy_assignment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.get_storage_policy_assignment - -Delete Assignment ------------------ - -To delete a storage policy assignment, call [`storage_policy_assignment.delete()`][delete]. This method returns `True` -to indicate that the deletion was successful. - - -```python -client.storage_policy_assignment(assignment_id='12345').delete() -print('The storage policy assignment was successfully delete!') -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete diff --git a/docs/usage/task.md b/docs/usage/task.md deleted file mode 100644 index 151ed84df..000000000 --- a/docs/usage/task.md +++ /dev/null @@ -1,210 +0,0 @@ -Tasks -===== - -Tasks enable file-centric workflows in Box. User can create tasks on files and assign them to collaborators on Box. - - - - - -- [Get a Task's Information](#get-a-tasks-information) -- [List Tasks on File](#list-tasks-on-file) -- [Add Task to File](#add-task-to-file) -- [Update Task Info](#update-task-info) -- [Delete a Task](#delete-a-task) -- [Assign a Task](#assign-a-task) -- [Assign a Task with User Login](#assign-a-task-with-user-login) -- [List Task Assignments](#list-task-assignments) -- [Get Information about Task Assignment](#get-information-about-task-assignment) -- [Update Task Assignment](#update-task-assignment) -- [Delete Task Assignment](#delete-task-assignment) - - - -Get a Task's Information ------------------------- - -To get a task object, first call [`client.task(task_id)`][task] to construct the appropriate [`Task`][task_class] -object, and then calling [`task.get(*, fields=None, headers=None, **kwargs)`][get] will return the [`Task`][task_class] object populated with data -from the API, leaving the original object unmodified. - - -```python -task = client.task(task_id='12345').get() -print(f'Task ID is {task.id} and the type is {task.type}') -``` - -[task]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.task -[task_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.task.Task -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -List Tasks on File ------------------- - -To retrieve all tasks on a file, call [`file.get_tasks(fields=None)`]['get_tasks'] will return a `BoxObjectCollection` -that allows you to iterate over the [`Task`][task_class] objects in the collection. - - -```python -tasks = client.file(file_id='11111').get_tasks() -for task in tasks: - print(f'Task ID is {task.id} and the type is {task.type}') -``` - -[get_tasks]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_tasks() -[task_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.task.Task - -Add Task to File ----------------- - -To create a single task for a single user on a single file, call [`file.create_task(message=None, due_at=None)`][create_task] -will return a newly created [`Task`][task_class] object populated with data from the API. - - -```python -message = 'Please review this' -due_at = "2014-04-03T11:09:43-07:00" -task = client.file(file_id='11111').create_task(message, due_at) -print(f'Task message is {task.message} and it is due at {task.due_at}') -``` - -[create_task]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.create_Task - -Update Task Info ----------------- - -To update a task object, first call [`task.update_info(data=task_update)`][update_info] with a `dict` of properties to -update on the task. This method returns a newly updated [`Task`][task_class] object, leaving the original object unmodified. - - -```python -task_update = {'message': 'New Message', 'due_at': '2014-04-03T11:09:43-10:00'} -updated_task = client.task(task_id='12345').update_info(data=task_update) -print(f'New task message is {updated_task.message} and the new due time is {updated_task.due_at}') -``` - -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info -[task_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.task.Task - -Delete a Task -------------- - -To delete a task, first call [`client.task(task_id)`][task] to construct the appropriate task object, and then call -[`task.delete()`][delete]. This method returns `True` to indicate that the deleteion was successful. - - -```python -client.client.task('12345').delete() -print('The task was successfully delete!') -``` - -[task]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.task -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete - - -Assign a Task --------------- - -To assign a task object, first call [`client.task(task_id)`][task] to construct the appropriate task object, then call -[`task.assign(user)`][assign] will return an [`Task Assignment`][assignment_class] object, populated with data -from the API. - - -```python -user = client.user(user_id='11111') -assignment = client.task(task_id='12345').assign(user) -print(f'Assignment ID is {assignment.id} and is assigned to user {assignment.assigned_to.name}') -``` - -[task]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client..html#boxsdk.client.client.Client.task -[assign]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.task.Task.assign -[assignment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.task_assignment.TaskAssignment - -Assign a Task with User Login ------------------------------ - -To assign a task object with a user login, first call [`task.assign_with_login(login)`][assign_with_login] with a -`unicode` value for user login. This method will return a [`TaskAssignment`][assignment_class] object, populated with -data from the API. - -```python -assignment = client.task(task_id='12345').assign_with_login('test_user@example.com') -print(f'Assignment ID is {assignment.id} and the assignee is {assignment.assigned_to.login}') -``` - -[assign_with_login]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.task_assignment.assign_with_login -[assignment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.task_assignment.TaskAssignment - -List Task Assignments ---------------------- - -To retrieve all assignments for an enterprise, first call [`client.task(task_id)`][task] to construct the appropriate -task object. Then call ['task.get_assignments(fields=None)'][get_assignments]. This method returns a -`BoxObjectCollection` that allows you to iterate over the ['TaskAssignment'][assignment_class] objects in the -collection. - - -```python -assignments = client.task(task_id='12345').get_assignments() -for assignment in assignments: - print(f'Assignment ID is {assignment.id} and the assignee is {assignment.assigned_to.login}') -``` - -[task]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.task -[get_assignments]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.task.Task.get_assignments - -Get Information about Task Assignment -------------------------------------- - -To get a task assignment object, first call [`client.task_assignment(assignment_id)`][assignment] to construct the -appropriate [`TaskAssignment`][assignment_class] object, and then calling ['task_assignment.get(*, fields=None, headers=None, **kwargs)'][get] -will return the [`TaskAssignment`][task_assignment] object populated with data from the API. - - -```python -assignment= client.task_assignment('12345').get() -print(f'Assignment ID is {assignment.id} and assignment type is {assignment.type}') -``` - -[assignment]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.task_assignment -[assignment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.task_assignment.TaskAssignment -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -Update Task Assignment ----------------------- - -To update a task assignment object, call [`assignment.update_info(data=updated_task)`][update_info] -with a `dict` of properties to update on a task assignment. This method returns a newly update -[`TaskAssignment`][assignment_class] object, leaving the original object unmodified. - - -```python -from boxsdk.object.task_assignment import ResolutionState -updated_task = {'resolution_state': ResolutionState.APPROVED} -updated_assignment = client.task_assignment(assignment_id='12345').update_info(data=updated_task) -print(f'Assignment ID is {updated_assignment.id} and resolution state is {updated_assignment.resolution_state}') -``` - - -```python -updated_task = {'message': 'new message'} -updated_assignment = client.task_assignment(assignment_id='12345').update_info(data=updated_task) -print(f'Assignment ID is {updated_assignment.id} and message is {updated_task.message}') -``` - -[assignment_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.task_assignment.TaskAssignment -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info - -Delete Task Assignment ----------------------- - -To delete a task assignment, call [`task_assignment.delete()`][delete]. This method returns `True` to indicate that the -deletion was successful. - - -```python -client.task_assignment(assignment_id='12345').delete() -print('The task assignment was successfully delete!') -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete diff --git a/docs/usage/terms_of_service.md b/docs/usage/terms_of_service.md deleted file mode 100644 index ae8ca9361..000000000 --- a/docs/usage/terms_of_service.md +++ /dev/null @@ -1,154 +0,0 @@ -Terms of Service -================ - -Terms of Service allows Box Admins to configure a custom Terms of Service for end users to -accept/re-accept/decline for platform applications - - - - - -- [Terms of Service](#terms-of-service) - - [Create a Terms of Service](#create-a-terms-of-service) - - [Edit a Terms of Service](#edit-a-terms-of-service) - - [Get Terms of Service](#get-terms-of-service) - - [List Terms of Service](#list-terms-of-service) - - [Accept or Decline a Terms of Service](#accept-or-decline-a-terms-of-service) - - [Get User Status for a Terms of Service](#get-user-status-for-a-terms-of-service) - - - -Create a Terms of Service -------------------------- - -A Terms of Service can be created in an enterprise. Please note that only two can be created. One external -and one managed. If a terms of service already exists please use the update call to change the current -terms of service. - -To create a Terms of Service object, calling [`client.create_terms_of_service(status, tos_type, text)`][create] will let -you create a new [`TermsOfService`][terms_of_service_class] object with the specified status, type, and text. This -method will return a newly created [`TermsOfService`][terms_of_service_class] object populated with data from the API. - - -```python -from boxsdk.object.terms_of_service import TermsOfServiceType, TermsOfServiceStatus -terms_of_service = client.create_terms_of_service(TermsOfServiceStatus.ENABLED,TermsOfServiceType.MANAGED, 'Example Text') -print(f'Terms of Service status is {terms_of_service.status} and the message is {terms_of_service.text}') -``` - -[create]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_terms_of_service -[terms_of_service_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.terms_of_service.TermsOfService - -Edit a Terms of Service ------------------------ - -To update a terms of service object, first call [`terms_of_service.update_info(data=update_object)`][update_info] with -a `dict` of properties to update on the terms of service. This method returns a newly updated [`TermsOfService`][terms_of_service] -object, leaving the original object unmodified. - - -```python -update_object = {'text': 'New Text'} -updated_tos = client.terms_of_service(tos_id='12345').update_info(data=update_object) -print(f'The updated message for your custom terms of service is {updated_tos.text} with ID {updated_tos.id}') -``` - -[terms_of_service]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.terms_of_service -[terms_of_service_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.terms_of_service.TermsOfService -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info - -Get Terms of Service --------------------- - -To get a terms of service object, call [`client.terms_of_service(service_id)`][terms_of_service] to construct the -appropriate [`TermsOfService`][terms_of_service_class], and then calling [`terms_of_service.get(*, fields=None, headers=None, **kwargs)`][get] -will return the [`TermsOfService`][terms_of_service_class] object populated with data from the API. - - -```python -terms_of_service = client.terms_of_service(tos_id='12345').get() -print(f'Terms of Service ID is {terms_of_service.id} and the message is {terms_of_service.text}') -``` - -[terms_of_service]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.terms_of_service -[terms_of_service_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.terms_of_service.TermsOfService -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -List Terms of Service ---------------------- - -To retrieve all terms of service for an enterprise, call -[`client.get_terms_of_services(limit=None, marker=None, fields=None)`][get_terms_of_services]. This method returns a -`BoxObjectCollection` that allows you to iterate over the [`TermOfService`][terms_of_service_class] objects in the -collection. - - -```python -terms_of_services = client.get_terms_of_services() -for terms_of_service in terms_of_services: - print(f'Terms of Service ID is {terms_of_service.id} and the message is {terms_of_service.text}') -``` - -[get_terms_of_services]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.terms_of_service - -Accept or Decline a Terms of Service ------------------------------------- - -To accept or decline a terms of service, calling [`terms_of_service.set_user_status(is_accepted, user)`][set_user_status] -will allow you to create a newly updated [`TermsOfServiceUserStatus`][terms_of_service_user_status_class] object -populated with data from the API, leaving the original object umodified if a [`TermsOfService`][terms_of_service_class] -object already exists for a user. If the user does not have a [`TermsOfService`][terms_of_service_class] object -assigned then [`terms_of_service.set_user_status(is_accepted, user)`][set_user_status] will create a new -[`TermsOfServiceUserStatus`][terms_of_service_user_status_class] object populated with data from the API. - - -```python -user = client.user(user_id='22222') -user_status = client.terms_of_service(tos_id='12345').set_user_status(is_accepted=True, user=user) -print(f'User status ID is {user_status.id} and the accepted status is {user_status.is_accepted}') -``` - -It is important to note that regardless of whether the user has taken action on this terms of service. This will create -and update the user status on the terms of service. - -Note that this example will make multiple API calls, if you know that your user has already accepted or decline a -Terms of Service and you wish to change their status, call [`terms_of_service_user_status.update_info(data=data_to_update)`][update_info] -with a `dict` of properties to update on the terms of service user status. This method returns a newly updated -[`TermsOfServiceUserStatus`][terms_of_service_user_status_class] object, leaving the original object unmodified. - - -```python -user_status = client.terms_of_service_user_status(tos_user_status_id='12345').update_info(data={'is_accepted': True}) -print(f'Terms of Service User Status ID is {user_status.id} and the accepted status is {user_status.is_accepted}') -``` - -It is important to note that this will accept or decline a custom terms of service for a user. For a user that has taken -action in this terms of service, this will update their status. If the user has never taken action on this terms of -service then this will return a 404 Not Found Error. - -[terms_of_service_user_status_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.terms_of_service_user_status.TermsOfServiceUserStatus -[user]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.user -[set_user_status]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.terms_of_service.TermsOfService.set_user_status -[terms_of_service_user_status]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.client.Client.terms_of_service_user_status -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info - -Get User Status for a Terms of Service -------------------------------------- - -To get a terms of service user status object, first call -[`client.terms_of_service_user_status(status_id)`][terms_of_service_user_status] -to construct the appropriate [`TermsOfServiceUserStatus`][terms_of_service_user_status_class] object. Then calling -[`client.user(user_id)`][user] to construct the user you wish to retrieve a -[`TermsOfServiceUserStatus`][terms_of_service_user_status_class] object for. Finally, calling -[`terms_of_service_user_status.get(*, fields=None, headers=None, **kwargs)`][get] will return the -[`TermsOfServiceUserStatus`][terms_of_service_user_status_class] object populated with data from the API. - - -```python -user = client.user(user_id='11111') -user_status = client.terms_of_service(tos_id='12345').get_user_status(user) -print(f'User status ID is {user_status.id} and the accepted status is {user_status.is_accepted}') -``` - -[user]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.user -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get diff --git a/docs/usage/trash.md b/docs/usage/trash.md deleted file mode 100644 index 4242cd3e9..000000000 --- a/docs/usage/trash.md +++ /dev/null @@ -1,134 +0,0 @@ -Trash -===== - -Under normal circumstances, when an item in Box is deleted, it is not actually erased immediately. Instead, it is -moved to the Trash. The Trash allows you to recover files and folders that have been deleted. By default, items in -the Trash will be purged after 30 days. - - - - - -- [List Trashed Items](#list-trashed-items) -- [Get Trashed Items](#get-trashed-items) -- [Restore Item from Trash](#restore-item-from-trash) -- [Permanently Delete Item](#permanently-delete-item) - - - -List Trashed Items ------------------- - -To retrieve all trashed items for an enterprise, call [`client.trash().get_items(imit=None, offset=None, fields=None)`][get_trashed_items]. -This method returns a `BoxObjectCollection` that allows you to iterate over the [`Trash`][trash] objects in the -collection. - - -```python -trashed_items = client.trash().get_items() -for trashed_item in trashed_items: - print(f'The item ID is {trashed_item.id} and the item name is {trashed_item.name}') -``` - -[get_trashed_item]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.trash.Trash.get_trashed_items -[trash]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.trash.Trash - -Get Trashed Items ------------------ - -To get a trashed item, call [`client.trash().get_item(item, fields)`][get_item] with the item you wish to retrieve passed in. -This method will return the ['Item'][item] object populated with data from the API. - - -```python -file_to_retrieve = client.file(file_id='11111') -file_from_trash = client.trash().get_item(file_to_retrieve) -print(f'File ID is {file_from_trash.id} and name is {file_from_trash.name}') -``` - - -```python -folder = client.folder(folder_id='22222') -folder_from_trash = client.trash().get_item(folder) -print(f'Folder ID is {folder_from_trash.id} and name is {folder_from_trash.name}') -``` - - -```python -web_link = client.web_link(web_link_i='33333') -web_link_from_trash = client.trash().get_item(web_link) -print(f'Web link ID is {web_link_from_trash.id} and name is {web_link_from_trash.name}') -``` - -[item]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item -[get_item]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.trash.Trash.get_item - -Restore Item from Trash ------------------------ - -To retore a trashed item, effectively undeleting it, call [`client.trash().restore(item, name=None, parent_folder, fields=None)`][restore_item] -with the constructed [`Item`][item_class] object will let you restore the specific object from your trash. This method -will return a [`Item`][item_class] object populated with data from the API, leaving the original object unmodified. - - -```python -file_to_restore = client.file(file_id='11111') -restored_file = client.trash().restore_item(file_to_restore) -print(f'File ID is {restored_file.id} and name is {restored_file.name}') -``` - -```python -folder_to_restore = client.folder(folder_id='22222') -restored_folder = client.trash().restore_item(folder_to_restore) -print(f'Folder ID is {restored_folder.id} and name is {restored_folder.name}') -``` - - -```python -web_link_to_restore = client.web_link(web_link_id='33333') -restored_web_link = client.trash().restore_item(web_link_to_restore) -print(f'Web link ID is {restored_web_link.id} and name is {restored_web_link.name}') -``` - -In order to avoid conflicts, you can set a new name and new parent folder for the item you wish to restore. - -```python -file_to_restore = client.file(file_id='11111') -new_name = 'New File Name' -new_parent_folder = client.folder(folder_id='22222') -restored_file = client.trash().restore_item(file_to_restore, new_name, new_parent_folder) -print(f'New name for file is {restored_file.name} and new parent folder is {restored_file.parent.name}') -``` - -[item_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item -[restore_item]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.trash.Trash.restore_item - -Permanently Delete Item ------------------------ - -To delete an [`Item`][item_class] object from trash, call [`client.trash().permanently_delete_item(item)`][delete]. -This method returns `True` to indicate that the deletion was successful. - - -```python -file_to_delete = client.file(file_id='11111') -client.trash().permanently_delete_item(file_to_delete) -print('The file was deleted from trash!') -``` - - -```python -folder = client.folder(folder_id='22222') -client.trash().permanently_delete_item(folder) -print('The folder was deleted from trash!') -``` - - -```python -web_link = client.web_link(web_link_id='33333') -client.trash().permanently_delete_item(web_link) -print('The web link was deleted from trash!') -``` - -[item_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.item.Item -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.trash.Trash.permanently_delete diff --git a/docs/usage/user.md b/docs/usage/user.md deleted file mode 100644 index 677237238..000000000 --- a/docs/usage/user.md +++ /dev/null @@ -1,284 +0,0 @@ -Users -===== - -Users represent an individual's account on Box. - - - - -- [Users](#users) - - [Get User Information](#get-user-information) - - [Get the Current User's Information](#get-the-current-users-information) - - [Create An Enterprise User](#create-an-enterprise-user) - - [Get the Avatar for a User](#get-the-avatar-for-a-user) - - [Upload user avatar](#upload-user-avatar) - - [Delete user avatar](#delete-user-avatar) - - [Create An App User](#create-an-app-user) - - [Update User](#update-user) - - [Delete User](#delete-user) - - [Invite User to Enterprise](#invite-user-to-enterprise) - - [Get Email Aliases](#get-email-aliases) - - [Add Email Alias](#add-email-alias) - - [Remove Email Alias](#remove-email-alias) - - [Get Enterprise Users](#get-enterprise-users) - - [Transfer User Content](#transfer-user-content) - - - -Get User Information --------------------- - -To get information about a user, call the [`user.get(*, fields=None, headers=None, **kwargs)`][object_get] method. -This method returns a new [`User`][user_class] object with fields populated by data from the API. - - -```python -user_id = '33333' -user = client.user(user_id).get() -``` - -You can specify which fields on the `User` object you want by passing an `Iterable` of field names: - -```python -user_id = '33333' -user = client.user(user_id).get(['id', 'name', 'login', 'is_sync_enabled']) - -if user.is_sync_enabled: - print(f'User {user.id} has sync enabled') -``` - -[object_get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get -[user_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User - -Get the Current User's Information ----------------------------------- - -To get the current user, call [`client.user(user_id='me')`][user_init] to create the [`User`][user_class] object and -then call [`user.get(*, fields=None, headers=None, **kwargs)`][object_get] to retrieve the user information from the API. - - -```python -current_user = client.user().get() -``` - -[user_init]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.user - -Create An Enterprise User -------------------------- - -To create an enterprise user, call the [`client.create_user(name, login, **user_attributes)`][create_user] method. -This method returns a new [`User`][user_class] object. - - -```python -new_user = client.create_user('Temp User', 'user@example.com') -``` - -[create_user]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_user - -Get the Avatar for a User -------------------------- - -To get the avatar for a user call the [`user.get_avatar()`][get_avatar] method with the [`User`][user_class] -object for the user you wish to retrieve an avatar for. This will return the user avatar to you in bytes. - - -```python -avatar = client.user('33333').get_avatar() -``` - -[get_avatar]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.user.html#boxsdk.user.User.get_avatar - -## Upload user avatar - -To add or update the avatar for a user, call the [`user.upload_avatar(image_path)`][upload-avatar] method with the [`User`][user_class]. -Put the path to your image as a method parameter. The supported image extensions are `jpg`, `jpeg` and `png`. -The image size cannot exceed 1024 * 1024 pixels or 1MB. - - -```python -avatar_urls = client.user('33333').upload_avatar(image_path='path/to/the/image.png') -``` -In return, you will get links to several representations of an avatar within Box account. - -Alternatively you can upload the avatar by passing the image byte stream and the image extension to -the method [`upload_avatar_stream(image_stream, image_extension)`][upload-avatar-stream]: - -```java -image_stream = open('path/to/the/image.png', 'rb') -avatar_urls = client.user('33333').upload_avatar_stream(image_stream=image_stream, image_extension='png') -``` - -[upload-avatar]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.user.User.upload_avatar -[upload-avatar-stream]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.user.User.upload_avatar_stream - - -## Delete user avatar - -To delete the user avatar image use [`delete_avatar()`][delete-avatar] method with the [`User`][user_class]. - - -```java -client.user('33333').delete_avatar() -``` - -[delete-avatar]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.user.User.delete_avatar - -Create An App User ------------------- - -Platform applications may create App Users, which represent a "headless" user managed exclusively by the application. -These users can only be accessed via the API, and cannot login to the web application or other Box services. - -To create a new app user, call [`client.create_user(name, login=None, **user_attributes)`][create_user] without a -`login` value. This returns the [`User`][user_class] object for the new app user. - - -```python -new_app_user = client.create_user('App User 123', login=None) -``` - -Update User ------------ - -To update a user object, call the [`user.update_info(data=data_to_update)`][update_info] method with a `dict` of fields to update -on the user. - - -```python -user_id = '33333' -user = client.user(user_id) -updated_user = user.update_info(data={'name': 'Smart User'}) -``` - -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info - -Delete User ------------ - -To delete a user call the [`user.delete(notify=True, force=False)`][delete] method. The method returns `True` to -indicate that the deletion succeeded. - -The `notify` parameter determines whether the user should receive an email about the deletion, -and the `force` parameter will cause the user to be deleted even if they still have files in their account. If `force` -is set to `False` and the user still has files in their account, the deletion will fail. - - -```python -user_id = '33333' -client.user(user_id).delete(force=True) -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.delete - -Invite User to Enterprise -------------------------- - -To invite an existing user to join an Enterprise call the [`enterprise.invite_user(user_email)`][invite_user] method. This -method returns an [`Invite`][invite_class] object representing the status of the invitation. - - -```python -enterprise = client.get_current_enterprise() -invitation = enterprise.invite_user('user@example.com') -``` - -[invite_user]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.enterprise.Enterprise.invite_user -[invite_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.invite.Invite - -Get Email Aliases ------------------ - -To get a user's email aliases call the [`user.get_email_aliases(limit=None, fields=None)`][get_email_aliases] method. -This method returns a [`BoxObjectCollection`][box_object_collection] used to iterate over the collection of -[`EmailAlias`][email_alias_class] objects. - - -```python -user_id = '33333' -user = client.user(user_id) -email_aliases = user.get_email_aliases() -for alias in email_aliases: - print(f'User {user.id} has email alias {alias.email}') -``` - -[get_email_aliases]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.get_email_aliases -[box_object_collection]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.pagination.box_object_collection.BoxObjectCollection -[email_alias_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.email_alias.EmailAlias - -Add Email Alias ---------------- - -To add an email alias for a user, call the [`user.add_email_alias(email)`][add_email_alias] method with the email -address to add as an email alias for the user. This will allow the user to log in and be collaborated by this email -in addition to their login email address. Not all emails addresses can be added as email aliases. Email addresses whose domains match the domain of the login email address can always be made aliases. Email addresses whose domains differ from the domain of the login email address can be made aliases depending on the Box account configuration. The method returns an [`EmailAlias`][email_alias_class] object. - - -```python -user_id = '33333' -user = client.user(user_id) -email_alias = user.add_email_alias('alias@example.com') -``` - -[add_email_alias]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.add_email_alias - -Remove Email Alias ------------------- - -To remove an email alias from a user, call the [`user.remove_email_alias(email_alias)`][remove_email_alias] method with -the [`EmailAlias`][email_alias_class] object to remove. The method returns `True` to signify that the removal succeeded. - - -```python -user_id = '33333' -email_alias_id = '12345' - -user = client.user(user_id) -email_alias = client.email_alias(email_alias_id) - -user.remove_email_alias(email_alias) -``` - -[remove_email_alias]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.remove_email_alias - -Get Enterprise Users --------------------- - -To get the users in an enterprise, call -[`client.users(limit=None, offset=0, filter_term=None, user_type=None, fields=None)`][get_users]. You can specify -a `filter_term` to filter on the user's `name` and `login` fields, or select a `user_type` to filter down to only -managed or external users. This method returns a [`BoxObjectCollection`][box_object_collection] used to iterate over -the collection of [`User`][user_class] objects. - - -```python -users = client.users(user_type='all') -for user in users: - print(f'{user.name} (User ID: {user.id})') -``` - -[get_users]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.users - -Transfer User Content ---------------------- - -To move all of a user's content to a different user, call the -[`user.transfer_content(self, destination_user, notify=None, fields=None)`][transfer_content] method with the -[`User`][user_class] object representing the destination user. This will create a new folder in the destination user's -account, containing all files and folders from the original user's account; the method returns a -[`Folder`][folder_class] object representing this new folder in the destination user's account. - - -```python -source_user_id = '33333' -destination_user_id = '44444' - -user = client.user(source_user_id) -destination_user = client.user(destination_user_id) - -folder = user.transfer_content(destination_user) -print(f'Created new folder "{folder.name}" in the account of user {destination_user.id}') -``` - -[transfer_content]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.transfer_content -[folder_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder diff --git a/docs/usage/watermarking.md b/docs/usage/watermarking.md deleted file mode 100644 index 2e9d2f4b3..000000000 --- a/docs/usage/watermarking.md +++ /dev/null @@ -1,86 +0,0 @@ -Watermarking -============ - -The ability to watermark files and folders is represented as a sub-resource on the Files and Folders resources, -respectively. You can think of the sub-resource as a "label" marking whether the file or folder is watermarked or not. -If you apply a watermark label to a folder, then all files inside of it will be protected by the watermark (e.g. -previews will be watermarked). However, those files' watermark sub-resource is independent from the folder that got -watermarked. This allows you to watermark files and folders independently. - - - - - -- [Watermarking](#watermarking) - - [Get Watermark on File or Folder](#get-watermark-on-file-or-folder) - - [Apply Watermark on File or Folder](#apply-watermark-on-file-or-folder) - - [Remove Watermark on File or Folder](#remove-watermark-on-file-or-folder) - - - -Get Watermark on File or Folder -------------------------------- - -To get a watermark object, call [`file.get_watermark()`][get_file_watermark] or -[`folder.get_watermark()`][get_folder_watermark] will return the [`Watermark`][watermark_class] object populated with -data from the API. - - -```python -watermark = client.file(file_id='12345').get_watermark() -print(f'Watermark created at {watermark.created_at} and modified at {watermark.modified_at}') -``` - - -```python -watermark = client.folder(folder_id='11111').get_watermark() -print(f'Watermark created at {watermark.created_at} and modified at {watermark.modified_at}') -``` - -[get_file_watermark]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.file.File.get_watermark() -[get_folder_watermark]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.get_watermark() -[watermark_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.watermark.Watermark - -Apply Watermark on File or Folder ---------------------------------- - -To assign a watermark on a file or folder, call [file.apply_watermark()][apply-file-watermark] or -[folder.apply_watermark()][apply-folder-watermark] will return the [`Watermark`][watermark_class] object populated with -data from the API. - - -```python -watermark = client.file(file_id='12345').apply_watermark() -print(f'Watermark created at {watermark.created_at} and modified at {watermark.modified_at}') -``` - - -```python -watermark = client.folder(folder_id='11111').apply_watermark() -print(f'Watermark created at {watermark.created_at} and modified at {watermark.modified_at}') -``` - -[apply-file-watermark]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.file.File.apply_watermark() -[apply_folder_watermark]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.folder.Folder.apply_watermark() -[watermark_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.watermark.Watermark - -Remove Watermark on File or Folder ----------------------------------- - -To remove a watermark from a file or folder, call [file.delete_watermark()][delete-file-watermark] or -[folder.delete_watermark()][delete-folder-watermark] will return `True` to indicate that the deletion was successful. - - -```python -client.file(file_id='12345').delete_watermark() -print('The file watermark was deleted!') -``` - - -```python -client.folder(folder_id='11111').delete_watermark() -print('The folder watermark was deleted!') -``` - -[delete-file-watermark]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.file.File.delete_watermark() -[delete_folder_watermark]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.folder.Folder.delete_watermark() diff --git a/docs/usage/web_link.md b/docs/usage/web_link.md deleted file mode 100644 index 518ca774d..000000000 --- a/docs/usage/web_link.md +++ /dev/null @@ -1,198 +0,0 @@ -Web Links -========= - -Web links are objects that point to URLs. These objects are also known as -bookmarks within the Box web application. Web link objects are treated -similarly to file objects. - - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Create Web Link](#create-web-link) -- [Get Web Link](#get-web-link) -- [Update Web Link](#update-web-link) -- [Move a Web Link](#move-a-web-link) -- [Copy a Web Link](#copy-a-web-link) -- [Rename a Web Link](#rename-a-web-link) -- [Delete Web Link](#delete-web-link) -- [Create a Shared Link](#create-a-shared-link) -- [Update a Shared Link](#update-a-shared-link) -- [Get a Shared Link](#get-a-shared-link) -- [Remove a Shared Link](#remove-a-shared-link) - - - -Create Web Link ---------------- - -To create a web link object, calling [`folder.create_web_link(target_url, name=None, description=None)`][create] -will let you create a new web link with a specified name and description. This method return an newly created [`WebLink`][web_link_class] -object populated with data from the API, leaving the original object unmodified. - - -```python -web_link = client.folder(folder_id='12345').create_web_link('https://example.com', 'Example Link', 'This is the description') -print(f'Web Link url is {web_link.url} and its description is {web_link.description}') -``` - -[create]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder.create_web_link -[web_link_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.web_link.WebLink - -Get Web Link ------------- - -To get a web link object, first call [`client.web_link(web_link_id)`][web_link] to construct the appropriate -[`WebLink`][web_link_class] object, and then calling [`web_link.get(*, fields=None, headers=None, **kwargs)`][get] -will return the [`WebLink`][web_link_class] object populated with data from the API, leaving the original object unmodified. - - -```python -web_link = client.web_link(web_link_id='12345').get() -print(f'Web Link ID is {web_link.id} and its type is {web_link.type}') -``` - -[web_link]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.web_link -[web_link_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.web_link.WebLink -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -Update Web Link ---------------- - -To update a web link object, call [`web_link.update_info(data=data_to_update)`][update_info] with a `dict` of -properties to update on the web link. This method returns a newly updated ['WebLink'][web_link_class] object, leaving -the original object unmodified. - -```python -updated_web_link = client.web_link(web_link_id='12345').update_info(data={'url': 'https://newurl.com'}) -``` - -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info -[web_link_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.web_link.WebLink - -Move a Web Link ------------ - -To move a web link from one folder into another, call [`web_link.move(parent_folder, name=None)`][move] with the destination -folder to move the web link into. You can optionally provide a `name` parameter to automatically rename the web link -in case of a name conflict in the destination folder. This method returns the updated [`WebLink`][web_link_class] -object in the new folder. - -```python -web_link_id = '11111' -destination_folder_id = '44444' - -web_link_to_move = client.web_link(web_link_id) -destination_folder = client.folder(destination_folder_id) - -moved_web_link = web_link_to_move.move(parent_folder=destination_folder) -print(f'Web link "{moved_web_link.name}" has been moved into folder "{moved_web_link.parent.name}"') -``` - -[move]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.move - -Copy a Web Link ------------ - -A web link can be copied to a new folder by calling [`web_link.copy(*, parent_folder, name=None, **_kwargs)`][copy] -with the destination folder and an optional new name for the web link in case there is a name conflict in the -destination folder. This method returns a [`WebLink`][web_link_class] object representing the copy of the web link -in the destination folder. - - -```python -web_link = client.web_link(web_link_id='12345') - -web_link_copy = web_link_to_copy.copy(parent_folder=destination_folder) -print(f'Web link "{web_link_copy.name}" has been copied into folder "{web_link_copy.parent.name}"') -``` - -[copy]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.copy - -Rename a Web Link ------------ - -A web link can be renamed by calling [`web_link.rename(name)`][rename]. This method returns the updated -[`WebLink`][web_link_class] object with a new name. - -```python -web_link = client.web_link(web_link_id='12345') - -renamed_web_link = web_link.rename("new-name") -print(f'Web link was renamed to "{renamed_web_link.name}"') -``` - -[rename]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_item.BaseItem.rename - - -Delete Web Link ---------------- - -To delete a web link, call [`web_link.delete()`][delete]. This method returns `True` to indicate that the deletion was -successful. - - -```python -client.web_link('12345').delete() -print('The web link was deleted!') -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete - -Create a Shared Link --------------------- - -A shared link for a web link can be generated by calling -[`web_link.get_shared_link(*, access=None, unshared_at=SDK_VALUE_NOT_SET, password=None, -vanity_name=None, **kwargs)`][get_shared_link]. This method returns a `unicode` string containing the shared link URL. - - -```python -url = client.web_link('12345').get_shared_link(access='open') -print(f'The web link shared link URL is: {url}') -``` - -[get_shared_link]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.web_link.WebLink.get_shared_link - -Update a Shared Link --------------------- - -A shared link for a web link can be updated by calling -[`web_link.get_shared_link(*, access=None, unshared_at=SDK_VALUE_NOT_SET, password=None, -vanity_name=None, **kwargs)`][update_shared_link] with an updated list of properties. - -This method returns a `unicode` string containing the shared link URL. - - -```python -url = client.web_link('12345').get_shared_link(access='open', password='letmein') -print(f'The web link shared link URL is: {url}') -``` - -[update_shared_link]: -https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.web_link.WebLink.get_shared_link - -Get a Shared Link --------------------- - -To check for an existing shared link on a web link, simply call `web_link.shared_link` -This method returns a `unicode` string containing the shared link URL. - - -```python -shared_link = client.web_link('12345').get().shared_link -url = shared_link['url'] -``` - -Remove a Shared Link --------------------- - -A shared link for a web link can be removed by calling [`web_link.remove_shared_link(**kwargs)`][remove_shared_link]. - - -```python -client.web_link('12345').remove_shared_link() -``` - -[remove_shared_link]: -https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.web_link.WebLink.remove_shared_link diff --git a/docs/usage/webhook.md b/docs/usage/webhook.md deleted file mode 100644 index 59d2236fd..000000000 --- a/docs/usage/webhook.md +++ /dev/null @@ -1,138 +0,0 @@ -Webhooks -======== - -Webhooks enable you to attach event triggers to Box files and folders. Event triggers monitor events on Box objects and -notify your application when they occur. A webhook notifies your application by sending HTTP requests to a URL of your -choosing. - - - - - -- [Get Information about Webhook](#get-information-about-webhook) -- [List all Webhooks](#list-all-webhooks) -- [Create Webhook](#create-webhook) -- [Delete Webhook](#delete-webhook) -- [Update Webhook](#update-webhook) -- [Validate Webhook Message](#validate-webhook-message) - - - -Get Information about Webhook ------------------------------ - -To get a webhook object, first call [`client.webhook(webhook_id)`][webhook] to construct the appropriate -[`Webhook`][webhook_class] object, and then calling [`webhook.get(*, fields=None, headers=None, **kwargs)`][get] -will return the [`Webhook`][webhook_class] object populated with data from the API. - - -```python -webhook = client.webhook(webhook_id='12345').get() -print(f'Webhooks ID is {webhook.id} and the address is {webhook.address}') -``` - -[webhook]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.webhook -[webhook_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.webhook.Webhook -[get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get - -List all Webhooks ------------------ - -To retrieve all webhooks for an enterprise, call [`client.get_webhooks(limit=None, marker=None, fields=None)`][get_webhooks]. -This method returns a `BoxObjectCollection` that allows you to iterate over the [`Webhook`][webhook_class] objects in -the collection. - - -```python -webhooks = client.get_webhooks() -for webhook in webhooks: - print(f'The webhook ID is {webhook.id} and the address is {webhook.address}') -``` - -[get_webhooks]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.get_webhooks -[webhook_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.webhook.Webhook - -Create Webhook --------------- - -To create a webhook object, call [`client.create_webhook(target_url, name=None, description=None)`][create] will let -you create a new webhook object with the specified target url, name, and description. This method will return an updated -[`Webhook`][webhook_class] object populated with data from the API, leaving the original object unmodified. - - -```python -file = client.file(file_id='12345') -webhook = client.create_webhook(file, ['FILE.PREVIEWED'], 'https://example.com') -print(f'Webhook ID is {webhook.id} and the address is {webhook.address}') -``` - - -```python -folder = client.folder(folder_id='12345') -webhook = client.create_webhook(folder, ['FILE.UPLOADED', 'FILE.PREVIEWED'], 'https://example.com') -print(f'Webhook ID is {webhook.id} and the address is {webhook.address}') -``` - -[create]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_webhook -[webhook_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.webhook.Webhook - -Delete Webhook --------------- - -To delete a webhook, call [`webhook.delete()`][delete]. This method returns `True` to indicate that the deletion was -successful. - - -```python -client.webhook(webhook_id='12345').delete() -print('The webhook was successfully deleted!') -``` - -[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.delete - - -Update Webhook --------------- - -To update a webhook object, first call [`client.webhook(webhook_id)`][webhook] to construct the appropriate [`Webhook`][webhook_class] -object, and then calling [`webhook.update_info(data=update_object)`][update_info] with a `dict` of properties to update on the -webhook. This method returns a new updated [`Webhook`][webhook_class] object, leaving the original object unmodified. - - -```python -update_object = { - 'triggers': ['FILE.COPIED'], - 'address': 'https://newexample.com', -} -webhook = client.webhook(webhook_id='12345').update_info(data=update_object) -print(f'Updated the webhook info for triggers: {webhook.triggers} and address: {webhook.address}') -``` - -[webhook]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_webhook -[webhook_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.webhook.Webhook -[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info - -Validate Webhook Message ------------------------- - -When you receive a webhook message from Box, you should validate it by calling -[`Webhook.validate_message(body, headers, primary_key, secondary_key)`][validate_webhook]. This will protect your -application against attacks. Verification ensures that the notifications were actually sent by Box and not by a -malicious party and that the contents of the notification haven't been changed. - - -```python -body = b'{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' -headers = { - 'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f', - 'box-delivery-timestamp': '2020-01-01T00:00:00-07:00', - 'box-signature-algorithm': 'HmacSHA256', - 'box-signature-primary': '4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=', - 'box-signature-secondary': 'yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=', - 'box-signature-version': '1', -} -is_validated = Webhook.validate_message(body, headers, primary_key, secondary_key) -print(f'The webhook message is validated to: {is_validated}') -``` - -[validated_webhook]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.webhook.Webhook.validate_message diff --git a/docs/usage/zip.md b/docs/usage/zip.md deleted file mode 100644 index 9ca3cf435..000000000 --- a/docs/usage/zip.md +++ /dev/null @@ -1,30 +0,0 @@ -Zip -======== - -Allows you to create a temporary zip file on Box, containing Box files and folders, and then download it. - - - - -- [Download a Zip File](#download-a-zip-file) - - - -Download a Zip File ------------------------------ - -Calling [`client.download_zip(name, items, writable_stream)`][create_zip] will let you create a new zip file -with the specified name and with the specified items and download it to the stream that is passed in. The response is a status `dict` that contains information about the download, including whether it was successful. The created zip file does not show up in your Box account. - - -```python -name = 'test' -file = mock_client.file('466239504569') -folder = mock_client.folder('466239504580') -items = [file, folder] -output_file = open('test.zip', 'wb') -status = client.download_zip(name, items, output_file) -print(f'The status of the zip download is {status["state"]}') -``` - -[download_zip]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.download_zip diff --git a/docs/user_collaborations.md b/docs/user_collaborations.md new file mode 100644 index 000000000..480860131 --- /dev/null +++ b/docs/user_collaborations.md @@ -0,0 +1,169 @@ +# UserCollaborationsManager + +- [Get collaboration](#get-collaboration) +- [Update collaboration](#update-collaboration) +- [Remove collaboration](#remove-collaboration) +- [Create collaboration](#create-collaboration) + +## Get collaboration + +Retrieves a single collaboration. + +This operation is performed by calling function `get_collaboration_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-collaborations-id/). + + + +```python +client.user_collaborations.get_collaboration_by_id(collaboration_id) +``` + +### Arguments + +- collaboration_id `str` + - The ID of the collaboration. Example: "1234" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Collaboration`. + +Returns a collaboration object. + +## Update collaboration + +Updates a collaboration. +Can be used to change the owner of an item, or to +accept collaboration invites. + +This operation is performed by calling function `update_collaboration_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-collaborations-id/). + + + +```python +client.user_collaborations.update_collaboration_by_id( + collaboration_id, UpdateCollaborationByIdRole.VIEWER +) +``` + +### Arguments + +- collaboration_id `str` + - The ID of the collaboration. Example: "1234" +- role `UpdateCollaborationByIdRole` + - The level of access granted. +- status `Optional[UpdateCollaborationByIdStatus]` + - Set the status of a `pending` collaboration invitation, effectively accepting, or rejecting the invite. +- expires_at `Optional[DateTime]` + - Update the expiration date for the collaboration. At this date, the collaboration will be automatically removed from the item. This feature will only work if the **Automatically remove invited collaborators: Allow folder owners to extend the expiry date** setting has been enabled in the **Enterprise Settings** of the **Admin Console**. When the setting is not enabled, collaborations can not have an expiry date and a value for this field will be result in an error. Additionally, a collaboration can only be given an expiration if it was created after the **Automatically remove invited collaborator** setting was enabled. +- can_view_path `Optional[bool]` + - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only owner or co-owners can invite collaborators with a `can_view_path` of `true`. `can_view_path` can only be used for folder collaborations. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Optional[Collaboration]`. + +Returns an updated collaboration object unless the owner has changed.If the role is changed to `owner`, the collaboration is deleted +and a new collaboration is created. The previous `owner` of +the old collaboration will be a `co-owner` on the new collaboration. + +## Remove collaboration + +Deletes a single collaboration. + +This operation is performed by calling function `delete_collaboration_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-collaborations-id/). + + + +```python +client.user_collaborations.delete_collaboration_by_id(collaboration_id) +``` + +### Arguments + +- collaboration_id `str` + - The ID of the collaboration. Example: "1234" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +A blank response is returned if the collaboration was +successfully deleted. + +## Create collaboration + +Adds a collaboration for a single user or a single group to a file +or folder. + +Collaborations can be created using email address, user IDs, or a +group IDs. + +If a collaboration is being created with a group, access to +this endpoint is dependent on the group's ability to be invited. + +If collaboration is in `pending` status, the following fields +are redacted: + +- `login` and `name` are hidden if a collaboration was created + using `user_id`, +- `name` is hidden if a collaboration was created using `login`. + +This operation is performed by calling function `create_collaboration`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-collaborations/). + + + +```python +client.user_collaborations.create_collaboration( + CreateCollaborationItem(type=CreateCollaborationItemTypeField.FOLDER, id=folder.id), + CreateCollaborationAccessibleBy( + type=CreateCollaborationAccessibleByTypeField.USER, id=user.id + ), + CreateCollaborationRole.EDITOR, +) +``` + +### Arguments + +- item `CreateCollaborationItem` + - The item to attach the comment to. +- accessible_by `CreateCollaborationAccessibleBy` + - The user or group to give access to the item. +- role `CreateCollaborationRole` + - The level of access granted. +- is_access_only `Optional[bool]` + - If set to `true`, collaborators have access to shared items, but such items won't be visible in the All Files list. Additionally, collaborators won't see the the path to the root folder for the shared item. +- can_view_path `Optional[bool]` + - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only owner or co-owners can invite collaborators with a `can_view_path` of `true`. `can_view_path` can only be used for folder collaborations. +- expires_at `Optional[DateTime]` + - Set the expiration date for the collaboration. At this date, the collaboration will be automatically removed from the item. This feature will only work if the **Automatically remove invited collaborators: Allow folder owners to extend the expiry date** setting has been enabled in the **Enterprise Settings** of the **Admin Console**. When the setting is not enabled, collaborations can not have an expiry date and a value for this field will be result in an error. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- notify `Optional[bool]` + - Determines if users should receive email notification for the action performed. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Collaboration`. + +Returns a new collaboration object. diff --git a/docs/users.md b/docs/users.md new file mode 100644 index 000000000..63a8191d2 --- /dev/null +++ b/docs/users.md @@ -0,0 +1,312 @@ +# UsersManager + +- [List enterprise users](#list-enterprise-users) +- [Create user](#create-user) +- [Get current user](#get-current-user) +- [Get user](#get-user) +- [Update user](#update-user) +- [Delete user](#delete-user) + +## List enterprise users + +Returns a list of all users for the Enterprise along with their `user_id`, +`public_name`, and `login`. + +The application and the authenticated user need to +have the permission to look up users in the entire +enterprise. + +This operation is performed by calling function `get_users`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-users/). + + + +```python +client.users.get_users() +``` + +### Arguments + +- filter_term `Optional[str]` + - Limits the results to only users who's `name` or `login` start with the search term. For externally managed users, the search term needs to completely match the in order to find the user, and it will only return one user at a time. +- user_type `Optional[GetUsersUserType]` + - Limits the results to the kind of user specified. _ `all` returns every kind of user for whom the `login` or `name` partially matches the `filter_term`. It will only return an external user if the login matches the `filter_term` completely, and in that case it will only return that user. _ `managed` returns all managed and app users for whom the `login` or `name` partially matches the `filter_term`. \* `external` returns all external users for whom the `login` matches the `filter_term` exactly. +- external_app_user_id `Optional[str]` + - Limits the results to app users with the given `external_app_user_id` value. When creating an app user, an `external_app_user_id` value can be set. This value can then be used in this endpoint to find any users that match that `external_app_user_id` value. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- offset `Optional[int]` + - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. +- limit `Optional[int]` + - The maximum number of items to return per page. +- usemarker `Optional[bool]` + - Specifies whether to use marker-based pagination instead of offset-based pagination. Only one pagination method can be used at a time. By setting this value to true, the API will return a `marker` field that can be passed as a parameter to this endpoint to get the next page of the response. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Users`. + +Returns all of the users in the enterprise. + +## Create user + +Creates a new managed user in an enterprise. This endpoint +is only available to users and applications with the right +admin permissions. + +This operation is performed by calling function `create_user`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-users/). + + + +```python +client.users.create_user(user_name, login=user_login, is_platform_access_only=True) +``` + +### Arguments + +- name `str` + - The name of the user. +- login `Optional[str]` + - The email address the user uses to log in Required, unless `is_platform_access_only` is set to `true`. +- is_platform_access_only `Optional[bool]` + - Specifies that the user is an app user. +- role `Optional[CreateUserRole]` + - The user’s enterprise role. +- language `Optional[str]` + - The language of the user, formatted in modified version of the [ISO 639-1](/guides/api-calls/language-codes) format. +- is_sync_enabled `Optional[bool]` + - Whether the user can use Box Sync. +- job_title `Optional[str]` + - The user’s job title. +- phone `Optional[str]` + - The user’s phone number. +- address `Optional[str]` + - The user’s address. +- space_amount `Optional[int]` + - The user’s total available space in bytes. Set this to `-1` to indicate unlimited storage. +- tracking_codes `Optional[List[TrackingCode]]` + - Tracking codes allow an admin to generate reports from the admin console and assign an attribute to a specific group of users. This setting must be enabled for an enterprise before it can be used. +- can_see_managed_users `Optional[bool]` + - Whether the user can see other enterprise users in their contact list. +- timezone `Optional[str]` + - The user's timezone. +- is_external_collab_restricted `Optional[bool]` + - Whether the user is allowed to collaborate with users outside their enterprise. +- is_exempt_from_device_limits `Optional[bool]` + - Whether to exempt the user from enterprise device limits. +- is_exempt_from_login_verification `Optional[bool]` + - Whether the user must use two-factor authentication. +- status `Optional[CreateUserStatus]` + - The user's account status. +- external_app_user_id `Optional[str]` + - An external identifier for an app user, which can be used to look up the user. This can be used to tie user IDs from external identity providers to Box users. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UserFull`. + +Returns a user object for the newly created user. + +## Get current user + +Retrieves information about the user who is currently authenticated. + +In the case of a client-side authenticated OAuth 2.0 application +this will be the user who authorized the app. + +In the case of a JWT, server-side authenticated application +this will be the service account that belongs to the application +by default. + +Use the `As-User` header to change who this API call is made on behalf of. + +This operation is performed by calling function `get_user_me`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-users-me/). + + + +```python +client.users.get_user_me() +``` + +### Arguments + +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UserFull`. + +Returns a single user object. + +## Get user + +Retrieves information about a user in the enterprise. + +The application and the authenticated user need to +have the permission to look up users in the entire +enterprise. + +This endpoint also returns a limited set of information +for external users who are collaborated on content +owned by the enterprise for authenticated users with the +right scopes. In this case, disallowed fields will return +null instead. + +This operation is performed by calling function `get_user_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-users-id/). + + + +```python +client.users.get_user_by_id(user.id) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UserFull`. + +Returns a single user object. + +Not all available fields are returned by default. Use the +[fields](#param-fields) query parameter to explicitly request +any specific fields using the [fields](#get-users-id--request--fields) +parameter. + +## Update user + +Updates a managed or app user in an enterprise. This endpoint +is only available to users and applications with the right +admin permissions. + +This operation is performed by calling function `update_user_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-users-id/). + + + +```python +client.users.update_user_by_id(user.id, name=updated_user_name) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- enterprise `Optional[str]` + - Set this to `null` to roll the user out of the enterprise and make them a free user. +- notify `Optional[bool]` + - Whether the user should receive an email when they are rolled out of an enterprise. +- name `Optional[str]` + - The name of the user. +- login `Optional[str]` + - The email address the user uses to log in Note: If the target user's email is not confirmed, then the primary login address cannot be changed. +- role `Optional[UpdateUserByIdRole]` + - The user’s enterprise role. +- language `Optional[str]` + - The language of the user, formatted in modified version of the [ISO 639-1](/guides/api-calls/language-codes) format. +- is_sync_enabled `Optional[bool]` + - Whether the user can use Box Sync. +- job_title `Optional[str]` + - The user’s job title. +- phone `Optional[str]` + - The user’s phone number. +- address `Optional[str]` + - The user’s address. +- tracking_codes `Optional[List[TrackingCode]]` + - Tracking codes allow an admin to generate reports from the admin console and assign an attribute to a specific group of users. This setting must be enabled for an enterprise before it can be used. +- can_see_managed_users `Optional[bool]` + - Whether the user can see other enterprise users in their contact list. +- timezone `Optional[str]` + - The user's timezone. +- is_external_collab_restricted `Optional[bool]` + - Whether the user is allowed to collaborate with users outside their enterprise. +- is_exempt_from_device_limits `Optional[bool]` + - Whether to exempt the user from enterprise device limits. +- is_exempt_from_login_verification `Optional[bool]` + - Whether the user must use two-factor authentication. +- is_password_reset_required `Optional[bool]` + - Whether the user is required to reset their password. +- status `Optional[UpdateUserByIdStatus]` + - The user's account status. +- space_amount `Optional[int]` + - The user’s total available space in bytes. Set this to `-1` to indicate unlimited storage. +- notification_email `Optional[UpdateUserByIdNotificationEmail]` + - An alternate notification email address to which email notifications are sent. When it's confirmed, this will be the email address to which notifications are sent instead of to the primary email address. Set this value to `null` to remove the notification email. +- external_app_user_id `Optional[str]` + - An external identifier for an app user, which can be used to look up the user. This can be used to tie user IDs from external identity providers to Box users. Note: In order to update this field, you need to request a token using the application that created the app user. +- fields `Optional[List[str]]` + - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `UserFull`. + +Returns the updated user object. + +## Delete user + +Deletes a user. By default this will fail if the user +still owns any content. Move their owned content first +before proceeding, or use the `force` field to delete +the user and their files. + +This operation is performed by calling function `delete_user_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-users-id/). + + + +```python +client.users.delete_user_by_id(user.id) +``` + +### Arguments + +- user_id `str` + - The ID of the user. Example: "12345" +- notify `Optional[bool]` + - Whether the user will receive email notification of the deletion. +- force `Optional[bool]` + - Whether the user should be deleted even if this user still own files. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Removes the user and returns an empty response. diff --git a/docs/web_links.md b/docs/web_links.md new file mode 100644 index 000000000..1700761fc --- /dev/null +++ b/docs/web_links.md @@ -0,0 +1,147 @@ +# WebLinksManager + +- [Create web link](#create-web-link) +- [Get web link](#get-web-link) +- [Update web link](#update-web-link) +- [Remove web link](#remove-web-link) + +## Create web link + +Creates a web link object within a folder. + +This operation is performed by calling function `create_web_link`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-web-links/). + + + +```python +client.web_links.create_web_link( + "https://www.box.com", + CreateWebLinkParent(id=parent.id), + name=get_uuid(), + description="Weblink description", +) +``` + +### Arguments + +- url `str` + - The URL that this web link links to. Must start with `"http://"` or `"https://"`. +- parent `CreateWebLinkParent` + - The parent folder to create the web link within. +- name `Optional[str]` + - Name of the web link. Defaults to the URL if not set. +- description `Optional[str]` + - Description of the web link. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `WebLink`. + +Returns the newly created web link object. + +## Get web link + +Retrieve information about a web link. + +This operation is performed by calling function `get_web_link_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-web-links-id/). + + + +```python +client.web_links.get_web_link_by_id(weblink.id) +``` + +### Arguments + +- web_link_id `str` + - The ID of the web link. Example: "12345" +- boxapi `Optional[str]` + - The URL, and optional password, for the shared link of this item. This header can be used to access items that have not been explicitly shared with a user. Use the format `shared_link=[link]` or if a password is required then use `shared_link=[link]&shared_link_password=[password]`. This header can be used on the file or folder shared, as well as on any files or folders nested within the item. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `WebLink`. + +Returns the web link object. + +## Update web link + +Updates a web link object. + +This operation is performed by calling function `update_web_link_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-web-links-id/). + + + +```python +client.web_links.update_web_link_by_id( + weblink.id, + name=updated_name, + shared_link=UpdateWebLinkByIdSharedLink( + access=UpdateWebLinkByIdSharedLinkAccessField.OPEN, password=password + ), +) +``` + +### Arguments + +- web_link_id `str` + - The ID of the web link. Example: "12345" +- url `Optional[str]` + - The new URL that the web link links to. Must start with `"http://"` or `"https://"`. +- parent `Optional[UpdateWebLinkByIdParent]` +- name `Optional[str]` + - A new name for the web link. Defaults to the URL if not set. +- description `Optional[str]` + - A new description of the web link. +- shared_link `Optional[UpdateWebLinkByIdSharedLink]` + - The settings for the shared link to update. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `WebLink`. + +Returns the updated web link object. + +## Remove web link + +Deletes a web link. + +This operation is performed by calling function `delete_web_link_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-web-links-id/). + + + +```python +client.web_links.delete_web_link_by_id(web_link_id) +``` + +### Arguments + +- web_link_id `str` + - The ID of the web link. Example: "12345" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +An empty response will be returned when the web link +was successfully deleted. diff --git a/docs/webhooks.md b/docs/webhooks.md new file mode 100644 index 000000000..810cd0a23 --- /dev/null +++ b/docs/webhooks.md @@ -0,0 +1,201 @@ +# WebhooksManager + +- [List all webhooks](#list-all-webhooks) +- [Create webhook](#create-webhook) +- [Get webhook](#get-webhook) +- [Update webhook](#update-webhook) +- [Remove webhook](#remove-webhook) +- [Validate a webhook message](#validate-a-webhook-message) + +## List all webhooks + +Returns all defined webhooks for the requesting application. + +This API only returns webhooks that are applied to files or folders that are +owned by the authenticated user. This means that an admin can not see webhooks +created by a service account unless the admin has access to those folders, and +vice versa. + +This operation is performed by calling function `get_webhooks`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-webhooks/). + + + +```python +client.webhooks.get_webhooks() +``` + +### Arguments + +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Webhooks`. + +Returns a list of webhooks. + +## Create webhook + +Creates a webhook. + +This operation is performed by calling function `create_webhook`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-webhooks/). + + + +```python +client.webhooks.create_webhook( + CreateWebhookTarget(id=folder.id, type=CreateWebhookTargetTypeField.FOLDER), + "https://example.com/new-webhook", + [CreateWebhookTriggers.FILE_UPLOADED], +) +``` + +### Arguments + +- target `CreateWebhookTarget` + - The item that will trigger the webhook. +- address `str` + - The URL that is notified by this webhook. +- triggers `List[CreateWebhookTriggers]` + - An array of event names that this webhook is to be triggered for. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Webhook`. + +Returns the new webhook object. + +## Get webhook + +Retrieves a specific webhook. + +This operation is performed by calling function `get_webhook_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-webhooks-id/). + + + +```python +client.webhooks.get_webhook_by_id(webhook.id) +``` + +### Arguments + +- webhook_id `str` + - The ID of the webhook. Example: "3321123" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Webhook`. + +Returns a webhook object. + +## Update webhook + +Updates a webhook. + +This operation is performed by calling function `update_webhook_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/put-webhooks-id/). + + + +```python +client.webhooks.update_webhook_by_id( + webhook.id, address="https://example.com/updated-webhook" +) +``` + +### Arguments + +- webhook_id `str` + - The ID of the webhook. Example: "3321123" +- target `Optional[UpdateWebhookByIdTarget]` + - The item that will trigger the webhook. +- address `Optional[str]` + - The URL that is notified by this webhook. +- triggers `Optional[List[UpdateWebhookByIdTriggers]]` + - An array of event names that this webhook is to be triggered for. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Webhook`. + +Returns the new webhook object. + +## Remove webhook + +Deletes a webhook. + +This operation is performed by calling function `delete_webhook_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-webhooks-id/). + + + +```python +client.webhooks.delete_webhook_by_id(webhook.id) +``` + +### Arguments + +- webhook_id `str` + - The ID of the webhook. Example: "3321123" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +An empty response will be returned when the webhook +was successfully deleted. + +## Validate a webhook message + +Validate a webhook message by verifying the signature and the delivery timestamp + +This operation is performed by calling function `validate_message`. + +```python +WebhooksManager.validate_message( + body, headers_with_correct_datetime, primary_key, secondary_key=secondary_key +) +``` + +### Arguments + +- body `str` + - The request body of the webhook message +- headers `Dict[str, str]` + - The headers of the webhook message +- primary_key `str` + - The primary signature to verify the message with +- secondary_key `Optional[str]` + - The secondary signature to verify the message with +- max_age `Optional[int]` + - The maximum age of the message in seconds, defaults to 10 minutes + +### Returns + +This function returns a value of type `bool`. diff --git a/docs/workflows.md b/docs/workflows.md new file mode 100644 index 000000000..e218ea94f --- /dev/null +++ b/docs/workflows.md @@ -0,0 +1,91 @@ +# WorkflowsManager + +- [List workflows](#list-workflows) +- [Starts workflow based on request body](#starts-workflow-based-on-request-body) + +## List workflows + +Returns list of workflows that act on a given `folder ID`, and +have a flow with a trigger type of `WORKFLOW_MANUAL_START`. + +You application must be authorized to use the `Manage Box Relay` application +scope within the developer console in to use this endpoint. + +This operation is performed by calling function `get_workflows`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-workflows/). + + + +```python +admin_client.workflows.get_workflows(workflow_folder_id) +``` + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. +- trigger_type `Optional[str]` + - Type of trigger to search for. +- limit `Optional[int]` + - The maximum number of items to return per page. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `Workflows`. + +Returns the workflow. + +## Starts workflow based on request body + +Initiates a flow with a trigger type of `WORKFLOW_MANUAL_START`. + +You application must be authorized to use the `Manage Box Relay` application +scope within the developer console. + +This operation is performed by calling function `start_workflow`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-workflows-id-start/). + + + +```python +admin_client.workflows.start_workflow( + workflow_to_run.id, + StartWorkflowFlow(type="flow", id=workflow_to_run.flows[0].id), + [StartWorkflowFiles(type=StartWorkflowFilesTypeField.FILE, id=workflow_file_id)], + StartWorkflowFolder( + type=StartWorkflowFolderTypeField.FOLDER, id=workflow_folder_id + ), + type=StartWorkflowType.WORKFLOW_PARAMETERS, +) +``` + +### Arguments + +- workflow_id `str` + - The ID of the workflow. Example: "12345" +- type `Optional[StartWorkflowType]` + - The type of the parameters object. +- flow `StartWorkflowFlow` + - The flow that will be triggered. +- files `List[StartWorkflowFiles]` + - The array of files for which the workflow should start. All files must be in the workflow's configured folder. +- folder `StartWorkflowFolder` + - The folder object for which the workflow is configured. +- outcomes `Optional[List[Outcome]]` + - A configurable outcome the workflow should complete. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Starts the workflow. diff --git a/docs/working-with-nulls.md b/docs/working-with-nulls.md new file mode 100644 index 000000000..8ebc3d103 --- /dev/null +++ b/docs/working-with-nulls.md @@ -0,0 +1,38 @@ +# Handling null values in Box Python SDK Gen + +While using Box Python SDK it's important to understand how null values behave. This document provides a general overview of null value behaviour in Box Python SDK to help developers manage data consistently and predictably. + +## Understanding null behaviour + +The Box Python SDK follows a consistent pattern when handling null values in update operations. This behaviour applies to most endpoints that modify resources such as users, files, folders and metadata. The updating field behaves differently depending on weather you omit it, set it to null, or provide a value: + +- Omitting the field: The field won't be included in request and the value will remain unchanged. +- Setting it to null: Setting a field to null, will cause sending HTTP request with field value set to null, what will result in removing its current value or disassociates it from the resource. +- Providing a value: Providing a non-null value assigns or updates the field to that value. + +## Example Usage + +The client.files.update_file_by_id() method demonstrates null handling when modifying the lock field while updating the file: + +```python +import null from box_sdk_gen + +def createUpdateFile(client): + uploaded_file_id = '12345' + + # locking the file + file_with_lock = client.files.update_file_by_id( + uploaded_file_id, + lock=UpdateFileByIdLock(access=UpdateFileByIdLockAccessField.LOCK), + fields=['lock'], + ) + + # unlocking the file using lock value as null + file_without_lock = client.files.update_file_by_id( + uploaded_file_id, lock=null, fields=['lock'] + ) +``` + +## Summary + +To summarize, if you omit the field, the field remains unchanged. If you set it to null, it clears/removes the value. If you provide a value to that field, the field gets updated to that specified value. diff --git a/docs/zip_downloads.md b/docs/zip_downloads.md new file mode 100644 index 000000000..2a4b8fc57 --- /dev/null +++ b/docs/zip_downloads.md @@ -0,0 +1,170 @@ +# ZipDownloadsManager + +- [Create zip download](#create-zip-download) +- [Download zip archive](#download-zip-archive) +- [Get zip download status](#get-zip-download-status) +- [Download ZIP](#download-zip) + +## Create zip download + +Creates a request to download multiple files and folders as a single `zip` +archive file. This API does not return the archive but instead performs all +the checks to ensure that the user has access to all the items, and then +returns a `download_url` and a `status_url` that can be used to download the +archive. + +The limit for an archive is either the Account's upload limit or +10,000 files, whichever is met first. + +**Note**: Downloading a large file can be +affected by various +factors such as distance, network latency, +bandwidth, and congestion, as well as packet loss +ratio and current server load. +For these reasons we recommend that a maximum ZIP archive +total size does not exceed 25GB. + +This operation is performed by calling function `create_zip_download`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-zip-downloads/). + + + +```python +client.zip_downloads.create_zip_download( + [ + CreateZipDownloadItems(id=file_1.id, type=DownloadZipItemsTypeField.FILE), + CreateZipDownloadItems(id=file_2.id, type=DownloadZipItemsTypeField.FILE), + CreateZipDownloadItems(id=folder_1.id, type=DownloadZipItemsTypeField.FOLDER), + ], + download_file_name="zip", +) +``` + +### Arguments + +- items `List[CreateZipDownloadItems]` + - A list of items to add to the `zip` archive. These can be folders or files. +- download_file_name `Optional[str]` + - The optional name of the `zip` archive. This name will be appended by the `.zip` file extension, for example `January Financials.zip`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ZipDownload`. + +If the `zip` archive is ready to be downloaded, the API will return a +response that will include a `download_url`, a `status_url`, as well as +any conflicts that might have occurred when creating the request. + +## Download zip archive + +Returns the contents of a `zip` archive in binary format. This URL does not +require any form of authentication and could be used in a user's browser to +download the archive to a user's device. + +By default, this URL is only valid for a few seconds from the creation of +the request for this archive. Once a download has started it can not be +stopped and resumed, instead a new request for a zip archive would need to +be created. + +The URL of this endpoint should not be considered as fixed. Instead, use +the [Create zip download](e://post_zip_downloads) API to request to create a +`zip` archive, and then follow the `download_url` field in the response to +this endpoint. + +This operation is performed by calling function `get_zip_download_content`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-zip-downloads-id-content/). + + + +```python +client.zip_downloads.get_zip_download_content(zip_download.download_url) +``` + +### Arguments + +- download_url `str` + - The URL that can be used to download created `zip` archive. Example: `https://dl.boxcloud.com/2.0/zip_downloads/29l00nfxDyHOt7RphI9zT_w==nDnZEDjY2S8iEWWCHEEiptFxwoWojjlibZjJ6geuE5xnXENDTPxzgbks_yY=/content` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ByteStream`. + +Returns the content of the items requested for this download, formatted as +a stream of files and folders in a `zip` archive. + +## Get zip download status + +Returns the download status of a `zip` archive, allowing an application to +inspect the progress of the download as well as the number of items that +might have been skipped. + +This endpoint can only be accessed once the download has started. +Subsequently this endpoint is valid for 12 hours from the start of the +download. + +The URL of this endpoint should not be considered as fixed. Instead, use +the [Create zip download](e://post_zip_downloads) API to request to create a +`zip` archive, and then follow the `status_url` field in the response to +this endpoint. + +This operation is performed by calling function `get_zip_download_status`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-zip-downloads-id-status/). + + + +```python +client.zip_downloads.get_zip_download_status(zip_download.status_url) +``` + +### Arguments + +- status_url `str` + - The URL that can be used to get the status of the `zip` archive being downloaded. Example: `https://dl.boxcloud.com/2.0/zip_downloads/29l00nfxDyHOt7RphI9zT_w==nDnZEDjY2S8iEWWCHEEiptFxwoWojjlibZjJ6geuE5xnXENDTPxzgbks_yY=/status` +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ZipDownloadStatus`. + +Returns the status of the `zip` archive that is being downloaded. + +## Download ZIP + +Creates a zip and downloads its content + +This operation is performed by calling function `download_zip`. + +```python +client.zip_downloads.download_zip( + [ + DownloadZipItems(id=file_1.id, type=DownloadZipItemsTypeField.FILE), + DownloadZipItems(id=file_2.id, type=DownloadZipItemsTypeField.FILE), + DownloadZipItems(id=folder_1.id, type=DownloadZipItemsTypeField.FOLDER), + ], + download_file_name="zip", +) +``` + +### Arguments + +- items `List[DownloadZipItems]` + - A list of items to add to the `zip` archive. These can be folders or files. +- download_file_name `Optional[str]` + - The optional name of the `zip` archive. This name will be appended by the `.zip` file extension, for example `January Financials.zip`. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ByteStream`. diff --git a/migration-guide.md b/migration-guide.md new file mode 100644 index 000000000..ec6dc7594 --- /dev/null +++ b/migration-guide.md @@ -0,0 +1,738 @@ +# Migration guide from `boxsdk` to `box-sdk-gen` + + + + +- [Introduction](#introduction) +- [Installation](#installation) +- [Key differences](#key-differences) + - [Manager approach](#manager-approach) + - [Explicitly defined schemas](#explicitly-defined-schemas) + - [Immutable design](#immutable-design) +- [Authentication](#authentication) + - [Developer Token](#developer-token) + - [JWT Auth](#jwt-auth) + - [Using JWT configuration file](#using-jwt-configuration-file) + - [Providing JWT configuration manually](#providing-jwt-configuration-manually) + - [Authenticate user](#authenticate-user) + - [Client Credentials Grant](#client-credentials-grant) + - [Obtaining Service Account token](#obtaining-service-account-token) + - [Obtaining User token](#obtaining-user-token) + - [Switching between Service Account and User](#switching-between-service-account-and-user) + - [OAuth 2.0 Auth](#oauth-20-auth) + - [Get Authorization URL](#get-authorization-url) + - [Authenticate](#authenticate) + - [Store token and retrieve token callbacks](#store-token-and-retrieve-token-callbacks) + - [Downscope token](#downscope-token) + - [Revoke token](#revoke-token) +- [Configuration](#configuration) + - [As-User header](#as-user-header) + - [Custom Base URLs](#custom-base-urls) +- [Convenience methods](#convenience-methods) + - [Webhook validation](#webhook-validation) + - [Chunked upload of big files](#chunked-upload-of-big-files) + + + +## Introduction + +The new `box-sdk-gen` SDK library, which helps Python developers to conveniently integrate with Box API. +In the contrary to the previous library (`boxsdk`), it is not manually maintained, but auto-generated +based on Open API Specification. This means you can leverage the most up-to-date Box API features in your +applications without delay. More information and benefits of using the new can be found in the +[README](https://github.com/box/box-python-sdk-gen/blob/main/README.md) file. + +## Installation + +To install a new Box Python SDK GENERATED use command: + +```console +pip install box-sdk-gen +``` + +The new Box Python SDK GENERATED library could be used in the same project along with the legacy one. +If you want to use a feature available only in the new SDK, you don't need to necessarily migrate all your code +to use Box Python SDK GENERATED at once. You can use a new feature from the new library, +while keeping the rest of your code unchanged. Note that it may be required to alias some imported names +from the new SDK to avoid conflicts with the old one. However, we recommend to fully migrate to the new SDK eventually. + +## Key differences + +### Manager approach + +The main difference between the old SDK and the new one is the way how API methods are aggregated into objects. + +**Old (`boxsdk`)** + +Firstly, in the old SDK to be able to perform any action on an API object, e.g. `User`, you first had to create its class. +To do it is required to call: + +```python +user = client.user(user_id='123456') +``` + +to create a class representing an already existing User with id '12345', or create a new one with a call: + +```python +user = client.create_user(name='Some User') +``` + +Then, you could perform any action on created class, which will affect the user, e.g. + +```python +updated_user = user.update_info(data={'name': 'New User Name'}) +``` + +**New (`box-sdk-gen`)** + +In the new SDK the API methods are grouped into dedicated manager classes, e.g. `User` object +has dedicated `UserManager` class. Each manager class instance is available in `BoxClient` object. +The fields storing references to the managers are named in the plural form of the resource that the +manager handles - `client.users` for `UsersManager`. If you want to perform any operation +connected with a `User` you need to call a respective method of `UserManager`. +For example, to get info about existing user you need to call: + +```python +user = client.users.get_user_by_id(user_id='123456') +``` + +or to create a new user: + +```python +user = client.users.create_user(name='Some User') +``` + +The `User` object returned by both of these methods is a data class - it does not contain any methods to call. +To perform any action on `User` object, you need to still use a `UserManager` method for that. +Usually these methods have a first argument, which accepts id of the object you want to access, +e.g. to update a user name, call method: + +```python +updated_user = client.users.update_user_by_id(user_id=user.id, name='New User Name') +``` + +### Explicitly defined schemas + +**Old (`boxsdk`)** + +In the old SDK there were no data types explicitly defined - +the responses were dynamically mapped into classes in the runtime. For example, if you get information about a file: + +```python +file = client.file(file_id='12345678').get() +``` + +you couldn't be sure which fields to expect in the response object until the runtime, +because `File` class doesn't have any predefined fields. + +**New (`box-sdk-gen`)** + +In the new SDK the data classe are defined in `schemas` module, so you know, which fields to expect before +actually making a call. For example `FileBase` class is defined this way: + +```python +class FileBase(BaseObject): + def __init__(self, id: str, *, etag: Optional[str] = None, type: FileBaseTypeField = FileBaseTypeField.FILE.value, **kwargs): + super().__init__(**kwargs) + self.id = id + self.type = type + self.etag = etag +``` + +### Immutable design + +The new SDK is designed to be mostly immutable. This means that methods, +which used to modify the existing object in old SDK now return a new instance of the class with the modified state. +This design pattern is used to avoid side effects and make the code more predictable and easier to reason about. +Methods, which returns a new modified instance of an object, will always have a prefix `with_` in their names, e.g. + +**New (`box-sdk-gen`)** + +```python +from box_sdk_gen import BoxClient + +as_user_client: BoxClient = client.with_as_user_header('USER_ID') +``` + +## Authentication + +The Box Python SDK GENERATED library offers the same authentication methods as the legacy one. +Let's see the differences of their usage: + +### Developer Token + +**Old (`boxsdk`)** + +```python +from boxsdk import Client, OAuth2 + +auth = OAuth2( + client_id='YOUR_CLIENT_ID', + client_secret='YOUR_CLIENT_SECRET', + access_token='DEVELOPER_TOKEN_GOES_HERE', +) +client = Client(auth) +``` + +The new SDK provides a convenient `BoxDeveloperTokenAuth`, which allows authenticating +using developer token without necessity to provide a Client ID and Client Secret + +**New (`box-sdk-gen`)** + +```python +from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth + +auth = BoxDeveloperTokenAuth(token='DEVELOPER_TOKEN_GOES_HERE') +client = BoxClient(auth=auth) +``` + +### JWT Auth + +#### Using JWT configuration file + +**Old (`boxsdk`)** + +The static method, which reads the JWT configuration file has been changed: + +```python +from boxsdk import JWTAuth, Client + +auth = JWTAuth.from_settings_file('/path/to/config.json') +client = Client(auth) +``` + +**New (`box-sdk-gen`)** + +```python +from box_sdk_gen import BoxClient, BoxJWTAuth, JWTConfig + +jwt_config = JWTConfig.from_config_file(config_file_path='/path/to/config.json') +auth = BoxJWTAuth(config=jwt_config) +client = BoxClient(auth=auth) +``` + +#### Providing JWT configuration manually + +Some params in `JWTConfig` constructor have slightly different names than one in old `JWTAuth` class. + +**Old (`boxsdk`)** + +```python +from boxsdk import JWTAuth + +auth = JWTAuth( + client_id='YOUR_CLIENT_ID', + client_secret='YOUR_CLIENT_SECRET', + enterprise_id='YOUR_ENTERPRISE_ID', + user_id='USER_ID', + jwt_key_id='YOUR_JWT_KEY_ID', + rsa_private_key_file_sys_path='CERT.PEM', + rsa_private_key_passphrase='PASSPHRASE', + jwt_algorithm='RS256', +) +``` + +**New (`box-sdk-gen`)** + +```python +from box_sdk_gen import BoxJWTAuth, JWTConfig, JwtAlgorithm + +jwt_config = JWTConfig( + client_id='YOUR_CLIENT_ID', + client_secret='YOUR_CLIENT_SECRET', + enterprise_id='YOUR_ENTERPRISE_ID', + user_id='USER_ID', + jwt_key_id='YOUR_JWT_KEY_ID', + private_key='YOUR_PRIVATE_KEY', + private_key_passphrase='PASSPHRASE', + algorithm=JwtAlgorithm.RS256, +) +auth = BoxJWTAuth(config=jwt_config) +``` + +#### Authenticate user + +In old SDK method for user authentication was named `authenticate_user(self, user: Union[str, 'User'] = None) -> str` +and was accepting either user object or user id. If none provided, user ID stored in `JWTAuth` class instance was used. +The `authenticate_user` method was modifying existing `BoxJWTAuth` class, which was exchanging the existing token with +the one with the user access. + +**Old (`boxsdk`)** + +```python +auth.authenticate_user(user) +``` + +or + +```python +auth.authenticate_user('USER_ID') +``` + +**New (`box-sdk-gen`)** + +In new SDK, to authenticate as user you need to call +`with_user_subject(self, user_id: str, *, token_storage: TokenStorage = None) -> BoxJWTAuth` method with id of the user +to authenticate. The method returns a new instance of `BoxJWTAuth` class, which will perform authentication call +in scope of the user on the first API call. The `token_storage` parameter is optional and allows to provide a custom +token storage for the new instance of `BoxJWTAuth` class. The new auth instance can be used to create a new user client +instance. + +```python +from box_sdk_gen import BoxJWTAuth, BoxClient +user_auth: BoxJWTAuth = auth.with_user_subject('USER_ID') +user_client: BoxClient = BoxClient(auth=user_auth) +``` + +### Client Credentials Grant + +#### Obtaining Service Account token + +To authenticate as enterprise, the only difference between the old and the new SDK, +is using the `CCGConfig` as a middle step. + +**Old (`boxsdk`)** + +```python +from boxsdk import CCGAuth, Client + +auth = CCGAuth( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + enterprise_id="YOUR_ENTERPRISE_ID", +) + +client = Client(auth) +``` + +**New (`box-sdk-gen`)** + +```python +from box_sdk_gen import BoxClient, BoxCCGAuth, CCGConfig + +ccg_config = CCGConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + enterprise_id="YOUR_ENTERPRISE_ID", +) +auth = BoxCCGAuth(config=ccg_config) +client = BoxClient(auth=auth) +``` + +#### Obtaining User token + +In old SDK `CCGAuth` was accepting both user object and User ID. In the box-sdk-gen the `BoxCCGAuth` constructor accepts +only User ID instead. + +**Old (`boxsdk`)** + +```python +from boxsdk import CCGAuth + +auth = CCGAuth( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + user="YOUR_USER_ID" +) +``` + +**New (`box-sdk-gen`)** + +```python +from box_sdk_gen import BoxCCGAuth, CCGConfig + +ccg_config = CCGConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + user_id="YOUR_USER_ID" +) +auth = BoxCCGAuth(config=ccg_config) +``` + +### Switching between Service Account and User + +In old SDK there were two methods which allowed to switch between using service and user account. Calling these methods +were modifying existing state of `CCGAuth` class, which was fetching a new token on the next API call. + +**Old (`boxsdk`)** + +```python +auth.authenticate_enterprise('ENTERPRISE_ID') +``` + +```python +auth.authenticate_user('USER_ID') +``` + +In the new SDK, to keep the immutability design, the methods switching authenticated subject were replaced with methods +returning a new instance of `BoxCCGAuth` class. The new instance will fetch a new token on the next API call. +The new auth instance can be used to create a new client instance. You can also specify `token_storage` parameter +to provide a custom token storage for the new instance. +The old instance of `BoxCCGAuth` class will remain unchanged and will still use the old token. + +**New (`box-sdk-gen`)** + +```python +from box_sdk_gen import BoxCCGAuth, BoxClient +enterprise_auth: BoxCCGAuth = auth.with_enterprise_subject(enterprise_id='ENTERPRISE_ID') +enterprise_client: BoxClient = BoxClient(auth=enterprise_auth) +``` + +```python +from box_sdk_gen import BoxCCGAuth, BoxClient +user_auth: BoxCCGAuth = auth.with_user_subject(user_id='USER_ID') +user_client: BoxClient = BoxClient(auth=user_auth) +``` + +Note that the new methods accept only user id or enterprise id, while the old ones were accepting +user and enterprise object too. + +### OAuth 2.0 Auth + +#### Get Authorization URL + +To get authorization url in the new SDK, you need to first create the `BoxOAuth` class (previously `OAuth2`) using +`OAuthConfig` class. Then to get authorization url, call +`get_authorize_url(self, *, options: GetAuthorizeUrlOptions = None) -> str` instead of +`get_authorization_url(self, redirect_url: Optional[str]) -> Tuple[str, str]`. Note that this method +now accepts the instance of `GetAuthorizeUrlOptions` class, which allows specifying extra options to API call. +The new function returns only the authentication url string, while the old one returns tuple of +authentication url and csrf_token. + +**Old (`boxsdk`)** + +```python +from boxsdk import OAuth2 + +auth = OAuth2( + client_id='YOUR_CLIENT_ID', + client_secret='YOUR_CLIENT_SECRET', +) + +auth_url, csrf_token = auth.get_authorization_url('http://YOUR_REDIRECT_URL') +``` + +**New (`box-sdk-gen`)** + +```python +from box_sdk_gen import BoxOAuth, OAuthConfig, GetAuthorizeUrlOptions + +auth = BoxOAuth( + OAuthConfig( + client_id='YOUR_CLIENT_ID', + client_secret='YOUR_CLIENT_SECRET', + ) +) +auth_url = auth.get_authorize_url(options=GetAuthorizeUrlOptions(redirect_uri='http://YOUR_REDIRECT_URL')) +``` + +#### Authenticate + +The signature of method for authenticating with obtained auth code got changed from: +`authenticate(self, auth_code: Optional[str]) -> Tuple[str, str]` to +`get_tokens_authorization_code_grant(self, authorization_code: str, *, network_session: Optional[NetworkSession] = None) -> AccessToken`. +The method now returns an AccessToken object with `access_token` and `refresh_token` fields, +while the old one was returning a tuple of access token and refresh token. + +**Old (`boxsdk`)** + +```python +from boxsdk import Client +access_token, refresh_token = auth.authenticate('YOUR_AUTH_CODE') +client = Client(auth) +``` + +**New (`box-sdk-gen`)** + +```python +from box_sdk_gen import BoxClient, AccessToken + +access_token: AccessToken = auth.get_tokens_authorization_code_grant('YOUR_AUTH_CODE') +client = BoxClient(auth) +``` + +### Store token and retrieve token callbacks + +In old SDK you could provide a `store_tokens` callback method to an authentication class, which was called each time +an access token was refreshed. It could be used to save your access token to a custom token storage +and allow to reuse this token later. +What is more, old SDK allowed also to provide `retrieve_tokens` callback, which is called each time the SDK needs to use +token to perform an API call. To provide that, it was required to use `CooperativelyManagedOAuth2` and provide +`retrieve_tokens` callback method to its constructor. + +**Old (`boxsdk`)** + +```python +from typing import Tuple +from boxsdk.auth import CooperativelyManagedOAuth2 +from boxsdk import Client + +def retrieve_tokens() -> Tuple[str, str]: + # retrieve access_token and refresh_token + return access_token, refresh_token + +def store_tokens(access_token: str, refresh_token: str): + # store access_token and refresh_token + pass + + +auth = CooperativelyManagedOAuth2( + client_id='YOUR_CLIENT_ID', + client_secret='YOUR_CLIENT_SECRET', + retrieve_tokens=retrieve_tokens, + store_tokens=store_tokens +) +access_token, refresh_token = auth.authenticate('YOUR_AUTH_CODE') +client = Client(auth) +``` + +In the new SDK you can define your own class delegated for storing and retrieving a token. It has to inherit from +`TokenStorage` and implement all of its abstract methods. Next step would be to pass an instance of this class to the +AuthConfig constructor. + +**New (`box-sdk-gen`)** + +```python +from typing import Optional +from box_sdk_gen import BoxOAuth, OAuthConfig, TokenStorage, AccessToken + +class MyCustomTokenStorage(TokenStorage): + def store(self, token: AccessToken) -> None: + # store token + pass + + def get(self) -> Optional[AccessToken]: + # get token + pass + + def clear(self) -> None: + # clear token + pass + + +auth = BoxOAuth( + OAuthConfig( + client_id='YOUR_CLIENT_ID', + client_secret='YOUR_CLIENT_SECRET', + token_storage=MyCustomTokenStorage() + ) +) +``` + +or reuse one of the provided implementations: `FileTokenStorage` or `FileWithInMemoryCacheTokenStorage`: + +```python +from box_sdk_gen import BoxOAuth, OAuthConfig, FileWithInMemoryCacheTokenStorage + +auth = BoxOAuth( + OAuthConfig( + client_id='YOUR_CLIENT_ID', + client_secret='YOUR_CLIENT_SECRET', + token_storage=FileWithInMemoryCacheTokenStorage() + ) +) +``` + +### Downscope token + +The process of downscoping token in the new SDK is similar to the old one. The main difference is that the new method +accepts the full resource path instead of file object. + +**Old (`boxsdk`)** + +```python +from boxsdk import Client, OAuth2 + +target_file = client.file(file_id='FILE_ID_HERE') +token_info = client.downscope_token(['item_preview'], target_file) +downscoped_auth = OAuth2( + client_id=None, + client_secret=None, + access_token=token_info.access_token +) +downscoped_client = Client(downscoped_auth) +``` + +**New (`box-sdk-gen`)** + +```python +from box_sdk_gen import BoxDeveloperTokenAuth, AccessToken, BoxClient + +resource = 'https://api.box.com/2.0/files/123456789' +downscoped_token: AccessToken = auth.downscope_token( + scopes=['item_preview'], + resource=resource, +) +downscoped_auth = BoxDeveloperTokenAuth(token=downscoped_token.access_token) +client = BoxClient(auth=downscoped_auth) +``` + +### Revoke token + +To revoke current client's tokens in the new SDK, you need to call `revoke_token` method of the auth class instead of +`revoke` method. + +**Old (`boxsdk`)** + +```python +oauth.revoke() +``` + +**New (`box-sdk-gen`)** + +```python +client.auth.revoke_token() +``` + +## Configuration + +### As-User header + +The As-User header is used by enterprise admins to make API calls on behalf of their enterprise's users. +This requires the API request to pass an `As-User: USER-ID` header. The following examples assume that the client has +been instantiated with an access token with appropriate privileges to make As-User calls. + +In old SDK you could call client `as_user(self, user: User)` method to create a new client to impersonate the provided user. + +**Old (`boxsdk`)** + +```python +from boxsdk import Client + +user_to_impersonate = client.user(user_id='USER_ID') +user_client: Client = client.as_user(user_to_impersonate) +``` + +**New (`box-sdk-gen`)** + +In the new SDK the method was renamed to `with_as_user_header(self, user_id: str) -> BoxClient` +and returns a new instance of `BoxClient` class with the As-User header appended to all API calls made by the client. +The method accepts only user id as a parameter. + +```python +from box_sdk_gen import BoxClient + +user_client: BoxClient = client.with_as_user_header(user_id='USER_ID') +``` + +Additionally `BoxClient` offers a `with_extra_headers(self, *, extra_headers: Dict[str, str] = None) -> BoxClient` +method, which allows you to specify the custom set of headers, which will be included in every API call made by client. +Calling the `client.with_extra_headers()` method creates a new client, leaving the original client unmodified. + +```python +from box_sdk_gen import BoxClient + +new_client: BoxClient = client.with_extra_headers(extra_headers={'customHeader': 'customValue'}) +``` + +### Custom Base URLs + +**Old (`boxsdk`)** + +In old SDK you could specify the custom base URLs, which will be used for API calls made by setting +the new values of static variables of the `API` class. + +```python +from boxsdk.config import API + +API.BASE_API_URL = 'https://new-base-url.com' +API.OAUTH2_API_URL = 'https://my-company.com/oauth2' +API.UPLOAD_URL = 'https://my-company-upload-url.com' +``` + +**New (`box-sdk-gen`)** + +In the new SDK this functionality has been implemented as part of the `BoxClient` class. +By calling the `client.with_custom_base_urls()` method, you can specify the custom base URLs that will be used for API +calls made by client. Following the immutability pattern, this call creates a new client, leaving the original client unmodified. + +```python +from box_sdk_gen import BoxClient, BaseUrls + +new_client: BoxClient = client.with_custom_base_urls(base_urls=BaseUrls( + base_url='https://new-base-url.com', + upload_url='https://my-company-upload-url.com', + oauth_2_url='https://my-company.com/oauth2', +)) +``` + +## Convenience methods + +### Webhook validation + +Webhook validation is used to validate a webhook message by verifying the signature and the delivery timestamp. + +**Old (`boxsdk`)** + +In the old SDK, you could pass the `body` as `bytes`, and it would return a `boolean` value indicating whether the message was valid. + +```python +body = b'{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' +headers = { + 'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f', + 'box-delivery-timestamp': '2020-01-01T00:00:00-07:00', + 'box-signature-algorithm': 'HmacSHA256', + 'box-signature-primary': '4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=', + 'box-signature-secondary': 'yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=', + 'box-signature-version': '1', +} +is_validated = Webhook.validate_message(body, headers, primary_key, secondary_key) +print(f'The webhook message is validated to: {is_validated}') +``` + +**New (`box-sdk-gen`)** + +In the new SDK, the `WebhooksManager.validate_message()` method requires the `body` to be of type `string` and +the rest of the code remains the same + +```python +from box_sdk_gen import WebhooksManager + +body = '{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' +headers = { + 'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f', + 'box-delivery-timestamp': '2020-01-01T00:00:00-07:00', + 'box-signature-algorithm': 'HmacSHA256', + 'box-signature-primary': '4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=', + 'box-signature-secondary': 'yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=', + 'box-signature-version': '1', +} +WebhooksManager.validate_message( + body=body, headers=headers, primary_key=primary_key, secondary_key=secondary_key +) +``` + +### Chunked upload of big files + +For large files or in cases where the network connection is less reliable, you may want to upload the file in parts. +This allows a single part to fail without aborting the entire upload, and failed parts are being retried automatically. + +**Old (`boxsdk`)** + +In the old SDK, you could use the `get_chunked_uploader()` method to create a chunked uploader object. +Then, you would call the `start()` method to begin the upload process. +The `get_chunked_uploader()` method requires the `file_path` and `file_name` parameters. + +```python +chunked_uploader = client.folder('0').get_chunked_uploader(file_path='/path/to/file.txt', file_name='new_name.txt') +uploaded_file = chunked_uploader.start() +print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}') +``` + +**New (`box-sdk-gen`)** + +In the new SDK, the equivalent method is `chunked_uploads.upload_big_file()`. It accepts a file-like object +as the `file` parameter, and the `file_name` and `file_size` parameters are now passed as arguments. +The `parent_folder_id` parameter is also required to specify the folder where the file will be uploaded. + +```python +import os + +with open('/path/to/file.txt', 'rb') as file_byte_stream: + file_name = 'new_name.txt' + file_size = os.path.getsize('/path/to/file.txt') + parent_folder_id = '0' # ID of the folder where the file will be uploaded + uploaded_file = client.chunked_uploads.upload_big_file( + file=file_byte_stream, file_name=file_name, file_size=file_size, parent_folder_id=parent_folder_id + ) +``` diff --git a/pytest.ini b/pytest.ini index 0cd3c7748..a336b62a9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,9 +1,9 @@ [pytest] -addopts = --strict --showlocals -r a --tb=long --ignore=test/integration_new/ +addopts = --strict-markers --showlocals -r a --tb=long xfail_strict = True -junit_suite_name = boxsdk +junit_suite_name = box_sdk_gen testpaths = test/ python_files=*.py -timeout=60 +timeout=90 diff --git a/requirements-dev.txt b/requirements-dev.txt index 26eb61604..6be7b2856 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1 +1 @@ --e .[dev,test,jwt,redis] +-e .[test,dev] diff --git a/requirements-test.txt b/requirements-test.txt index 1c1134863..a4af5aa10 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1 +1 @@ --e .[jwt,redis,test] +-e .[test] diff --git a/requirements.txt b/requirements.txt index e9a201a47..d6e1198b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ --e .[jwt,redis] +-e . diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2195a80c2..000000000 --- a/setup.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[bdist_wheel] -# This flag says that the code is written to work on both Python 2 and Python -# 3. If at all possible, it is good practice to do this. If you cannot, you -# will need to generate wheels for each Python version that you support. -universal=1 - -[metadata] -license_file=LICENSE - -[isort] -known_first_party=test diff --git a/setup.py b/setup.py index 8c8ac6618..7fd0792bd 100644 --- a/setup.py +++ b/setup.py @@ -1,113 +1,59 @@ -from codecs import open # pylint:disable=redefined-builtin -from os.path import dirname, join -import re -import sys - from setuptools import setup, find_packages -from setuptools.command.test import test as TestCommand - - -CLASSIFIERS = [ - 'Development Status :: 6 - Mature', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - 'Programming Language :: Python :: Implementation :: CPython', - 'Operating System :: OS Independent', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS :: MacOS X', - 'Topic :: Software Development :: Libraries :: Python Modules', -] - - -class PyTest(TestCommand): - # pylint:disable=attribute-defined-outside-init - user_options = [(b'pytest-args=', b'a', b"Arguments to pass to py.test")] - - def initialize_options(self): - TestCommand.initialize_options(self) - self.pytest_args = None - - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = [] - self.test_suite = True +from os.path import dirname, join - def run_tests(self): - # Do the import here, once the eggs are loaded. - # pylint:disable=import-outside-toplevel - import pytest - errno = pytest.main(self.pytest_args) - sys.exit(errno) +import re def main(): - base_dir = dirname(__file__) - install_requires = [ - 'attrs>=17.3.0', - 'urllib3', - 'requests>=2.4.3,<3', - 'requests-toolbelt>=0.4.0', - 'python-dateutil', # To be removed after dropping Python 3.6 - ] - redis_requires = ['redis>=2.10.3'] + install_requires = ['requests', 'requests-toolbelt'] + tests_require = ['pytest', 'pytest-timeout', 'pytest-cov', 'pytest-rerunfailures'] + dev_requires = ['tox'] jwt_requires = ['pyjwt>=1.7.0', 'cryptography>=3'] - coveralls_requires = ['coveralls'] - dev_requires = ['tox<=3.28.0', 'setuptools'] - gh_requires = ['tox-gh-actions'] - test_requires = [ - 'bottle', - 'jsonpatch>1.14', - 'sqlalchemy<1.4.0', - # pytest 8.0.0 is not compatible, so we need to use the latest version of pytest 7.x - 'pytest<8.0.0', - 'pytest-timeout<3.0.0', - 'pytest-cov<5.0.0', - 'pytest-lazy-fixture<1.0.0', - 'pytz', - 'urllib3<2' - ] - extra_requires = { - 'jwt': jwt_requires, - 'redis': redis_requires, - 'coveralls': coveralls_requires + dev_requires, + version_file = open(join(dirname(__file__), 'box_sdk_gen/networking/version.py')) + version_regex = re.compile('.*__version__ = \'(.*?)\'', re.S) + version_string_grouped = version_regex.match(version_file.read()) + __version__ = version_string_grouped.group(1) + extras_require = { + 'test': tests_require + jwt_requires, 'dev': dev_requires, - 'gh': gh_requires + dev_requires, - 'test': test_requires, + 'jwt': jwt_requires, } - with open('boxsdk/version.py', encoding='utf-8') as config_py: - version = re.search(r'^\s*__version__\s*=\s*[\'"]([^\'"]*)[\'"]', config_py.read(), re.MULTILINE).group(1) setup( - name='boxsdk', - version=version, - description='Official Box Python SDK', - long_description_content_type="text/markdown", - long_description=open(join(base_dir, 'README.md'), encoding='utf-8').read(), # pylint:disable=consider-using-with + name='box-sdk-gen', + version=__version__, + description='Official Box Python Generated SDK', + url='https://github.com/box/box-python-sdk-gen.git', + licence='Apache-2.0, http://www.apache.org/licenses/LICENSE-2.0', author='Box', + long_description_content_type='text/markdown', + long_description=open( + join(dirname(__file__), 'README.md'), encoding='utf-8' + ).read(), author_email='oss@box.com', - url='https://github.com/box/box-python-sdk', - project_urls={ - 'Changelog': 'https://github.com/box/box-python-sdk/blob/main/CHANGELOG.md', - }, - packages=find_packages(exclude=['demo', 'docs', 'test', 'test*', '*test', '*test*']), + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Operating System :: OS Independent', + 'Operating System :: POSIX', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS :: MacOS X', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + keywords='box, sdk, api, rest, boxsdk, box-sdk-gen', install_requires=install_requires, - extras_require=extra_requires, - tests_require=test_requires, - cmdclass={'test': PyTest}, - classifiers=CLASSIFIERS, - keywords='box oauth2 sdk', - license='Apache Software License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0', - package_data={'boxsdk': ['py.typed']}, + tests_require=tests_require, + extras_require=extras_require, + packages=find_packages(exclude=['docs', '*test*']), ) diff --git a/test/ai.py b/test/ai.py new file mode 100644 index 000000000..9668804b5 --- /dev/null +++ b/test/ai.py @@ -0,0 +1,441 @@ +from typing import Union + +from typing import Optional + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.managers.ai import GetAiAgentDefaultConfigMode + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.ai_response_full import AiResponseFull + +from box_sdk_gen.managers.ai import CreateAiAskMode + +from box_sdk_gen.schemas.ai_item_ask import AiItemAsk + +from box_sdk_gen.schemas.ai_item_ask import AiItemAskTypeField + +from box_sdk_gen.schemas.ai_response import AiResponse + +from box_sdk_gen.managers.ai import CreateAiTextGenItems + +from box_sdk_gen.managers.ai import CreateAiTextGenItemsTypeField + +from box_sdk_gen.schemas.ai_dialogue_history import AiDialogueHistory + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.ai_item_base import AiItemBase + +from box_sdk_gen.schemas.ai_extract_structured_response import ( + AiExtractStructuredResponse, +) + +from box_sdk_gen.managers.ai import CreateAiExtractStructuredFields + +from box_sdk_gen.managers.ai import CreateAiExtractStructuredFieldsOptionsField + +from box_sdk_gen.schemas.metadata_template import MetadataTemplate + +from box_sdk_gen.managers.metadata_templates import CreateMetadataTemplateFields + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsTypeField, +) + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsOptionsField, +) + +from box_sdk_gen.managers.ai import CreateAiExtractStructuredMetadataTemplate + +from box_sdk_gen.managers.metadata_templates import DeleteMetadataTemplateScope + +from test.commons import get_default_client + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import string_to_byte_stream + +from box_sdk_gen.internal.utils import delay_in_seconds + +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 box_sdk_gen.internal.utils import get_value_from_object_raw_data + +from test.commons import upload_new_file + +from box_sdk_gen.schemas.ai_agent_ask import AiAgentAsk + +from box_sdk_gen.schemas.ai_agent_text_gen import AiAgentTextGen + +from box_sdk_gen.schemas.ai_agent_extract import AiAgentExtract + +from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured + +from box_sdk_gen.schemas.ai_agent_long_text_tool import AiAgentLongTextTool + +client: BoxClient = get_default_client() + + +def testAskAISingleItem(): + ai_agent_config: Union[ + AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured + ] = client.ai.get_ai_agent_default_config( + GetAiAgentDefaultConfigMode.ASK, language='en-US' + ) + ai_ask_agent_config: AiAgentAsk = ai_agent_config + file_to_ask: FileFull = upload_new_file() + response: Optional[AiResponseFull] = client.ai.create_ai_ask( + CreateAiAskMode.SINGLE_ITEM_QA, + 'which direction sun rises', + [ + AiItemAsk( + id=file_to_ask.id, + type=AiItemAskTypeField.FILE, + content='Sun rises in the East', + ) + ], + ai_agent=ai_ask_agent_config, + ) + 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: Optional[AiResponseFull] = client.ai.create_ai_ask( + CreateAiAskMode.MULTIPLE_ITEM_QA, + 'Which direction sun rises?', + [ + AiItemAsk( + id=file_to_ask_1.id, + type=AiItemAskTypeField.FILE, + content='Earth goes around the sun', + ), + AiItemAsk( + id=file_to_ask_2.id, + type=AiItemAskTypeField.FILE, + 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() + ai_agent_config: Union[ + AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured + ] = client.ai.get_ai_agent_default_config( + GetAiAgentDefaultConfigMode.TEXT_GEN, language='en-US' + ) + ai_text_gen_agent_config: AiAgentTextGen = ai_agent_config + response: AiResponse = client.ai.create_ai_text_gen( + 'Parapharse the document.s', + [ + CreateAiTextGenItems( + id=file_to_ask.id, + type=CreateAiTextGenItemsTypeField.FILE, + content='The Earth goes around the sun. Sun rises in the East in the morning.', + ) + ], + dialogue_history=[ + AiDialogueHistory( + prompt='What does the earth go around?', + answer='The sun', + created_at=date_time_from_string('2021-01-01T00:00:00Z'), + ), + AiDialogueHistory( + prompt='On Earth, where does the sun rise?', + answer='East', + created_at=date_time_from_string('2021-01-01T00:00:00Z'), + ), + ], + ai_agent=ai_text_gen_agent_config, + ) + assert 'sun' in response.answer + assert response.completion_reason == 'done' + client.files.delete_file_by_id(file_to_ask.id) + + +def testGettingAIAskAgentConfig(): + ai_agent_config: Union[ + AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured + ] = client.ai.get_ai_agent_default_config( + GetAiAgentDefaultConfigMode.ASK, language='en-US' + ) + assert ai_agent_config.type == 'ai_agent_ask' + ai_agent_ask_config: AiAgentAsk = ai_agent_config + assert not ai_agent_ask_config.basic_text.model == '' + assert not ai_agent_ask_config.basic_text.prompt_template == '' + assert ai_agent_ask_config.basic_text.num_tokens_for_completion > -1 + assert not ai_agent_ask_config.basic_text.llm_endpoint_params == None + assert not ai_agent_ask_config.basic_text_multi.model == '' + assert not ai_agent_ask_config.basic_text_multi.prompt_template == '' + assert ai_agent_ask_config.basic_text_multi.num_tokens_for_completion > -1 + assert not ai_agent_ask_config.basic_text_multi.llm_endpoint_params == None + assert not ai_agent_ask_config.long_text.model == '' + assert not ai_agent_ask_config.long_text.prompt_template == '' + assert ai_agent_ask_config.long_text.num_tokens_for_completion > -1 + assert not ai_agent_ask_config.long_text.embeddings.model == '' + assert not ai_agent_ask_config.long_text.embeddings.strategy.id == '' + assert not ai_agent_ask_config.long_text.llm_endpoint_params == None + assert not ai_agent_ask_config.long_text_multi.model == '' + assert not ai_agent_ask_config.long_text_multi.prompt_template == '' + assert ai_agent_ask_config.long_text_multi.num_tokens_for_completion > -1 + assert not ai_agent_ask_config.long_text_multi.embeddings.model == '' + assert not ai_agent_ask_config.long_text_multi.embeddings.strategy.id == '' + assert not ai_agent_ask_config.long_text_multi.llm_endpoint_params == None + + +def testGettingAITextGenAgentConfig(): + ai_agent_config: Union[ + AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured + ] = client.ai.get_ai_agent_default_config( + GetAiAgentDefaultConfigMode.TEXT_GEN, language='en-US' + ) + assert ai_agent_config.type == 'ai_agent_text_gen' + ai_agent_text_gen_config: AiAgentTextGen = ai_agent_config + assert not ai_agent_text_gen_config.basic_gen.llm_endpoint_params == None + assert not ai_agent_text_gen_config.basic_gen.model == '' + assert not ai_agent_text_gen_config.basic_gen.prompt_template == '' + assert ai_agent_text_gen_config.basic_gen.num_tokens_for_completion > -1 + assert not ai_agent_text_gen_config.basic_gen.content_template == '' + assert not ai_agent_text_gen_config.basic_gen.embeddings.model == '' + assert not ai_agent_text_gen_config.basic_gen.embeddings.strategy.id == '' + + +def testAIExtract(): + ai_agent_config: Union[ + AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured + ] = client.ai.get_ai_agent_default_config( + GetAiAgentDefaultConfigMode.EXTRACT, language='en-US' + ) + ai_extract_agent_config: AiAgentExtract = ai_agent_config + long_text_config_with_no_embeddings: AiAgentLongTextTool = AiAgentLongTextTool( + system_message=ai_extract_agent_config.long_text.system_message, + prompt_template=ai_extract_agent_config.long_text.prompt_template, + model=ai_extract_agent_config.long_text.model, + num_tokens_for_completion=ai_extract_agent_config.long_text.num_tokens_for_completion, + llm_endpoint_params=ai_extract_agent_config.long_text.llm_endpoint_params, + ) + agent_ignoring_overriding_embeddings_model: AiAgentExtract = AiAgentExtract( + basic_text=ai_extract_agent_config.basic_text, + long_text=long_text_config_with_no_embeddings, + ) + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=''.join([get_uuid(), '.txt']), + parent=UploadFileAttributesParentField(id='0'), + ), + string_to_byte_stream( + 'My name is John Doe. I live in San Francisco. I was born in 1990. I work at Box.' + ), + ) + file: FileFull = uploaded_files.entries[0] + delay_in_seconds(5) + response: AiResponse = client.ai.create_ai_extract( + 'firstName, lastName, location, yearOfBirth, company', + [AiItemBase(id=file.id)], + ai_agent=agent_ignoring_overriding_embeddings_model, + ) + expected_response: str = ( + '{"firstName": "John", "lastName": "Doe", "location": "San Francisco", "yearOfBirth": "1990", "company": "Box"}' + ) + assert response.answer == expected_response + assert response.completion_reason == 'done' + client.files.delete_file_by_id(file.id) + + +def testAIExtractStructuredWithFields(): + ai_agent_config: Union[ + AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured + ] = client.ai.get_ai_agent_default_config( + GetAiAgentDefaultConfigMode.EXTRACT_STRUCTURED, language='en-US' + ) + ai_extract_structured_agent_config: AiAgentExtractStructured = ai_agent_config + long_text_config_with_no_embeddings: AiAgentLongTextTool = AiAgentLongTextTool( + system_message=ai_extract_structured_agent_config.long_text.system_message, + prompt_template=ai_extract_structured_agent_config.long_text.prompt_template, + model=ai_extract_structured_agent_config.long_text.model, + num_tokens_for_completion=ai_extract_structured_agent_config.long_text.num_tokens_for_completion, + llm_endpoint_params=ai_extract_structured_agent_config.long_text.llm_endpoint_params, + ) + agent_ignoring_overriding_embeddings_model: AiAgentExtractStructured = ( + AiAgentExtractStructured( + basic_text=ai_extract_structured_agent_config.basic_text, + long_text=long_text_config_with_no_embeddings, + ) + ) + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=''.join([get_uuid(), '.txt']), + parent=UploadFileAttributesParentField(id='0'), + ), + string_to_byte_stream( + 'My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar.' + ), + ) + file: FileFull = uploaded_files.entries[0] + delay_in_seconds(5) + response: AiExtractStructuredResponse = client.ai.create_ai_extract_structured( + [AiItemBase(id=file.id)], + fields=[ + CreateAiExtractStructuredFields( + key='firstName', + display_name='First name', + description='Person first name', + prompt='What is the your first name?', + type='string', + ), + CreateAiExtractStructuredFields( + key='lastName', + display_name='Last name', + description='Person last name', + prompt='What is the your last name?', + type='string', + ), + CreateAiExtractStructuredFields( + key='dateOfBirth', + display_name='Birth date', + description='Person date of birth', + prompt='What is the date of your birth?', + type='date', + ), + CreateAiExtractStructuredFields( + key='age', + display_name='Age', + description='Person age', + prompt='How old are you?', + type='float', + ), + CreateAiExtractStructuredFields( + key='hobby', + display_name='Hobby', + description='Person hobby', + prompt='What is your hobby?', + type='multiSelect', + options=[ + CreateAiExtractStructuredFieldsOptionsField(key='guitar'), + CreateAiExtractStructuredFieldsOptionsField(key='books'), + ], + ), + ], + ai_agent=agent_ignoring_overriding_embeddings_model, + ) + assert to_string( + get_value_from_object_raw_data(response, 'answer.hobby') + ) == to_string(['guitar']) + assert ( + to_string(get_value_from_object_raw_data(response, 'answer.firstName')) + == 'John' + ) + assert ( + to_string(get_value_from_object_raw_data(response, 'answer.lastName')) == 'Doe' + ) + assert ( + to_string(get_value_from_object_raw_data(response, 'answer.dateOfBirth')) + == '1990-07-04' + ) + assert to_string(get_value_from_object_raw_data(response, 'answer.age')) == '34' + assert response.completion_reason == 'done' + client.files.delete_file_by_id(file.id) + + +def testAIExtractStructuredWithMetadataTemplate(): + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=''.join([get_uuid(), '.txt']), + parent=UploadFileAttributesParentField(id='0'), + ), + string_to_byte_stream( + 'My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar.' + ), + ) + file: FileFull = uploaded_files.entries[0] + delay_in_seconds(5) + template_key: str = ''.join(['key', get_uuid()]) + template: MetadataTemplate = client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + key='firstName', + display_name='First name', + description='Person first name', + type=CreateMetadataTemplateFieldsTypeField.STRING, + ), + CreateMetadataTemplateFields( + key='lastName', + display_name='Last name', + description='Person last name', + type=CreateMetadataTemplateFieldsTypeField.STRING, + ), + CreateMetadataTemplateFields( + key='dateOfBirth', + display_name='Birth date', + description='Person date of birth', + type=CreateMetadataTemplateFieldsTypeField.DATE, + ), + CreateMetadataTemplateFields( + key='age', + display_name='Age', + description='Person age', + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + ), + CreateMetadataTemplateFields( + key='hobby', + display_name='Hobby', + description='Person hobby', + type=CreateMetadataTemplateFieldsTypeField.MULTISELECT, + options=[ + CreateMetadataTemplateFieldsOptionsField(key='guitar'), + CreateMetadataTemplateFieldsOptionsField(key='books'), + ], + ), + ], + ) + response: AiExtractStructuredResponse = client.ai.create_ai_extract_structured( + [AiItemBase(id=file.id)], + metadata_template=CreateAiExtractStructuredMetadataTemplate( + template_key=template_key, scope='enterprise' + ), + ) + assert ( + to_string(get_value_from_object_raw_data(response, 'answer.firstName')) + == 'John' + ) + assert ( + to_string(get_value_from_object_raw_data(response, 'answer.lastName')) == 'Doe' + ) + assert ( + to_string(get_value_from_object_raw_data(response, 'answer.dateOfBirth')) + == '1990-07-04T00:00:00Z' + ) + assert to_string(get_value_from_object_raw_data(response, 'answer.age')) == '34' + assert to_string( + get_value_from_object_raw_data(response, 'answer.hobby') + ) == to_string(['guitar']) + assert response.completion_reason == 'done' + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template.template_key + ) + client.files.delete_file_by_id(file.id) diff --git a/test/ai_studio.py b/test/ai_studio.py new file mode 100644 index 000000000..793965a1b --- /dev/null +++ b/test/ai_studio.py @@ -0,0 +1,92 @@ +from box_sdk_gen.internal.utils import to_string + +from typing import Optional + +from box_sdk_gen.schemas.ai_single_agent_response_full import AiSingleAgentResponseFull + +from box_sdk_gen.schemas.ai_studio_agent_ask import AiStudioAgentAsk + +from box_sdk_gen.schemas.ai_multiple_agent_response import AiMultipleAgentResponse + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.ai_response_full import AiResponseFull + +from box_sdk_gen.managers.ai import CreateAiAskMode + +from box_sdk_gen.schemas.ai_item_ask import AiItemAsk + +from box_sdk_gen.schemas.ai_item_ask import AiItemAskTypeField + +from box_sdk_gen.box.developer_token_auth import BoxDeveloperTokenAuth + +from box_sdk_gen.client import BoxClient + +from test.commons import get_default_client + +from test.commons import upload_new_file + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +client: BoxClient = get_default_client() + + +def testAiStudioCRUD(): + agent_name: str = get_uuid() + created_agent: AiSingleAgentResponseFull = client.ai_studio.create_ai_agent( + agent_name, + 'enabled', + ask=AiStudioAgentAsk(access_state='enabled', description='desc1'), + ) + assert created_agent.name == agent_name + agents: AiMultipleAgentResponse = client.ai_studio.get_ai_agents() + num_agents: int = len(agents.entries) + assert to_string(agents.entries[0].type) == 'ai_agent' + retrieved_agent: AiSingleAgentResponseFull = client.ai_studio.get_ai_agent_by_id( + created_agent.id, fields=['ask'] + ) + assert retrieved_agent.name == agent_name + assert to_string(retrieved_agent.access_state) == 'enabled' + assert to_string(retrieved_agent.ask.access_state) == 'enabled' + assert retrieved_agent.ask.description == 'desc1' + updated_agent: AiSingleAgentResponseFull = client.ai_studio.update_ai_agent_by_id( + created_agent.id, + agent_name, + 'enabled', + ask=AiStudioAgentAsk(access_state='disabled', description='desc2'), + ) + assert to_string(updated_agent.access_state) == 'enabled' + assert to_string(updated_agent.ask.access_state) == 'disabled' + assert updated_agent.ask.description == 'desc2' + client.ai_studio.delete_ai_agent_by_id(created_agent.id) + agents_after_delete: AiMultipleAgentResponse = client.ai_studio.get_ai_agents() + assert len(agents_after_delete.entries) == num_agents - 1 + + +def testUseAIAgentReferenceInAIAsk(): + agent_name: str = get_uuid() + created_agent: AiSingleAgentResponseFull = client.ai_studio.create_ai_agent( + agent_name, + 'enabled', + ask=AiStudioAgentAsk(access_state='enabled', description='desc1'), + ) + file_to_ask: FileFull = upload_new_file() + response: Optional[AiResponseFull] = client.ai.create_ai_ask( + CreateAiAskMode.SINGLE_ITEM_QA, + 'which direction sun rises', + [ + AiItemAsk( + id=file_to_ask.id, + type=AiItemAskTypeField.FILE, + content='Sun rises in the East', + ) + ], + ai_agent=AiAgentReference(id=created_agent.id), + ) + assert 'East' in response.answer + assert response.completion_reason == 'done' + assert len(response.ai_agent_info.models) > 0 + client.files.delete_file_by_id(file_to_ask.id) + client.ai_studio.delete_ai_agent_by_id(created_agent.id) diff --git a/test/app_item_associations.py b/test/app_item_associations.py new file mode 100644 index 000000000..e4a8b7071 --- /dev/null +++ b/test/app_item_associations.py @@ -0,0 +1,55 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.app_item_associations import AppItemAssociations + +from box_sdk_gen.schemas.app_item_association import AppItemAssociation + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client_with_user_subject + + +def testListFileAppItemAssocations(): + client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + file_id: str = get_env_var('APP_ITEM_ASSOCIATION_FILE_ID') + file_app_item_associations: AppItemAssociations = ( + client.app_item_associations.get_file_app_item_associations(file_id) + ) + assert len(file_app_item_associations.entries) == 1 + association: AppItemAssociation = file_app_item_associations.entries[0] + assert not association.id == '' + assert to_string(association.app_item.application_type) == 'hubs' + assert to_string(association.app_item.type) == 'app_item' + assert to_string(association.item.type) == 'file' + assert association.item.id == file_id + file: FileFull = client.files.get_file_by_id( + file_id, fields=['is_associated_with_app_item'] + ) + assert file.is_associated_with_app_item == True + + +def testListFolderAppItemAssocations(): + client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + folder_id: str = get_env_var('APP_ITEM_ASSOCIATION_FOLDER_ID') + folder_app_item_associations: AppItemAssociations = ( + client.app_item_associations.get_folder_app_item_associations(folder_id) + ) + assert len(folder_app_item_associations.entries) == 1 + association: AppItemAssociation = folder_app_item_associations.entries[0] + assert not association.id == '' + assert to_string(association.app_item.application_type) == 'hubs' + assert to_string(association.app_item.type) == 'app_item' + assert to_string(association.item.type) == 'folder' + assert association.item.id == folder_id + folder: FolderFull = client.folders.get_folder_by_id( + folder_id, fields=['is_associated_with_app_item'] + ) + assert folder.is_associated_with_app_item == True diff --git a/test/archives.py b/test/archives.py new file mode 100644 index 000000000..49ffb2d5f --- /dev/null +++ b/test/archives.py @@ -0,0 +1,31 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.v2025_r0.archive_v2025_r0 import ArchiveV2025R0 + +from box_sdk_gen.schemas.v2025_r0.archives_v2025_r0 import ArchivesV2025R0 + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client_with_user_subject + +user_id: str = get_env_var('USER_ID') + +client: BoxClient = get_default_client_with_user_subject(user_id) + + +def testArchivesCreateListDelete(): + archive_name: str = get_uuid() + archive: ArchiveV2025R0 = client.archives.create_archive_v2025_r0(archive_name) + assert to_string(archive.type) == 'archive' + assert archive.name == archive_name + archives: ArchivesV2025R0 = client.archives.get_archives_v2025_r0(limit=100) + assert len(archives.entries) > 0 + client.archives.delete_archive_by_id_v2025_r0(archive.id) + with pytest.raises(Exception): + client.archives.delete_archive_by_id_v2025_r0(archive.id) diff --git a/test/auth.py b/test/auth.py new file mode 100644 index 000000000..8bb0b6c33 --- /dev/null +++ b/test/auth.py @@ -0,0 +1,365 @@ +import pytest + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.access_token import AccessToken + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.internal.utils import decode_base_64 + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import buffer_equals + +from box_sdk_gen.internal.utils import read_byte_stream + +from box_sdk_gen.internal.utils import generate_byte_buffer + +from box_sdk_gen.internal.utils import generate_byte_stream + +from box_sdk_gen.internal.utils import decode_base_64_byte_stream + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.box.ccg_auth import BoxCCGAuth + +from box_sdk_gen.box.ccg_auth import CCGConfig + +from box_sdk_gen.box.developer_token_auth import BoxDeveloperTokenAuth + +from box_sdk_gen.box.developer_token_auth import DeveloperTokenConfig + +from box_sdk_gen.box.oauth import BoxOAuth + +from box_sdk_gen.box.oauth import OAuthConfig + +from box_sdk_gen.box.token_storage import InMemoryTokenStorage + +from box_sdk_gen.box.jwt_auth import BoxJWTAuth + +from box_sdk_gen.box.jwt_auth import JWTConfig + + +def test_jwt_auth(): + user_id: str = get_env_var('USER_ID') + enterprise_id: str = get_env_var('ENTERPRISE_ID') + jwt_config: JWTConfig = JWTConfig.from_config_json_string( + decode_base_64(get_env_var('JWT_CONFIG_BASE_64')) + ) + auth: BoxJWTAuth = BoxJWTAuth(config=jwt_config) + user_auth: BoxJWTAuth = auth.with_user_subject(user_id) + user_client: BoxClient = BoxClient(auth=user_auth) + current_user: UserFull = user_client.users.get_user_me() + assert current_user.id == user_id + enterprise_auth: BoxJWTAuth = auth.with_enterprise_subject(enterprise_id) + enterprise_client: BoxClient = BoxClient(auth=enterprise_auth) + new_user: UserFull = enterprise_client.users.get_user_me(fields=['enterprise']) + assert not new_user.enterprise == None + assert new_user.enterprise.id == enterprise_id + assert not new_user.id == user_id + + +def test_jwt_auth_downscope(): + jwt_config: JWTConfig = JWTConfig.from_config_json_string( + decode_base_64(get_env_var('JWT_CONFIG_BASE_64')) + ) + auth: BoxJWTAuth = BoxJWTAuth(config=jwt_config) + parent_client: BoxClient = BoxClient(auth=auth) + uploaded_files: Files = parent_client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(1024 * 1024), + ) + file: FileFull = uploaded_files.entries[0] + resource_path: str = ''.join(['https://api.box.com/2.0/files/', file.id]) + downscoped_token: AccessToken = auth.downscope_token( + ['item_rename', 'item_preview'], resource=resource_path + ) + assert not downscoped_token.access_token == None + downscoped_client: BoxClient = BoxClient( + auth=BoxDeveloperTokenAuth(token=downscoped_token.access_token) + ) + downscoped_client.files.update_file_by_id(file.id, name=get_uuid()) + with pytest.raises(Exception): + downscoped_client.files.delete_file_by_id(file.id) + parent_client.files.delete_file_by_id(file.id) + + +def test_jwt_downscope_token_succeeds_if_no_token_available(): + jwt_config: JWTConfig = JWTConfig.from_config_json_string( + decode_base_64(get_env_var('JWT_CONFIG_BASE_64')) + ) + auth: BoxJWTAuth = BoxJWTAuth(config=jwt_config) + downscoped_token: AccessToken = auth.downscope_token(['root_readonly']) + assert not downscoped_token.access_token == None + downscoped_client: BoxClient = BoxClient( + auth=BoxDeveloperTokenAuth(token=downscoped_token.access_token) + ) + with pytest.raises(Exception): + downscoped_client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(1024 * 1024), + ) + + +def test_jwt_auth_revoke(): + jwt_config: JWTConfig = JWTConfig.from_config_json_string( + decode_base_64(get_env_var('JWT_CONFIG_BASE_64')) + ) + auth: BoxJWTAuth = BoxJWTAuth(config=jwt_config) + token_from_storage_before_revoke: AccessToken = auth.retrieve_token() + assert not token_from_storage_before_revoke == None + auth.revoke_token() + token_from_storage_after_revoke: AccessToken = auth.retrieve_token() + assert ( + not token_from_storage_before_revoke.access_token + == token_from_storage_after_revoke.access_token + ) + + +def test_oauth_auth_authorizeUrl(): + config: OAuthConfig = OAuthConfig( + client_id='OAUTH_CLIENT_ID', client_secret='OAUTH_CLIENT_SECRET' + ) + auth: BoxOAuth = BoxOAuth(config=config) + auth_url: str = auth.get_authorize_url() + assert ( + auth_url + == 'https://account.box.com/api/oauth2/authorize?client_id=OAUTH_CLIENT_ID&response_type=code' + or auth_url + == 'https://account.box.com/api/oauth2/authorize?response_type=code&client_id=OAUTH_CLIENT_ID' + ) + + +def test_oauth_downscope_token_succeeds_if_no_token_available(): + config: OAuthConfig = OAuthConfig( + client_id=get_env_var('CLIENT_ID'), client_secret=get_env_var('CLIENT_SECRET') + ) + auth: BoxOAuth = BoxOAuth(config=config) + resource_path: str = ''.join(['https://api.box.com/2.0/files/12345']) + with pytest.raises(Exception): + auth.downscope_token(['item_rename', 'item_preview'], resource=resource_path) + + +def test_ccg_auth(): + user_id: str = get_env_var('USER_ID') + enterprise_id: str = get_env_var('ENTERPRISE_ID') + ccg_config: CCGConfig = CCGConfig( + client_id=get_env_var('CLIENT_ID'), + client_secret=get_env_var('CLIENT_SECRET'), + enterprise_id=enterprise_id, + user_id=user_id, + ) + auth: BoxCCGAuth = BoxCCGAuth(config=ccg_config) + user_auth: BoxCCGAuth = auth.with_user_subject(user_id) + user_client: BoxClient = BoxClient(auth=user_auth) + current_user: UserFull = user_client.users.get_user_me() + assert current_user.id == user_id + enterprise_auth: BoxCCGAuth = auth.with_enterprise_subject(enterprise_id) + enterprise_client: BoxClient = BoxClient(auth=enterprise_auth) + new_user: UserFull = enterprise_client.users.get_user_me(fields=['enterprise']) + assert not new_user.enterprise == None + assert new_user.enterprise.id == enterprise_id + assert not new_user.id == user_id + + +def test_ccg_auth_downscope(): + ccg_config: CCGConfig = CCGConfig( + client_id=get_env_var('CLIENT_ID'), + client_secret=get_env_var('CLIENT_SECRET'), + user_id=get_env_var('USER_ID'), + ) + auth: BoxCCGAuth = BoxCCGAuth(config=ccg_config) + parent_client: BoxClient = BoxClient(auth=auth) + folder: FolderFull = parent_client.folders.create_folder( + get_uuid(), CreateFolderParent(id='0') + ) + resource_path: str = ''.join(['https://api.box.com/2.0/folders/', folder.id]) + downscoped_token: AccessToken = auth.downscope_token( + ['item_rename', 'item_preview'], resource=resource_path + ) + assert not downscoped_token.access_token == None + downscoped_client: BoxClient = BoxClient( + auth=BoxDeveloperTokenAuth(token=downscoped_token.access_token) + ) + downscoped_client.folders.update_folder_by_id(folder.id, name=get_uuid()) + with pytest.raises(Exception): + downscoped_client.folders.delete_folder_by_id(folder.id) + parent_client.folders.delete_folder_by_id(folder.id) + + +def test_ccg_downscope_token_succeeds_if_no_token_available(): + ccg_config: CCGConfig = CCGConfig( + client_id=get_env_var('CLIENT_ID'), + client_secret=get_env_var('CLIENT_SECRET'), + user_id=get_env_var('USER_ID'), + ) + auth: BoxCCGAuth = BoxCCGAuth(config=ccg_config) + downscoped_token: AccessToken = auth.downscope_token(['root_readonly']) + assert not downscoped_token.access_token == None + downscoped_client: BoxClient = BoxClient( + auth=BoxDeveloperTokenAuth(token=downscoped_token.access_token) + ) + with pytest.raises(Exception): + downscoped_client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(1024 * 1024), + ) + + +def test_ccg_auth_revoke(): + ccg_config: CCGConfig = CCGConfig( + client_id=get_env_var('CLIENT_ID'), + client_secret=get_env_var('CLIENT_SECRET'), + user_id=get_env_var('USER_ID'), + ) + auth: BoxCCGAuth = BoxCCGAuth(config=ccg_config) + token_from_storage_before_revoke: AccessToken = auth.retrieve_token() + assert not token_from_storage_before_revoke == None + auth.revoke_token() + token_from_storage_after_revoke: AccessToken = auth.retrieve_token() + assert ( + not token_from_storage_before_revoke.access_token + == token_from_storage_after_revoke.access_token + ) + + +def test_developer_downscope_token_succeeds_if_no_token_available(): + developer_token_config: DeveloperTokenConfig = DeveloperTokenConfig( + client_id=get_env_var('CLIENT_ID'), client_secret=get_env_var('CLIENT_SECRET') + ) + auth: BoxDeveloperTokenAuth = BoxDeveloperTokenAuth( + token='', config=developer_token_config + ) + resource_path: str = ''.join(['https://api.box.com/2.0/folders/12345']) + with pytest.raises(Exception): + auth.downscope_token(['item_rename', 'item_preview'], resource=resource_path) + + +def get_access_token() -> AccessToken: + user_id: str = get_env_var('USER_ID') + enterprise_id: str = get_env_var('ENTERPRISE_ID') + ccg_config: CCGConfig = CCGConfig( + client_id=get_env_var('CLIENT_ID'), + client_secret=get_env_var('CLIENT_SECRET'), + enterprise_id=enterprise_id, + user_id=user_id, + ) + auth: BoxCCGAuth = BoxCCGAuth(config=ccg_config) + auth_user: BoxCCGAuth = auth.with_user_subject(user_id) + return auth_user.retrieve_token() + + +def test_developer_token_auth_revoke(): + developer_token_config: DeveloperTokenConfig = DeveloperTokenConfig( + client_id=get_env_var('CLIENT_ID'), client_secret=get_env_var('CLIENT_SECRET') + ) + token: AccessToken = get_access_token() + auth: BoxDeveloperTokenAuth = BoxDeveloperTokenAuth( + token=token.access_token, config=developer_token_config + ) + token_from_storage_before_revoke: AccessToken = auth.retrieve_token() + assert not token_from_storage_before_revoke == None + auth.revoke_token() + with pytest.raises(Exception): + auth.retrieve_token() + + +def test_developer_token_auth_downscope(): + developer_token_config: DeveloperTokenConfig = DeveloperTokenConfig( + client_id=get_env_var('CLIENT_ID'), client_secret=get_env_var('CLIENT_SECRET') + ) + token: AccessToken = get_access_token() + auth: BoxDeveloperTokenAuth = BoxDeveloperTokenAuth( + token=token.access_token, config=developer_token_config + ) + parent_client: BoxClient = BoxClient(auth=auth) + folder: FolderFull = parent_client.folders.create_folder( + get_uuid(), CreateFolderParent(id='0') + ) + resource_path: str = ''.join(['https://api.box.com/2.0/folders/', folder.id]) + downscoped_token: AccessToken = auth.downscope_token( + ['item_rename', 'item_preview'], resource=resource_path + ) + assert not downscoped_token.access_token == None + downscoped_client: BoxClient = BoxClient( + auth=BoxDeveloperTokenAuth(token=downscoped_token.access_token) + ) + downscoped_client.folders.update_folder_by_id(folder.id, name=get_uuid()) + with pytest.raises(Exception): + downscoped_client.folders.delete_folder_by_id(folder.id) + parent_client.folders.delete_folder_by_id(folder.id) + + +def test_developer_token_auth(): + user_id: str = get_env_var('USER_ID') + token: AccessToken = get_access_token() + dev_auth: BoxDeveloperTokenAuth = BoxDeveloperTokenAuth(token=token.access_token) + client: BoxClient = BoxClient(auth=dev_auth) + current_user: UserFull = client.users.get_user_me() + assert current_user.id == user_id + + +def test_oauth_auth_revoke(): + token: AccessToken = get_access_token() + token_storage: InMemoryTokenStorage = InMemoryTokenStorage(token=token) + config: OAuthConfig = OAuthConfig( + client_id=get_env_var('CLIENT_ID'), + client_secret=get_env_var('CLIENT_SECRET'), + token_storage=token_storage, + ) + auth: BoxOAuth = BoxOAuth(config=config) + client: BoxClient = BoxClient(auth=auth) + client.users.get_user_me() + auth.revoke_token() + with pytest.raises(Exception): + client.users.get_user_me() + + +def test_oauth_auth_downscope(): + token: AccessToken = get_access_token() + token_storage: InMemoryTokenStorage = InMemoryTokenStorage(token=token) + config: OAuthConfig = OAuthConfig( + client_id=get_env_var('CLIENT_ID'), + client_secret=get_env_var('CLIENT_SECRET'), + token_storage=token_storage, + ) + auth: BoxOAuth = BoxOAuth(config=config) + parent_client: BoxClient = BoxClient(auth=auth) + uploaded_files: Files = parent_client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(1024 * 1024), + ) + file: FileFull = uploaded_files.entries[0] + resource_path: str = ''.join(['https://api.box.com/2.0/files/', file.id]) + downscoped_token: AccessToken = auth.downscope_token( + ['item_rename', 'item_preview'], resource=resource_path + ) + assert not downscoped_token.access_token == None + downscoped_client: BoxClient = BoxClient( + auth=BoxDeveloperTokenAuth(token=downscoped_token.access_token) + ) + downscoped_client.files.update_file_by_id(file.id, name=get_uuid()) + with pytest.raises(Exception): + downscoped_client.files.delete_file_by_id(file.id) + parent_client.files.delete_file_by_id(file.id) diff --git a/test/avatars.py b/test/avatars.py new file mode 100644 index 000000000..021959a61 --- /dev/null +++ b/test/avatars.py @@ -0,0 +1,47 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.user_avatar import UserAvatar + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.internal.utils import decode_base_64 + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import buffer_equals + +from box_sdk_gen.internal.utils import read_byte_stream + +from box_sdk_gen.internal.utils import generate_byte_buffer + +from box_sdk_gen.internal.utils import decode_base_64_byte_stream + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testAvatars(): + user: UserFull = client.users.get_user_me() + created_avatar: UserAvatar = client.avatars.create_user_avatar( + user.id, + decode_base_64_byte_stream( + 'iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAAA1BMVEW10NBjBBbqAAAAH0lEQVRoge3BAQ0AAADCoPdPbQ43oAAAAAAAAAAAvg0hAAABmmDh1QAAAABJRU5ErkJggg==' + ), + pic_file_name='avatar.png', + pic_content_type='image/png', + ) + assert not created_avatar.pic_urls.small == None + assert not created_avatar.pic_urls.large == None + assert not created_avatar.pic_urls.preview == None + response: ByteStream = client.avatars.get_user_avatar(user.id) + assert buffer_equals(read_byte_stream(response), generate_byte_buffer(0)) == False + client.avatars.delete_user_avatar(user.id) + with pytest.raises(Exception): + client.avatars.get_user_avatar(user.id) diff --git a/test/box_network_client.py b/test/box_network_client.py new file mode 100644 index 000000000..ab95206ee --- /dev/null +++ b/test/box_network_client.py @@ -0,0 +1,1226 @@ +import pytest +import json +from collections import OrderedDict +from io import BytesIO, RawIOBase, UnsupportedOperation, SEEK_SET +from unittest import mock +from unittest.mock import Mock, patch +from requests import Session, Response, RequestException + +from box_sdk_gen import ( + NetworkSession, + BoxAPIError, + Authentication, + BoxSDKError, + BoxClient, + ResponseFormat, + DataSanitizer, +) +from box_sdk_gen.networking.box_network_client import ( + BoxNetworkClient, + USER_AGENT_HEADER, + X_BOX_UA_HEADER, + APIRequest, + APIResponse, +) +from box_sdk_gen.networking import ( + FetchOptions, + MultipartItem, + FetchResponse, + BoxRetryStrategy, +) +from box_sdk_gen.networking.proxy_config import ProxyConfig + + +@pytest.fixture +def mock_requests_session(): + return Mock(Session) + + +@pytest.fixture +def mock_byte_stream(): + return BytesIO(b"123") + + +@pytest.fixture +def mock_non_seekable_stream(): + return NonSeekableStream(b"123") + + +class NonSeekableStream(RawIOBase): + def __init__(self, data: bytes): + self._buffer = BytesIO(data) + + def read(self, size=-1): + return self._buffer.read(size) + + def seekable(self) -> bool: + return False + + def seek(self, offset, whence=SEEK_SET): + raise UnsupportedOperation("Stream is not seekable") + + +@pytest.fixture +def response_202(): + response = Mock(Response) + response.url = 'https://example.com' + response.status_code = 202 + response.ok = True + response.text = "" + response.content = None + response.headers = { + "content-type": "text/html", + } + return response + + +@pytest.fixture +def response_202_with_retry_after(): + response = Mock(Response) + response.url = 'https://example.com' + response.status_code = 202 + response.ok = True + response.text = "" + response.content = b'' + response.headers = {"Retry-After": "0"} + return response + + +@pytest.fixture +def response_500(): + response = Mock(Response) + response.url = 'https://example.com' + response.status_code = 500 + response.ok = False + response.text = "" + response.content = b'' + response.headers = {"Retry-After": "0"} + return response + + +@pytest.fixture +def response_401(): + response = Mock(Response) + response.url = 'https://example.com' + response.status_code = 401 + response.ok = False + response.text = "" + response.content = b'' + response.headers = {} + return response + + +@pytest.fixture +def response_429(): + response = Mock(Response) + response.url = 'https://example.com' + response.status_code = 429 + response.ok = False + response.text = "" + response.content = b'' + response.headers = {} + return response + + +@pytest.fixture +def response_200(): + response = Mock(Response) + response.url = 'https://example.com' + response.status_code = 200 + response.ok = True + response.headers = {} + response.text = "" + response.content = b'' + return response + + +@pytest.fixture +def response_302(): + response = Mock(Response) + response.url = 'https://example.com' + response.status_code = 302 + response.ok = True + response.headers = { + "location": "https://example.com/redirected", + "content-length": "0", + } + response.text = "" + response.content = b'' + return response + + +@pytest.fixture +def response_failure_no_status(): + response = Mock(Response) + response.url = 'https://example.com' + response.ok = False + response.text = "" + response.content = b'' + response.headers = {"Retry-After": "0"} + return response + + +@pytest.fixture +def token_mock(): + return "token123" + + +@pytest.fixture +def token2_mock(): + return "new_token321" + + +@pytest.fixture +def network_session_mock(): + return NetworkSession() + + +@pytest.fixture +def network_client(mock_requests_session): + return BoxNetworkClient(mock_requests_session) + + +@pytest.fixture +def data_sanitizer(): + return DataSanitizer() + + +def reauthenticate_mock(auth, token): + auth.retrieve_authorization_header.return_value = f"Bearer {token}" + + +@pytest.fixture +def authentication_mock(token_mock, token2_mock): + auth = Mock(Authentication) + auth.retrieve_authorization_header.return_value = f"Bearer {token_mock}" + auth.refresh_token = lambda network_session: reauthenticate_mock(auth, token2_mock) + return auth + + +def test_use_session_and_max_attempts_from_network_session( + network_client, mock_requests_session, response_500 +): + mock_requests_session.request.return_value = response_500 + + network_session = NetworkSession(retry_strategy=BoxRetryStrategy(max_attempts=3)) + + options = FetchOptions( + url="https://example.com", + method="GET", + network_session=network_session, + ) + + with pytest.raises(BoxAPIError): + network_client.fetch(options) + + assert mock_requests_session.request.call_count == 3 + + +def test_use_default_session_and_max_attempts_when_network_session_not_provided( + network_client, mock_requests_session, response_500, network_session_mock +): + mock_requests_session.request.return_value = response_500 + with patch("requests.Session", return_value=mock_requests_session): + options = FetchOptions(url="https://example.com", method="GET") + + with pytest.raises(BoxAPIError): + network_client.fetch(options) + + assert mock_requests_session.request.call_count == 5 + + +def test_prepare_headers(network_client, authentication_mock, token_mock): + network_session = NetworkSession(additional_headers={"additional_header": "test"}) + options = FetchOptions( + url="https://example.com", + method="GET", + network_session=network_session, + headers={"header": "test"}, + auth=authentication_mock, + ) + + headers = network_client._prepare_headers(options) + + assert headers == { + "Authorization": f"Bearer {token_mock}", + "header": "test", + "additional_header": "test", + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + } + + +def test_prepare_headers_reauthenticate( + network_client, authentication_mock, token2_mock +): + network_session = NetworkSession(additional_headers={"additional_header": "test"}) + options = FetchOptions( + url="https://example.com", + method="GET", + network_session=network_session, + headers={"header": "test"}, + auth=authentication_mock, + ) + + headers = network_client._prepare_headers(options, reauthenticate=True) + + assert headers == { + "Authorization": f"Bearer {token2_mock}", + "header": "test", + "additional_header": "test", + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + } + + +@pytest.mark.parametrize( + "content_type, data, expected_body", + [ + ("application/json", {"key": "value"}, '{"key": "value"}'), + ("application/json-patch+json", {"key": "value"}, '{"key": "value"}'), + ("application/x-www-form-urlencoded", {"key": "value"}, "key=value"), + ("multipart/form-data", mock_byte_stream, mock_byte_stream), + ("application/octet-stream", mock_byte_stream, mock_byte_stream), + ], +) +def test_prepare_body_valid_content_type( + network_client, content_type, data, expected_body, mock_byte_stream +): + body = network_client._prepare_body(content_type, data) + assert body == expected_body + + +def test_prepare_body_invalid_content_type(network_client): + with pytest.raises(Exception): + network_client._prepare_body("invalid_content_type", {}) + + +def test_prepare_json_request(network_client): + options = FetchOptions( + url="https://example.com", + method="POST", + data={"key": "value"}, + headers={"header": "test"}, + params={"param": "value"}, + content_type="application/json", + ) + + api_request = network_client._prepare_request(options=options) + + assert api_request == APIRequest( + method="POST", + url="https://example.com", + headers={ + "header": "test", + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + "Content-Type": "application/json", + }, + params={"param": "value"}, + data='{"key": "value"}', + ) + + +def test_prepare_multipart_request(network_client, mock_byte_stream): + options = FetchOptions( + url="https://example.com", + method="POST", + data={"key": "value"}, + content_type="multipart/form-data", + multipart_data=[ + MultipartItem(part_name="attributes", data={"name": "file.pdf"}), + MultipartItem( + part_name="file", file_stream=mock_byte_stream, file_name="file.pdf" + ), + ], + ) + + api_request = network_client._prepare_request(options=options) + + assert api_request.method == "POST" + assert api_request.url == "https://example.com" + assert api_request.headers["User-Agent"] == USER_AGENT_HEADER + assert api_request.headers["X-Box-UA"] == X_BOX_UA_HEADER + assert api_request.headers["Content-Type"].startswith( + "multipart/form-data; boundary=" + ) + assert api_request.params == {} + assert api_request.data.fields == OrderedDict( + [ + ("attributes", '{"name": "file.pdf"}'), + ("file", ("file.pdf", mock_byte_stream, None)), + ] + ) + + +def test_make_request(network_client, mock_requests_session, response_200): + request_params = { + "method": "POST", + "url": "https://example.com", + "headers": { + "header": "test", + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + "Content-Type": "application/json", + }, + "params": {"param": "value"}, + "data": '{"key": "value"}', + "allow_redirects": True, + } + mock_requests_session.request.return_value = response_200 + api_request = APIRequest(**request_params) + + api_response = network_client._make_request(api_request) + + assert api_response == APIResponse( + network_response=response_200, + reauthentication_needed=False, + raised_exception=None, + ) + assert mock_requests_session.request.call_count == 1 + mock_requests_session.request.assert_called_once_with( + **request_params, stream=True, timeout=(5, 60) + ) + + +def test_make_request_unauthorised(network_client, mock_requests_session, response_401): + mock_requests_session.request.return_value = response_401 + api_request = APIRequest( + "GET", "https://example.com", headers={}, params={}, data="" + ) + api_response = network_client._make_request(api_request) + + assert api_response == APIResponse( + network_response=response_401, + reauthentication_needed=False, + raised_exception=None, + ) + assert mock_requests_session.request.call_count == 1 + + +@pytest.mark.parametrize( + "exc_message, expected_reauthentication_needed", + [ + ("Connection cancelled", False), + ( + "Connection broken: ConnectionResetError(54, 'Connection reset by peer')", + False, + ), + ( + "SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2396)')))", + True, + ), + ], +) +def test_make_request_network_exception( + network_client, mock_requests_session, exc_message, expected_reauthentication_needed +): + requests_exception = RequestException(exc_message) + mock_requests_session.request.side_effect = [requests_exception] + api_request = APIRequest( + "GET", "https://example.com", headers={}, params={}, data="" + ) + api_response = network_client._make_request(api_request) + + assert api_response == APIResponse( + network_response=None, + reauthentication_needed=expected_reauthentication_needed, + raised_exception=requests_exception, + ) + + +def test_fetch_successfully_retry_network_exception( + network_client, mock_requests_session, network_session_mock, response_200 +): + requests_exception = RequestException("Connection cancelled") + mock_requests_session.request.side_effect = [requests_exception, response_200] + + with patch("time.sleep"): + response = network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + ) + ) + assert response.status == 200 + + +def test_fetch_retries_network_exception_max_attempts( + network_client, mock_requests_session, network_session_mock +): + requests_exception = RequestException("Connection cancelled") + mock_requests_session.request.side_effect = [ + requests_exception, + requests_exception, + requests_exception, + ] + network_session_mock.retry_strategy = BoxRetryStrategy(max_retries_on_exception=2) + + with patch("time.sleep"): + with pytest.raises(BoxSDKError, match="Connection cancelled"): + network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + ) + ) + + assert mock_requests_session.request.call_count == 3 + + +def test_fetch_get_json_format_response_success( + network_client, mock_requests_session, network_session_mock, response_200 +): + response_200.text = '{"id": "123456"}' + mock_requests_session.request.return_value = response_200 + + fetch_response = network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + response_format=ResponseFormat.JSON, + ) + ) + + assert fetch_response.status == 200 + assert fetch_response.data == {"id": "123456"} + assert fetch_response.headers == {} + + +def test_fetch_get_binary_format_response_success( + network_client, mock_requests_session, network_session_mock, response_200 +): + content = b"binary data" + response_200.iter_content.return_value = BytesIO(content) + mock_requests_session.request.return_value = response_200 + + fetch_response = network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + response_format=ResponseFormat.BINARY, + ) + ) + + assert fetch_response.status == 200 + assert fetch_response.content.read() == content + assert fetch_response.headers == {} + + +@pytest.mark.parametrize("retryable_status_code", [429, 500, 503]) +def test_retryable_status_codes( + network_client, + mock_requests_session, + network_session_mock, + response_200, + retryable_status_code, + response_failure_no_status, +): + response_failure_no_status.status_code = retryable_status_code + response_200.text = '{"id": "123456"}' + mock_requests_session.request.side_effect = [ + response_failure_no_status, + response_failure_no_status, + response_200, + ] + + fetch_response = network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + ) + ) + assert fetch_response.status == 200 + assert fetch_response.data == {"id": "123456"} + assert mock_requests_session.request.call_count == 3 + + +@pytest.mark.parametrize("retryable_status_code", [429, 500, 503]) +def test_retryable_status_codes_with_invalid__response_body( + network_client, + mock_requests_session, + network_session_mock, + response_200, + retryable_status_code, + response_failure_no_status, +): + response_failure_no_status.status_code = retryable_status_code + response_failure_no_status.text = 'Invalid JSON' + response_200.text = '{"id": "123456"}' + mock_requests_session.request.side_effect = [ + response_failure_no_status, + response_failure_no_status, + response_200, + ] + + fetch_response = network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + response_format=ResponseFormat.JSON, + ) + ) + assert fetch_response.status == 200 + assert fetch_response.data == {"id": "123456"} + assert mock_requests_session.request.call_count == 3 + + +def test_status_code_202_with_no_retry_after_header( + network_client, mock_requests_session, network_session_mock, response_202 +): + mock_requests_session.request.return_value = response_202 + + fetch_response = network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + ) + ) + assert fetch_response.status == 202 + assert fetch_response.data == {} + + +def test_retryable_status_code_202( + network_client, + mock_requests_session, + network_session_mock, + response_202_with_retry_after, + response_200, +): + response_200.text = '{"id": "123456"}' + response_200.headers = {"Retry-After": "0"} + mock_requests_session.request.side_effect = [ + response_202_with_retry_after, + response_202_with_retry_after, + response_200, + ] + + with patch("time.sleep"): + fetch_response = network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + ) + ) + + assert fetch_response.status == 200 + assert fetch_response.data == {"id": "123456"} + assert mock_requests_session.request.call_count == 3 + + +def test_202_should_be_returned_if_retry_limit_is_reached( + network_client, + mock_requests_session, + network_session_mock, + response_202_with_retry_after, +): + network_session_mock.MAX_ATTEMPTS = 5 + mock_requests_session.request.return_value = response_202_with_retry_after + + with patch("time.sleep"): + fetch_response = network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + ) + ) + + assert fetch_response.status == 202 + assert fetch_response.data == {} + + +@pytest.mark.parametrize("not_retryable_status_code", [404, 403, 400]) +def test_not_retryable_status_codes( + network_client, + mock_requests_session, + network_session_mock, + not_retryable_status_code, + response_failure_no_status, + response_200, +): + response_failure_no_status.status_code = not_retryable_status_code + mock_requests_session.request.side_effect = [ + response_failure_no_status, + response_failure_no_status, + response_200, + ] + + with pytest.raises(BoxSDKError, match=f"Status code: {not_retryable_status_code}"): + network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + ) + ) + + assert mock_requests_session.request.call_count == 1 + + +def test_retrying_401_response_with_new_token_and_auth_provided( + network_client, + mock_requests_session, + network_session_mock, + response_401, + response_200, + authentication_mock, + token_mock, + token2_mock, +): + response_200.text = '{"id": "123456"}' + mock_requests_session.request.side_effect = [response_401, response_200] + + with patch("time.sleep"): + fetch_response = network_client.fetch( + FetchOptions( + url="https://example.com", + method="GET", + network_session=network_session_mock, + auth=authentication_mock, + ) + ) + + assert mock_requests_session.request.call_count == 2 + mock_requests_session.request.assert_has_calls( + [ + mock.call( + method="GET", + url="https://example.com", + headers={ + "Authorization": f"Bearer {token_mock}", + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + "Content-Type": "application/json", + }, + params={}, + data=None, + stream=True, + allow_redirects=True, + timeout=(5, 60), + ), + mock.call( + method="GET", + url="https://example.com", + headers={ + "Authorization": f"Bearer {token2_mock}", + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + "Content-Type": "application/json", + }, + params={}, + data=None, + stream=True, + allow_redirects=True, + timeout=(5, 60), + ), + ], + ) + assert fetch_response.status == 200 + assert fetch_response.data == {"id": "123456"} + + +def test_not_retrying_401_when_auth_not_provided( + network_client, + mock_requests_session, + network_session_mock, + response_401, + response_200, + authentication_mock, +): + mock_requests_session.request.side_effect = [response_401, response_200] + + with pytest.raises(BoxSDKError, match="Status code: 401"): + network_client.fetch( + FetchOptions( + url="https://example.com", + method="GET", + network_session=network_session_mock, + ) + ) + + assert mock_requests_session.request.call_count == 1 + mock_requests_session.request.assert_called_once_with( + method="GET", + url="https://example.com", + headers={ + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + "Content-Type": "application/json", + }, + params={}, + data=None, + stream=True, + allow_redirects=True, + timeout=(5, 60), + ) + + +def test_reaching_retry_limit( + network_client, + mock_requests_session, + network_session_mock, + response_202_with_retry_after, +): + network_session_mock.MAX_ATTEMPTS = 5 + mock_requests_session.request.return_value = response_202_with_retry_after + + with pytest.raises(BoxSDKError, match="Status code: 202"): + network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + ) + ) + assert mock_requests_session.request.call_count == 5 + + +def test_reaching_retry_limit( + network_client, mock_requests_session, network_session_mock, response_500 +): + network_session_mock.MAX_ATTEMPTS = 5 + mock_requests_session.request.return_value = response_500 + + with pytest.raises(BoxSDKError, match="Status code: 500"): + network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + ) + ) + assert mock_requests_session.request.call_count == 5 + + +def test_get_retry_after_time_use_retry_after_header_value(network_session_mock): + fetch_options = FetchOptions( + url="example.com", method="GET", network_session=network_session_mock + ) + fetch_response = FetchResponse(status=200, headers={'Retry-After': '213'}) + for attempt_number in range(1, 5): + sleep_time = network_session_mock.retry_strategy.retry_after( + fetch_options, fetch_response, attempt_number + ) + assert sleep_time == 213 + + +def test_get_retry_after_time_use_exponential_backoff(network_session_mock): + fetch_options = FetchOptions( + url="example.com", method="GET", network_session=network_session_mock + ) + fetch_response = FetchResponse(status=200, headers={}) + for attempt_number in range(1, 5): + sleep_time = network_session_mock.retry_strategy.retry_after( + fetch_options, fetch_response, attempt_number + ) + assert sleep_time > 0 + + +def test_pass_retry_after_header_to_get_retry_after_time_method( + network_client, + mock_requests_session, + network_session_mock, + response_429, + response_200, +): + response_429.headers = {"Retry-After": "123"} + mock_requests_session.request.side_effect = [response_429, response_200] + + with patch("time.sleep") as sleep_mock: + network_client.fetch( + FetchOptions( + method="get", + url="https://example.com", + network_session=network_session_mock, + ) + ) + assert mock_requests_session.request.call_count == 2 + sleep_mock.assert_called_once_with(123) + + +def test_raising_api_error_with_valid_json_body(network_client, data_sanitizer): + client_error_response = Mock(Response) + client_error_response.status_code = 400 + client_error_response.ok = False + client_error_response.headers = {} + client_error_response.text = """{ + "type": "error", + "code": "item_name_invalid", + "context_info": { + "message": "Something went wrong." + }, + "help_url": "https://developer.box.com/guides/api-calls/permissions-and-errors/common-errors/", + "message": "Method Not Allowed", + "request_id": "abcdef123456", + "status": 400 + }""" + client_error_response.json.return_value = json.loads(client_error_response.text) + + request = APIRequest( + method="POST", + url="https://example.com", + headers={ + "header": "test", + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + "Content-Type": "application/json", + }, + params={"param": "value"}, + data='{"key": "value"}', + ) + + response = APIResponse( + network_response=client_error_response, + reauthentication_needed=False, + raised_exception=None, + ) + try: + network_client._raise_on_unsuccessful_request(request, response, data_sanitizer) + except BoxAPIError as e: + assert e.request_info.method == request.method + assert e.request_info.url == request.url + assert e.request_info.query_params == request.params + assert e.request_info.headers == request.headers + assert e.request_info.body == request.data + + assert e.response_info.status_code == client_error_response.status_code + assert e.response_info.headers == client_error_response.headers + assert e.response_info.body == { + "type": "error", + "code": "item_name_invalid", + "context_info": {"message": "Something went wrong."}, + "help_url": ( + "https://developer.box.com/guides/api-calls/permissions-and-errors/common-errors/" + ), + "message": "Method Not Allowed", + "request_id": "abcdef123456", + "status": 400, + } + assert e.response_info.raw_body == client_error_response.text + assert e.response_info.code == "item_name_invalid" + assert e.response_info.context_info == {"message": "Something went wrong."} + assert e.response_info.request_id == "abcdef123456" + assert ( + e.response_info.help_url + == "https://developer.box.com/guides/api-calls/permissions-and-errors/common-errors/" + ) + + assert e.message == "400 Method Not Allowed; Request ID: abcdef123456" + assert e.error is None + assert e.name == "BoxAPIError" + + +def test_sensitive_data_are_sanitized_from_box_api_error( + network_client, data_sanitizer +): + client_error_response = Mock(Response) + client_error_response.status_code = 400 + client_error_response.ok = False + client_error_response.headers = {'token': 'my_token'} + client_error_response.text = """{ + "client_secret": "secret", + "password": "change-me", + "message": "Method Not Allowed", + "request_id": "abcdef123456", + "status": 400 + }""" + client_error_response.json.return_value = json.loads(client_error_response.text) + + request = APIRequest( + method="POST", + url="https://example.com", + headers={ + "header": "test", + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + "Content-Type": "application/json", + "Authorization": "Bearer acbdef123456", + }, + params={"param": "value"}, + data='{"key": "value"}', + ) + + response = APIResponse( + network_response=client_error_response, + reauthentication_needed=False, + raised_exception=None, + ) + try: + network_client._raise_on_unsuccessful_request(request, response, data_sanitizer) + except BoxAPIError as e: + exception_message = str(e) + assert "Authorization': '---[redacted]---'" in exception_message + assert "client_secret': '---[redacted]---'" in exception_message + assert "password': '---[redacted]---'" in exception_message + assert "token': '---[redacted]---'" in exception_message + assert "message': 'Method Not Allowed'" in exception_message + + +@pytest.mark.parametrize('response_body', ['', 'Invalid json', 123]) +def test_raising_api_error_without_valid_json_body( + network_client, response_body, data_sanitizer +): + client_error_response = Mock(Response) + client_error_response.status_code = 400 + client_error_response.ok = False + client_error_response.headers = {} + client_error_response.text = response_body + + request = APIRequest( + method="POST", + url="https://example.com", + headers={ + "header": "test", + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + "Content-Type": "application/json", + }, + params={"param": "value"}, + data='{"key": "value"}', + ) + + response = APIResponse( + network_response=client_error_response, + reauthentication_needed=False, + raised_exception=None, + ) + try: + network_client._raise_on_unsuccessful_request(request, response, data_sanitizer) + except BoxAPIError as e: + assert e.request_info.method == request.method + assert e.request_info.url == request.url + assert e.request_info.query_params == request.params + assert e.request_info.headers == request.headers + assert e.request_info.body == request.data + + assert e.response_info.status_code == client_error_response.status_code + assert e.response_info.headers == client_error_response.headers + assert e.response_info.body == {} + assert e.response_info.raw_body == client_error_response.text + assert e.response_info.code is None + assert e.response_info.context_info == {} + assert e.response_info.request_id is None + assert e.response_info.help_url is None + + assert e.error is None + assert e.name == "BoxAPIError" + + +def test_raising_exception_raised_by_network_layer(network_client, data_sanitizer): + requests_exception = RequestException("Something went wrong") + request = APIRequest( + method="POST", + url="https://example.com", + headers={ + "header": "test", + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + "Content-Type": "application/json", + }, + params={"param": "value"}, + data='{"key": "value"}', + ) + + response = APIResponse( + network_response=None, + reauthentication_needed=False, + raised_exception=requests_exception, + ) + + try: + network_client._raise_on_unsuccessful_request(request, response, data_sanitizer) + except BoxSDKError as e: + assert e.message == "Something went wrong" + assert e.error == requests_exception + assert e.name == "BoxSDKError" + + +def test_proxy_config(): + client = BoxClient(auth=None).with_proxy( + ProxyConfig(url="http://127.0.0.1:3128/", username="user", password="pass") + ) + assert client.network_session.proxy_url == "http://user:pass@127.0.0.1:3128/" + requests_session = client.network_session.network_client.requests_session + assert requests_session.proxies["http"] == "http://user:pass@127.0.0.1:3128/" + assert requests_session.proxies["https"] == "http://user:pass@127.0.0.1:3128/" + + +@pytest.mark.parametrize( + "response_body, expected_json", + [ + ('', {}), + ('Invalid json', {}), + (123, {}), + ('{"name": "John"}', {'name': 'John'}), + ], +) +def test_read_json_body(response_body, expected_json): + assert BoxNetworkClient._read_json_body(response_body) == expected_json + + +def test_get_options_stream_position(network_client, mock_byte_stream): + mock_byte_stream.seek(1) + options = FetchOptions( + url="https://example.com", + method="POST", + data={"key": "value"}, + file_stream=mock_byte_stream, + ) + + assert network_client._get_options_stream_position(options) == 1 + + +def test_get_multipart_stream_position(network_client, mock_byte_stream): + mock_byte_stream.seek(1) + options = FetchOptions( + url="https://example.com", + method="POST", + data={"key": "value"}, + content_type="multipart/form-data", + multipart_data=[ + MultipartItem(part_name="attributes", data={"name": "file.pdf"}), + MultipartItem( + part_name="file", file_stream=mock_byte_stream, file_name="file.pdf" + ), + ], + ) + + assert network_client._get_multipart_stream_positions(options) == {"file": 1} + + +def test_get_multipart_stream_position_empty(network_client): + options = FetchOptions( + url="https://example.com", + method="POST", + data={"key": "value"}, + content_type="multipart/form-data", + multipart_data=[ + MultipartItem(part_name="attributes", data={"name": "file.pdf"}), + ], + ) + + assert network_client._get_multipart_stream_positions(options) == {} + + +def test_reset_options_stream(network_client, mock_byte_stream): + options = FetchOptions( + url="https://example.com", + method="POST", + data={"key": "value"}, + content_type="multipart/form-data", + file_stream=mock_byte_stream, + ) + + mock_byte_stream.seek(1) + original_position = network_client._get_options_stream_position(options) + mock_byte_stream.seek(2) + + network_client._reset_options_stream(options, original_position, None) + + assert options.file_stream.tell() == 1 + + +def test_reset_options_stream_non_seekable_stream( + network_client, mock_non_seekable_stream +): + options = FetchOptions( + url="https://example.com", + method="POST", + data={"key": "value"}, + content_type="multipart/form-data", + file_stream=mock_non_seekable_stream, + ) + + original_position = network_client._get_options_stream_position(options) + + with pytest.raises(BoxSDKError): + network_client._reset_options_stream(options, original_position, None) + + +def test_reset_multipart_stream(network_client, mock_byte_stream): + options = FetchOptions( + url="https://example.com", + method="POST", + data={"key": "value"}, + content_type="multipart/form-data", + multipart_data=[ + MultipartItem(part_name="attributes", data={"name": "file.pdf"}), + MultipartItem( + part_name="file", file_stream=mock_byte_stream, file_name="file.pdf" + ), + ], + ) + + mock_byte_stream.seek(1) + original_positions = network_client._get_multipart_stream_positions(options) + mock_byte_stream.seek(2) + + network_client._reset_multipart_streams(options, original_positions, None) + + assert options.multipart_data[1].file_stream.tell() == 1 + + +def test_reset_multipart_non_seekable_stream(network_client, mock_non_seekable_stream): + options = FetchOptions( + url="https://example.com", + method="POST", + data={"key": "value"}, + content_type="multipart/form-data", + multipart_data=[ + MultipartItem(part_name="attributes", data={"name": "file.pdf"}), + MultipartItem( + part_name="file", + file_stream=mock_non_seekable_stream, + file_name="file.pdf", + ), + ], + ) + + original_positions = network_client._get_multipart_stream_positions(options) + + with pytest.raises(BoxSDKError): + network_client._reset_multipart_streams(options, original_positions, None) + + +def test_disable_follow_redirects( + network_client, + mock_requests_session, + network_session_mock, + response_302, + response_200, +): + mock_requests_session.request.side_effect = [response_302, response_200] + + network_client.fetch( + FetchOptions( + url="https://example.com", + method="GET", + network_session=network_session_mock, + follow_redirects=False, + ) + ) + + assert mock_requests_session.request.call_count == 1 + mock_requests_session.request.assert_called_once_with( + method="GET", + url="https://example.com", + headers={ + "User-Agent": USER_AGENT_HEADER, + "X-Box-UA": X_BOX_UA_HEADER, + "Content-Type": "application/json", + }, + params={}, + data=None, + stream=True, + allow_redirects=False, + timeout=(5, 60), + ) diff --git a/test/chunked_uploads.py b/test/chunked_uploads.py new file mode 100644 index 000000000..0719fceef --- /dev/null +++ b/test/chunked_uploads.py @@ -0,0 +1,267 @@ +from typing import List + +from box_sdk_gen.internal.utils import to_string + +from typing import Optional + +from box_sdk_gen.internal.utils import Buffer + +from box_sdk_gen.internal.utils import HashName + +from box_sdk_gen.internal.utils import Iterator + +from box_sdk_gen.internal.utils import generate_byte_stream_from_buffer + +from box_sdk_gen.internal.utils import hex_to_base_64 + +from box_sdk_gen.internal.utils import iterate_chunks + +from box_sdk_gen.internal.utils import read_byte_stream + +from box_sdk_gen.internal.utils import reduce_iterator + +from box_sdk_gen.internal.utils import Hash + +from box_sdk_gen.internal.utils import buffer_length + +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 ByteStream + +from test.commons import get_default_client + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.upload_session import UploadSession + +from box_sdk_gen.schemas.upload_part import UploadPart + +from box_sdk_gen.schemas.upload_parts import UploadParts + +from box_sdk_gen.schemas.uploaded_part import UploadedPart + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.client import BoxClient + +client: BoxClient = get_default_client() + + +class _TestPartAccumulator: + def __init__( + self, + last_index: int, + parts: List[UploadPart], + file_size: int, + file_hash: Hash, + *, + upload_part_url: str = '', + upload_session_id: str = '' + ): + self.last_index = last_index + self.parts = parts + self.file_size = file_size + self.file_hash = file_hash + self.upload_part_url = upload_part_url + self.upload_session_id = upload_session_id + + +def _reducer_by_id( + acc: _TestPartAccumulator, chunk: ByteStream +) -> _TestPartAccumulator: + last_index: int = acc.last_index + parts: List[UploadPart] = acc.parts + chunk_buffer: Buffer = read_byte_stream(chunk) + hash: Hash = Hash(algorithm=HashName.SHA1) + hash.update_hash(chunk_buffer) + sha_1: str = hash.digest_hash('base64') + digest: str = ''.join(['sha=', sha_1]) + chunk_size: int = buffer_length(chunk_buffer) + bytes_start: int = last_index + 1 + bytes_end: int = last_index + chunk_size + content_range: str = ''.join( + [ + 'bytes ', + to_string(bytes_start), + '-', + to_string(bytes_end), + '/', + to_string(acc.file_size), + ] + ) + uploaded_part: UploadedPart = client.chunked_uploads.upload_file_part( + acc.upload_session_id, + generate_byte_stream_from_buffer(chunk_buffer), + digest, + content_range, + ) + part: UploadPart = uploaded_part.part + part_sha_1: str = hex_to_base_64(part.sha_1) + assert part_sha_1 == sha_1 + assert part.size == chunk_size + assert part.offset == bytes_start + acc.file_hash.update_hash(chunk_buffer) + return _TestPartAccumulator( + last_index=bytes_end, + parts=parts + [part], + file_size=acc.file_size, + upload_session_id=acc.upload_session_id, + file_hash=acc.file_hash, + ) + + +def testChunkedManualProcessById(): + file_size: int = (20 * 1024) * 1024 + file_byte_stream: ByteStream = generate_byte_stream(file_size) + file_name: str = get_uuid() + parent_folder_id: str = '0' + upload_session: UploadSession = client.chunked_uploads.create_file_upload_session( + parent_folder_id, file_size, file_name + ) + upload_session_id: str = upload_session.id + part_size: int = upload_session.part_size + total_parts: int = upload_session.total_parts + assert part_size * total_parts >= file_size + assert upload_session.num_parts_processed == 0 + file_hash: Hash = Hash(algorithm=HashName.SHA1) + chunks_iterator: Iterator = iterate_chunks(file_byte_stream, part_size, file_size) + results: _TestPartAccumulator = reduce_iterator( + chunks_iterator, + _reducer_by_id, + _TestPartAccumulator( + last_index=-1, + parts=[], + file_size=file_size, + upload_session_id=upload_session_id, + file_hash=file_hash, + ), + ) + parts: List[UploadPart] = results.parts + processed_session_parts: UploadParts = ( + client.chunked_uploads.get_file_upload_session_parts(upload_session_id) + ) + assert processed_session_parts.total_count == total_parts + processed_session: UploadSession = ( + client.chunked_uploads.get_file_upload_session_by_id(upload_session_id) + ) + assert processed_session.id == upload_session_id + sha_1: str = file_hash.digest_hash('base64') + digest: str = ''.join(['sha=', sha_1]) + committed_session: Optional[Files] = ( + client.chunked_uploads.create_file_upload_session_commit( + upload_session_id, parts, digest + ) + ) + assert committed_session.entries[0].name == file_name + client.chunked_uploads.delete_file_upload_session_by_id(upload_session_id) + + +def _reducer_by_url( + acc: _TestPartAccumulator, chunk: ByteStream +) -> _TestPartAccumulator: + last_index: int = acc.last_index + parts: List[UploadPart] = acc.parts + chunk_buffer: Buffer = read_byte_stream(chunk) + hash: Hash = Hash(algorithm=HashName.SHA1) + hash.update_hash(chunk_buffer) + sha_1: str = hash.digest_hash('base64') + digest: str = ''.join(['sha=', sha_1]) + chunk_size: int = buffer_length(chunk_buffer) + bytes_start: int = last_index + 1 + bytes_end: int = last_index + chunk_size + content_range: str = ''.join( + [ + 'bytes ', + to_string(bytes_start), + '-', + to_string(bytes_end), + '/', + to_string(acc.file_size), + ] + ) + uploaded_part: UploadedPart = client.chunked_uploads.upload_file_part_by_url( + acc.upload_part_url, + generate_byte_stream_from_buffer(chunk_buffer), + digest, + content_range, + ) + part: UploadPart = uploaded_part.part + part_sha_1: str = hex_to_base_64(part.sha_1) + assert part_sha_1 == sha_1 + assert part.size == chunk_size + assert part.offset == bytes_start + acc.file_hash.update_hash(chunk_buffer) + return _TestPartAccumulator( + last_index=bytes_end, + parts=parts + [part], + file_size=acc.file_size, + upload_part_url=acc.upload_part_url, + file_hash=acc.file_hash, + ) + + +def testChunkedManualProcessByUrl(): + file_size: int = (20 * 1024) * 1024 + file_byte_stream: ByteStream = generate_byte_stream(file_size) + file_name: str = get_uuid() + parent_folder_id: str = '0' + upload_session: UploadSession = client.chunked_uploads.create_file_upload_session( + parent_folder_id, file_size, file_name + ) + upload_part_url: str = upload_session.session_endpoints.upload_part + commit_url: str = upload_session.session_endpoints.commit + list_parts_url: str = upload_session.session_endpoints.list_parts + status_url: str = upload_session.session_endpoints.status + abort_url: str = upload_session.session_endpoints.abort + upload_session_id: str = upload_session.id + part_size: int = upload_session.part_size + total_parts: int = upload_session.total_parts + assert part_size * total_parts >= file_size + assert upload_session.num_parts_processed == 0 + file_hash: Hash = Hash(algorithm=HashName.SHA1) + chunks_iterator: Iterator = iterate_chunks(file_byte_stream, part_size, file_size) + results: _TestPartAccumulator = reduce_iterator( + chunks_iterator, + _reducer_by_url, + _TestPartAccumulator( + last_index=-1, + parts=[], + file_size=file_size, + upload_part_url=upload_part_url, + file_hash=file_hash, + ), + ) + parts: List[UploadPart] = results.parts + processed_session_parts: UploadParts = ( + client.chunked_uploads.get_file_upload_session_parts_by_url(list_parts_url) + ) + assert processed_session_parts.total_count == total_parts + processed_session: UploadSession = ( + client.chunked_uploads.get_file_upload_session_by_url(status_url) + ) + assert processed_session.id == upload_session_id + sha_1: str = file_hash.digest_hash('base64') + digest: str = ''.join(['sha=', sha_1]) + committed_session: Optional[Files] = ( + client.chunked_uploads.create_file_upload_session_commit_by_url( + commit_url, parts, digest + ) + ) + assert committed_session.entries[0].name == file_name + client.chunked_uploads.delete_file_upload_session_by_url(abort_url) + + +def testChunkedUploadConvenienceMethod(): + file_size: int = (20 * 1024) * 1024 + file_byte_stream: ByteStream = generate_byte_stream(file_size) + file_name: str = get_uuid() + parent_folder_id: str = '0' + uploaded_file: File = client.chunked_uploads.upload_big_file( + file_byte_stream, file_name, file_size, parent_folder_id + ) + assert uploaded_file.name == file_name + assert uploaded_file.size == file_size + assert uploaded_file.parent.id == parent_folder_id + client.files.delete_file_by_id(uploaded_file.id) diff --git a/test/classifications.py b/test/classifications.py new file mode 100644 index 000000000..36b76770c --- /dev/null +++ b/test/classifications.py @@ -0,0 +1,79 @@ +from typing import List + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.classification_template import ( + ClassificationTemplateFieldsOptionsField, +) + +from box_sdk_gen.managers.classifications import UpdateClassificationRequestBody + +from box_sdk_gen.managers.classifications import ( + UpdateClassificationRequestBodyDataField, +) + +from box_sdk_gen.managers.classifications import ( + UpdateClassificationRequestBodyDataStaticConfigField, +) + +from box_sdk_gen.managers.classifications import ( + UpdateClassificationRequestBodyDataStaticConfigClassificationField, +) + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +from test.commons import get_or_create_classification_template + +from test.commons import get_or_create_classification + +from box_sdk_gen.schemas.classification_template import ClassificationTemplate + +client: BoxClient = get_default_client() + + +def testClassifications(): + classification_template: ClassificationTemplate = ( + get_or_create_classification_template() + ) + classification: ClassificationTemplateFieldsOptionsField = ( + get_or_create_classification(classification_template) + ) + assert not classification.key == '' + assert not classification.static_config.classification.color_id == 100 + assert ( + not classification.static_config.classification.classification_definition == '' + ) + updated_classification_name: str = get_uuid() + updated_classification_description: str = get_uuid() + classification_template_with_updated_classification: ClassificationTemplate = ( + client.classifications.update_classification( + [ + UpdateClassificationRequestBody( + enum_option_key=classification.key, + data=UpdateClassificationRequestBodyDataField( + key=updated_classification_name, + static_config=UpdateClassificationRequestBodyDataStaticConfigField( + classification=UpdateClassificationRequestBodyDataStaticConfigClassificationField( + color_id=2, + classification_definition=updated_classification_description, + ) + ), + ), + ) + ] + ) + ) + updated_classifications: List[ClassificationTemplateFieldsOptionsField] = ( + classification_template_with_updated_classification.fields[0].options + ) + updated_classification: ClassificationTemplateFieldsOptionsField = ( + updated_classifications[0] + ) + assert updated_classification.key == updated_classification_name + assert updated_classification.static_config.classification.color_id == 2 + assert ( + updated_classification.static_config.classification.classification_definition + == updated_classification_description + ) diff --git a/test/client.py b/test/client.py new file mode 100644 index 000000000..91eeb8ef9 --- /dev/null +++ b/test/client.py @@ -0,0 +1,220 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.internal.utils import Buffer + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.user_full import UserFull + +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 buffer_equals + +from box_sdk_gen.internal.utils import generate_byte_buffer + +from box_sdk_gen.internal.utils import generate_byte_stream_from_buffer + +from box_sdk_gen.internal.utils import read_byte_stream + +from test.commons import get_default_client + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_options import MultipartItem + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.networking.base_urls import BaseUrls + +from box_sdk_gen.serialization.json import json_to_serialized_data + +from box_sdk_gen.serialization.json import get_sd_value_by_key + +client: BoxClient = get_default_client() + + +def testMakeRequestJsonCRUD(): + new_folder_name: str = get_uuid() + request_body_post: str = ''.join( + ['{"name": "', new_folder_name, '", "parent": { "id": "0"}}'] + ) + create_folder_response: FetchResponse = client.make_request( + FetchOptions( + method='post', + url='https://api.box.com/2.0/folders', + data=json_to_serialized_data(request_body_post), + ) + ) + assert create_folder_response.status == 201 + created_folder: SerializedData = create_folder_response.data + assert get_sd_value_by_key(created_folder, 'name') == new_folder_name + updated_name: str = get_uuid() + request_body_put: str = ''.join(['{"name": "', updated_name, '"}']) + update_folder_response: FetchResponse = client.make_request( + FetchOptions( + method='put', + url=''.join( + [ + 'https://api.box.com/2.0/folders/', + get_sd_value_by_key(created_folder, 'id'), + ] + ), + data=json_to_serialized_data(request_body_put), + ) + ) + assert update_folder_response.status == 200 + updated_folder: SerializedData = update_folder_response.data + assert get_sd_value_by_key(updated_folder, 'name') == updated_name + assert get_sd_value_by_key(updated_folder, 'id') == get_sd_value_by_key( + created_folder, 'id' + ) + get_folder_response: FetchResponse = client.make_request( + FetchOptions( + url=''.join( + [ + 'https://api.box.com/2.0/folders/', + get_sd_value_by_key(created_folder, 'id'), + ] + ), + method='GET', + ) + ) + assert get_folder_response.status == 200 + received_folder: SerializedData = get_folder_response.data + assert get_sd_value_by_key(received_folder, 'name') == updated_name + assert get_sd_value_by_key(received_folder, 'id') == get_sd_value_by_key( + updated_folder, 'id' + ) + delete_folder_response: FetchResponse = client.make_request( + FetchOptions( + url=''.join( + [ + 'https://api.box.com/2.0/folders/', + get_sd_value_by_key(received_folder, 'id'), + ] + ), + method='DELETE', + ) + ) + assert delete_folder_response.status == 204 + + +def testMakeRequestMultipart(): + new_folder_name: str = get_uuid() + new_folder: FolderFull = client.folders.create_folder( + new_folder_name, CreateFolderParent(id='0') + ) + new_folder_id: str = new_folder.id + new_file_name: str = ''.join([get_uuid(), '.pdf']) + file_content_stream: ByteStream = generate_byte_stream(1024 * 1024) + multipart_attributes: str = ''.join( + ['{"name": "', new_file_name, '", "parent": { "id":', new_folder_id, '}}'] + ) + upload_file_response: FetchResponse = client.make_request( + FetchOptions( + method='POST', + url='https://upload.box.com/api/2.0/files/content', + content_type='multipart/form-data', + multipart_data=[ + MultipartItem( + part_name='attributes', + data=json_to_serialized_data(multipart_attributes), + ), + MultipartItem(part_name='file', file_stream=file_content_stream), + ], + ) + ) + assert upload_file_response.status == 201 + client.folders.delete_folder_by_id(new_folder_id, recursive=True) + + +def testMakeRequestBinaryFormat(): + new_file_name: str = get_uuid() + file_buffer: Buffer = generate_byte_buffer(1024 * 1024) + file_content_stream: ByteStream = generate_byte_stream_from_buffer(file_buffer) + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=new_file_name, parent=UploadFileAttributesParentField(id='0') + ), + file_content_stream, + ) + uploaded_file: FileFull = uploaded_files.entries[0] + download_file_response: FetchResponse = client.make_request( + FetchOptions( + method='GET', + url=''.join( + ['https://api.box.com/2.0/files/', uploaded_file.id, '/content'] + ), + response_format=ResponseFormat.BINARY, + ) + ) + assert download_file_response.status == 200 + assert buffer_equals(read_byte_stream(download_file_response.content), file_buffer) + client.files.delete_file_by_id(uploaded_file.id) + + +def testWithAsUserHeader(): + user_name: str = get_uuid() + created_user: UserFull = client.users.create_user( + user_name, is_platform_access_only=True + ) + as_user_client: BoxClient = client.with_as_user_header(created_user.id) + admin_user: UserFull = client.users.get_user_me() + assert not to_string(admin_user.name) == user_name + app_user: UserFull = as_user_client.users.get_user_me() + assert to_string(app_user.name) == user_name + client.users.delete_user_by_id(created_user.id) + + +def testWithSuppressedNotifications(): + new_client: BoxClient = client.with_suppressed_notifications() + user: UserFull = new_client.users.get_user_me() + assert not user.id == '' + + +def testWithExtraHeaders(): + user_name: str = get_uuid() + created_user: UserFull = client.users.create_user( + user_name, is_platform_access_only=True + ) + as_user_client: BoxClient = client.with_extra_headers( + extra_headers={'As-User': created_user.id} + ) + admin_user: UserFull = client.users.get_user_me() + assert not to_string(admin_user.name) == user_name + app_user: UserFull = as_user_client.users.get_user_me() + assert to_string(app_user.name) == user_name + client.users.delete_user_by_id(created_user.id) + + +def testWithCustomBaseUrls(): + new_base_urls: BaseUrls = BaseUrls( + base_url='https://box.com/', + upload_url='https://box.com/', + oauth_2_url='https://box.com/', + ) + custom_base_client: BoxClient = client.with_custom_base_urls(new_base_urls) + with pytest.raises(Exception): + custom_base_client.users.get_user_me() diff --git a/test/collaboration_allowlist_entries.py b/test/collaboration_allowlist_entries.py new file mode 100644 index 000000000..8d35b8724 --- /dev/null +++ b/test/collaboration_allowlist_entries.py @@ -0,0 +1,54 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.collaboration_allowlist_entries import ( + CollaborationAllowlistEntries, +) + +from box_sdk_gen.schemas.collaboration_allowlist_entry import ( + CollaborationAllowlistEntry, +) + +from box_sdk_gen.managers.collaboration_allowlist_entries import ( + CreateCollaborationWhitelistEntryDirection, +) + +from test.commons import get_default_client + +from box_sdk_gen.internal.utils import get_uuid + +client: BoxClient = get_default_client() + + +def testCollaborationAllowlistEntries(): + allowlist: CollaborationAllowlistEntries = ( + client.collaboration_allowlist_entries.get_collaboration_whitelist_entries() + ) + assert len(allowlist.entries) >= 0 + domain: str = ''.join([get_uuid(), 'example.com']) + new_entry: CollaborationAllowlistEntry = ( + client.collaboration_allowlist_entries.create_collaboration_whitelist_entry( + domain, CreateCollaborationWhitelistEntryDirection.INBOUND + ) + ) + assert to_string(new_entry.type) == 'collaboration_whitelist_entry' + assert to_string(new_entry.direction) == 'inbound' + assert new_entry.domain == domain + entry: CollaborationAllowlistEntry = ( + client.collaboration_allowlist_entries.get_collaboration_whitelist_entry_by_id( + new_entry.id + ) + ) + assert entry.id == new_entry.id + assert to_string(entry.direction) == to_string(new_entry.direction) + assert entry.domain == domain + client.collaboration_allowlist_entries.delete_collaboration_whitelist_entry_by_id( + entry.id + ) + with pytest.raises(Exception): + client.collaboration_allowlist_entries.get_collaboration_whitelist_entry_by_id( + entry.id + ) diff --git a/test/collaboration_allowlist_exempt_targets.py b/test/collaboration_allowlist_exempt_targets.py new file mode 100644 index 000000000..ef5e06266 --- /dev/null +++ b/test/collaboration_allowlist_exempt_targets.py @@ -0,0 +1,59 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.collaboration_allowlist_exempt_targets import ( + CollaborationAllowlistExemptTargets, +) + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.collaboration_allowlist_exempt_target import ( + CollaborationAllowlistExemptTarget, +) + +from box_sdk_gen.managers.collaboration_allowlist_exempt_targets import ( + CreateCollaborationWhitelistExemptTargetUser, +) + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testCollaborationAllowlistExemptTargets(): + exempt_targets: CollaborationAllowlistExemptTargets = ( + client.collaboration_allowlist_exempt_targets.get_collaboration_whitelist_exempt_targets() + ) + assert len(exempt_targets.entries) >= 0 + user: UserFull = client.users.create_user( + get_uuid(), + login=''.join([get_uuid(), '@boxdemo.com']), + is_platform_access_only=True, + ) + new_exempt_target: CollaborationAllowlistExemptTarget = ( + client.collaboration_allowlist_exempt_targets.create_collaboration_whitelist_exempt_target( + CreateCollaborationWhitelistExemptTargetUser(id=user.id) + ) + ) + assert to_string(new_exempt_target.type) == 'collaboration_whitelist_exempt_target' + assert new_exempt_target.user.id == user.id + exempt_target: CollaborationAllowlistExemptTarget = ( + client.collaboration_allowlist_exempt_targets.get_collaboration_whitelist_exempt_target_by_id( + new_exempt_target.id + ) + ) + assert exempt_target.id == new_exempt_target.id + assert exempt_target.user.id == user.id + client.collaboration_allowlist_exempt_targets.delete_collaboration_whitelist_exempt_target_by_id( + exempt_target.id + ) + with pytest.raises(Exception): + client.collaboration_allowlist_exempt_targets.get_collaboration_whitelist_exempt_target_by_id( + exempt_target.id + ) + client.users.delete_user_by_id(user.id) diff --git a/test/collections.py b/test/collections.py new file mode 100644 index 000000000..154840ef1 --- /dev/null +++ b/test/collections.py @@ -0,0 +1,45 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.collections import Collections + +from box_sdk_gen.schemas.collection import Collection + +from box_sdk_gen.schemas.items_offset_paginated import ItemsOffsetPaginated + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.managers.folders import UpdateFolderByIdCollections + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testCollections(): + collections: Collections = client.collections.get_collections() + favourite_collection: Collection = client.collections.get_collection_by_id( + collections.entries[0].id + ) + assert to_string(favourite_collection.type) == 'collection' + assert to_string(favourite_collection.collection_type) == 'favorites' + collection_items: ItemsOffsetPaginated = client.collections.get_collection_items( + favourite_collection.id + ) + folder: FolderFull = client.folders.create_folder( + get_uuid(), CreateFolderParent(id='0') + ) + client.folders.update_folder_by_id( + folder.id, collections=[UpdateFolderByIdCollections(id=favourite_collection.id)] + ) + collection_items_after_update: ItemsOffsetPaginated = ( + client.collections.get_collection_items(favourite_collection.id) + ) + assert collection_items_after_update.total_count > 0 + client.folders.update_folder_by_id(folder.id, collections=[]) + client.folders.delete_folder_by_id(folder.id) diff --git a/test/comments.py b/test/comments.py new file mode 100644 index 000000000..01f7f33c2 --- /dev/null +++ b/test/comments.py @@ -0,0 +1,67 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.comments import Comments + +from box_sdk_gen.schemas.comment_full import CommentFull + +from box_sdk_gen.managers.comments import CreateCommentItem + +from box_sdk_gen.managers.comments import CreateCommentItemTypeField + +from box_sdk_gen.internal.utils import generate_byte_stream + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testComments(): + file_size: int = 256 + file_name: str = get_uuid() + file_byte_stream: ByteStream = generate_byte_stream(file_size) + parent_id: str = '0' + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=file_name, parent=UploadFileAttributesParentField(id=parent_id) + ), + file_byte_stream, + ) + file_id: str = uploaded_files.entries[0].id + comments: Comments = client.comments.get_file_comments(file_id) + assert comments.total_count == 0 + message: str = 'Hello there!' + new_comment: CommentFull = client.comments.create_comment( + message, CreateCommentItem(id=file_id, type=CreateCommentItemTypeField.FILE) + ) + assert new_comment.message == message + assert new_comment.is_reply_comment == False + assert new_comment.item.id == file_id + new_reply_comment: CommentFull = client.comments.create_comment( + message, + CreateCommentItem(id=new_comment.id, type=CreateCommentItemTypeField.COMMENT), + ) + assert new_reply_comment.message == message + assert new_reply_comment.is_reply_comment == True + new_message: str = 'Hi!' + client.comments.update_comment_by_id(new_reply_comment.id, message=new_message) + new_comments: Comments = client.comments.get_file_comments(file_id) + assert new_comments.total_count == 2 + assert new_comments.entries[1].message == new_message + received_comment: CommentFull = client.comments.get_comment_by_id(new_comment.id) + assert received_comment.message == new_comment.message + client.comments.delete_comment_by_id(new_comment.id) + with pytest.raises(Exception): + client.comments.get_comment_by_id(new_comment.id) + client.files.delete_file_by_id(file_id) diff --git a/test/commons.py b/test/commons.py new file mode 100644 index 000000000..70c33c1f8 --- /dev/null +++ b/test/commons.py @@ -0,0 +1,201 @@ +from box_sdk_gen.internal.utils import to_string + +from typing import List + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.terms_of_services import TermsOfServices + +from box_sdk_gen.managers.terms_of_services import CreateTermsOfServiceStatus + +from box_sdk_gen.managers.terms_of_services import CreateTermsOfServiceTosType + +from box_sdk_gen.schemas.classification_template import ( + ClassificationTemplateFieldsOptionsField, +) + +from box_sdk_gen.managers.classifications import AddClassificationRequestBody + +from box_sdk_gen.managers.classifications import AddClassificationRequestBodyDataField + +from box_sdk_gen.managers.classifications import ( + AddClassificationRequestBodyDataStaticConfigField, +) + +from box_sdk_gen.managers.classifications import ( + AddClassificationRequestBodyDataStaticConfigClassificationField, +) + +from box_sdk_gen.managers.classifications import CreateClassificationTemplateFields + +from box_sdk_gen.schemas.shield_information_barrier import ShieldInformationBarrier + +from box_sdk_gen.schemas.shield_information_barriers import ShieldInformationBarriers + +from box_sdk_gen.schemas.enterprise_base import EnterpriseBase + +from box_sdk_gen.internal.utils import decode_base_64 + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import generate_byte_stream + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.classification_template import ClassificationTemplate + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.terms_of_service import TermsOfService + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.box.ccg_auth import BoxCCGAuth + +from box_sdk_gen.box.ccg_auth import CCGConfig + +from box_sdk_gen.internal.utils import is_browser + + +def get_ccg_auth() -> BoxCCGAuth: + ccg_config: CCGConfig = CCGConfig( + client_id=get_env_var('CLIENT_ID'), + client_secret=get_env_var('CLIENT_SECRET'), + enterprise_id=get_env_var('ENTERPRISE_ID'), + ) + auth: BoxCCGAuth = BoxCCGAuth(config=ccg_config) + return auth + + +from box_sdk_gen.box.jwt_auth import BoxJWTAuth + +from box_sdk_gen.box.jwt_auth import JWTConfig + + +def get_jwt_auth() -> BoxJWTAuth: + jwt_config: JWTConfig = JWTConfig.from_config_json_string( + decode_base_64(get_env_var('JWT_CONFIG_BASE_64')) + ) + auth: BoxJWTAuth = BoxJWTAuth(config=jwt_config) + return auth + + +def get_default_client_with_user_subject(user_id: str) -> BoxClient: + if is_browser(): + ccg_auth: BoxCCGAuth = get_ccg_auth() + ccg_auth_user: BoxCCGAuth = ccg_auth.with_user_subject(user_id) + return BoxClient(auth=ccg_auth_user) + auth: BoxJWTAuth = get_jwt_auth() + auth_user: BoxJWTAuth = auth.with_user_subject(user_id) + return BoxClient(auth=auth_user) + + +def get_default_client() -> BoxClient: + client: BoxClient = BoxClient( + auth=get_ccg_auth() if is_browser() else get_jwt_auth() + ) + return client + + +def create_new_folder() -> FolderFull: + client: BoxClient = get_default_client() + new_folder_name: str = get_uuid() + return client.folders.create_folder(new_folder_name, CreateFolderParent(id='0')) + + +def upload_new_file() -> FileFull: + client: BoxClient = get_default_client() + new_file_name: str = ''.join([get_uuid(), '.pdf']) + file_content_stream: ByteStream = generate_byte_stream(1024 * 1024) + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=new_file_name, parent=UploadFileAttributesParentField(id='0') + ), + file_content_stream, + ) + return uploaded_files.entries[0] + + +def get_or_create_terms_of_services() -> TermsOfService: + client: BoxClient = get_default_client() + tos: TermsOfServices = client.terms_of_services.get_terms_of_service() + number_of_tos: int = len(tos.entries) + if number_of_tos >= 1: + first_tos: TermsOfService = tos.entries[0] + if to_string(first_tos.tos_type) == 'managed': + return first_tos + if number_of_tos >= 2: + second_tos: TermsOfService = tos.entries[1] + if to_string(second_tos.tos_type) == 'managed': + return second_tos + return client.terms_of_services.create_terms_of_service( + CreateTermsOfServiceStatus.DISABLED, + 'Test TOS', + tos_type=CreateTermsOfServiceTosType.MANAGED, + ) + + +def get_or_create_classification( + classification_template: ClassificationTemplate, +) -> ClassificationTemplateFieldsOptionsField: + client: BoxClient = get_default_client() + classifications: List[ClassificationTemplateFieldsOptionsField] = ( + classification_template.fields[0].options + ) + current_number_of_classifications: int = len(classifications) + if current_number_of_classifications == 0: + classification_template_with_new_classification: ClassificationTemplate = ( + client.classifications.add_classification( + [ + AddClassificationRequestBody( + data=AddClassificationRequestBodyDataField( + key=get_uuid(), + static_config=AddClassificationRequestBodyDataStaticConfigField( + classification=AddClassificationRequestBodyDataStaticConfigClassificationField( + color_id=3, + classification_definition='Some description', + ) + ), + ) + ) + ] + ) + ) + return classification_template_with_new_classification.fields[0].options[0] + return classifications[0] + + +def get_or_create_classification_template() -> ClassificationTemplate: + client: BoxClient = get_default_client() + try: + return client.classifications.get_classification_template() + except Exception: + return client.classifications.create_classification_template( + [CreateClassificationTemplateFields(options=[])] + ) + + +def get_or_create_shield_information_barrier( + client: BoxClient, enterprise_id: str +) -> ShieldInformationBarrier: + barriers: ShieldInformationBarriers = ( + client.shield_information_barriers.get_shield_information_barriers() + ) + number_of_barriers: int = len(barriers.entries) + if number_of_barriers == 0: + return client.shield_information_barriers.create_shield_information_barrier( + EnterpriseBase(id=enterprise_id) + ) + return barriers.entries[number_of_barriers - 1] diff --git a/test/conftest.py b/test/conftest.py deleted file mode 100644 index d3dd33d2b..000000000 --- a/test/conftest.py +++ /dev/null @@ -1,287 +0,0 @@ -import datetime -import json -import logging -import sys -from unittest.mock import Mock - -import pytest -import pytz - -from boxsdk.network.default_network import DefaultNetworkResponse - - -@pytest.fixture(autouse=True, scope='session') -def logger(): - logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) - return logging.getLogger(__name__.split('.', maxsplit=1)[0]) - - -def _set_content_and_json_from_json(mock_response, json_value): - mock_response.json.return_value = json_value - mock_response.content = content = json.dumps(json_value).encode('utf-8') - mock_response.headers['Content-Length'] = str(len(content)) - - -def _set_content_and_json_from_content(mock_response, content): - if not isinstance(content, bytes): - raise TypeError(f"Expected 'content' to be byte string, got {content.__class__.__name__!r}.") - mock_response.content = content - mock_response.headers['Content-Length'] = str(len(content)) - try: - mock_response.json.return_value = json.loads(content.decode('utf-8')) - except ValueError as exc: - mock_response.json.side_effect = exc - - -@pytest.fixture() -def generic_successful_request_response(): - mock_request_response = Mock(headers={f'header{i}': f'value{i}' for i in range(4)}) - _set_content_and_json_from_json(mock_request_response, json_value={f'key{i}': f'value{i}' for i in range(8)}) - mock_request_response.status_code = 200 - mock_request_response.ok = True - mock_request_response.request = Mock() - return mock_request_response - - -def _network_response_mock_from_request_response(request_response): - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.request_response = request_response - mock_network_response.json.side_effect = request_response.json - mock_network_response.content = request_response.content - mock_network_response.headers = request_response.headers - mock_network_response.status_code = request_response.status_code - mock_network_response.ok = request_response.ok - return mock_network_response - - -@pytest.fixture() -def generic_successful_response(generic_successful_request_response): - return _network_response_mock_from_request_response(generic_successful_request_response) - - -@pytest.fixture(scope='session') -def successful_token_json_response(access_token, refresh_token): - # pylint:disable=redefined-outer-name - return { - 'access_token': access_token, - 'expires_in': 3600, - 'restricted_to': [], - 'token_type': 'bearer', - 'refresh_token': refresh_token, - } - - -@pytest.fixture() -def successful_token_request_response(successful_token_json_response): - # pylint:disable=redefined-outer-name - successful_token_mock = Mock(headers={}) - _set_content_and_json_from_json(successful_token_mock, json_value=successful_token_json_response) - successful_token_mock.ok = True - successful_token_mock.status_code = 200 - return successful_token_mock - - -@pytest.fixture() -def successful_token_response(successful_token_request_response): - return _network_response_mock_from_request_response(successful_token_request_response) - - -@pytest.fixture() -def successful_token_mock(successful_token_response): - return successful_token_response - - -@pytest.fixture() -def unauthorized_response(): - res = Mock(DefaultNetworkResponse, headers={}) - _set_content_and_json_from_content(res, content=b'') - res.status_code = 401 - res.ok = False - res.request = Mock() - return res - - -@pytest.fixture() -def non_json_response(): - mock_network_response = Mock(DefaultNetworkResponse, headers={}) - _set_content_and_json_from_content(mock_network_response, content=b'') - mock_network_response.ok = True - mock_network_response.status_code = 200 - return mock_network_response - - -def _retry_after_response(status_code): - mock_network_response = Mock(DefaultNetworkResponse, headers={}) - mock_network_response.status_code = status_code - mock_network_response.headers.update({'Retry-After': '1'}) - return mock_network_response - - -@pytest.fixture() -def retry_after_response_202(): - return _retry_after_response(202) - - -@pytest.fixture() -def retry_after_response_429(): - return _retry_after_response(429) - - -@pytest.fixture(params=[202, 429]) -def retry_after_response(retry_after_response_202, retry_after_response_429, request): - if request.param == 202: - return retry_after_response_202 - if request.param == 429: - return retry_after_response_429 - - raise ValueError - - -def _server_error_request_response(status_code): - mock_request_response = Mock(headers={f'header{i}': f'value{i}' for i in range(4)}) - _set_content_and_json_from_json(mock_request_response, json_value={f'key{i}': f'value{i}' for i in range(8)}) - mock_request_response.status_code = status_code - mock_request_response.ok = False - return mock_request_response - - -@pytest.fixture() -def server_error_request_response_502(): - return _server_error_request_response(502) - - -@pytest.fixture() -def server_error_request_response_503(): - return _server_error_request_response(503) - - -@pytest.fixture(params=[502, 503]) -def server_error_request_response(server_error_request_response_502, server_error_request_response_503, request): - if request.param == 502: - return server_error_request_response_502 - if request.param == 503: - return server_error_request_response_503 - raise ValueError - - -@pytest.fixture -def server_error_response(server_error_request_response): - return _network_response_mock_from_request_response(server_error_request_response) - - -@pytest.fixture() -def bad_network_response(): - mock_network_response = Mock(DefaultNetworkResponse, headers={}) - mock_network_response.status_code = 404 - _set_content_and_json_from_json(mock_network_response, json_value={'code': 404, 'message': 'Not Found'}) - mock_network_response.ok = False - return mock_network_response - - -@pytest.fixture() -def bad_network_response_400(): - mock_network_response = Mock(DefaultNetworkResponse, headers={}) - mock_network_response.status_code = 400 - _set_content_and_json_from_json(mock_network_response, json_value={'error': 'Example Error', 'error_description': 'Example Error Description'}) - mock_network_response.ok = False - return mock_network_response - - -@pytest.fixture() -def failed_non_json_response(): - mock_network_response = Mock(DefaultNetworkResponse, headers={}) - mock_network_response.status_code = 404 - _set_content_and_json_from_content(mock_network_response, content=b'') - mock_network_response.ok = False - return mock_network_response - - -@pytest.fixture(scope='session') -def access_token(): - return 'T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl' - - -@pytest.fixture(scope='session') -def new_access_token(): - # Must be distinct from access_token. - return 'ZFoWjFHvNbvVqHjlT9cE5asGnuyYCCqI' - - -@pytest.fixture(scope='session') -def refresh_token(): - return 'J7rxTiWOHMoSC1isKZKBZWizoRXjkQzig5C6jFgCVJ9bUnsUfGMinKBDLZWP9BgRb' - - -@pytest.fixture(scope='session') -def test_url(): - return 'https://box.com/test/url' - - -@pytest.fixture(scope='session') -def client_id(): - return 'fake_client_id' - - -@pytest.fixture(scope='session') -def client_secret(): - return 'fake_client_secret' - - -@pytest.fixture(scope='session') -def auth_code(): - return 'fake_auth_code' - - -@pytest.fixture(params=[ - b'Hello', - b'Goodbye', - b'42', -]) -def test_file_content(request): - return request.param - - -@pytest.fixture() -def update_file_content(test_file_content): - # pylint:disable=redefined-outer-name - return test_file_content - - -@pytest.fixture() -def test_file_path(): - return 'path/to/file' - - -@pytest.fixture(scope='module') -def mock_object_id(): - return '42' - - -@pytest.fixture(scope='module') -def mock_user_id(): - return 'fake-user-100' - - -@pytest.fixture(scope='module') -def mock_enterprise_id(): - return 'fake-enterprise-100' - - -@pytest.fixture(scope='module') -def mock_group_id(): - return 'fake-group-99' - - -@pytest.fixture(scope='module') -def mock_datetime_rfc3339_str(): - return '2035-03-04T10:14:24+14:00' - - -@pytest.fixture(scope='module') -def mock_timezone_aware_datetime_obj(): - return datetime.datetime(2035, 3, 4, 10, 14, 24, microsecond=500, tzinfo=pytz.timezone('US/Alaska')) - - -@pytest.fixture(scope='module') -def mock_timezone_naive_datetime_obj(): - return datetime.datetime(2035, 3, 4, 10, 14, 24, microsecond=500) diff --git a/test/device_pinners.py b/test/device_pinners.py new file mode 100644 index 000000000..93b309f83 --- /dev/null +++ b/test/device_pinners.py @@ -0,0 +1,32 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.device_pinners import DevicePinners + +from box_sdk_gen.managers.device_pinners import GetEnterpriseDevicePinnersDirection + +from test.commons import get_default_client + +from box_sdk_gen.internal.utils import get_env_var + +client: BoxClient = get_default_client() + + +def testDevicePinners(): + enterprise_id: str = get_env_var('ENTERPRISE_ID') + device_pinners: DevicePinners = client.device_pinners.get_enterprise_device_pinners( + enterprise_id + ) + assert len(device_pinners.entries) >= 0 + device_pinners_in_desc_direction: DevicePinners = ( + client.device_pinners.get_enterprise_device_pinners( + enterprise_id, direction=GetEnterpriseDevicePinnersDirection.DESC + ) + ) + assert len(device_pinners_in_desc_direction.entries) >= 0 + device_pinner_id: str = '123456' + with pytest.raises(Exception): + client.device_pinners.get_device_pinner_by_id(device_pinner_id) + with pytest.raises(Exception): + client.device_pinners.delete_device_pinner_by_id(device_pinner_id) diff --git a/test/docgen.py b/test/docgen.py new file mode 100644 index 000000000..a41bc0342 --- /dev/null +++ b/test/docgen.py @@ -0,0 +1,108 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_base_v2025_r0 import ( + DocGenTemplateBaseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_batch_base_v2025_r0 import ( + DocGenBatchBaseV2025R0, +) + +from box_sdk_gen.managers.docgen import CreateDocgenBatchV2025R0DestinationFolder + +from box_sdk_gen.schemas.v2025_r0.doc_gen_document_generation_data_v2025_r0 import ( + DocGenDocumentGenerationDataV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_jobs_v2025_r0 import DocGenJobsV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_jobs_full_v2025_r0 import ( + DocGenJobsFullV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_full_v2025_r0 import DocGenJobFullV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_v2025_r0 import DocGenJobV2025R0 + +from test.commons import get_default_client + +from test.commons import upload_new_file + +from test.commons import create_new_folder + +client: BoxClient = get_default_client() + + +def testDocgenBatchAndJobs(): + uploaded_file: FileFull = upload_new_file() + folder: FolderFull = create_new_folder() + created_docgen_template: DocGenTemplateBaseV2025R0 = ( + client.docgen_template.create_docgen_template_v2025_r0( + FileReferenceV2025R0(id=uploaded_file.id) + ) + ) + docgen_batch: DocGenBatchBaseV2025R0 = client.docgen.create_docgen_batch_v2025_r0( + FileReferenceV2025R0(id=uploaded_file.id), + 'api', + CreateDocgenBatchV2025R0DestinationFolder(id=folder.id), + 'pdf', + [ + DocGenDocumentGenerationDataV2025R0( + generated_file_name='test', user_input={'abc': 'xyz'} + ) + ], + ) + assert not docgen_batch.id == '' + assert to_string(docgen_batch.type) == 'docgen_batch' + docgen_batch_jobs: DocGenJobsV2025R0 = ( + client.docgen.get_docgen_batch_job_by_id_v2025_r0(docgen_batch.id) + ) + assert len(docgen_batch_jobs.entries) >= 1 + assert not docgen_batch_jobs.entries[0].id == '' + assert to_string(docgen_batch_jobs.entries[0].type) == 'docgen_job' + assert docgen_batch_jobs.entries[0].output_type == 'pdf' + assert not to_string(docgen_batch_jobs.entries[0].status) == '' + assert docgen_batch_jobs.entries[0].template_file.id == uploaded_file.id + assert docgen_batch_jobs.entries[0].batch.id == docgen_batch.id + docgen_jobs: DocGenJobsFullV2025R0 = client.docgen.get_docgen_jobs_v2025_r0( + limit=500 + ) + assert len(docgen_jobs.entries) >= 1 + assert not docgen_jobs.entries[0].batch.id == '' + assert not docgen_jobs.entries[0].created_by.id == '' + assert not docgen_jobs.entries[0].enterprise.id == '' + assert not docgen_jobs.entries[0].id == '' + assert not docgen_jobs.entries[0].output_type == '' + assert not docgen_jobs.entries[0].source == '' + assert not to_string(docgen_jobs.entries[0].status) == '' + assert to_string(docgen_jobs.entries[0].template_file.type) == 'file' + assert not docgen_jobs.entries[0].template_file.id == '' + assert ( + to_string(docgen_jobs.entries[0].template_file_version.type) == 'file_version' + ) + assert not docgen_jobs.entries[0].template_file_version.id == '' + assert to_string(docgen_jobs.entries[0].type) == 'docgen_job' + index_of_item: int = 0 + docgen_job_item_from_list: DocGenJobFullV2025R0 = docgen_jobs.entries[index_of_item] + docgen_job: DocGenJobV2025R0 = client.docgen.get_docgen_job_by_id_v2025_r0( + docgen_job_item_from_list.id + ) + assert not docgen_job.batch.id == '' + assert not docgen_job.id == '' + assert not docgen_job.output_type == '' + assert not to_string(docgen_job.status) == '' + assert to_string(docgen_job.template_file.type) == 'file' + assert not docgen_job.template_file.id == '' + assert to_string(docgen_job.template_file_version.type) == 'file_version' + assert not docgen_job.template_file_version.id == '' + assert to_string(docgen_job.type) == 'docgen_job' + client.folders.delete_folder_by_id(folder.id) + client.files.delete_file_by_id(uploaded_file.id) diff --git a/test/docgen_template.py b/test/docgen_template.py new file mode 100644 index 000000000..b0fefb369 --- /dev/null +++ b/test/docgen_template.py @@ -0,0 +1,59 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_base_v2025_r0 import ( + DocGenTemplateBaseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_templates_v2025_r0 import ( + DocGenTemplatesV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.doc_gen_template_v2025_r0 import DocGenTemplateV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_tags_v2025_r0 import DocGenTagsV2025R0 + +from box_sdk_gen.schemas.v2025_r0.doc_gen_jobs_v2025_r0 import DocGenJobsV2025R0 + +from test.commons import get_default_client + +from test.commons import upload_new_file + +client: BoxClient = get_default_client() + + +def testDocgenTemplateCRUD(): + file: FileFull = upload_new_file() + created_docgen_template: DocGenTemplateBaseV2025R0 = ( + client.docgen_template.create_docgen_template_v2025_r0( + FileReferenceV2025R0(id=file.id) + ) + ) + docgen_templates: DocGenTemplatesV2025R0 = ( + client.docgen_template.get_docgen_templates_v2025_r0() + ) + assert len(docgen_templates.entries) > 0 + fetched_docgen_template: DocGenTemplateV2025R0 = ( + client.docgen_template.get_docgen_template_by_id_v2025_r0( + created_docgen_template.file.id + ) + ) + assert fetched_docgen_template.file.id == created_docgen_template.file.id + docgen_template_tags: DocGenTagsV2025R0 = ( + client.docgen_template.get_docgen_template_tags_v2025_r0( + fetched_docgen_template.file.id + ) + ) + docgen_template_jobs: DocGenJobsV2025R0 = ( + client.docgen_template.get_docgen_template_job_by_id_v2025_r0( + fetched_docgen_template.file.id + ) + ) + assert len(docgen_template_jobs.entries) == 0 + client.docgen_template.delete_docgen_template_by_id_v2025_r0( + created_docgen_template.file.id + ) + client.files.delete_file_by_id(file.id) diff --git a/test/downloads.py b/test/downloads.py new file mode 100644 index 000000000..4752afc0a --- /dev/null +++ b/test/downloads.py @@ -0,0 +1,90 @@ +from typing import Optional + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.internal.utils import Buffer + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.internal.utils import OutputStream + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import generate_byte_buffer + +from box_sdk_gen.internal.utils import generate_byte_stream_from_buffer + +from box_sdk_gen.internal.utils import buffer_equals + +from box_sdk_gen.internal.utils import read_byte_stream + +from box_sdk_gen.internal.utils import get_file_output_stream + +from box_sdk_gen.internal.utils import close_file_output_stream + +from box_sdk_gen.internal.utils import read_buffer_from_file + +from test.commons import get_default_client + +from test.commons import upload_new_file + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +client: BoxClient = get_default_client() + + +def test_download_file(): + new_file_name: str = get_uuid() + file_buffer: Buffer = generate_byte_buffer(1024 * 1024) + file_content_stream: ByteStream = generate_byte_stream_from_buffer(file_buffer) + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=new_file_name, parent=UploadFileAttributesParentField(id='0') + ), + file_content_stream, + ) + uploaded_file: FileFull = uploaded_files.entries[0] + downloaded_file_content: Optional[ByteStream] = client.downloads.download_file( + uploaded_file.id + ) + assert buffer_equals(read_byte_stream(downloaded_file_content), file_buffer) + client.files.delete_file_by_id(uploaded_file.id) + + +def test_get_download_url(): + uploaded_file: FileFull = upload_new_file() + download_url: str = client.downloads.get_download_file_url(uploaded_file.id) + assert not download_url == None + assert 'https://' in download_url + client.files.delete_file_by_id(uploaded_file.id) + + +def test_download_file_to_output_stream(): + new_file_name: str = get_uuid() + file_buffer: Buffer = generate_byte_buffer(1024 * 1024) + file_content_stream: ByteStream = generate_byte_stream_from_buffer(file_buffer) + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=new_file_name, parent=UploadFileAttributesParentField(id='0') + ), + file_content_stream, + ) + uploaded_file: FileFull = uploaded_files.entries[0] + file_output_stream: OutputStream = get_file_output_stream(new_file_name) + client.downloads.download_file_to_output_stream( + uploaded_file.id, file_output_stream + ) + close_file_output_stream(file_output_stream) + downloaded_file_content: Buffer = read_buffer_from_file(new_file_name) + assert buffer_equals(downloaded_file_content, file_buffer) + client.files.delete_file_by_id(uploaded_file.id) diff --git a/test/email_aliases.py b/test/email_aliases.py new file mode 100644 index 000000000..81e6d0221 --- /dev/null +++ b/test/email_aliases.py @@ -0,0 +1,36 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.email_aliases import EmailAliases + +from box_sdk_gen.schemas.email_alias import EmailAlias + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testEmailAliases(): + new_user_name: str = get_uuid() + new_user_login: str = ''.join([get_uuid(), '@boxdemo.com']) + new_user: UserFull = client.users.create_user(new_user_name, login=new_user_login) + aliases: EmailAliases = client.email_aliases.get_user_email_aliases(new_user.id) + assert aliases.total_count == 0 + new_alias_email: str = ''.join([new_user.id, '@boxdemo.com']) + new_alias: EmailAlias = client.email_aliases.create_user_email_alias( + new_user.id, new_alias_email + ) + updated_aliases: EmailAliases = client.email_aliases.get_user_email_aliases( + new_user.id + ) + assert updated_aliases.total_count == 1 + assert updated_aliases.entries[0].email == new_alias_email + client.email_aliases.delete_user_email_alias_by_id(new_user.id, new_alias.id) + final_aliases: EmailAliases = client.email_aliases.get_user_email_aliases( + new_user.id + ) + assert final_aliases.total_count == 0 + client.users.delete_user_by_id(new_user.id) diff --git a/test/events.py b/test/events.py new file mode 100644 index 000000000..2db83dc41 --- /dev/null +++ b/test/events.py @@ -0,0 +1,105 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.events import Events + +from box_sdk_gen.schemas.event import Event + +from box_sdk_gen.managers.events import GetEventsStreamType + +from box_sdk_gen.managers.events import GetEventsEventType + +from box_sdk_gen.schemas.realtime_servers import RealtimeServers + +from box_sdk_gen.schemas.realtime_server import RealtimeServer + +from box_sdk_gen.internal.utils import DateTime + +from test.commons import get_default_client + +from box_sdk_gen.schemas.event_source import EventSource + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.schemas.user import User + +from box_sdk_gen.internal.utils import date_time_from_string + +from box_sdk_gen.internal.utils import get_epoch_time_in_seconds + +from box_sdk_gen.internal.utils import epoch_seconds_to_date_time + +client: BoxClient = get_default_client() + + +def testEvents(): + events: Events = client.events.get_events() + assert len(events.entries) > 0 + first_event: Event = events.entries[0] + assert to_string(first_event.created_by.type) == 'user' + assert not to_string(first_event.event_type) == '' + + +def testEventUpload(): + events: Events = client.events.get_events( + stream_type=GetEventsStreamType.ADMIN_LOGS, + event_type=[GetEventsEventType.UPLOAD], + ) + assert len(events.entries) > 0 + first_event: Event = events.entries[0] + assert to_string(first_event.event_type) == 'UPLOAD' + source: EventSource = first_event.source + assert ( + to_string(source.item_type) == 'file' or to_string(source.item_type) == 'folder' + ) + assert not source.item_id == '' + assert not source.item_name == '' + + +def testEventDeleteUser(): + events: Events = client.events.get_events( + stream_type=GetEventsStreamType.ADMIN_LOGS, + event_type=[GetEventsEventType.DELETE_USER], + ) + assert len(events.entries) > 0 + first_event: Event = events.entries[0] + assert to_string(first_event.event_type) == 'DELETE_USER' + source: User = first_event.source + assert to_string(source.type) == 'user' + assert not source.id == '' + + +def testEventSourceFileOrFolder(): + events: Events = client.events.get_events(stream_type=GetEventsStreamType.CHANGES) + assert len(events.entries) > 0 + + +def testGetEventsWithLongPolling(): + servers: RealtimeServers = client.events.get_events_with_long_polling() + assert len(servers.entries) > 0 + server: RealtimeServer = servers.entries[0] + assert to_string(server.type) == 'realtime_server' + assert not server.url == '' + + +def testGetEventsWithDateFilters(): + current_epoch_time_in_seconds: int = get_epoch_time_in_seconds() + epoch_time_in_seconds_a_week_ago: int = current_epoch_time_in_seconds - ( + ((7 * 24) * 60) * 60 + ) + created_after_date: DateTime = epoch_seconds_to_date_time( + epoch_time_in_seconds_a_week_ago + ) + created_before_date: DateTime = epoch_seconds_to_date_time( + current_epoch_time_in_seconds + ) + servers: Events = client.events.get_events( + stream_type=GetEventsStreamType.ADMIN_LOGS, + limit=1, + created_after=created_after_date, + created_before=created_before_date, + ) + assert len(servers.entries) == 1 diff --git a/test/file_classifications.py b/test/file_classifications.py new file mode 100644 index 000000000..7fcd57823 --- /dev/null +++ b/test/file_classifications.py @@ -0,0 +1,114 @@ +from typing import List + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.classification_template import ( + ClassificationTemplateFieldsOptionsField, +) + +from box_sdk_gen.managers.classifications import AddClassificationRequestBody + +from box_sdk_gen.managers.classifications import AddClassificationRequestBodyDataField + +from box_sdk_gen.managers.classifications import ( + AddClassificationRequestBodyDataStaticConfigField, +) + +from box_sdk_gen.managers.classifications import ( + AddClassificationRequestBodyDataStaticConfigClassificationField, +) + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.classification import Classification + +from box_sdk_gen.managers.file_classifications import ( + UpdateClassificationOnFileRequestBody, +) + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +from test.commons import upload_new_file + +from test.commons import get_or_create_classification_template + +from test.commons import get_or_create_classification + +from box_sdk_gen.schemas.classification_template import ClassificationTemplate + +client: BoxClient = get_default_client() + + +def get_or_create_second_classification( + classification_template: ClassificationTemplate, +) -> ClassificationTemplateFieldsOptionsField: + classifications: List[ClassificationTemplateFieldsOptionsField] = ( + classification_template.fields[0].options + ) + current_number_of_classifications: int = len(classifications) + if current_number_of_classifications == 1: + classification_template_with_new_classification: ClassificationTemplate = ( + client.classifications.add_classification( + [ + AddClassificationRequestBody( + data=AddClassificationRequestBodyDataField( + key=get_uuid(), + static_config=AddClassificationRequestBodyDataStaticConfigField( + classification=AddClassificationRequestBodyDataStaticConfigClassificationField( + color_id=4, + classification_definition='Other description', + ) + ), + ) + ) + ] + ) + ) + return classification_template_with_new_classification.fields[0].options[1] + return classifications[1] + + +def testFileClassifications(): + classification_template: ClassificationTemplate = ( + get_or_create_classification_template() + ) + classification: ClassificationTemplateFieldsOptionsField = ( + get_or_create_classification(classification_template) + ) + file: FileFull = upload_new_file() + with pytest.raises(Exception): + client.file_classifications.get_classification_on_file(file.id) + created_file_classification: Classification = ( + client.file_classifications.add_classification_to_file( + file.id, box_security_classification_key=classification.key + ) + ) + assert ( + created_file_classification.box_security_classification_key + == classification.key + ) + file_classification: Classification = ( + client.file_classifications.get_classification_on_file(file.id) + ) + assert file_classification.box_security_classification_key == classification.key + second_classification: ClassificationTemplateFieldsOptionsField = ( + get_or_create_second_classification(classification_template) + ) + updated_file_classification: Classification = ( + client.file_classifications.update_classification_on_file( + file.id, + [UpdateClassificationOnFileRequestBody(value=second_classification.key)], + ) + ) + assert ( + updated_file_classification.box_security_classification_key + == second_classification.key + ) + client.file_classifications.delete_classification_from_file(file.id) + with pytest.raises(Exception): + client.file_classifications.get_classification_on_file(file.id) + client.files.delete_file_by_id(file.id) diff --git a/test/file_metadata.py b/test/file_metadata.py new file mode 100644 index 000000000..44164a6cc --- /dev/null +++ b/test/file_metadata.py @@ -0,0 +1,275 @@ +from box_sdk_gen.internal.utils import to_string + +from typing import List + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.metadata_template import MetadataTemplate + +from box_sdk_gen.managers.metadata_templates import CreateMetadataTemplateFields + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsTypeField, +) + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsOptionsField, +) + +from box_sdk_gen.schemas.metadata_full import MetadataFull + +from box_sdk_gen.managers.file_metadata import CreateFileMetadataByIdScope + +from box_sdk_gen.managers.file_metadata import UpdateFileMetadataByIdScope + +from box_sdk_gen.managers.file_metadata import UpdateFileMetadataByIdRequestBody + +from box_sdk_gen.managers.file_metadata import UpdateFileMetadataByIdRequestBodyOpField + +from box_sdk_gen.managers.file_metadata import DeleteFileMetadataByIdScope + +from box_sdk_gen.managers.metadata_templates import DeleteMetadataTemplateScope + +from box_sdk_gen.schemas.metadatas import Metadatas + +from box_sdk_gen.managers.file_metadata import GetFileMetadataByIdScope + +from box_sdk_gen.internal.utils import generate_byte_stream + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +from test.commons import upload_new_file + +client: BoxClient = get_default_client() + + +def testUpdatingFileMetadata(): + file: FileFull = upload_new_file() + template_key: str = ''.join(['key', get_uuid()]) + template: MetadataTemplate = client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key='name', + display_name='name', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + key='age', + display_name='age', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.DATE, + key='birthDate', + display_name='birthDate', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.ENUM, + key='countryCode', + display_name='countryCode', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='US'), + CreateMetadataTemplateFieldsOptionsField(key='CA'), + ], + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.MULTISELECT, + key='sports', + display_name='sports', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='basketball'), + CreateMetadataTemplateFieldsOptionsField(key='football'), + CreateMetadataTemplateFieldsOptionsField(key='tennis'), + ], + ), + ], + ) + created_metadata: MetadataFull = client.file_metadata.create_file_metadata_by_id( + file.id, + CreateFileMetadataByIdScope.ENTERPRISE, + template_key, + { + 'name': 'John', + 'age': 23, + 'birthDate': '2001-01-03T02:20:50.520Z', + 'countryCode': 'US', + 'sports': ['basketball', 'tennis'], + }, + ) + updated_metadata: MetadataFull = client.file_metadata.update_file_metadata_by_id( + file.id, + UpdateFileMetadataByIdScope.ENTERPRISE, + template_key, + [ + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, + path='/name', + value='Jack', + ), + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, + path='/age', + value=24, + ), + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, + path='/birthDate', + value='2000-01-03T02:20:50.520Z', + ), + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, + path='/countryCode', + value='CA', + ), + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, + path='/sports', + value=['football'], + ), + ], + ) + assert to_string(updated_metadata.template) == template_key + assert to_string(updated_metadata.extra_data.get('name')) == 'Jack' + assert to_string(updated_metadata.extra_data.get('age')) == '24' + assert ( + to_string(updated_metadata.extra_data.get('birthDate')) + == '2000-01-03T02:20:50.520Z' + ) + assert to_string(updated_metadata.extra_data.get('countryCode')) == 'CA' + sports: List[str] = updated_metadata.extra_data.get('sports') + assert sports[0] == 'football' + client.file_metadata.delete_file_metadata_by_id( + file.id, DeleteFileMetadataByIdScope.ENTERPRISE, template_key + ) + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template_key + ) + client.files.delete_file_by_id(file.id) + + +def testGlobalFileMetadata(): + file: FileFull = upload_new_file() + file_metadata: Metadatas = client.file_metadata.get_file_metadata(file.id) + assert len(file_metadata.entries) == 0 + created_metadata: MetadataFull = client.file_metadata.create_file_metadata_by_id( + file.id, CreateFileMetadataByIdScope.GLOBAL, 'properties', {'abc': 'xyz'} + ) + assert to_string(created_metadata.template) == 'properties' + assert to_string(created_metadata.scope) == 'global' + assert created_metadata.version == 0 + received_metadata: MetadataFull = client.file_metadata.get_file_metadata_by_id( + file.id, GetFileMetadataByIdScope.GLOBAL, 'properties' + ) + assert to_string(received_metadata.extra_data.get('abc')) == 'xyz' + new_value: str = 'bar' + client.file_metadata.update_file_metadata_by_id( + file.id, + UpdateFileMetadataByIdScope.GLOBAL, + 'properties', + [ + UpdateFileMetadataByIdRequestBody( + op=UpdateFileMetadataByIdRequestBodyOpField.REPLACE, + path='/abc', + value=new_value, + ) + ], + ) + received_updated_metadata: MetadataFull = ( + client.file_metadata.get_file_metadata_by_id( + file.id, GetFileMetadataByIdScope.GLOBAL, 'properties' + ) + ) + assert to_string(received_updated_metadata.extra_data.get('abc')) == new_value + client.file_metadata.delete_file_metadata_by_id( + file.id, DeleteFileMetadataByIdScope.GLOBAL, 'properties' + ) + with pytest.raises(Exception): + client.file_metadata.get_file_metadata_by_id( + file.id, GetFileMetadataByIdScope.GLOBAL, 'properties' + ) + client.files.delete_file_by_id(file.id) + + +def testEnterpriseFileMetadata(): + file: FileFull = upload_new_file() + template_key: str = ''.join(['key', get_uuid()]) + client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key='name', + display_name='name', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + key='age', + display_name='age', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.DATE, + key='birthDate', + display_name='birthDate', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.ENUM, + key='countryCode', + display_name='countryCode', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='US'), + CreateMetadataTemplateFieldsOptionsField(key='CA'), + ], + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.MULTISELECT, + key='sports', + display_name='sports', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='basketball'), + CreateMetadataTemplateFieldsOptionsField(key='football'), + CreateMetadataTemplateFieldsOptionsField(key='tennis'), + ], + ), + ], + ) + created_metadata: MetadataFull = client.file_metadata.create_file_metadata_by_id( + file.id, + CreateFileMetadataByIdScope.ENTERPRISE, + template_key, + { + 'name': 'John', + 'age': 23, + 'birthDate': '2001-01-03T02:20:50.520Z', + 'countryCode': 'US', + 'sports': ['basketball', 'tennis'], + }, + ) + assert to_string(created_metadata.template) == template_key + assert to_string(created_metadata.extra_data.get('name')) == 'John' + assert to_string(created_metadata.extra_data.get('age')) == '23' + assert ( + to_string(created_metadata.extra_data.get('birthDate')) + == '2001-01-03T02:20:50.520Z' + ) + assert to_string(created_metadata.extra_data.get('countryCode')) == 'US' + sports: List[str] = created_metadata.extra_data.get('sports') + assert sports[0] == 'basketball' + assert sports[1] == 'tennis' + client.file_metadata.delete_file_metadata_by_id( + file.id, DeleteFileMetadataByIdScope.ENTERPRISE, template_key + ) + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template_key + ) + client.files.delete_file_by_id(file.id) diff --git a/test/file_requests.py b/test/file_requests.py new file mode 100644 index 000000000..8be459d1a --- /dev/null +++ b/test/file_requests.py @@ -0,0 +1,44 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_request import FileRequest + +from box_sdk_gen.managers.file_requests import CreateFileRequestCopyFolder + +from box_sdk_gen.managers.file_requests import CreateFileRequestCopyFolderTypeField + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client_with_user_subject + + +def testGetCopyUpdateDeleteFileRequest(): + file_request_id: str = get_env_var('BOX_FILE_REQUEST_ID') + user_id: str = get_env_var('USER_ID') + client: BoxClient = get_default_client_with_user_subject(user_id) + file_request: FileRequest = client.file_requests.get_file_request_by_id( + file_request_id + ) + assert file_request.id == file_request_id + assert to_string(file_request.type) == 'file_request' + copied_file_request: FileRequest = client.file_requests.create_file_request_copy( + file_request_id, + CreateFileRequestCopyFolder( + id=file_request.folder.id, type=CreateFileRequestCopyFolderTypeField.FOLDER + ), + ) + assert not copied_file_request.id == file_request_id + assert copied_file_request.title == file_request.title + assert copied_file_request.description == file_request.description + updated_file_request: FileRequest = client.file_requests.update_file_request_by_id( + copied_file_request.id, title='updated title', description='updated description' + ) + assert updated_file_request.id == copied_file_request.id + assert updated_file_request.title == 'updated title' + assert updated_file_request.description == 'updated description' + client.file_requests.delete_file_request_by_id(updated_file_request.id) + with pytest.raises(Exception): + client.file_requests.get_file_request_by_id(updated_file_request.id) diff --git a/test/file_version_legal_holds.py b/test/file_version_legal_holds.py new file mode 100644 index 000000000..06cdbb89a --- /dev/null +++ b/test/file_version_legal_holds.py @@ -0,0 +1,30 @@ +import pytest + +from box_sdk_gen.schemas.file_version_legal_holds import FileVersionLegalHolds + +from box_sdk_gen.internal.utils import generate_byte_stream + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.client import BoxClient + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testGetFileVersionLegalHolds(): + policy_id: str = '1234567890' + file_version_legal_holds: FileVersionLegalHolds = ( + client.file_version_legal_holds.get_file_version_legal_holds(policy_id) + ) + file_version_legal_holds_count: int = len(file_version_legal_holds.entries) + assert file_version_legal_holds_count >= 0 + + +def testGetFileVersionLegalHoldById(): + file_version_legal_hold_id: str = '987654321' + with pytest.raises(Exception): + client.file_version_legal_holds.get_file_version_legal_hold_by_id( + file_version_legal_hold_id + ) diff --git a/test/file_version_retentions.py b/test/file_version_retentions.py new file mode 100644 index 000000000..19e04ec13 --- /dev/null +++ b/test/file_version_retentions.py @@ -0,0 +1,112 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.schemas.retention_policy import RetentionPolicy + +from box_sdk_gen.managers.retention_policies import CreateRetentionPolicyPolicyType + +from box_sdk_gen.managers.retention_policies import ( + CreateRetentionPolicyDispositionAction, +) + +from box_sdk_gen.managers.retention_policies import CreateRetentionPolicyRetentionType + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.retention_policy_assignment import RetentionPolicyAssignment + +from box_sdk_gen.managers.retention_policy_assignments import ( + CreateRetentionPolicyAssignmentAssignTo, +) + +from box_sdk_gen.managers.retention_policy_assignments import ( + CreateRetentionPolicyAssignmentAssignToTypeField, +) + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.managers.uploads import UploadFileVersionAttributes + +from box_sdk_gen.schemas.file_version_retentions import FileVersionRetentions + +from box_sdk_gen.schemas.file_version_retention import FileVersionRetention + +from box_sdk_gen.internal.utils import generate_byte_stream + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.client import BoxClient + +from test.commons import create_new_folder + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testCreateUpdateGetDeleteRetentionPolicy(): + description: str = get_uuid() + retention_policy: RetentionPolicy = ( + client.retention_policies.create_retention_policy( + get_uuid(), + CreateRetentionPolicyPolicyType.FINITE, + CreateRetentionPolicyDispositionAction.REMOVE_RETENTION, + description=description, + retention_length='1', + retention_type=CreateRetentionPolicyRetentionType.MODIFIABLE, + can_owner_extend_retention=False, + ) + ) + assert retention_policy.description == description + assert retention_policy.can_owner_extend_retention == False + assert to_string(retention_policy.retention_type) == 'modifiable' + folder: FolderFull = create_new_folder() + retention_policy_assignment: RetentionPolicyAssignment = ( + client.retention_policy_assignments.create_retention_policy_assignment( + retention_policy.id, + CreateRetentionPolicyAssignmentAssignTo( + id=folder.id, + type=CreateRetentionPolicyAssignmentAssignToTypeField.FOLDER, + ), + ) + ) + assert retention_policy_assignment.retention_policy.id == retention_policy.id + assert retention_policy_assignment.assigned_to.id == folder.id + assert to_string(retention_policy_assignment.assigned_to.type) == to_string( + folder.type + ) + files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), parent=UploadFileAttributesParentField(id=folder.id) + ), + generate_byte_stream(10), + ) + file: FileFull = files.entries[0] + new_files: Files = client.uploads.upload_file_version( + file.id, UploadFileVersionAttributes(name=file.name), generate_byte_stream(20) + ) + new_file: FileFull = new_files.entries[0] + assert new_file.id == file.id + file_version_retentions: FileVersionRetentions = ( + client.file_version_retentions.get_file_version_retentions() + ) + file_version_retentions_count: int = len(file_version_retentions.entries) + assert file_version_retentions_count >= 0 + if file_version_retentions_count == 0: + client.retention_policies.delete_retention_policy_by_id(retention_policy.id) + client.folders.delete_folder_by_id(folder.id, recursive=True) + return None + file_version_retention: FileVersionRetention = file_version_retentions.entries[0] + file_version_retention_by_id: FileVersionRetention = ( + client.file_version_retentions.get_file_version_retention_by_id( + file_version_retention.id + ) + ) + assert file_version_retention_by_id.id == file_version_retention.id + client.retention_policies.delete_retention_policy_by_id(retention_policy.id) + client.folders.delete_folder_by_id(folder.id, recursive=True) diff --git a/test/file_versions.py b/test/file_versions.py new file mode 100644 index 000000000..84a3d076f --- /dev/null +++ b/test/file_versions.py @@ -0,0 +1,115 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.managers.uploads import UploadFileVersionAttributes + +from box_sdk_gen.schemas.file_versions import FileVersions + +from box_sdk_gen.schemas.file_version_full import FileVersionFull + +from box_sdk_gen.managers.file_versions import PromoteFileVersionType + +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 create_null + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testCreateListGetPromoteFileVersion(): + old_name: str = get_uuid() + new_name: str = get_uuid() + files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=old_name, parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(10), + ) + file: FileFull = files.entries[0] + assert file.name == old_name + assert file.size == 10 + new_files: Files = client.uploads.upload_file_version( + file.id, UploadFileVersionAttributes(name=new_name), generate_byte_stream(20) + ) + new_file: FileFull = new_files.entries[0] + assert new_file.name == new_name + assert new_file.size == 20 + file_versions: FileVersions = client.file_versions.get_file_versions(file.id) + assert file_versions.total_count == 1 + file_version: FileVersionFull = client.file_versions.get_file_version_by_id( + file.id, file_versions.entries[0].id + ) + assert file_version.id == file_versions.entries[0].id + client.file_versions.promote_file_version( + file.id, + id=file_versions.entries[0].id, + type=PromoteFileVersionType.FILE_VERSION, + ) + file_with_promoted_version: FileFull = client.files.get_file_by_id(file.id) + assert file_with_promoted_version.name == old_name + assert file_with_promoted_version.size == 10 + client.file_versions.delete_file_version_by_id(file.id, file_version.id) + client.files.delete_file_by_id(file.id) + + +def testRemoveAndRestoreFileVersion(): + old_name: str = get_uuid() + new_name: str = get_uuid() + files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=old_name, parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(10), + ) + file: FileFull = files.entries[0] + client.uploads.upload_file_version( + file.id, UploadFileVersionAttributes(name=new_name), generate_byte_stream(20) + ) + file_versions: FileVersions = client.file_versions.get_file_versions(file.id) + assert file_versions.total_count == 1 + file_version: FileVersionFull = client.file_versions.get_file_version_by_id( + file.id, + file_versions.entries[0].id, + fields=['trashed_at', 'trashed_by', 'restored_at', 'restored_by'], + ) + assert file_version.trashed_at == None + assert file_version.trashed_by == None + assert file_version.restored_at == None + assert file_version.restored_by == None + client.file_versions.delete_file_version_by_id(file.id, file_version.id) + deleted_file_version: FileVersionFull = client.file_versions.get_file_version_by_id( + file.id, + file_versions.entries[0].id, + fields=['trashed_at', 'trashed_by', 'restored_at', 'restored_by'], + ) + assert not deleted_file_version.trashed_at == None + assert not deleted_file_version.trashed_by == None + assert deleted_file_version.restored_at == None + assert deleted_file_version.restored_by == None + client.file_versions.update_file_version_by_id( + file.id, file_version.id, trashed_at=create_null() + ) + restored_file_version: FileVersionFull = ( + client.file_versions.get_file_version_by_id( + file.id, + file_versions.entries[0].id, + fields=['trashed_at', 'trashed_by', 'restored_at', 'restored_by'], + ) + ) + assert restored_file_version.trashed_at == None + assert restored_file_version.trashed_by == None + assert not restored_file_version.restored_at == None + assert not restored_file_version.restored_by == None + client.file_versions.delete_file_version_by_id(file.id, file_version.id) + client.files.delete_file_by_id(file.id) diff --git a/test/file_watermarks.py b/test/file_watermarks.py new file mode 100644 index 000000000..e2a4b1178 --- /dev/null +++ b/test/file_watermarks.py @@ -0,0 +1,49 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.watermark import Watermark + +from box_sdk_gen.managers.file_watermarks import UpdateFileWatermarkWatermark + +from box_sdk_gen.managers.file_watermarks import ( + UpdateFileWatermarkWatermarkImprintField, +) + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import generate_byte_stream + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testCreateGetDeleteFileWatermark(): + file_name: str = ''.join([get_uuid(), '.txt']) + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=file_name, parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(10), + ) + file: FileFull = uploaded_files.entries[0] + created_watermark: Watermark = client.file_watermarks.update_file_watermark( + file.id, + UpdateFileWatermarkWatermark( + imprint=UpdateFileWatermarkWatermarkImprintField.DEFAULT + ), + ) + watermark: Watermark = client.file_watermarks.get_file_watermark(file.id) + client.file_watermarks.delete_file_watermark(file.id) + with pytest.raises(Exception): + client.file_watermarks.get_file_watermark(file.id) + client.files.delete_file_by_id(file.id) diff --git a/test/files.py b/test/files.py new file mode 100644 index 000000000..366109406 --- /dev/null +++ b/test/files.py @@ -0,0 +1,158 @@ +from typing import Optional + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.managers.files import GetFileThumbnailUrlExtension + +from box_sdk_gen.internal.utils import Buffer + +from box_sdk_gen.managers.files import GetFileThumbnailByIdExtension + +from box_sdk_gen.schemas.trash_file import TrashFile + +from box_sdk_gen.managers.files import UpdateFileByIdLock + +from box_sdk_gen.managers.files import UpdateFileByIdLockAccessField + +from box_sdk_gen.managers.files import CopyFileParent + +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 read_byte_stream + +from box_sdk_gen.internal.utils import generate_byte_stream_from_buffer + +from box_sdk_gen.internal.utils import generate_byte_buffer + +from box_sdk_gen.internal.utils import buffer_equals + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.internal.utils import create_null + +from test.commons import upload_new_file + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def upload_file(file_name: str, file_stream: ByteStream) -> FileFull: + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=file_name, parent=UploadFileAttributesParentField(id='0') + ), + file_stream, + ) + return uploaded_files.entries[0] + + +def testGetFileThumbnailUrl(): + thumbnail_file_name: str = get_uuid() + thumbnail_content_stream: ByteStream = generate_byte_stream(1024 * 1024) + thumbnail_file: FileFull = upload_file( + thumbnail_file_name, thumbnail_content_stream + ) + download_url: str = client.files.get_file_thumbnail_url( + thumbnail_file.id, GetFileThumbnailUrlExtension.PNG + ) + assert not download_url == None + assert 'https://' in download_url + client.files.delete_file_by_id(thumbnail_file.id) + + +def testGetFileThumbnail(): + thumbnail_file_name: str = get_uuid() + thumbnail_buffer: Buffer = generate_byte_buffer(1024 * 1024) + thumbnail_content_stream: ByteStream = generate_byte_stream_from_buffer( + thumbnail_buffer + ) + thumbnail_file: FileFull = upload_file( + thumbnail_file_name, thumbnail_content_stream + ) + thumbnail: Optional[ByteStream] = client.files.get_file_thumbnail_by_id( + thumbnail_file.id, GetFileThumbnailByIdExtension.PNG + ) + assert not buffer_equals(read_byte_stream(thumbnail), thumbnail_buffer) == True + client.files.delete_file_by_id(thumbnail_file.id) + + +def testGetFileFullExtraFields(): + new_file_name: str = get_uuid() + file_stream: ByteStream = generate_byte_stream(1024 * 1024) + uploaded_file: FileFull = upload_file(new_file_name, file_stream) + file: FileFull = client.files.get_file_by_id( + uploaded_file.id, fields=['is_externally_owned', 'has_collaborations'] + ) + assert file.is_externally_owned == False + assert file.has_collaborations == False + client.files.delete_file_by_id(file.id) + + +def testCreateGetAndDeleteFile(): + new_file_name: str = get_uuid() + updated_content_stream: ByteStream = generate_byte_stream(1024 * 1024) + uploaded_file: FileFull = upload_file(new_file_name, updated_content_stream) + file: FileFull = client.files.get_file_by_id(uploaded_file.id) + with pytest.raises(Exception): + client.files.get_file_by_id( + uploaded_file.id, + fields=['name'], + extra_headers={'if-none-match': file.etag}, + ) + assert file.name == new_file_name + client.files.delete_file_by_id(uploaded_file.id) + trashed_file: TrashFile = client.trashed_files.get_trashed_file_by_id( + uploaded_file.id + ) + assert file.id == trashed_file.id + + +def testUpdateFile(): + file_to_update: FileFull = upload_new_file() + updated_name: str = get_uuid() + updated_file: FileFull = client.files.update_file_by_id( + file_to_update.id, name=updated_name, description='Updated description' + ) + assert updated_file.name == updated_name + assert updated_file.description == 'Updated description' + client.files.delete_file_by_id(updated_file.id) + + +def testFileLock(): + file: FileFull = upload_new_file() + file_with_lock: FileFull = client.files.update_file_by_id( + file.id, + lock=UpdateFileByIdLock(access=UpdateFileByIdLockAccessField.LOCK), + fields=['lock'], + ) + assert not file_with_lock.lock == None + file_without_lock: FileFull = client.files.update_file_by_id( + file.id, lock=create_null(), fields=['lock'] + ) + assert file_without_lock.lock == None + client.files.delete_file_by_id(file.id) + + +def testCopyFile(): + file_origin: FileFull = upload_new_file() + copied_file_name: str = get_uuid() + copied_file: FileFull = client.files.copy_file( + file_origin.id, CopyFileParent(id='0'), name=copied_file_name + ) + assert copied_file.parent.id == '0' + assert copied_file.name == copied_file_name + client.files.delete_file_by_id(file_origin.id) + client.files.delete_file_by_id(copied_file.id) diff --git a/test/folder_classifications.py b/test/folder_classifications.py new file mode 100644 index 000000000..5e66e3cd5 --- /dev/null +++ b/test/folder_classifications.py @@ -0,0 +1,114 @@ +from typing import List + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.classification_template import ( + ClassificationTemplateFieldsOptionsField, +) + +from box_sdk_gen.managers.classifications import AddClassificationRequestBody + +from box_sdk_gen.managers.classifications import AddClassificationRequestBodyDataField + +from box_sdk_gen.managers.classifications import ( + AddClassificationRequestBodyDataStaticConfigField, +) + +from box_sdk_gen.managers.classifications import ( + AddClassificationRequestBodyDataStaticConfigClassificationField, +) + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.classification import Classification + +from box_sdk_gen.managers.folder_classifications import ( + UpdateClassificationOnFolderRequestBody, +) + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +from test.commons import create_new_folder + +from test.commons import get_or_create_classification_template + +from test.commons import get_or_create_classification + +from box_sdk_gen.schemas.classification_template import ClassificationTemplate + +client: BoxClient = get_default_client() + + +def get_or_create_second_classification( + classification_template: ClassificationTemplate, +) -> ClassificationTemplateFieldsOptionsField: + classifications: List[ClassificationTemplateFieldsOptionsField] = ( + classification_template.fields[0].options + ) + current_number_of_classifications: int = len(classifications) + if current_number_of_classifications == 1: + classification_template_with_new_classification: ClassificationTemplate = ( + client.classifications.add_classification( + [ + AddClassificationRequestBody( + data=AddClassificationRequestBodyDataField( + key=get_uuid(), + static_config=AddClassificationRequestBodyDataStaticConfigField( + classification=AddClassificationRequestBodyDataStaticConfigClassificationField( + color_id=4, + classification_definition='Other description', + ) + ), + ) + ) + ] + ) + ) + return classification_template_with_new_classification.fields[0].options[1] + return classifications[1] + + +def testFolderClassifications(): + classification_template: ClassificationTemplate = ( + get_or_create_classification_template() + ) + classification: ClassificationTemplateFieldsOptionsField = ( + get_or_create_classification(classification_template) + ) + folder: FolderFull = create_new_folder() + with pytest.raises(Exception): + client.folder_classifications.get_classification_on_folder(folder.id) + created_folder_classification: Classification = ( + client.folder_classifications.add_classification_to_folder( + folder.id, box_security_classification_key=classification.key + ) + ) + assert ( + created_folder_classification.box_security_classification_key + == classification.key + ) + folder_classification: Classification = ( + client.folder_classifications.get_classification_on_folder(folder.id) + ) + assert folder_classification.box_security_classification_key == classification.key + second_classification: ClassificationTemplateFieldsOptionsField = ( + get_or_create_second_classification(classification_template) + ) + updated_folder_classification: Classification = ( + client.folder_classifications.update_classification_on_folder( + folder.id, + [UpdateClassificationOnFolderRequestBody(value=second_classification.key)], + ) + ) + assert ( + updated_folder_classification.box_security_classification_key + == second_classification.key + ) + client.folder_classifications.delete_classification_from_folder(folder.id) + with pytest.raises(Exception): + client.folder_classifications.get_classification_on_folder(folder.id) + client.folders.delete_folder_by_id(folder.id) diff --git a/test/folder_locks.py b/test/folder_locks.py new file mode 100644 index 000000000..965c4a76c --- /dev/null +++ b/test/folder_locks.py @@ -0,0 +1,40 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.folder_locks import FolderLocks + +from box_sdk_gen.schemas.folder_lock import FolderLock + +from box_sdk_gen.managers.folder_locks import CreateFolderLockLockedOperations + +from box_sdk_gen.managers.folder_locks import CreateFolderLockFolder + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +from test.commons import create_new_folder + +client: BoxClient = get_default_client() + + +def testFolderLocks(): + folder: FolderFull = create_new_folder() + folder_locks: FolderLocks = client.folder_locks.get_folder_locks(folder.id) + assert len(folder_locks.entries) == 0 + folder_lock: FolderLock = client.folder_locks.create_folder_lock( + CreateFolderLockFolder(id=folder.id, type='folder'), + locked_operations=CreateFolderLockLockedOperations(move=True, delete=True), + ) + assert folder_lock.folder.id == folder.id + assert folder_lock.locked_operations.move == True + assert folder_lock.locked_operations.delete == True + client.folder_locks.delete_folder_lock_by_id(folder_lock.id) + with pytest.raises(Exception): + client.folder_locks.delete_folder_lock_by_id(folder_lock.id) + new_folder_locks: FolderLocks = client.folder_locks.get_folder_locks(folder.id) + assert len(new_folder_locks.entries) == 0 + client.folders.delete_folder_by_id(folder.id) diff --git a/test/folder_metadata.py b/test/folder_metadata.py new file mode 100644 index 000000000..4ed19ddbf --- /dev/null +++ b/test/folder_metadata.py @@ -0,0 +1,286 @@ +from box_sdk_gen.internal.utils import to_string + +from typing import List + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.metadata_template import MetadataTemplate + +from box_sdk_gen.managers.metadata_templates import CreateMetadataTemplateFields + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsTypeField, +) + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsOptionsField, +) + +from box_sdk_gen.schemas.metadata_full import MetadataFull + +from box_sdk_gen.managers.folder_metadata import CreateFolderMetadataByIdScope + +from box_sdk_gen.managers.folder_metadata import UpdateFolderMetadataByIdScope + +from box_sdk_gen.managers.folder_metadata import UpdateFolderMetadataByIdRequestBody + +from box_sdk_gen.managers.folder_metadata import ( + UpdateFolderMetadataByIdRequestBodyOpField, +) + +from box_sdk_gen.managers.folder_metadata import DeleteFolderMetadataByIdScope + +from box_sdk_gen.managers.metadata_templates import DeleteMetadataTemplateScope + +from box_sdk_gen.schemas.metadatas import Metadatas + +from box_sdk_gen.managers.folder_metadata import GetFolderMetadataByIdScope + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +from test.commons import create_new_folder + +client: BoxClient = get_default_client() + + +def testUpdatingFolderMetadata(): + folder: FolderFull = create_new_folder() + template_key: str = ''.join(['key', get_uuid()]) + template: MetadataTemplate = client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key='name', + display_name='name', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + key='age', + display_name='age', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.DATE, + key='birthDate', + display_name='birthDate', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.ENUM, + key='countryCode', + display_name='countryCode', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='US'), + CreateMetadataTemplateFieldsOptionsField(key='CA'), + ], + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.MULTISELECT, + key='sports', + display_name='sports', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='basketball'), + CreateMetadataTemplateFieldsOptionsField(key='football'), + CreateMetadataTemplateFieldsOptionsField(key='tennis'), + ], + ), + ], + ) + created_metadata: MetadataFull = ( + client.folder_metadata.create_folder_metadata_by_id( + folder.id, + CreateFolderMetadataByIdScope.ENTERPRISE, + template_key, + { + 'name': 'John', + 'age': 23, + 'birthDate': '2001-01-03T02:20:50.520Z', + 'countryCode': 'US', + 'sports': ['basketball', 'tennis'], + }, + ) + ) + updated_metadata: MetadataFull = ( + client.folder_metadata.update_folder_metadata_by_id( + folder.id, + UpdateFolderMetadataByIdScope.ENTERPRISE, + template_key, + [ + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, + path='/name', + value='Jack', + ), + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, + path='/age', + value=24, + ), + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, + path='/birthDate', + value='2000-01-03T02:20:50.520Z', + ), + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, + path='/countryCode', + value='CA', + ), + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, + path='/sports', + value=['football'], + ), + ], + ) + ) + assert to_string(updated_metadata.template) == template_key + assert to_string(updated_metadata.extra_data.get('name')) == 'Jack' + assert to_string(updated_metadata.extra_data.get('age')) == '24' + assert ( + to_string(updated_metadata.extra_data.get('birthDate')) + == '2000-01-03T02:20:50.520Z' + ) + assert to_string(updated_metadata.extra_data.get('countryCode')) == 'CA' + sports: List[str] = updated_metadata.extra_data.get('sports') + assert sports[0] == 'football' + client.folder_metadata.delete_folder_metadata_by_id( + folder.id, DeleteFolderMetadataByIdScope.ENTERPRISE, template_key + ) + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template_key + ) + client.folders.delete_folder_by_id(folder.id) + + +def testGlobalFolderMetadata(): + folder: FolderFull = create_new_folder() + folder_metadata: Metadatas = client.folder_metadata.get_folder_metadata(folder.id) + assert len(folder_metadata.entries) == 0 + created_metadata: MetadataFull = ( + client.folder_metadata.create_folder_metadata_by_id( + folder.id, + CreateFolderMetadataByIdScope.GLOBAL, + 'properties', + {'abc': 'xyz'}, + ) + ) + assert to_string(created_metadata.template) == 'properties' + assert to_string(created_metadata.scope) == 'global' + assert created_metadata.version == 0 + received_metadata: MetadataFull = client.folder_metadata.get_folder_metadata_by_id( + folder.id, GetFolderMetadataByIdScope.GLOBAL, 'properties' + ) + assert to_string(received_metadata.extra_data.get('abc')) == 'xyz' + new_value: str = 'bar' + client.folder_metadata.update_folder_metadata_by_id( + folder.id, + UpdateFolderMetadataByIdScope.GLOBAL, + 'properties', + [ + UpdateFolderMetadataByIdRequestBody( + op=UpdateFolderMetadataByIdRequestBodyOpField.REPLACE, + path='/abc', + value=new_value, + ) + ], + ) + received_updated_metadata: MetadataFull = ( + client.folder_metadata.get_folder_metadata_by_id( + folder.id, GetFolderMetadataByIdScope.GLOBAL, 'properties' + ) + ) + assert to_string(received_updated_metadata.extra_data.get('abc')) == new_value + client.folder_metadata.delete_folder_metadata_by_id( + folder.id, DeleteFolderMetadataByIdScope.GLOBAL, 'properties' + ) + with pytest.raises(Exception): + client.folder_metadata.get_folder_metadata_by_id( + folder.id, GetFolderMetadataByIdScope.GLOBAL, 'properties' + ) + client.folders.delete_folder_by_id(folder.id) + + +def testEnterpriseFolderMetadata(): + folder: FolderFull = create_new_folder() + template_key: str = ''.join(['key', get_uuid()]) + template: MetadataTemplate = client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key='name', + display_name='name', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + key='age', + display_name='age', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.DATE, + key='birthDate', + display_name='birthDate', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.ENUM, + key='countryCode', + display_name='countryCode', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='US'), + CreateMetadataTemplateFieldsOptionsField(key='CA'), + ], + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.MULTISELECT, + key='sports', + display_name='sports', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='basketball'), + CreateMetadataTemplateFieldsOptionsField(key='football'), + CreateMetadataTemplateFieldsOptionsField(key='tennis'), + ], + ), + ], + ) + created_metadata: MetadataFull = ( + client.folder_metadata.create_folder_metadata_by_id( + folder.id, + CreateFolderMetadataByIdScope.ENTERPRISE, + template_key, + { + 'name': 'John', + 'age': 23, + 'birthDate': '2001-01-03T02:20:50.520Z', + 'countryCode': 'US', + 'sports': ['basketball', 'tennis'], + }, + ) + ) + assert to_string(created_metadata.template) == template_key + assert to_string(created_metadata.extra_data.get('name')) == 'John' + assert to_string(created_metadata.extra_data.get('age')) == '23' + assert ( + to_string(created_metadata.extra_data.get('birthDate')) + == '2001-01-03T02:20:50.520Z' + ) + assert to_string(created_metadata.extra_data.get('countryCode')) == 'US' + sports: List[str] = created_metadata.extra_data.get('sports') + assert sports[0] == 'basketball' + assert sports[1] == 'tennis' + client.folder_metadata.delete_folder_metadata_by_id( + folder.id, DeleteFolderMetadataByIdScope.ENTERPRISE, template_key + ) + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template_key + ) + client.folders.delete_folder_by_id(folder.id) diff --git a/test/folder_watermarks.py b/test/folder_watermarks.py new file mode 100644 index 000000000..1408dc935 --- /dev/null +++ b/test/folder_watermarks.py @@ -0,0 +1,39 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.schemas.watermark import Watermark + +from box_sdk_gen.managers.folder_watermarks import UpdateFolderWatermarkWatermark + +from box_sdk_gen.managers.folder_watermarks import ( + UpdateFolderWatermarkWatermarkImprintField, +) + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testCreateGetDeleteFolderWatermark(): + folder_name: str = get_uuid() + folder: FolderFull = client.folders.create_folder( + folder_name, CreateFolderParent(id='0') + ) + created_watermark: Watermark = client.folder_watermarks.update_folder_watermark( + folder.id, + UpdateFolderWatermarkWatermark( + imprint=UpdateFolderWatermarkWatermarkImprintField.DEFAULT + ), + ) + watermark: Watermark = client.folder_watermarks.get_folder_watermark(folder.id) + client.folder_watermarks.delete_folder_watermark(folder.id) + with pytest.raises(Exception): + client.folder_watermarks.get_folder_watermark(folder.id) + client.folders.delete_folder_by_id(folder.id) diff --git a/test/folders.py b/test/folders.py new file mode 100644 index 000000000..b0ec36fcb --- /dev/null +++ b/test/folders.py @@ -0,0 +1,88 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.managers.folders import CopyFolderParent + +from box_sdk_gen.managers.folders import UpdateFolderByIdParent + +from box_sdk_gen.schemas.items import Items + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def test_get_folder_info(): + root_folder: FolderFull = client.folders.get_folder_by_id('0') + assert root_folder.id == '0' + assert root_folder.name == 'All Files' + assert to_string(root_folder.type) == 'folder' + + +def test_get_folder_full_info_with_extra_fields(): + root_folder: FolderFull = client.folders.get_folder_by_id( + '0', fields=['has_collaborations', 'tags'] + ) + assert root_folder.id == '0' + assert root_folder.has_collaborations == False + tags_length: int = len(root_folder.tags) + assert tags_length == 0 + + +def test_create_and_delete_folder(): + new_folder_name: str = get_uuid() + new_folder: FolderFull = client.folders.create_folder( + new_folder_name, CreateFolderParent(id='0') + ) + created_folder: FolderFull = client.folders.get_folder_by_id(new_folder.id) + assert created_folder.name == new_folder_name + client.folders.delete_folder_by_id(new_folder.id) + with pytest.raises(Exception): + client.folders.get_folder_by_id(new_folder.id) + + +def test_update_folder(): + folder_to_update_name: str = get_uuid() + folder_to_update: FolderFull = client.folders.create_folder( + folder_to_update_name, CreateFolderParent(id='0') + ) + updated_name: str = get_uuid() + updated_folder: FolderFull = client.folders.update_folder_by_id( + folder_to_update.id, name=updated_name, description='Updated description' + ) + assert updated_folder.name == updated_name + assert updated_folder.description == 'Updated description' + client.folders.delete_folder_by_id(updated_folder.id) + + +def test_copy_move_folder_and_list_folder_items(): + folder_origin_name: str = get_uuid() + folder_origin: FolderFull = client.folders.create_folder( + folder_origin_name, CreateFolderParent(id='0') + ) + copied_folder_name: str = get_uuid() + copied_folder: FolderFull = client.folders.copy_folder( + folder_origin.id, CopyFolderParent(id='0'), name=copied_folder_name + ) + assert copied_folder.parent.id == '0' + moved_folder_name: str = get_uuid() + moved_folder: FolderFull = client.folders.update_folder_by_id( + copied_folder.id, + name=moved_folder_name, + parent=UpdateFolderByIdParent(id=folder_origin.id), + ) + assert moved_folder.parent.id == folder_origin.id + folder_items: Items = client.folders.get_folder_items(folder_origin.id) + assert folder_items.entries[0].id == moved_folder.id + assert folder_items.entries[0].name == moved_folder_name + client.folders.delete_folder_by_id(moved_folder.id) + client.folders.delete_folder_by_id(folder_origin.id) diff --git a/test/functional/__init__.py b/test/functional/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/functional/conftest.py b/test/functional/conftest.py deleted file mode 100644 index ed3ef78d0..000000000 --- a/test/functional/conftest.py +++ /dev/null @@ -1,122 +0,0 @@ -from urllib import parse -from test.functional.mock_box.box import Box -from test.util.streamable_mock_open import streamable_mock_open -from unittest.mock import patch - -import re -import pytest -import requests - -from boxsdk.auth.oauth2 import OAuth2 -from boxsdk.client import LoggingClient -from boxsdk.config import API -from boxsdk.network.default_network import DefaultNetwork -from boxsdk.session.session import Session, AuthorizedSession - - -@pytest.fixture(scope='session') -def network_layer(): - return DefaultNetwork() - - -@pytest.fixture() -def box_client(box_oauth, box_session): - # pylint:disable=redefined-outer-name - return LoggingClient(box_oauth, session=box_session) - - -@pytest.fixture -def unauthorized_session(network_layer): - return Session(network_layer=network_layer) - - -@pytest.fixture -def box_session(box_oauth, network_layer): - # pylint:disable=redefined-outer-name - return AuthorizedSession(oauth=box_oauth, network_layer=network_layer) - - -@pytest.fixture() -def box_oauth(client_id, client_secret, user_login, unauthorized_session): - # pylint:disable=redefined-outer-name - oauth2 = OAuth2(client_id, client_secret, box_device_name='mock_box functional test', session=unauthorized_session) - url, _ = oauth2.get_authorization_url('http://localhost') - form = requests.get(url + '&box_login=' + user_login).content.decode('utf-8') - form_action = re.search('action="([^"]*)"', form).group(1) - auth_response = requests.post(form_action, allow_redirects=False, data={ - 'login': user_login, - 'client_id': client_id, - 'client_secret': client_secret, - }) - redirect_url = auth_response.headers['Location'] - query_string = parse.urlparse(redirect_url).query - parsed_query_string_dict = parse.parse_qs(query_string) - - # Get the OAuth2 authorization code from `parsed_query_string_dict` - # (NOTE: the values in the dictionary are lists of strings), - # and if necessary decode it from a utf-8 encoded byte string to - # a unicode string. - auth_code = parsed_query_string_dict['code'][0] - if isinstance(auth_code, bytes): - auth_code = auth_code.decode('utf-8') - - oauth2.authenticate(auth_code) - return oauth2 - - -@pytest.fixture(scope='session') -def mock_box_server(request): - box = Box() - request.addfinalizer(box.shutdown) - return box - - -@pytest.fixture(autouse=True) -def mock_box(mock_box_server, monkeypatch, client_id, client_secret, user_name, user_login): - # pylint:disable=redefined-outer-name - mock_box_server.reset_filesystem([(user_name, user_login)], [(client_id, client_secret, 0)]) - monkeypatch.setattr(API, 'BASE_API_URL', f'http://localhost:{Box.API_PORT}') - monkeypatch.setattr(API, 'UPLOAD_URL', f'http://localhost:{Box.UPLOAD_PORT}') - monkeypatch.setattr(API, 'OAUTH2_API_URL', f'http://localhost:{Box.OAUTH_API_PORT}') - monkeypatch.setattr(API, 'OAUTH2_AUTHORIZE_URL', f'http://localhost:{Box.OAUTH_AUTHORIZE_PORT}') - return mock_box_server - - -@pytest.fixture(params=[ - 'foo.txt', - 'bar.docx', - 'foo.txt', -]) -def file_name(request): - return request.param - - -@pytest.fixture(params=[ - 'some_folder', - 'Ѵȁćȁƭȉőń Ρȉćƭȕŕȅŝ', -]) -def folder_name(request): - return request.param - - -@pytest.fixture(scope='session') -def user_name(): - return 'User 1' - - -@pytest.fixture(scope='session') -def user_login(): - return 'user.1@example.com' - - -@pytest.fixture() -def uploaded_file(box_client, test_file_path, test_file_content, file_name): - # pylint:disable=redefined-outer-name - with patch('boxsdk.object.folder.open', streamable_mock_open(read_data=test_file_content), create=True): - return box_client.folder('0').upload(test_file_path, file_name) - - -@pytest.fixture() -def created_subfolder(box_client, folder_name): - # pylint:disable=redefined-outer-name - return box_client.folder('0').create_subfolder(folder_name) diff --git a/test/functional/mock_box/__init__.py b/test/functional/mock_box/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/functional/mock_box/behavior/__init__.py b/test/functional/mock_box/behavior/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/functional/mock_box/behavior/event_behavior.py b/test/functional/mock_box/behavior/event_behavior.py deleted file mode 100644 index fcf7a1ce4..000000000 --- a/test/functional/mock_box/behavior/event_behavior.py +++ /dev/null @@ -1,98 +0,0 @@ -from datetime import datetime, timedelta -from threading import Event - -from bottle import request -from sqlalchemy import event - -from test.functional.mock_box.db_model.event_model import EventModel -from test.functional.mock_box.db_model.file_model import FileModel -from test.functional.mock_box.db_model.folder_model import FolderModel -from test.functional.mock_box.util import json_utils as json - - -class EventBehavior: - def __init__(self, db_session): - self._db_session = db_session - self._subscribe_event = Event() - self._epoch = datetime.utcnow() - - def handle_event(*_): - self._subscribe_event.set() - - event.listen(EventModel, 'after_insert', handle_event) - - def _ms_since_epoch(self, when): - delta = when - self._epoch - total_seconds = (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10 ** 6) / 10 ** 6 - return int(total_seconds * 1000) - - def _ms_after_epoch(self, when): - return self._epoch + timedelta(seconds=when / 1000) - - def get_events(self): - stream_position = request.params.get('stream_position', '0') - limit = int(request.params.get('limit', '100')) - if stream_position == 'now': - return json.dumps({ - 'total_count': 0, - 'next_stream_position': self._ms_since_epoch(datetime.utcnow()), - 'entries': [], - }) - stream_position = int(stream_position) - event_query = self._db_session.query(EventModel).filter( - EventModel.stream_position >= self._ms_after_epoch(stream_position or 1), - ) - total_count = event_query.count() - results = event_query.order_by(EventModel.sequence_id).limit(limit).all() - max_stream_position = max(e.stream_position for e in results) if results else self._epoch - next_stream_position = datetime.utcnow() if total_count < limit else max_stream_position - timedelta(seconds=5) - file_ids = [e.source_id for e in results if e.source_type == 'file'] - folder_ids = [e.source_id for e in results if e.source_type == 'folder'] - file_records = self._db_session.query(FileModel).filter(FileModel.file_id.in_(file_ids)).all() if file_ids else [] - folder_records = self._db_session.query(FolderModel).filter(FolderModel.folder_id.in_(folder_ids)).all() if folder_ids else [] - if results: - results = [{ - 'type': 'event', - 'event_id': e.event_id, - 'event_type': e.event_type, - 'source_id': e.source_id, - 'source': next( - (f for f in file_records if f.file_id == e.source_id), - None - ) or next((f for f in folder_records if f.folder_id == e.source_id), None), - } for e in results] - return json.dumps({ - 'chunk_size': total_count, - 'next_stream_position': self._ms_since_epoch(next_stream_position), - 'entries': results, - }) - - @staticmethod - def get_long_poll_url(): - return { - 'chunk_size': 1, - 'entries': [{ - 'type': 'realtime_server', - 'url': 'http://localhost:18089/subscribe?channel=cc807c9c4869ffb1c81a&stream_type=all', - 'ttl': 10, - 'max_retries': 10, - 'retry_timeout': 610, - }] - } - - def subscribe(self): - if self._subscribe_event.isSet(): - self._subscribe_event.clear() - return { - 'message': 'new_change' - } - got_event = self._subscribe_event.wait(timeout=600) - if got_event: - self._subscribe_event.clear() - return { - 'message': 'new_change' - } - else: - return { - 'message': 'reconnect' - } diff --git a/test/functional/mock_box/behavior/file_behavior.py b/test/functional/mock_box/behavior/file_behavior.py deleted file mode 100644 index 93e06d837..000000000 --- a/test/functional/mock_box/behavior/file_behavior.py +++ /dev/null @@ -1,135 +0,0 @@ -from hashlib import sha1 - -from bottle import response, request -from sqlalchemy.orm import make_transient - -from test.functional.mock_box.behavior.item_behavior import ItemBehavior -from test.functional.mock_box.db_model.event_model import EventModel -from test.functional.mock_box.db_model.file_model import FileModel -from test.functional.mock_box.util.db_utils import get_file_by_id, get_folder_by_id, get_user_from_header -from test.functional.mock_box.util.http_utils import abort -from test.functional.mock_box.util import json_utils as json - - -class FileBehavior(ItemBehavior): - @staticmethod - def _check_file_lock(file_object, is_download=False): - if len(file_object.locks): - lock_object = file_object.locks[0] - if not is_download or lock_object.is_download_prevented: - abort(403, 'File is locked.') - - def update_file(self, file_id): - """ - https://developers.box.com/docs/#files-upload-a-new-version-of-a-file - """ - file_object = get_file_by_id(self._db_session, file_id) - self._check_file_lock(file_object) - content = request.files.file.file.read() - file_hash = sha1() - file_hash.update(content) - file_object.content = content - file_object.sha1 = file_hash.hexdigest() - file_object.size = len(content) - self._db_session.add(EventModel(event_type='ITEM_UPLOAD', source_id=file_object.file_id, source_type='file')) - self._db_session.commit() - return json.dumps({'entries': [file_object]}) - - def upload_file(self): - """ - https://developers.box.com/docs/#files-upload-a-file - """ - attributes = request.forms.get('attributes') or request.forms.get('metadata') - if not attributes: - abort(400, 'Missing parameter: attributes') - attributes = json.loads(attributes) - parent = attributes.get('parent') - if parent is None or 'id' not in parent: - abort(400, 'Missing parameter: parent(id)') - parent_id = parent['id'] - folder = get_folder_by_id(self._db_session, parent_id) - content = request.files.file.file.read() - file_hash = sha1() - file_hash.update(content) - owner = get_user_from_header(self._db_session) - file_object = FileModel( - content=content, - name=attributes.get('name', request.files.file.name), - parent_id=folder.id, - sha1=file_hash.hexdigest(), - size=len(content), - owned_by=owner, - created_by=owner, - ) - self._db_session.add(file_object) - self._db_session.commit() - self._db_session.add(EventModel(event_type='ITEM_UPLOAD', source_id=file_object.file_id, source_type='file')) - self._db_session.commit() - return json.dumps({'entries': [file_object]}) - - def get_file_info(self, file_id): - """ - https://developers.box.com/docs/#files-get - """ - file_object = get_file_by_id(self._db_session, file_id) - return json.dumps(file_object) - - def update_file_info(self, file_id): - file_object = get_file_by_id(self._db_session, file_id) - self._check_file_lock(file_object) - params = json.load(request.body) - for key, value in params.items(): - if not hasattr(FileModel, key): - abort(400, f'File has no attribute {key}.') - if key == 'parent': - # Move - parent_id = value['id'] - parent_folder = get_folder_by_id(self._db_session, parent_id) - file_object.parent_id = parent_folder.id - self._db_session.add( - EventModel(event_type='ITEM_MOVE', source_id=file_object.file_id, source_type='file'), - ) - else: - setattr(file_object, key, value) - if key == 'name': - self._db_session.add( - EventModel(event_type='ITEM_RENAME', source_id=file_object.file_id, source_type='file'), - ) - elif key == 'sync_state': - event_type = 'ITEM_SYNC' if value == 'synced' else 'ITEM_UNSYNC' - self._db_session.add( - EventModel(event_type=event_type, source_id=file_object.file_id, source_type='file'), - ) - self._db_session.commit() - return json.dumps(file_object) - - def copy_file(self, file_id): - file_object = get_file_by_id(self._db_session, file_id) - self._check_file_lock(file_object) - parent_folder = self._get_parent() - self._db_session.expunge(file_object) - make_transient(file_object) - file_object.id = None - file_object.file_id = None - file_object.parent_id = parent_folder.id - self._db_session.add(file_object) - self._db_session.commit() - self._db_session.add(EventModel(event_type='ITEM_COPY', source_id=file_object.file_id, source_type='file')) - self._db_session.commit() - return json.dumps(file_object) - - def download_file(self, file_id): - """ - https://developers.box.com/docs/#files-download-a-file - """ - file_object = get_file_by_id(self._db_session, file_id) - self._check_file_lock(file_object, is_download=True) - return file_object.content - - def delete_file(self, file_id): - file_object = get_file_by_id(self._db_session, file_id) - self._check_file_lock(file_object) - self._db_session.delete(file_object) - self._db_session.add(EventModel(event_type='ITEM_TRASH', source_id=file_object.file_id, source_type='file')) - self._db_session.commit() - response.status = 204 diff --git a/test/functional/mock_box/behavior/folder_behavior.py b/test/functional/mock_box/behavior/folder_behavior.py deleted file mode 100644 index 950517a80..000000000 --- a/test/functional/mock_box/behavior/folder_behavior.py +++ /dev/null @@ -1,126 +0,0 @@ -from bottle import request, response -from sqlalchemy.orm import make_transient -from sqlalchemy.orm.exc import NoResultFound - -from boxsdk.object.folder import FolderSyncState -from test.functional.mock_box.behavior.item_behavior import ItemBehavior -from test.functional.mock_box.db_model.event_model import EventModel -from test.functional.mock_box.db_model.file_model import FileModel -from test.functional.mock_box.db_model.folder_model import FolderModel -from test.functional.mock_box.util.db_utils import get_folder_by_id, get_user_from_header -from test.functional.mock_box.util.http_utils import abort -from test.functional.mock_box.util import json_utils as json - - -class FolderBehavior(ItemBehavior): - def get_folder_info(self, folder_id): - """ - https://developers.box.com/docs/#folders-get-information-about-a-folder - """ - folder = get_folder_by_id(self._db_session, folder_id) - return json.dumps(folder) - - def update_folder_info(self, folder_id): - folder = get_folder_by_id(self._db_session, folder_id) - params = json.load(request.body) - for key, value in params.items(): - if not hasattr(FolderModel, key): - abort(400, f'Folder has no attribute {key}.') - if key == 'parent': - # Move - parent_id = value['id'] - parent_folder = get_folder_by_id(self._db_session, parent_id) - folder.parent_id = parent_folder.id - self._db_session.add( - EventModel(event_type='ITEM_MOVE', source_id=folder.folder_id, source_type='folder'), - ) - else: - setattr(folder, key, value) - if key == 'name': - self._db_session.add( - EventModel(event_type='ITEM_RENAME', source_id=folder.folder_id, source_type='folder'), - ) - elif key == 'sync_state': - event_type = 'ITEM_SYNC' if value == FolderSyncState.IS_SYNCED else 'ITEM_UNSYNC' - self._db_session.add( - EventModel(event_type=event_type, source_id=folder.folder_id, source_type='folder'), - ) - self._db_session.commit() - return json.dumps(folder) - - def copy_folder(self, folder_id): - folder = get_folder_by_id(self._db_session, folder_id) - parent_folder = self._get_parent() - self._db_session.expunge(folder) - make_transient(folder) - folder.id = None - folder.folder_id = None - folder.parent_id = parent_folder.id - self._db_session.add(folder) - self._db_session.commit() - self._db_session.add(EventModel(event_type='ITEM_COPY', source_id=folder.folder_id, source_type='folder')) - self._db_session.commit() - return json.dumps(folder) - - def delete_folder(self, folder_id): - folder = get_folder_by_id(self._db_session, folder_id) - self._db_session.delete(folder) - self._db_session.commit() - self._db_session.add(EventModel(event_type='ITEM_TRASH', source_id=folder.folder_id, source_type='folder')) - response.status = 204 - - def _gat(self, folder_id): - # pylint:disable=unused-argument - files = self._db_session.query(FileModel).all() - folders = self._db_session.query(FolderModel).filter(FolderModel.folder_id != '0').all() - return json.dumps({'items': files + folders}) - - def get_folder_items(self, folder_id): - view = request.params.get('view', None) - if view is not None and view == 'subfolder_sync_forked_tree': - return self._gat(folder_id) - limit = int(request.params.get('limit', 100)) - offset = int(request.params.get('offset', 0)) - # fields = request.params.get('fields') - # fields = set(fields.split(',')) if fields else set() | set(['name']) - folder = get_folder_by_id(self._db_session, folder_id) - folder_count = self._db_session.query(FolderModel).filter_by(parent_id=folder.id).count() - folders = [] - if folder_count > offset: - folders = self._db_session.query( - FolderModel, - ).filter_by(parent_id=folder.id).limit(limit).offset(offset).all() - files = [] - if len(folders) < limit: - limit -= len(folders) - offset -= folder_count - files = self._db_session.query(FileModel).filter_by(parent_id=folder.id).limit(limit).offset(offset).all() - items = folders + files - return json.dumps({'total_count': len(items), 'entries': items}) - - def create_folder(self): - """ - https://developers.box.com/docs/#folders-create-a-new-folder - """ - params = json.load(request.body) - name = params.get('name') - if name is None: - abort(400, 'Missing parameter: name') - parent = params.get('parent') - if parent is None or 'id' not in parent: - abort(400, 'Missing parameter: parent(id)') - parent_id = parent['id'] - try: - parent_folder = self._db_session.query(FolderModel).filter_by(folder_id=parent_id).one() - except NoResultFound: - abort(404) - owner = get_user_from_header(self._db_session) - name_in_use = self._db_session.query(FolderModel).filter_by(name=name).count() - if name_in_use: - abort(409, 'An item with that name already exists.') - folder = FolderModel(name=name, parent_id=parent_folder.id, owned_by=owner, created_by=owner) - self._db_session.add(folder) - self._db_session.commit() - self._db_session.add(EventModel(event_type='ITEM_CREATE', source_id=folder.folder_id, source_type='folder')) - self._db_session.commit() - return json.dumps(folder) diff --git a/test/functional/mock_box/behavior/item_behavior.py b/test/functional/mock_box/behavior/item_behavior.py deleted file mode 100644 index eaa524d8b..000000000 --- a/test/functional/mock_box/behavior/item_behavior.py +++ /dev/null @@ -1,17 +0,0 @@ -from bottle import request -from test.functional.mock_box.util.db_utils import get_folder_by_id -from test.functional.mock_box.util.http_utils import abort -from test.functional.mock_box.util import json_utils as json - - -class ItemBehavior: - def __init__(self, db_session): - self._db_session = db_session - - def _get_parent(self): - params = json.load(request.body) - parent = params.get('parent') - if parent is None or 'id' not in parent: - abort(400, 'Missing parameter: parent(id)') - parent_id = parent['id'] - return get_folder_by_id(self._db_session, parent_id) diff --git a/test/functional/mock_box/behavior/oauth2_behavior.py b/test/functional/mock_box/behavior/oauth2_behavior.py deleted file mode 100644 index d5c792061..000000000 --- a/test/functional/mock_box/behavior/oauth2_behavior.py +++ /dev/null @@ -1,151 +0,0 @@ -from datetime import datetime, timedelta -import json -from uuid import uuid4 - -from bottle import request, redirect -from sqlalchemy.orm.exc import NoResultFound - -from test.functional.mock_box.db_model.application_model import ApplicationModel -from test.functional.mock_box.db_model.token_model import TokenModel -from test.functional.mock_box.db_model.user_model import UserModel -from test.functional.mock_box.util.db_utils import get_token_record_by_token -from test.functional.mock_box.util.http_utils import abort - - -class OAuth2Behavior: - ACCESS_TOKEN_DURATION_SECONDS = 3600 - REFRESH_TOKEN_DURATION_DAYS = 60 - AUTH_CODE_DURATION_SECONDS = 30 - - def __init__(self, db_session): - self._db_session = db_session - self._auth_request = {} - - def add_application(self, client_id, client_secret, user_ids): - users = self._db_session.query(UserModel).filter(UserModel.user_id.in_(user_ids)).all() - app = ApplicationModel(client_id=client_id, client_secret=client_secret, users=users) - self._db_session.add(app) - self._db_session.commit() - - def _get_application_by_id(self, client_id): - try: - return self._db_session.query(ApplicationModel).filter_by(client_id=client_id).one() - except NoResultFound: - abort(400, f'Invalid client id: {client_id}') - - def _get_user_by_login(self, user_login): - try: - return self._db_session.query(UserModel).filter_by(login=user_login).one() - except NoResultFound: - abort(401) - - def check_authorization_header(self): - """ - Check that the request has an auth header and that its token matches the currently valid token. - Further check that the token isn't expired. - - Called by methods decorated with the authorize decorator. - """ - authorization_header = request.headers.get('Authorization') - if not authorization_header or not authorization_header.startswith('Bearer '): - abort(401) - token = authorization_header[7:] - token_record = get_token_record_by_token(self._db_session, token) - if datetime.utcnow() > token_record.expires_at: - abort(401) - - def _create_tokens(self, client_id, user_login=None, owned_by_id=None): - if owned_by_id is None: - user = self._get_user_by_login(user_login) - owned_by_id = user.id - app = self._get_application_by_id(client_id) - access_token, refresh_token = uuid4().hex, uuid4().hex - access_token_valid_until = datetime.utcnow() + timedelta(seconds=self.ACCESS_TOKEN_DURATION_SECONDS) - refresh_token_valid_until = datetime.utcnow() + timedelta(days=self.REFRESH_TOKEN_DURATION_DAYS) - self._db_session.add(TokenModel( - token=access_token, - expires_at=access_token_valid_until, - authorized_application_id=app.id, - owned_by_id=owned_by_id, - token_type='access' - )) - self._db_session.add(TokenModel( - token=refresh_token, - expires_at=refresh_token_valid_until, - authorized_application_id=app.id, - owned_by_id=owned_by_id, - token_type='refresh' - )) - return access_token, access_token_valid_until, refresh_token, refresh_token_valid_until - - def oauth2_authorize(self): - """ - Shortcut OAuth2 authorize method. Instead of presenting a webview for a user to login, provides the code - directly. Saves the tokens so they can be issued by calls to token. - """ - code = uuid4().hex - state = request.params.state - client_id = request.params.client_id - redirect_uri = request.params.redirect_uri - user_login = request.params.get('box_login', '') - self._auth_request = { - 'code': code, - 'client_id': client_id, - 'redirect_uri': redirect_uri, - 'state': state, - } - return {'user_login': user_login, 'action': '{}://{}{}'.format(*request.urlparts[:3])} - - def oauth2_finish_loop(self): - user_login = request.forms.get('login') - client_id = self._auth_request['client_id'] - access_token, _, refresh_token, _ = self._create_tokens(client_id, user_login) - self._auth_request['access_token'] = access_token - self._auth_request['refresh_token'] = refresh_token - redirect_uri = self._auth_request['redirect_uri'] - code = self._auth_request['code'] - state = self._auth_request['state'] - self._db_session.commit() - redirect(f'{redirect_uri}?code={code}&state={state}') - - def oauth2_token(self): - """ - OAuth2 /token method. - Either exchanges an auth code for an access/refresh token pair, or refreshes a token. - """ - grant_type = request.forms.get('grant_type') - client_id, client_secret = request.forms.get('client_id'), request.forms.get('client_secret') - app = self._get_application_by_id(client_id) - if client_secret != app.client_secret: - abort(400, f'Invalid client secret: {client_secret}') - - if grant_type == 'authorization_code': - code = request.forms.get('code') - if self._auth_request is None: - abort(400, f'Invalid code: {code}') - access_token, refresh_token = self._auth_request['access_token'], self._auth_request['refresh_token'] - elif grant_type == 'refresh_token': - refresh_token = request.forms.get('refresh_token') - refresh_token_record = get_token_record_by_token(self._db_session, refresh_token) - if refresh_token_record.token_type == 'refresh': - if datetime.utcnow() > refresh_token_record.expires_at: - abort(400, f'Token expired: {refresh_token}') - access_token, _, refresh_token, _ = self._create_tokens( - client_id, - owned_by_id=refresh_token_record.owned_by_id, - ) - else: - abort(400, f'Invalid token: {refresh_token}') - else: - abort(400, f'Invalid grant type: {grant_type}') - - return json.dumps({ - 'access_token': access_token, - 'refresh_token': refresh_token, - 'expires_in': self.ACCESS_TOKEN_DURATION_SECONDS, - }) - - def expire_token(self, token): - token_record = get_token_record_by_token(self._db_session, token) - token_record.expires_at = datetime.utcnow() - self._db_session.commit() diff --git a/test/functional/mock_box/behavior/user_behavior.py b/test/functional/mock_box/behavior/user_behavior.py deleted file mode 100644 index 719711bb3..000000000 --- a/test/functional/mock_box/behavior/user_behavior.py +++ /dev/null @@ -1,29 +0,0 @@ -from sqlalchemy.orm.exc import NoResultFound -from test.functional.mock_box.db_model.user_model import UserModel -from test.functional.mock_box.util.db_utils import get_user_from_header -from test.functional.mock_box.util.http_utils import abort - - -class UserBehavior: - def __init__(self, db_session): - self._db_session = db_session - - def _get_user_by_id(self, user_id): - try: - return self._db_session.query(UserModel).filter_by(user_id=user_id).one() - except NoResultFound: - abort(401) - - def get_user_info(self, user_id): - if user_id == 'me': - user_id = get_user_from_header(self._db_session).user_id - else: - user_id = int(user_id) - user = self._get_user_by_id(user_id) - return { - 'type': 'user', - 'login': user.login, - 'enterprise': None, - 'id': user.user_id, - 'name': user.name, - } diff --git a/test/functional/mock_box/box.py b/test/functional/mock_box/box.py deleted file mode 100644 index fbcfcec35..000000000 --- a/test/functional/mock_box/box.py +++ /dev/null @@ -1,336 +0,0 @@ -from datetime import datetime -from os.path import dirname, join - -from bottle import Bottle, debug, request, response, view, TEMPLATE_PATH -import sqlalchemy -from sqlalchemy.orm import sessionmaker -from sqlalchemy.pool import StaticPool - -from test.functional.mock_box.behavior.event_behavior import EventBehavior -from test.functional.mock_box.behavior.file_behavior import FileBehavior -from test.functional.mock_box.behavior.folder_behavior import FolderBehavior -from test.functional.mock_box.behavior.oauth2_behavior import OAuth2Behavior -from test.functional.mock_box.behavior.user_behavior import UserBehavior -from test.functional.mock_box.db_model import DbModel -# pylint:disable=unused-import -from test.functional.mock_box.db_model.collaboration_model import CollaborationModel -from test.functional.mock_box.db_model.event_model import EventModel -from test.functional.mock_box.db_model.file_model import FileModel -from test.functional.mock_box.db_model.folder_model import FolderModel -from test.functional.mock_box.db_model.group_model import GroupModel -from test.functional.mock_box.db_model.lock_model import LockModel -from test.functional.mock_box.db_model.share_model import ShareFileModel, ShareFolderModel -# pylint:enable=unused-import -from test.functional.mock_box.db_model.user_model import UserModel -from test.functional.mock_box.util.chaos_utils import allow_chaos -from test.functional.mock_box.util.http_utils import ( - authorize, - log_request, - rate_limit, - retry_after, - DELETE, - GET, - OPTIONS, - POST, - PUT, - StoppableWSGIRefServer, -) - - -TEMPLATE_PATH.insert(0, join(dirname(__file__), 'views')) - - -class Box: - """ - Fake Box. Sets up 4 webservers - one for auth, one for upload, one for events, and one for the rest of the api. - """ - API_PORT = 18086 - UPLOAD_PORT = 18087 - OAUTH_API_PORT = 18088 - EVENT_PORT = 18089 - OAUTH_AUTHORIZE_PORT = 18090 - RATE_LIMIT_THRESHOLD = 100 - RATE_LIMIT_REQUEST_PER_SECOND = 4 - - def __init__(self): - debug(True) - self._db_engine = None - self._db_session = None - self._db_session_maker = None - self.reset_filesystem() - # Mock Box consists of 3 webservers - one for the content API, one for the upload API, and one for OAuth2 - api, upload, oauth_api, event, oauth_authorize = Bottle(), Bottle(), Bottle(), Bottle(), Bottle() - app_mapping = { - self.API_PORT: api, - self.EVENT_PORT: event, - self.OAUTH_API_PORT: oauth_api, - self.UPLOAD_PORT: upload, - self.OAUTH_AUTHORIZE_PORT: oauth_authorize, - } - # Since we don't instantiate the servers until Box is instantiated, we have to apply the routes now - for routed_method in (getattr(self, m) for m in dir(self) if hasattr(getattr(self, m), 'route')): - app_port = routed_method.app - app = app_mapping[app_port] - app.route(routed_method.route, routed_method.verb, routed_method) - for code in [400, 401, 404, 409, 429, 500]: - for app in app_mapping.values(): - app.error(code)(self.handle_error) - self._api = StoppableWSGIRefServer(host='localhost', port=self.API_PORT).run(api) - self._upload = StoppableWSGIRefServer(host='localhost', port=self.UPLOAD_PORT).run(upload) - self._oauth_api = StoppableWSGIRefServer(host='localhost', port=self.OAUTH_API_PORT).run(oauth_api) - self._event = StoppableWSGIRefServer(host='localhost', port=self.EVENT_PORT).run(event) - self._oauth_authorize = StoppableWSGIRefServer(host='localhost', port=self.OAUTH_AUTHORIZE_PORT).run(oauth_authorize) - self._rate_limit_bucket = (self.RATE_LIMIT_THRESHOLD, datetime.utcnow()) - - @staticmethod - def handle_error(error): - response.content_type = 'application/json' - return error.body - - def shutdown(self): - """Shutdown the webservers and wait for them to exit.""" - self._api.shutdown() - self._upload.shutdown() - self._oauth_api.shutdown() - self._oauth_authorize.shutdown() - self._api.wait() - self._upload.wait() - self._oauth_api.wait() - self._oauth_authorize.wait() - - def reset_filesystem(self, users=(), applications=()): - """ - Create in-memory DB that can be accessed by multiple threads. - Set up auth requests, the rate limit bucket, and the request log. - """ - self._db_engine = sqlalchemy.create_engine( - 'sqlite:///:memory:', - connect_args={'check_same_thread': False}, - poolclass=StaticPool, - ) - DbModel.metadata.create_all(self._db_engine) - self._db_session_maker = sessionmaker(bind=self._db_engine, autoflush=True) - self._db_session = self._db_session_maker() - self._db_session.add(FolderModel(folder_id=0)) - self._db_session.commit() - self._rate_limit_bucket = (self.RATE_LIMIT_THRESHOLD, datetime.utcnow()) - self._request_log = [] - self._oauth_behavior = OAuth2Behavior(self._db_session) - self._file_behavior = FileBehavior(self._db_session) - self._folder_behavior = FolderBehavior(self._db_session) - self._event_behavior = EventBehavior(self._db_session) - self._user_behavior = UserBehavior(self._db_session) - user_ids = [] - for user_info in users: - user_name, user_login = user_info - user_id = self.add_user(user_name, user_login) - user_ids.append(user_id) - for app_info in applications: - client_id, client_secret, user_index = app_info - self.add_application(client_id, client_secret, user_ids[user_index]) - - @property - def requests(self): - return self._request_log - - @property - def oauth(self): - return self._oauth_behavior - - def add_application(self, client_id, client_secret, user_ids): - return self._oauth_behavior.add_application(client_id, client_secret, user_ids) - - def add_user(self, name, login): - user = UserModel(name=name, login=login) - self._db_session.add(user) - self._db_session.commit() - return user.user_id - - def check_authorization_header(self): - """ - Check that the request has an auth header and that its token matches the currently valid token. - Further check that the token isn't expired. - - Called by methods decorated with the authorize decorator. - """ - return self._oauth_behavior.check_authorization_header() - - def check_rate_limits(self): - """ - Implements the token bucket algorithm, whereby incoming requests remove tokens from a fixed capacity bucket - that is refilling at the steady state rate limit. - In this case, the bucket capacity is RATE_LIMIT_THRESHOLD and the refill rate is RATE_LIMIT_REQUEST_PER_SECOND. - - Requests that are over the rate limit are aborted with 429 (Too Many Requests). A Retry-After header is - specified that estimates when another request will succeed. - """ - capactiy, timestamp = self._rate_limit_bucket - now = datetime.utcnow() - delta = self.RATE_LIMIT_REQUEST_PER_SECOND * (now - timestamp).microseconds / 1e6 - new_capacity = min(self.RATE_LIMIT_THRESHOLD, capactiy + delta) - self._rate_limit_bucket = (new_capacity - 1, now) - if new_capacity < 1: - delay = (-(new_capacity - 4) / self.RATE_LIMIT_REQUEST_PER_SECOND) * 2 - retry_after(delay) - - def append_to_request_log(self): - self._request_log.append((request.method, request.route)) - - @log_request - @allow_chaos - @GET(OAUTH_AUTHORIZE_PORT, '/') - @view('oauth2') - def oauth2_authorize(self): - return self._oauth_behavior.oauth2_authorize() - - @log_request - @allow_chaos - @POST(OAUTH_AUTHORIZE_PORT, '/') - def oauth2_finish_loop(self): - return self._oauth_behavior.oauth2_finish_loop() - - @log_request - @allow_chaos - @POST(OAUTH_API_PORT, '/token') - def oauth2_token(self): - return self._oauth_behavior.oauth2_token() - - @log_request - @rate_limit - @authorize - @allow_chaos - @POST(UPLOAD_PORT, '/files//content') - def update_file(self, file_id): - return self._file_behavior.update_file(file_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @POST(UPLOAD_PORT, '/files/content') - def upload_file(self): - return self._file_behavior.upload_file() - - @log_request - @rate_limit - @authorize - @allow_chaos - @GET(API_PORT, '/files/') - def get_file_info(self, file_id): - return self._file_behavior.get_file_info(file_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @PUT(API_PORT, '/files/') - def update_file_info(self, file_id): - return self._file_behavior.update_file_info(file_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @POST(API_PORT, '/files//copy') - def copy_file(self, file_id): - return self._file_behavior.copy_file(file_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @GET(API_PORT, '/files//content') - def download_file(self, file_id): - return self._file_behavior.download_file(file_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @DELETE(API_PORT, '/files/') - def delete_file(self, file_id): - return self._file_behavior.delete_file(file_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @GET(API_PORT, '/folders/') - def get_folder_info(self, folder_id): - return self._folder_behavior.get_folder_info(folder_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @PUT(API_PORT, '/folders/') - def update_folder_info(self, folder_id): - return self._folder_behavior.update_folder_info(folder_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @POST(API_PORT, '/folders//copy') - def copy_folder(self, folder_id): - return self._folder_behavior.copy_folder(folder_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @DELETE(API_PORT, '/folders/') - def delete_folder(self, folder_id): - return self._folder_behavior.delete_folder(folder_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @GET(API_PORT, '/folders//items') - def get_folder_items(self, folder_id): - return self._folder_behavior.get_folder_items(folder_id) - - @log_request - @rate_limit - @authorize - @allow_chaos - @POST(API_PORT, '/folders') - def create_folder(self): - return self._folder_behavior.create_folder() - - @log_request - @rate_limit - @authorize - @allow_chaos - @GET(API_PORT, '/events') - def get_events(self): - return self._event_behavior.get_events() - - @log_request - @rate_limit - @authorize - @allow_chaos - @OPTIONS(API_PORT, '/events') - def get_long_poll_url(self): - return self._event_behavior.get_long_poll_url() - - @log_request - @allow_chaos - @GET(EVENT_PORT, '/subscribe') - def subscribe(self): - return self._event_behavior.subscribe() - - @log_request - @allow_chaos - @GET(EVENT_PORT, '/check') - def check(self): - return self._event_behavior.subscribe() - - @log_request - @rate_limit - @authorize - @allow_chaos - @GET(API_PORT, '/users/') - def get_user_info(self, user_id): - return self._user_behavior.get_user_info(user_id) diff --git a/test/functional/mock_box/db_model/__init__.py b/test/functional/mock_box/db_model/__init__.py deleted file mode 100644 index 79db94586..000000000 --- a/test/functional/mock_box/db_model/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from sqlalchemy.ext.declarative import declarative_base - - -DbModel = declarative_base() diff --git a/test/functional/mock_box/db_model/application_model.py b/test/functional/mock_box/db_model/application_model.py deleted file mode 100644 index 7f852798a..000000000 --- a/test/functional/mock_box/db_model/application_model.py +++ /dev/null @@ -1,22 +0,0 @@ -import sqlalchemy -from sqlalchemy.orm import relationship -from test.functional.mock_box.db_model import DbModel - - -_user_app_table = sqlalchemy.Table( - 'user_app_association', - DbModel.metadata, - sqlalchemy.Column('app_id', sqlalchemy.Integer, sqlalchemy.ForeignKey('box_application.id')), - sqlalchemy.Column('user_id', sqlalchemy.Integer, sqlalchemy.ForeignKey('box_user.id')) -) - - -class ApplicationModel(DbModel): - """DB Model for Box users.""" - __tablename__ = 'box_application' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) # pylint:disable=invalid-name - client_id = sqlalchemy.Column(sqlalchemy.String(32), nullable=False) - client_secret = sqlalchemy.Column(sqlalchemy.String(32), nullable=False) - auth_tokens = relationship('TokenModel', backref='application', cascade='save-update, delete') - users = relationship('UserModel', secondary=_user_app_table, backref='apps') diff --git a/test/functional/mock_box/db_model/collaboration_model.py b/test/functional/mock_box/db_model/collaboration_model.py deleted file mode 100644 index 6ff0a6f9f..000000000 --- a/test/functional/mock_box/db_model/collaboration_model.py +++ /dev/null @@ -1,16 +0,0 @@ -import sqlalchemy -import uuid -from test.functional.mock_box.db_model import DbModel - - -class CollaborationModel(DbModel): - """DB Model for Box collaborations.""" - __tablename__ = 'box_collaboration' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) # pylint:disable=invalid-name - collab_id = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: uuid.uuid4().hex) - item_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_folder.id')) - created_at = sqlalchemy.Column(sqlalchemy.DateTime) - modified_at = sqlalchemy.Column(sqlalchemy.DateTime) - created_by_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_user.id')) - status = sqlalchemy.Column(sqlalchemy.String(10)) diff --git a/test/functional/mock_box/db_model/event_model.py b/test/functional/mock_box/db_model/event_model.py deleted file mode 100644 index ce8769693..000000000 --- a/test/functional/mock_box/db_model/event_model.py +++ /dev/null @@ -1,36 +0,0 @@ -from datetime import datetime -import sqlalchemy -import uuid -from test.functional.mock_box.db_model import DbModel - - -class EventModel(DbModel): - """DB Model for Box events.""" - __tablename__ = 'box_event' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) # pylint:disable=invalid-name - event_id = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: uuid.uuid4().hex) - stream_position = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.utcnow) - created_by_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_user.id')) - event_type = sqlalchemy.Column(sqlalchemy.Enum( - 'ITEM_CREATE', - 'ITEM_UPLOAD', - 'ITEM_MOVE', - 'ITEM_COPY', - 'LOCK_CREATE', - 'LOCK_DESTROY', - 'ITEM_TRASH', - 'ITEM_UNDELETE_VIA_TRASH', - 'COLLAB_ADD_COLLABORATOR', - 'COLLAB_REMOVE_COLLABORATOR', - 'ITEM_SYNC', - 'ITEM_UNSYNC', - 'ITEM_RENAME', - )) - source_id = sqlalchemy.Column(sqlalchemy.String(32)) - source_type = sqlalchemy.Column(sqlalchemy.Enum('file', 'folder')) - sequence_id = sqlalchemy.Column(sqlalchemy.Integer, nullable=False) - - __mapper_args__ = { - "version_id_col": sequence_id, - } diff --git a/test/functional/mock_box/db_model/file_model.py b/test/functional/mock_box/db_model/file_model.py deleted file mode 100644 index 58d52d368..000000000 --- a/test/functional/mock_box/db_model/file_model.py +++ /dev/null @@ -1,35 +0,0 @@ -from datetime import datetime -import sqlalchemy -from sqlalchemy.orm import relationship, backref -import uuid -from test.functional.mock_box.db_model import DbModel - - -class FileModel(DbModel): - """DB Model for Box files.""" - __tablename__ = 'box_file' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) # pylint:disable=invalid-name - file_id = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: str(uuid.uuid4().int)) - name = sqlalchemy.Column(sqlalchemy.String(255)) - parent_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_folder.id')) - content = sqlalchemy.Column(sqlalchemy.LargeBinary) - created_at = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.now) - modified_at = sqlalchemy.Column(sqlalchemy.DateTime, onupdate=datetime.now) - created_by_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_user.id')) - created_by = relationship('UserModel', foreign_keys=[created_by_id]) - owned_by_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_user.id')) - shared_link = relationship( - 'ShareFileModel', - backref=backref('shared_file', remote_side=[id]), - cascade='save-update, delete', - ) - locks = relationship('LockModel', backref='item', cascade='save-update, delete') - etag = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: uuid.uuid4().hex) - sequence_id = sqlalchemy.Column(sqlalchemy.Integer, nullable=False) - sha1 = sqlalchemy.Column(sqlalchemy.String(40)) - size = sqlalchemy.Column(sqlalchemy.Integer) - - __mapper_args__ = { - "version_id_col": sequence_id, - } diff --git a/test/functional/mock_box/db_model/folder_model.py b/test/functional/mock_box/db_model/folder_model.py deleted file mode 100644 index f2bc0691b..000000000 --- a/test/functional/mock_box/db_model/folder_model.py +++ /dev/null @@ -1,35 +0,0 @@ -from datetime import datetime -import uuid -import sqlalchemy -from sqlalchemy.orm import backref, relationship -from test.functional.mock_box.db_model import DbModel - - -class FolderModel(DbModel): - """DB Model for Box folders.""" - __tablename__ = 'box_folder' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) # pylint:disable=invalid-name - folder_id = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: str(uuid.uuid4().int)) - name = sqlalchemy.Column(sqlalchemy.String(255)) - files = relationship('FileModel', backref='parent', cascade='save-update, delete') - parent_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_folder.id')) - folders = relationship('FolderModel', backref=backref('parent', remote_side=[id]), cascade='save-update, delete') - created_by_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_user.id')) - created_by = relationship('UserModel', foreign_keys=[created_by_id]) - created_at = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.now) - modified_at = sqlalchemy.Column(sqlalchemy.DateTime, onupdate=datetime.now) - owned_by_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_user.id')) - collaborations = relationship('CollaborationModel', backref='item', cascade='save-update, delete') - shared_link = relationship( - 'ShareFolderModel', - backref=backref('shared_folder', remote_side=[id]), - cascade='save-update, delete', - ) - sync_state = sqlalchemy.Column(sqlalchemy.Enum('not_synced', 'partially_synced', 'synced'), default='not_synced') - sequence_id = sqlalchemy.Column(sqlalchemy.Integer, nullable=False) - etag = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: uuid.uuid4().hex) - - __mapper_args__ = { - "version_id_col": sequence_id, - } diff --git a/test/functional/mock_box/db_model/group_model.py b/test/functional/mock_box/db_model/group_model.py deleted file mode 100644 index 3a1db3e73..000000000 --- a/test/functional/mock_box/db_model/group_model.py +++ /dev/null @@ -1,22 +0,0 @@ -import uuid -import sqlalchemy -from sqlalchemy.orm import relationship -from test.functional.mock_box.db_model import DbModel - - -_user_group_table = sqlalchemy.Table( - 'user_group_association', - DbModel.metadata, - sqlalchemy.Column('group_id', sqlalchemy.Integer, sqlalchemy.ForeignKey('box_group.id')), - sqlalchemy.Column('user_id', sqlalchemy.Integer, sqlalchemy.ForeignKey('box_user.id')) -) - - -class GroupModel(DbModel): - """DB Model for Box groups.""" - __tablename__ = 'box_group' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) # pylint:disable=invalid-name - group_id = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: uuid.uuid4().hex) - name = sqlalchemy.Column(sqlalchemy.String(255)) - users = relationship('UserModel', secondary=_user_group_table, backref='groups') diff --git a/test/functional/mock_box/db_model/lock_model.py b/test/functional/mock_box/db_model/lock_model.py deleted file mode 100644 index e1f6a485d..000000000 --- a/test/functional/mock_box/db_model/lock_model.py +++ /dev/null @@ -1,16 +0,0 @@ -import sqlalchemy -import uuid -from test.functional.mock_box.db_model import DbModel - - -class LockModel(DbModel): - """DB Model for Box locks.""" - __tablename__ = 'box_lock' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) # pylint:disable=invalid-name - lock_id = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: uuid.uuid4().hex) - item_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_file.id')) - created_at = sqlalchemy.Column(sqlalchemy.DateTime) - expires_at = sqlalchemy.Column(sqlalchemy.DateTime) - created_by_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_user.id')) - is_download_prevented = sqlalchemy.Column(sqlalchemy.Boolean) diff --git a/test/functional/mock_box/db_model/share_model.py b/test/functional/mock_box/db_model/share_model.py deleted file mode 100644 index 241d1de31..000000000 --- a/test/functional/mock_box/db_model/share_model.py +++ /dev/null @@ -1,22 +0,0 @@ -import sqlalchemy -import uuid -from test.functional.mock_box.db_model import DbModel - - -class ShareModel(DbModel): - """DB Model for Box shared links.""" - __tablename__ = 'box_share' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) # pylint:disable=invalid-name - share_id = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: uuid.uuid4().hex) - url = sqlalchemy.Column(sqlalchemy.String(50)) - - -class ShareFileModel(ShareModel): - """DB Model for Box shared links to files.""" - file_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_file.id')) - - -class ShareFolderModel(ShareModel): - """DB Model for Box shared links to folders.""" - folder_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_folder.id')) diff --git a/test/functional/mock_box/db_model/token_model.py b/test/functional/mock_box/db_model/token_model.py deleted file mode 100644 index 096ec7256..000000000 --- a/test/functional/mock_box/db_model/token_model.py +++ /dev/null @@ -1,15 +0,0 @@ -import sqlalchemy -import uuid -from test.functional.mock_box.db_model import DbModel - - -class TokenModel(DbModel): - """DB Model for Box tokens.""" - __tablename__ = 'box_token' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) # pylint:disable=invalid-name - owned_by_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_user.id')) - authorized_application_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('box_application.id')) - expires_at = sqlalchemy.Column(sqlalchemy.DateTime) - token_type = sqlalchemy.Column(sqlalchemy.Enum('access', 'refresh'), nullable=False) - token = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: uuid.uuid4().hex) diff --git a/test/functional/mock_box/db_model/user_model.py b/test/functional/mock_box/db_model/user_model.py deleted file mode 100644 index 13b30bca4..000000000 --- a/test/functional/mock_box/db_model/user_model.py +++ /dev/null @@ -1,29 +0,0 @@ -import sqlalchemy -import uuid -from sqlalchemy.orm import relationship -from test.functional.mock_box.db_model import DbModel - - -class UserModel(DbModel): - """DB Model for Box users.""" - __tablename__ = 'box_user' - - id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True) # pylint:disable=invalid-name - user_id = sqlalchemy.Column(sqlalchemy.String(32), default=lambda: uuid.uuid4().hex) - name = sqlalchemy.Column(sqlalchemy.String(255)) - login = sqlalchemy.Column(sqlalchemy.String(255)) - owned_files = relationship( - 'FileModel', - backref='owned_by', - cascade='save-update, delete', - foreign_keys='[FileModel.owned_by_id]', - ) - owned_folders = relationship( - 'FolderModel', - backref='owned_by', - cascade='save-update, delete', - foreign_keys='[FolderModel.owned_by_id]', - ) - created_collaborations = relationship('CollaborationModel', backref='created_by', cascade='save-update, delete') - created_locks = relationship('LockModel', backref='created_by', cascade='save-update, delete') - authorized_application_tokens = relationship('TokenModel', backref='owned_by', cascade='save-update, delete') diff --git a/test/functional/mock_box/util/__init__.py b/test/functional/mock_box/util/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/functional/mock_box/util/chaos_utils.py b/test/functional/mock_box/util/chaos_utils.py deleted file mode 100644 index a844b843b..000000000 --- a/test/functional/mock_box/util/chaos_utils.py +++ /dev/null @@ -1,82 +0,0 @@ -from functools import wraps -import json -import jsonpatch -from time import sleep - -from bottle import template - -from test.functional.mock_box.util.http_utils import abort - - -def allow_chaos(method): - """Decorator for a method to allow erroneous operation.""" - method.call_number = 0 - added_chaos = [] - - def add_chaos(chaos, should_apply=lambda call_number, *_: True): - added_chaos.append((chaos, should_apply)) - - def reset_chaos(call_number=0): - del added_chaos[:] - method.call_number = call_number - - @wraps(method) - def chaotic_method(self, *args, **kwargs): - skip_chaos = kwargs.pop('skip_chaos', False) - method_to_apply = method - if not skip_chaos: - method.call_number += 1 - for chaos, should_apply in added_chaos: - should_apply_result = False - if isinstance(should_apply, int): - should_apply_result = method.call_number == should_apply - elif hasattr(should_apply, '__iter__'): - should_apply_result = method.call_number in should_apply - elif hasattr(should_apply, '__call__'): - should_apply_result = should_apply(method.call_number, args, kwargs) - if should_apply_result: - method_to_apply = chaos(method_to_apply) - return method_to_apply(self, *args, **kwargs) - - chaotic_method.add_chaos = add_chaos - chaotic_method.reset_chaos = reset_chaos - - return chaotic_method - - -def delay(delay_seconds): - return lambda delayed_function: lambda *args, **kwargs: sleep(delay_seconds) or delayed_function(*args, **kwargs) - - -def error(code, message=None, headers=None): - return lambda erroneous_function: lambda *args, **kwargs: abort(code, message=message, headers=headers) - - -def html(method): - return lambda *args, **kwargs: template('html_response', response=method(*args, **kwargs)) - - -def xml(method): - return lambda *args, **kwargs: template('xml_response', response=method(*args, **kwargs)) - - -def patch(operations): - - json_patch = jsonpatch.JsonPatch(operations) - - def patcher(doc): - return json_patch.apply(doc) - - def inner(patched_function): - def patched_inner(*args, **kwargs): - return_value = patched_function(*args, **kwargs) - not_json = False - if not isinstance(return_value, str): - return_value = json.dumps(return_value) - not_json = True - return_value = patcher(return_value) - if not_json: - return_value = json.loads(return_value) - return return_value - return patched_inner - return inner diff --git a/test/functional/mock_box/util/db_utils.py b/test/functional/mock_box/util/db_utils.py deleted file mode 100644 index 5736bffa3..000000000 --- a/test/functional/mock_box/util/db_utils.py +++ /dev/null @@ -1,34 +0,0 @@ -from bottle import request -from sqlalchemy.orm.exc import NoResultFound -from test.functional.mock_box.db_model.file_model import FileModel -from test.functional.mock_box.db_model.folder_model import FolderModel -from test.functional.mock_box.db_model.token_model import TokenModel -from test.functional.mock_box.util.http_utils import abort - - -def get_file_by_id(db_session, file_id): - try: - return db_session.query(FileModel).filter_by(file_id=file_id).one() - except NoResultFound: - abort(404) - - -def get_folder_by_id(db_session, folder_id): - try: - return db_session.query(FolderModel).filter_by(folder_id=folder_id).one() - except NoResultFound: - abort(404) - - -def get_token_record_by_token(db_session, token): - try: - return db_session.query(TokenModel).filter_by(token=token).one() - except NoResultFound: - abort(401) - - -def get_user_from_header(db_session): - token = request.headers.get('Authorization')[7:] - token_record = get_token_record_by_token(db_session, token) - user_id = token_record.owned_by - return user_id diff --git a/test/functional/mock_box/util/http_utils.py b/test/functional/mock_box/util/http_utils.py deleted file mode 100644 index 8dba62656..000000000 --- a/test/functional/mock_box/util/http_utils.py +++ /dev/null @@ -1,113 +0,0 @@ -from bottle import HTTPError, ServerAdapter -from functools import partial, wraps -from threading import Thread - - -RETRY_AFTER_HEADER = str('Retry-After') - - -def abort(code, message=None, headers=None): - """ - Abort a request and send a response with the given code, and optional message and headers. - :raises: - :class:`HTTPError` - """ - raise HTTPError(code, {'message': message}, headers=headers) - - -def retry_after(delay, code=429): - """ - Abort a request and send a response, including a Retry-After header informing the client when a retry of - the request will be accepted. - """ - abort(code, headers={RETRY_AFTER_HEADER: delay}) - - -def authorize(method): - """Decorator for a method that requires authorization. Unauthorized requests will be aborted with a 401.""" - @wraps(method) - def authorized_method(self, *args, **kwargs): - skip_auth = kwargs.pop('skip_auth', False) - if not skip_auth: - self.check_authorization_header() - return method(self, *args, **kwargs) - return authorized_method - - -def rate_limit(method): - """Decorator for a method that requires rate limiting. Too many requests will be aborted with a 429.""" - @wraps(method) - def limited_method(self, *args, **kwargs): - skip_limit = kwargs.pop('skip_limit', False) - if not skip_limit: - self.check_rate_limits() - return method(self, *args, **kwargs) - return limited_method - - -def _route(verb, app, route): - """Helper decorator to apply methods to routes.""" - def routed_method(method): - setattr(method, 'verb', verb) - setattr(method, 'app', app) - setattr(method, 'route', route) - return method - return routed_method - - -def log_request(method): - """Decorator for a method to add its request to the request log.""" - @wraps(method) - def logged_method(self, *args, **kwargs): - skip_log = kwargs.pop('skip_log', False) - if not skip_log: - self.append_to_request_log() - return method(self, *args, **kwargs) - return logged_method - - -GET = partial(_route, 'GET') -POST = partial(_route, 'POST') -PUT = partial(_route, 'PUT') -DELETE = partial(_route, 'DELETE') -OPTIONS = partial(_route, 'OPTIONS') - - -class StoppableWSGIRefServer(ServerAdapter): - """ - Subclass of built-in Bottle server adapter that allows the server to be stopped. - This is important for testing, since we don't want to "serve forever". - """ - def __init__(self, host='127.0.0.1', port=8080, **options): - super().__init__(host, port, **options) - self.srv = None - self._thread = None - - def run(self, app): - from wsgiref.simple_server import WSGIRequestHandler, WSGIServer - from wsgiref.simple_server import make_server - - class FixedHandler(WSGIRequestHandler): - def address_string(self): - return self.client_address[0] - - parent = self - - def log_request(self, *args, **kw): - if not self.parent.quiet: - return WSGIRequestHandler.log_request(self, *args, **kw) - - handler_cls = self.options.get('handler_class', FixedHandler) - server_cls = self.options.get('server_class', WSGIServer) - - self.srv = make_server(self.host, self.port, app, server_cls, handler_cls) - thread = Thread(target=self.srv.serve_forever) - thread.daemon = True - thread.start() - self._thread = thread - self.srv.wait = self.wait - return self.srv - - def wait(self): - self.srv.server_close() - self._thread.join() diff --git a/test/functional/mock_box/util/json_utils.py b/test/functional/mock_box/util/json_utils.py deleted file mode 100644 index b44fa0ee8..000000000 --- a/test/functional/mock_box/util/json_utils.py +++ /dev/null @@ -1,59 +0,0 @@ -import codecs -import json -from test.functional.mock_box.db_model.file_model import FileModel -from test.functional.mock_box.db_model import DbModel - - -def serializer_factory(cls): - - class BoxObjectSerializer(json.JSONEncoder): - # pylint:disable=method-hidden - _denylist = ['metadata', 'files', 'folders'] - _cls = cls - - def default(self, o): - if hasattr(o, 'isoformat'): - return o.isoformat() - if isinstance(o, DbModel): - if DbModel not in self._cls.__bases__: - self._cls = o.__class__ - fields = {} - for field in (x for x in dir(o) if not x.startswith('_') and x not in self._denylist): - data = o.__getattribute__(field) - if isinstance(o, self._cls): - fields[field] = data - else: - try: - json.dumps(data) - fields[field] = data - except (TypeError, UnicodeDecodeError): - fields[field] = None - object_type = type(o).__name__.lower()[:-5] - fields['type'] = object_type - object_id = object_type + '_id' - if hasattr(o, 'id') and hasattr(o, object_id): - fields['id'] = o.__getattribute__(object_id) - del fields[object_id] - if isinstance(o, FileModel): - del fields['content'] - return fields - return super().default(o) - - return BoxObjectSerializer - - -def loads(string, **kwargs): - return json.loads(string, **kwargs) - - -def load(file_pointer, **kwargs): - reader = codecs.getreader('utf-8') - return json.load(reader(file_pointer), **kwargs) - - -def dump(obj, file_pointer, **kwargs): - return json.dump(obj, file_pointer, cls=serializer_factory(obj.__class__), check_circular=False, **kwargs) - - -def dumps(obj, **kwargs): - return json.dumps(obj, cls=serializer_factory(obj.__class__), check_circular=False, **kwargs) diff --git a/test/functional/mock_box/views/__init__.py b/test/functional/mock_box/views/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/functional/mock_box/views/html_response.tpl b/test/functional/mock_box/views/html_response.tpl deleted file mode 100644 index 5eb79184c..000000000 --- a/test/functional/mock_box/views/html_response.tpl +++ /dev/null @@ -1,11 +0,0 @@ - - - BAM! It's HTML instead of JSON! - - -
-
You're probably looking for the information below.
-
{{response}}
-
- - \ No newline at end of file diff --git a/test/functional/mock_box/views/oauth2.tpl b/test/functional/mock_box/views/oauth2.tpl deleted file mode 100644 index 5b6c640e8..000000000 --- a/test/functional/mock_box/views/oauth2.tpl +++ /dev/null @@ -1,7 +0,0 @@ -
- % - - % - - -
diff --git a/test/functional/mock_box/views/xml_response.tpl b/test/functional/mock_box/views/xml_response.tpl deleted file mode 100644 index 39c4b92e2..000000000 --- a/test/functional/mock_box/views/xml_response.tpl +++ /dev/null @@ -1,4 +0,0 @@ - - - {{response}} - \ No newline at end of file diff --git a/test/functional/test_delete.py b/test/functional/test_delete.py deleted file mode 100644 index 80e2af5b7..000000000 --- a/test/functional/test_delete.py +++ /dev/null @@ -1,35 +0,0 @@ -from test.util.streamable_mock_open import streamable_mock_open -from unittest.mock import patch - -import pytest - -from boxsdk.client import Client -from boxsdk.exception import BoxAPIException - - -def test_upload_then_delete(box_client, test_file_path, test_file_content, file_name): - with patch('boxsdk.object.folder.open', streamable_mock_open(read_data=test_file_content), create=True): - file_object = box_client.folder('0').upload(test_file_path, file_name) - assert file_object.delete() - folder_items = box_client.folder('0').get_items(1) - item_count = 0 - for _ in folder_items: - item_count += 1 - assert item_count == 0 - - -def test_create_folder_then_update_info(box_client, folder_name): - folder = box_client.folder('0').create_subfolder(folder_name) - assert folder.delete() - folder_items = box_client.folder('0').get_items(1) - item_count = 0 - for _ in folder_items: - item_count += 1 - assert item_count == 0 - - -@pytest.mark.parametrize('constructor', [Client.file, Client.folder]) -def test_get_item_info_for_missing_file(box_client, constructor): - with pytest.raises(BoxAPIException) as exc_info: - constructor(box_client, '1').delete() - assert exc_info.value.status == 404 diff --git a/test/functional/test_events.py b/test/functional/test_events.py deleted file mode 100644 index f158ff35a..000000000 --- a/test/functional/test_events.py +++ /dev/null @@ -1,189 +0,0 @@ -from threading import Event, Thread - -import pytest -import requests - -from boxsdk.object.folder import FolderSyncState -from boxsdk.object.event import Event as BoxEvent - - -@pytest.fixture -def box_events(box_client): - return box_client.events() - - -@pytest.fixture -def move_target(box_client): - return box_client.folder('0').create_subfolder('move target') - - -@pytest.fixture -def copy_target(box_client): - return box_client.folder('0').create_subfolder('copy target') - - -@pytest.fixture -def assert_event(box_events): - # pylint:disable=redefined-outer-name - def helper(get_item, event_type, stream_position=0): - item = get_item() - events = box_events.get_events(stream_position=stream_position)['entries'] - assert len(events) == 1 - event = events[0] - assert event['event_type'] == event_type - assert event['source']['name'] == item.name - assert event['source']['id'] == item.id - assert isinstance(event, BoxEvent) - - return helper - - -def test_get_long_poll_url(box_client): - options = box_client.events().get_long_poll_options() - with pytest.raises(requests.Timeout): - requests.get(options['url'], timeout=0.11) - - -def test_upload_causes_upload_event(uploaded_file, assert_event): - # pylint:disable=redefined-outer-name - assert_event(lambda: uploaded_file, 'ITEM_UPLOAD') - - -def test_create_folder_causes_create_event(created_subfolder, assert_event): - # pylint:disable=redefined-outer-name - assert_event(lambda: created_subfolder, 'ITEM_CREATE') - - -def test_move_file_causes_move_event(box_events, move_target, uploaded_file, assert_event): - # pylint:disable=redefined-outer-name - assert_event(lambda: uploaded_file.move(move_target), 'ITEM_MOVE', box_events.get_latest_stream_position()) - - -def test_move_folder_causes_move_event(box_events, move_target, created_subfolder, assert_event): - # pylint:disable=redefined-outer-name - assert_event(lambda: created_subfolder.move(move_target), 'ITEM_MOVE', box_events.get_latest_stream_position()) - - -def test_rename_file_causes_rename_event(box_events, uploaded_file, assert_event): - # pylint:disable=redefined-outer-name - updated_name = f'updated_{uploaded_file.name}' - assert_event(lambda: uploaded_file.rename(updated_name), 'ITEM_RENAME', box_events.get_latest_stream_position()) - - -def test_rename_folder_causes_rename_event(box_events, created_subfolder, assert_event): - # pylint:disable=redefined-outer-name - updated_name = f'updated_{created_subfolder.name}' - assert_event(lambda: created_subfolder.rename(updated_name), 'ITEM_RENAME', box_events.get_latest_stream_position()) - - -def test_copy_file_causes_copy_event(box_events, copy_target, uploaded_file, assert_event): - # pylint:disable=redefined-outer-name - assert_event(lambda: uploaded_file.copy(parent_folder=copy_target), 'ITEM_COPY', box_events.get_latest_stream_position()) - - -def test_copy_folder_causes_copy_event(box_events, copy_target, created_subfolder, assert_event): - # pylint:disable=redefined-outer-name - assert_event(lambda: created_subfolder.copy(parent_folder=copy_target), 'ITEM_COPY', box_events.get_latest_stream_position()) - - -@pytest.mark.xfail(reason='trash event has no source') -def test_delete_file_causes_trash_event(box_events, uploaded_file, assert_event): - # pylint:disable=redefined-outer-name - assert_event(uploaded_file.delete, 'ITEM_TRASH', box_events.get_latest_stream_position()) - - -@pytest.mark.xfail(reason='trash event has no source') -def test_delete_folder_causes_trash_event(box_events, created_subfolder, assert_event): - # pylint:disable=redefined-outer-name - assert_event(created_subfolder.delete, 'ITEM_TRASH', box_events.get_latest_stream_position()) - - -@pytest.mark.parametrize('sync_state,event_type', [ - (FolderSyncState.IS_SYNCED, 'ITEM_SYNC'), - (FolderSyncState.NOT_SYNCED, 'ITEM_UNSYNC'), -]) -def test_sync_folder_causes_sync_event(box_events, created_subfolder, assert_event, sync_state, event_type): - # pylint:disable=redefined-outer-name - assert_event( - lambda: created_subfolder.update_sync_state(sync_state.value), - event_type, - box_events.get_latest_stream_position(), - ) - - -@pytest.fixture -def long_poll_generator(box_events, uploaded_file, request): - # pylint:disable=redefined-outer-name - generator = box_events.generate_events_with_long_polling(stream_position=0) - - def long_poll(): - for event in generator: - long_poll_thread.events.append(event) - long_poll_thread.event_ready.set() - if long_poll_thread.should_stop_polling: - return - long_poll_thread.consumed_events.wait() - long_poll_thread.consumed_events.clear() - - long_poll_thread = Thread(target=long_poll) - - long_poll_thread.should_stop_polling = False - long_poll_thread.events = [] - long_poll_thread.event_ready = Event() - long_poll_thread.consumed_events = Event() - long_poll_thread.start() - - def fin(): - long_poll_thread.should_stop_polling = True - uploaded_file.delete() - long_poll_thread.event_ready.wait() - generator.close() - long_poll_thread.join() - - request.addfinalizer(fin) - - return long_poll_thread - - -def test_generate_events_with_long_polling(long_poll_generator, created_subfolder, uploaded_file): - # pylint:disable=redefined-outer-name - long_poll_generator.event_ready.wait() - long_poll_generator.event_ready.clear() - long_poll_generator.consumed_events.set() - long_poll_generator.event_ready.wait() - long_poll_generator.event_ready.clear() - long_poll_generator.consumed_events.set() - assert not long_poll_generator.event_ready.wait(timeout=0.01) - - assert len(long_poll_generator.events) == 2 - folder_event = next(e for e in long_poll_generator.events if e['source']['type'] == 'folder') - file_event = next(e for e in long_poll_generator.events if e['source']['type'] == 'file') - assert folder_event['event_type'] == 'ITEM_CREATE' - assert file_event['event_type'] == 'ITEM_UPLOAD' - assert folder_event['source']['id'] == created_subfolder.id - assert file_event['source']['id'] == uploaded_file.id - assert folder_event['source']['name'] == created_subfolder.name - assert file_event['source']['name'] == uploaded_file.name - - del long_poll_generator.events[:] - - uploaded_file.rename(f'updated_{uploaded_file.name}') - created_subfolder.rename(f'updated_{created_subfolder.name}') - - long_poll_generator.event_ready.wait() - long_poll_generator.event_ready.clear() - long_poll_generator.consumed_events.set() - long_poll_generator.event_ready.wait() - long_poll_generator.event_ready.clear() - long_poll_generator.consumed_events.set() - assert not long_poll_generator.event_ready.wait(timeout=0.01) - - assert len(long_poll_generator.events) == 2 - folder_event = next(e for e in long_poll_generator.events if e['source']['type'] == 'folder') - file_event = next(e for e in long_poll_generator.events if e['source']['type'] == 'file') - assert folder_event['event_type'] == 'ITEM_RENAME' - assert file_event['event_type'] == 'ITEM_RENAME' - assert folder_event['source']['id'] == created_subfolder.id - assert file_event['source']['id'] == uploaded_file.id - assert folder_event['source']['name'] == f'updated_{created_subfolder.name}' - assert file_event['source']['name'] == f'updated_{uploaded_file.name}' diff --git a/test/functional/test_file_upload_update_download.py b/test/functional/test_file_upload_update_download.py deleted file mode 100644 index ac1ec3ba3..000000000 --- a/test/functional/test_file_upload_update_download.py +++ /dev/null @@ -1,94 +0,0 @@ -from logging import Logger -from io import BytesIO -from unittest.mock import patch, Mock -from test.util.streamable_mock_open import streamable_mock_open -import pytest - -from boxsdk.network.default_network import DefaultNetworkResponse -from boxsdk.network import default_network - - -@pytest.fixture -def logger(): - return Mock(Logger) - - -@pytest.fixture(autouse=True) -def mocked_logger(logger): - with patch.object(default_network, 'getLogger') as get_logger: - get_logger.return_value = logger - yield - - -def test_upload_then_update(box_client, test_file_path, test_file_content, update_file_content, file_name): - with patch('boxsdk.object.folder.open', streamable_mock_open(read_data=test_file_content), create=True): - file_object = box_client.folder('0').upload(test_file_path, file_name) - assert file_object.name == file_name - file_object_with_info = file_object.get() - assert file_object_with_info.id == file_object.object_id - assert file_object_with_info.name == file_name - file_content = file_object.content() - expected_file_content = test_file_content.encode('utf-8') if isinstance(test_file_content, str)\ - else test_file_content - assert file_content == expected_file_content - folder_items = box_client.folder('0').get_items(100) - item = folder_items.next() - item_count = 1 - for _ in folder_items: - item_count += 1 - assert item_count == 1 - assert item.object_id == file_object.object_id - assert item.name == file_object.name - with patch('boxsdk.object.file.open', streamable_mock_open(read_data=update_file_content), create=True): - updated_file_object = file_object.update_contents(test_file_path) - assert updated_file_object.name == file_name - file_object_with_info = updated_file_object.get() - assert file_object_with_info.id == updated_file_object.object_id - assert file_object_with_info.name == file_name - file_content = updated_file_object.content() - expected_file_content = update_file_content.encode('utf-8') if isinstance(update_file_content, str)\ - else update_file_content - assert file_content == expected_file_content - folder_items = box_client.folder('0').get_items(100) - item = folder_items.next() - item_count = 1 - for _ in folder_items: - item_count += 1 - assert item_count == 1 - assert item.object_id == file_object.object_id - assert item.name == file_object.name - - -def test_upload_then_download(box_client, test_file_path, test_file_content, file_name): - with patch('boxsdk.object.folder.open', streamable_mock_open(read_data=test_file_content), create=True): - file_object = box_client.folder('0').upload(test_file_path, file_name) - writeable_stream = BytesIO() - file_object.download_to(writeable_stream) - expected_file_content = test_file_content.encode('utf-8') if isinstance(test_file_content, str)\ - else test_file_content - assert writeable_stream.getvalue() == expected_file_content - - -def test_do_not_log_downloaded_file_content_stream(box_client, test_file_path, test_file_content, file_name, logger): - with patch('boxsdk.object.folder.open', streamable_mock_open(read_data=test_file_content), create=True): - file_object = box_client.folder('0').upload(test_file_path, file_name) - writeable_stream = BytesIO() - - file_object.download_to(writeable_stream) - - expected_file_content = test_file_content.encode('utf-8') if isinstance(test_file_content, str) \ - else test_file_content - assert writeable_stream.getvalue() == expected_file_content - assert logger.info.call_args[0][1]['content'] == DefaultNetworkResponse.CONTENT_NOT_LOGGED - - -def test_do_not_log_content_of_downloaded_file(box_client, test_file_path, test_file_content, file_name, logger): - with patch('boxsdk.object.folder.open', streamable_mock_open(read_data=test_file_content), create=True): - file_object = box_client.folder('0').upload(test_file_path, file_name) - - file_content = file_object.content() - - expected_file_content = test_file_content.encode('utf-8') if isinstance(test_file_content, str) \ - else test_file_content - assert file_content == expected_file_content - assert logger.info.call_args[0][1]['content'] == DefaultNetworkResponse.CONTENT_NOT_LOGGED diff --git a/test/functional/test_item_info.py b/test/functional/test_item_info.py deleted file mode 100644 index b888d0632..000000000 --- a/test/functional/test_item_info.py +++ /dev/null @@ -1,110 +0,0 @@ -import pytest -from boxsdk.client import Client -from boxsdk.exception import BoxAPIException - - -def test_create_folder_then_update_info(created_subfolder): - # pylint:disable=redefined-outer-name - _test_create_then_update_info(created_subfolder) - - -def test_create_file_then_update_info(uploaded_file): - # pylint:disable=redefined-outer-name - _test_create_then_update_info(uploaded_file) - - -def _test_create_then_update_info(item): - updated_name = f'updated_{item.name}' - updated_item = item.update_info(data={'name': updated_name}) - assert updated_item.name == updated_name - assert item.get().name == updated_name - - -def test_create_folder_then_rename(created_subfolder): - # pylint:disable=redefined-outer-name - _test_create_then_rename(created_subfolder) - - -def test_create_file_then_rename(uploaded_file): - # pylint:disable=redefined-outer-name - _test_create_then_rename(uploaded_file) - - -def _test_create_then_rename(item): - updated_name = f'updated_{item.name}' - updated_item = item.rename(updated_name) - assert updated_item.name == updated_name - assert item.get().name == updated_name - - -def test_create_folder_then_move(box_client, created_subfolder): - # pylint:disable=redefined-outer-name - _test_create_then_move(box_client, created_subfolder) - - -def test_create_file_then_move(box_client, uploaded_file): - # pylint:disable=redefined-outer-name - _test_create_then_move(box_client, uploaded_file) - - -def _test_create_then_move(box_client, item): - item_name = item.name - move_target = box_client.folder('0').create_subfolder('move target') - item.move(move_target) - item = item.get() - assert item.name == item_name - assert item.parent['id'] == move_target.object_id - item_count = 0 - for _ in box_client.folder('0').get_items(10): - item_count += 1 - assert item_count == 1 - item_count = 0 - for _ in move_target.get_items(10): - item_count += 1 - assert item_count == 1 - - -def test_create_folder_then_copy(box_client, created_subfolder): - # pylint:disable=redefined-outer-name - _test_create_then_copy(box_client, created_subfolder) - - -def test_create_file_then_copy(box_client, uploaded_file): - # pylint:disable=redefined-outer-name - _test_create_then_copy(box_client, uploaded_file) - - -def _test_create_then_copy(box_client, item): - # pylint:disable=redefined-outer-name - copy_target = box_client.folder('0').create_subfolder('copy target') - copied_item = item.copy(parent_folder=copy_target) - item = item.get() - copied_item = copied_item.get() - assert item.id != copied_item.id - assert item.name == copied_item.name - assert copied_item.parent['id'] == copy_target.object_id - item_count = 0 - for _ in box_client.folder('0').get_items(10): - item_count += 1 - assert item_count == 2 - item_count = 0 - for _ in copy_target.get_items(10): - item_count += 1 - assert item_count == 1 - - -@pytest.mark.parametrize('constructor', [Client.file, Client.folder]) -def test_get_item_info_for_missing_item(box_client, constructor): - with pytest.raises(BoxAPIException) as exc_info: - constructor(box_client, '1').get() - assert exc_info.value.status == 404 - - -@pytest.mark.parametrize('sync_state', ['synced', 'not_synced']) -def test_set_sync_state(created_subfolder, sync_state): - # pylint:disable=redefined-outer-name - folder_with_info = created_subfolder.get(fields='sync_state') - created_subfolder.update_sync_state(sync_state) - updated_folder_with_info = created_subfolder.get(fields='sync_state') - assert folder_with_info.sync_state == 'not_synced' - assert updated_folder_with_info.sync_state == sync_state diff --git a/test/functional/test_object_clone.py b/test/functional/test_object_clone.py deleted file mode 100644 index 11913e99f..000000000 --- a/test/functional/test_object_clone.py +++ /dev/null @@ -1,41 +0,0 @@ -import pytest -from boxsdk.object.folder import FolderSyncState - - -@pytest.fixture() -def extra_network_parameters(): - return {'timeout': 1} - - -def test_folder_clone_during_create_subfolder(created_subfolder, extra_network_parameters): - # pylint:disable=redefined-outer-name - # pylint:disable=protected-access - original_folder = created_subfolder - original_folder_id = original_folder._object_id - original_folder_response_object = original_folder._response_object - original_session = original_folder._session - returned_folder = original_folder.create_subfolder('subfolder', extra_network_parameters=extra_network_parameters) - returned_session = returned_folder._session - - assert original_session is not returned_session - assert original_session._default_network_request_kwargs == {} - assert returned_session._default_network_request_kwargs == {'timeout': 1} - assert original_folder._object_id == original_folder_id - assert original_folder._response_object == original_folder_response_object - - -def test_folder_clone_during_update_sync_state(created_subfolder, extra_network_parameters): - # pylint:disable=redefined-outer-name - # pylint:disable=protected-access - original_folder = created_subfolder - original_folder_id = original_folder._object_id - original_folder_response_object = original_folder._response_object - original_session = original_folder._session - returned_folder = original_folder.update_sync_state(FolderSyncState.IS_SYNCED, extra_network_parameters=extra_network_parameters) - returned_session = returned_folder._session - - assert original_session is not returned_session - assert original_session._default_network_request_kwargs == {} - assert returned_session._default_network_request_kwargs == {'timeout': 1} - assert original_folder._object_id == original_folder_id - assert original_folder._response_object == original_folder_response_object diff --git a/test/functional/test_rate_limits.py b/test/functional/test_rate_limits.py deleted file mode 100644 index 9bcf946e3..000000000 --- a/test/functional/test_rate_limits.py +++ /dev/null @@ -1,9 +0,0 @@ -from unittest.mock import patch - - -def test_too_many_requests_causes_retry(box_client, mock_box, monkeypatch): - monkeypatch.setattr(mock_box, 'RATE_LIMIT_THRESHOLD', 1) - with patch('random.uniform', return_value=1): - box_client.folder('0').get() - box_client.folder('0').get() - assert len(mock_box.requests) == 6 # 3 auth requests, 2 real requests, and a retry diff --git a/test/functional/test_recovery.py b/test/functional/test_recovery.py deleted file mode 100644 index 8548ae154..000000000 --- a/test/functional/test_recovery.py +++ /dev/null @@ -1,95 +0,0 @@ -from test.functional.mock_box.util.chaos_utils import error, html, xml -from test.functional.mock_box.util.http_utils import RETRY_AFTER_HEADER - -import pytest - -from boxsdk.exception import BoxAPIException - - -@pytest.fixture(params=[500, 501]) -def error_code(request): - return request.param - - -@pytest.fixture(params=[202, 429]) -def retry_code(request): - return request.param - - -@pytest.fixture(autouse=True) -def mock_sleep(monkeypatch): - monkeypatch.setattr('time.sleep', lambda seconds: None) - - -@pytest.fixture(params=[1, 2, (1, 2), (1, 3)]) -def should_apply(request): - expected_num_requests = 4 # 3 auth requests and 1 folder info request - # Figure out how many times the call should fail. If should_apply is an int, it should fail iff should_apply == 1 - # If it's a sequence, figure out the first time it will succeed. It should fail until then. - if isinstance(request.param, int): - if request.param == 1: - expected_num_requests += 1 - else: - expected_num_requests += next( - (a for a in enumerate(sorted(request.param)) if a[0] + 1 != a[1]), - [0], - )[0] or len(request.param) - return request.param, expected_num_requests - - -@pytest.fixture(autouse=True) -def reset_chaos(mock_box, request): - mock_box.get_folder_info.reset_chaos() - request.addfinalizer(mock_box.get_folder_info.reset_chaos) - - -def test_client_retries_on_server_error(box_client, mock_box, error_code, should_apply): - # pylint:disable=redefined-outer-name - should_apply, expected_num_requests = should_apply - mock_box.get_folder_info.add_chaos(error(error_code), should_apply) - box_client.folder('0').get() - assert len(mock_box.requests) == expected_num_requests - - -def test_client_retries_on_retry_after(box_client, mock_box, retry_code, should_apply): - # pylint:disable=redefined-outer-name - should_apply, expected_num_requests = should_apply - mock_box.get_folder_info.add_chaos(error(retry_code, headers={RETRY_AFTER_HEADER: 1}), should_apply) - box_client.folder('0').get() - assert len(mock_box.requests) == expected_num_requests - - -def test_client_stops_retrying_after_10_server_errors(box_client, mock_box, error_code): - # pylint:disable=redefined-outer-name - mock_box.get_folder_info.add_chaos(error(error_code)) - with pytest.raises(BoxAPIException) as exc_info: - box_client.folder('0').get() - assert exc_info.value.status == error_code - assert len(mock_box.requests) == 9 # 3 auth requests, 1 try, and 5 retries - - -@pytest.mark.parametrize('chaos', [html, xml]) -def test_non_json_response_raises(box_client, mock_box, chaos): - # pylint:disable=redefined-outer-name - mock_box.get_folder_info.add_chaos(chaos) - with pytest.raises(BoxAPIException) as exc_info: - box_client.folder('0').get() - assert exc_info.value.status == 200 - assert 'json' in exc_info.value.message - - -def test_unknown_resource_raises(box_client): - folder = box_client.folder('0') - with pytest.raises(BoxAPIException) as exc_info: - # pylint:disable=protected-access - box_client.make_request('get', folder.get_url('unknown')).get_info() - # pylint:enable=protected-access - assert exc_info.value.status == 404 - - -def test_make_request_equivalent_to_get_items(box_client): - folder = box_client.folder('0') - # pylint:disable=protected-access - response = box_client.make_request('get', folder.get_url()) - # pylint:enable=protected-access - assert response.status_code == 200 diff --git a/test/functional/test_token_refresh.py b/test/functional/test_token_refresh.py deleted file mode 100644 index b27e2b943..000000000 --- a/test/functional/test_token_refresh.py +++ /dev/null @@ -1,19 +0,0 @@ -import pytest -from boxsdk.exception import BoxOAuthException - - -def test_expired_access_token_is_refreshed(box_oauth, box_client, mock_box): - # pylint:disable=protected-access - mock_box.oauth.expire_token(box_oauth._access_token) - # pylint:enable=protected-access - box_client.folder('0').get() - assert len(mock_box.requests) == 6 # GET /authorize, POST /authorize, /token, get_info, refresh /token, get_info - - -def test_expired_refresh_token_raises(box_oauth, box_client, mock_box): - # pylint:disable=protected-access - mock_box.oauth.expire_token(box_oauth._access_token) - mock_box.oauth.expire_token(box_oauth._refresh_token) - # pylint:enable=protected-access - with pytest.raises(BoxOAuthException): - box_client.folder('0').get() diff --git a/test/groups.py b/test/groups.py new file mode 100644 index 000000000..2a068adda --- /dev/null +++ b/test/groups.py @@ -0,0 +1,40 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.groups import Groups + +from box_sdk_gen.schemas.group_full import GroupFull + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def test_get_groups(): + groups: Groups = client.groups.get_groups() + assert groups.total_count >= 0 + + +def test_create_get_delete_group(): + group_name: str = get_uuid() + group_description: str = 'Group description' + group: GroupFull = client.groups.create_group( + group_name, description=group_description + ) + assert group.name == group_name + group_by_id: GroupFull = client.groups.get_group_by_id( + group.id, fields=['id', 'name', 'description', 'group_type'] + ) + assert group_by_id.id == group.id + assert group_by_id.description == group_description + updated_group_name: str = get_uuid() + updated_group: GroupFull = client.groups.update_group_by_id( + group.id, name=updated_group_name + ) + assert updated_group.name == updated_group_name + client.groups.delete_group_by_id(group.id) + with pytest.raises(Exception): + client.groups.get_group_by_id(group.id) diff --git a/test/hub_collaborations.py b/test/hub_collaborations.py new file mode 100644 index 000000000..5a1a108f9 --- /dev/null +++ b/test/hub_collaborations.py @@ -0,0 +1,88 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.v2025_r0.hubs_v2025_r0 import HubsV2025R0 + +from box_sdk_gen.managers.hubs import GetHubsV2025R0Direction + +from box_sdk_gen.schemas.v2025_r0.hub_v2025_r0 import HubV2025R0 + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.v2025_r0.hub_collaboration_v2025_r0 import ( + HubCollaborationV2025R0, +) + +from box_sdk_gen.managers.hub_collaborations import CreateHubCollaborationV2025R0Hub + +from box_sdk_gen.managers.hub_collaborations import ( + CreateHubCollaborationV2025R0AccessibleBy, +) + +from box_sdk_gen.schemas.v2025_r0.hub_collaborations_v2025_r0 import ( + HubCollaborationsV2025R0, +) + +from test.commons import get_default_client_with_user_subject + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + + +def testCRUDHubCollaboration(): + hubs: HubsV2025R0 = client.hubs.get_hubs_v2025_r0( + scope='all', sort='name', direction=GetHubsV2025R0Direction.ASC + ) + hub: HubV2025R0 = hubs.entries[0] + user_name: str = get_uuid() + user_login: str = ''.join([get_uuid(), '@gmail.com']) + user: UserFull = client.users.create_user( + user_name, login=user_login, is_platform_access_only=True + ) + created_collaboration: HubCollaborationV2025R0 = ( + client.hub_collaborations.create_hub_collaboration_v2025_r0( + CreateHubCollaborationV2025R0Hub(id=hub.id), + CreateHubCollaborationV2025R0AccessibleBy(type='user', id=user.id), + 'viewer', + ) + ) + assert not created_collaboration.id == '' + assert to_string(created_collaboration.type) == 'hub_collaboration' + assert created_collaboration.hub.id == hub.id + assert to_string(created_collaboration.accessible_by.type) == 'user' + assert created_collaboration.accessible_by.id == user.id + assert created_collaboration.role == 'viewer' + updated_collaboration: HubCollaborationV2025R0 = ( + client.hub_collaborations.update_hub_collaboration_by_id_v2025_r0( + created_collaboration.id, role='editor' + ) + ) + assert not updated_collaboration.id == '' + assert to_string(updated_collaboration.type) == 'hub_collaboration' + assert updated_collaboration.hub.id == hub.id + assert to_string(updated_collaboration.accessible_by.type) == 'user' + assert updated_collaboration.accessible_by.id == user.id + assert updated_collaboration.role == 'editor' + collaborations: HubCollaborationsV2025R0 = ( + client.hub_collaborations.get_hub_collaborations_v2025_r0(hub.id) + ) + assert len(collaborations.entries) >= 1 + retrieved_collaboration: HubCollaborationV2025R0 = ( + client.hub_collaborations.get_hub_collaboration_by_id_v2025_r0( + created_collaboration.id + ) + ) + assert retrieved_collaboration.id == created_collaboration.id + assert to_string(retrieved_collaboration.type) == 'hub_collaboration' + assert retrieved_collaboration.hub.id == hub.id + assert to_string(retrieved_collaboration.accessible_by.type) == 'user' + assert retrieved_collaboration.accessible_by.id == user.id + assert retrieved_collaboration.role == 'editor' + client.hub_collaborations.delete_hub_collaboration_by_id_v2025_r0( + created_collaboration.id + ) + client.users.delete_user_by_id(user.id) diff --git a/test/hub_items.py b/test/hub_items.py new file mode 100644 index 000000000..8e843929e --- /dev/null +++ b/test/hub_items.py @@ -0,0 +1,93 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.schemas.v2025_r0.hub_v2025_r0 import HubV2025R0 + +from box_sdk_gen.schemas.v2025_r0.hub_items_v2025_r0 import HubItemsV2025R0 + +from box_sdk_gen.schemas.v2025_r0.hub_items_manage_response_v2025_r0 import ( + HubItemsManageResponseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_item_operation_v2025_r0 import ( + HubItemOperationV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_item_operation_v2025_r0 import ( + HubItemOperationV2025R0ActionField, +) + +from box_sdk_gen.schemas.v2025_r0.hub_item_operation_result_v2025_r0 import ( + HubItemOperationResultV2025R0, +) + +from test.commons import get_default_client_with_user_subject + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.schemas.v2025_r0.folder_reference_v2025_r0 import ( + FolderReferenceV2025R0, +) + +client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + + +def testCreateDeleteGetHubItems(): + hub_title: str = get_uuid() + folder: FolderFull = client.folders.create_folder( + get_uuid(), CreateFolderParent(id='0') + ) + created_hub: HubV2025R0 = client.hubs.create_hub_v2025_r0(hub_title) + hub_items_before_add: HubItemsV2025R0 = client.hub_items.get_hub_items_v2025_r0( + created_hub.id + ) + assert len(hub_items_before_add.entries) == 0 + added_hub_items: HubItemsManageResponseV2025R0 = ( + client.hub_items.manage_hub_items_v2025_r0( + created_hub.id, + operations=[ + HubItemOperationV2025R0( + action=HubItemOperationV2025R0ActionField.ADD, + item=FolderReferenceV2025R0(id=folder.id), + ) + ], + ) + ) + added_hub_item: HubItemOperationResultV2025R0 = added_hub_items.operations[0] + assert to_string(added_hub_item.action) == 'add' + assert added_hub_item.status == 200 + assert added_hub_item.item.id == folder.id + assert added_hub_item.item.type == 'folder' + hub_items_after_add: HubItemsV2025R0 = client.hub_items.get_hub_items_v2025_r0( + created_hub.id + ) + assert len(hub_items_after_add.entries) == 1 + removed_hub_items: HubItemsManageResponseV2025R0 = ( + client.hub_items.manage_hub_items_v2025_r0( + created_hub.id, + operations=[ + HubItemOperationV2025R0( + action=HubItemOperationV2025R0ActionField.REMOVE, + item=FolderReferenceV2025R0(id=folder.id), + ) + ], + ) + ) + removed_hub_item: HubItemOperationResultV2025R0 = removed_hub_items.operations[0] + assert to_string(removed_hub_item.action) == 'remove' + assert removed_hub_item.status == 200 + assert removed_hub_item.item.id == folder.id + assert removed_hub_item.item.type == 'folder' + hub_items_after_remove: HubItemsV2025R0 = client.hub_items.get_hub_items_v2025_r0( + created_hub.id + ) + assert len(hub_items_after_remove.entries) == 0 + client.hubs.delete_hub_by_id_v2025_r0(created_hub.id) + client.folders.delete_folder_by_id(folder.id) diff --git a/test/hubs.py b/test/hubs.py new file mode 100644 index 000000000..bc61cf7bc --- /dev/null +++ b/test/hubs.py @@ -0,0 +1,74 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.v2025_r0.hub_v2025_r0 import HubV2025R0 + +from box_sdk_gen.schemas.v2025_r0.hubs_v2025_r0 import HubsV2025R0 + +from box_sdk_gen.managers.hubs import GetHubsV2025R0Direction + +from box_sdk_gen.managers.hubs import GetEnterpriseHubsV2025R0Direction + +from test.commons import get_default_client_with_user_subject + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + + +def testCreateUpdateGetAndDeleteHubs(): + hub_title: str = get_uuid() + hub_description: str = 'new Hub description' + created_hub: HubV2025R0 = client.hubs.create_hub_v2025_r0( + hub_title, description=hub_description + ) + assert created_hub.title == hub_title + assert created_hub.description == hub_description + assert to_string(created_hub.type) == 'hubs' + hub_id: str = created_hub.id + users_hubs: HubsV2025R0 = client.hubs.get_hubs_v2025_r0( + scope='all', sort='name', direction=GetHubsV2025R0Direction.ASC + ) + assert len(users_hubs.entries) > 0 + enterprise_hubs: HubsV2025R0 = client.hubs.get_enterprise_hubs_v2025_r0( + sort='name', direction=GetEnterpriseHubsV2025R0Direction.ASC + ) + assert len(enterprise_hubs.entries) > 0 + hub_by_id: HubV2025R0 = client.hubs.get_hub_by_id_v2025_r0(hub_id) + assert hub_by_id.id == hub_id + assert hub_by_id.title == hub_title + assert hub_by_id.description == hub_description + assert to_string(hub_by_id.type) == 'hubs' + new_hub_title: str = get_uuid() + new_hub_description: str = 'updated Hub description' + updated_hub: HubV2025R0 = client.hubs.update_hub_by_id_v2025_r0( + hub_id, title=new_hub_title, description=new_hub_description + ) + assert updated_hub.title == new_hub_title + assert updated_hub.description == new_hub_description + client.hubs.delete_hub_by_id_v2025_r0(hub_id) + with pytest.raises(Exception): + client.hubs.delete_hub_by_id_v2025_r0(hub_id) + + +def copyHub(): + hub_title: str = get_uuid() + hub_description: str = 'new Hub description' + created_hub: HubV2025R0 = client.hubs.create_hub_v2025_r0( + hub_title, description=hub_description + ) + copied_hub_title: str = get_uuid() + copied_hub_description: str = 'copied Hub description' + copied_hub: HubV2025R0 = client.hubs.copy_hub_v2025_r0( + created_hub.id, title=copied_hub_title, description=copied_hub_description + ) + assert not copied_hub.id == created_hub.id + assert copied_hub.title == copied_hub_title + assert copied_hub.description == copied_hub_description + client.hubs.delete_hub_by_id_v2025_r0(created_hub.id) + client.hubs.delete_hub_by_id_v2025_r0(copied_hub.id) diff --git a/test/integration/__init__.py b/test/integration/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/integration/conftest.py b/test/integration/conftest.py deleted file mode 100644 index 1490d410b..000000000 --- a/test/integration/conftest.py +++ /dev/null @@ -1,53 +0,0 @@ -from unittest.mock import Mock -import pytest - -from boxsdk import Client -from boxsdk.auth.oauth2 import OAuth2 -from boxsdk.session.session import Session, AuthorizedSession - -from .mock_network import MockNetwork - - -@pytest.fixture() -def box_client(box_oauth, mock_box_session): - # pylint:disable=redefined-outer-name - return Client(box_oauth, session=mock_box_session) - - -@pytest.fixture() -def box_oauth(unauthorized_session, client_id, client_secret, access_token, refresh_token): - # pylint:disable=redefined-outer-name - return OAuth2( - client_id, - client_secret, - session=unauthorized_session, - access_token=access_token, - refresh_token=refresh_token, - ) - - -@pytest.fixture() -def mock_box_network(): - return MockNetwork() - - -@pytest.fixture() -def unauthorized_session(mock_box_network): - return Session(network_layer=mock_box_network) - - -@pytest.fixture() -def mock_box_session(mock_box_network, box_oauth): - return AuthorizedSession(box_oauth, network_layer=mock_box_network) - - -@pytest.fixture -def generic_successful_response(generic_successful_request_response): - generic_successful_request_response.request = Mock() - return generic_successful_request_response - - -@pytest.fixture -def successful_token_mock(successful_token_request_response): - successful_token_request_response.request = Mock() - return successful_token_request_response diff --git a/test/integration/mock_network.py b/test/integration/mock_network.py deleted file mode 100644 index 1fb777e86..000000000 --- a/test/integration/mock_network.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import Mock -import requests -from boxsdk.network.default_network import DefaultNetworkResponse -from boxsdk.network.network_interface import Network - - -class MockNetwork(Network): - """Mock implementation of the network interface for testing purposes.""" - - def __init__(self): - super().__init__() - self._session = Mock(requests.Session) - self._retries = [] - - def request(self, method, url, access_token, **kwargs): - """Base class override. - Make a mock network request using a mock requests.Session. - """ - return DefaultNetworkResponse(self._session.request(method, url, **kwargs), access_token) - - def retry_after(self, delay, request_method, *args, **kwargs): - """Base class override. - Retry immediately, recording the retry request. - """ - self._retries.append((delay, request_method, args, kwargs)) - return request_method(*args, **kwargs) - - @property - def session(self): - return self._session diff --git a/test/integration/test_as_user.py b/test/integration/test_as_user.py deleted file mode 100644 index 94766b6b0..000000000 --- a/test/integration/test_as_user.py +++ /dev/null @@ -1,129 +0,0 @@ -from unittest.mock import call -import pytest -from boxsdk.config import API, Client -from boxsdk.object.user import User - - -@pytest.fixture -def as_user_headers(mock_user_id, access_token): - return { - 'Authorization': f'Bearer {access_token}', - 'As-User': mock_user_id, - 'User-Agent': Client.USER_AGENT_STRING, - 'X-Box-UA': Client.BOX_UA_STRING, - } - - -def test_client_as_user_causes_as_user_header_to_be_added( - box_client, - mock_box_network, - generic_successful_response, - mock_user_id, - as_user_headers, -): - # pylint:disable=redefined-outer-name - mock_box_network.session.request.side_effect = [generic_successful_response] - box_client.as_user(User(None, mock_user_id)).folder('0').get() - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - f'{API.BASE_API_URL}/folders/0', - headers=as_user_headers, - params=None, - log_response_content=True, - ), - ] - - -def test_folder_object_as_user_causes_as_user_header_to_be_added( - box_client, - mock_box_network, - generic_successful_response, - mock_user_id, - as_user_headers, -): - # pylint:disable=redefined-outer-name - mock_box_network.session.request.side_effect = [ - generic_successful_response, - ] - box_client.folder('0').as_user(User(None, mock_user_id)).get() - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - f'{API.BASE_API_URL}/folders/0', - headers=as_user_headers, - params=None, - log_response_content=True, - ), - ] - - -def test_group_membership_object_as_user_causes_as_user_header_to_be_added( - box_client, - mock_box_network, - generic_successful_response, - mock_user_id, - as_user_headers, -): - # pylint:disable=redefined-outer-name - mock_box_network.session.request.side_effect = [ - generic_successful_response, - ] - box_client.group_membership('0').as_user(User(None, mock_user_id)).get() - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - f'{API.BASE_API_URL}/group_memberships/0', - headers=as_user_headers, - params=None, - log_response_content=True, - ), - ] - - -def test_events_endpoint_as_user_causes_as_user_header_to_be_added( - box_client, - mock_box_network, - generic_successful_response, - mock_user_id, - as_user_headers, -): - # pylint:disable=redefined-outer-name - mock_box_network.session.request.side_effect = [ - generic_successful_response, - ] - stream_position = 1348790499819 - options = {'url': f'{API.BASE_API_URL}/events', 'retry_timeout': 60} - box_client.events().as_user(User(None, mock_user_id)).long_poll(options, stream_position) - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - options['url'], - headers=as_user_headers, - timeout=options['retry_timeout'], - params={'stream_position': stream_position}, - log_response_content=True, - ), - ] - - -def test_metadata_endpoint_as_user_causes_as_user_header_to_be_added( - box_client, - mock_box_network, - generic_successful_response, - mock_user_id, - as_user_headers, -): - # pylint:disable=redefined-outer-name - mock_box_network.session.request.side_effect = [ - generic_successful_response, - ] - box_client.file('0').metadata().as_user(User(None, mock_user_id)).get() - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - f'{API.BASE_API_URL}/files/0/metadata/global/properties', - headers=as_user_headers, - log_response_content=True, - ), - ] diff --git a/test/integration/test_retry_and_refresh.py b/test/integration/test_retry_and_refresh.py deleted file mode 100644 index 0117419da..000000000 --- a/test/integration/test_retry_and_refresh.py +++ /dev/null @@ -1,40 +0,0 @@ -from unittest.mock import ANY, call -from boxsdk.config import API - - -def test_automatic_refresh( - box_client, - mock_box_network, - generic_successful_response, - successful_token_mock, - unauthorized_response, -): - mock_box_network.session.request.side_effect = [ - unauthorized_response, - successful_token_mock, - generic_successful_response, - ] - box_client.folder('0').get() - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - f'{API.BASE_API_URL}/folders/0', - headers=ANY, - params=None, - log_response_content=True, - ), - call( - 'POST', - f'{API.OAUTH2_API_URL}/token', - data=ANY, - headers={'content-type': 'application/x-www-form-urlencoded', 'User-Agent': ANY, 'X-Box-UA': ANY}, - log_response_content=True, - ), - call( - 'GET', - f'{API.BASE_API_URL}/folders/0', - headers=ANY, - params=None, - log_response_content=True, - ), - ] diff --git a/test/integration/test_with_shared_link.py b/test/integration/test_with_shared_link.py deleted file mode 100644 index 7a0d9a253..000000000 --- a/test/integration/test_with_shared_link.py +++ /dev/null @@ -1,148 +0,0 @@ -from unittest.mock import call -import pytest -from boxsdk.config import API, Client -from boxsdk.util.shared_link import get_shared_link_header - - -@pytest.fixture -def shared_link(): - return 'https://app.box.com/s/q2i1024dvguiads6mzj2avsq9hmz43du' - - -@pytest.fixture(params=(None, 'shared_link_password')) -def shared_link_password(request): - return request.param - - -@pytest.fixture -def box_api_headers(shared_link, shared_link_password, access_token): - # pylint:disable=redefined-outer-name - box_api_header = get_shared_link_header(shared_link, shared_link_password)['BoxApi'] - return { - 'Authorization': f'Bearer {access_token}', - 'BoxApi': box_api_header, - 'User-Agent': Client.USER_AGENT_STRING, - 'X-Box-UA': Client.BOX_UA_STRING, - } - - -def test_client_with_shared_link_causes_box_api_header_to_be_added( - box_client, - mock_box_network, - generic_successful_response, - shared_link, - shared_link_password, - box_api_headers, -): - # pylint:disable=redefined-outer-name - mock_box_network.session.request.side_effect = [ - generic_successful_response, - ] - box_client.with_shared_link(shared_link, shared_link_password).folder('0').get() - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - f'{API.BASE_API_URL}/folders/0', - headers=box_api_headers, - params=None, - log_response_content=True, - ), - ] - - -def test_folder_object_with_shared_link_causes_box_api_header_to_be_added( - box_client, - mock_box_network, - generic_successful_response, - shared_link, - shared_link_password, - box_api_headers, -): - # pylint:disable=redefined-outer-name - mock_box_network.session.request.side_effect = [ - generic_successful_response, - ] - box_client.folder('0').with_shared_link(shared_link, shared_link_password).get() - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - f'{API.BASE_API_URL}/folders/0', - headers=box_api_headers, - params=None, - log_response_content=True, - ), - ] - - -def test_group_membership_object_with_shared_link_causes_box_api_header_to_be_added( - box_client, - mock_box_network, - generic_successful_response, - shared_link, - shared_link_password, - box_api_headers, -): - # pylint:disable=redefined-outer-name - mock_box_network.session.request.side_effect = [ - generic_successful_response, - ] - box_client.group_membership('0').with_shared_link(shared_link, shared_link_password).get() - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - f'{API.BASE_API_URL}/group_memberships/0', - headers=box_api_headers, - params=None, - log_response_content=True, - ), - ] - - -def test_events_endpoint_with_shared_link_causes_box_api_header_to_be_added( - box_client, - mock_box_network, - generic_successful_response, - shared_link, - shared_link_password, - box_api_headers, -): - # pylint:disable=redefined-outer-name - mock_box_network.session.request.side_effect = [ - generic_successful_response, - ] - stream_position = 1348790499819 - options = {'url': f'{API.BASE_API_URL}/events', 'retry_timeout': 60} - box_client.events().with_shared_link(shared_link, shared_link_password).long_poll(options, stream_position) - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - options['url'], - headers=box_api_headers, - timeout=options['retry_timeout'], - params={'stream_position': stream_position}, - log_response_content=True, - ), - ] - - -def test_metadata_endpoint_with_shared_link_causes_box_api_header_to_be_added( - box_client, - mock_box_network, - generic_successful_response, - shared_link, - shared_link_password, - box_api_headers, -): - # pylint:disable=redefined-outer-name - mock_box_network.session.request.side_effect = [ - generic_successful_response, - ] - box_client.file('0').metadata().with_shared_link(shared_link, shared_link_password).get() - assert mock_box_network.session.request.mock_calls == [ - call( - 'GET', - f'{API.BASE_API_URL}/files/0/metadata/global/properties', - headers=box_api_headers, - log_response_content=True, - ), - ] diff --git a/test/integration_mappings.py b/test/integration_mappings.py new file mode 100644 index 000000000..a7d938cc3 --- /dev/null +++ b/test/integration_mappings.py @@ -0,0 +1,145 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationItem + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationItemTypeField + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationAccessibleBy + +from box_sdk_gen.managers.user_collaborations import ( + CreateCollaborationAccessibleByTypeField, +) + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationRole + +from box_sdk_gen.schemas.integration_mappings import IntegrationMappings + +from box_sdk_gen.schemas.integration_mapping_partner_item_slack import ( + IntegrationMappingPartnerItemSlack, +) + +from box_sdk_gen.schemas.integration_mapping_box_item_slack import ( + IntegrationMappingBoxItemSlack, +) + +from box_sdk_gen.schemas.integration_mapping import IntegrationMapping + +from box_sdk_gen.schemas.integration_mapping_partner_item_teams_create_request import ( + IntegrationMappingPartnerItemTeamsCreateRequest, +) + +from box_sdk_gen.schemas.integration_mapping_partner_item_teams_create_request import ( + IntegrationMappingPartnerItemTeamsCreateRequestTypeField, +) + +from box_sdk_gen.schemas.folder_reference import FolderReference + +from box_sdk_gen.internal.utils import generate_byte_stream + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import to_string + +from test.commons import get_default_client + +from test.commons import get_default_client_with_user_subject + +client: BoxClient = get_default_client() + + +def testSlackIntegrationMappings(): + user_id: str = get_env_var('USER_ID') + slack_automation_user_id: str = get_env_var('SLACK_AUTOMATION_USER_ID') + slack_org_id: str = get_env_var('SLACK_ORG_ID') + slack_partner_item_id: str = get_env_var('SLACK_PARTNER_ITEM_ID') + user_client: BoxClient = get_default_client_with_user_subject(user_id) + folder: FolderFull = user_client.folders.create_folder( + get_uuid(), CreateFolderParent(id='0') + ) + user_client.user_collaborations.create_collaboration( + CreateCollaborationItem( + type=CreateCollaborationItemTypeField.FOLDER, id=folder.id + ), + CreateCollaborationAccessibleBy( + type=CreateCollaborationAccessibleByTypeField.USER, + id=slack_automation_user_id, + ), + CreateCollaborationRole.CO_OWNER, + ) + slack_integrations: IntegrationMappings = ( + user_client.integration_mappings.get_slack_integration_mapping() + ) + if len(slack_integrations.entries) == 0: + user_client.integration_mappings.create_slack_integration_mapping( + IntegrationMappingPartnerItemSlack( + id=slack_partner_item_id, slack_org_id=slack_org_id + ), + IntegrationMappingBoxItemSlack(id=folder.id), + ) + slack_mappings: IntegrationMappings = ( + user_client.integration_mappings.get_slack_integration_mapping() + ) + assert len(slack_mappings.entries) >= 1 + slack_integration_mapping: IntegrationMapping = slack_mappings.entries[0] + assert to_string(slack_integration_mapping.integration_type) == 'slack' + assert to_string(slack_integration_mapping.type) == 'integration_mapping' + assert to_string(slack_integration_mapping.box_item.type) == 'folder' + assert slack_integration_mapping.partner_item.id == slack_partner_item_id + assert slack_integration_mapping.partner_item.slack_workspace_id == slack_org_id + assert to_string(slack_integration_mapping.partner_item.type) == 'channel' + updated_slack_mapping: IntegrationMapping = ( + user_client.integration_mappings.update_slack_integration_mapping_by_id( + slack_integration_mapping.id, + box_item=IntegrationMappingBoxItemSlack(id=folder.id), + ) + ) + assert to_string(updated_slack_mapping.box_item.type) == 'folder' + assert updated_slack_mapping.box_item.id == folder.id + if len(slack_mappings.entries) > 2: + user_client.integration_mappings.delete_slack_integration_mapping_by_id( + slack_integration_mapping.id + ) + user_client.folders.delete_folder_by_id(folder.id) + + +def testTeamsIntegrationMappings(): + folder: FolderFull = client.folders.create_folder( + get_uuid(), CreateFolderParent(id='0') + ) + tenant_id: str = '1' + team_id: str = '2' + partner_item_id: str = '3' + user_id: str = get_env_var('USER_ID') + user_client: BoxClient = get_default_client_with_user_subject(user_id) + with pytest.raises(Exception): + user_client.integration_mappings.create_teams_integration_mapping( + IntegrationMappingPartnerItemTeamsCreateRequest( + type=IntegrationMappingPartnerItemTeamsCreateRequestTypeField.CHANNEL, + id=partner_item_id, + tenant_id=tenant_id, + team_id=team_id, + ), + FolderReference(id=folder.id), + ) + with pytest.raises(Exception): + user_client.integration_mappings.get_teams_integration_mapping() + integration_mapping_id: str = '123456' + with pytest.raises(Exception): + user_client.integration_mappings.update_teams_integration_mapping_by_id( + integration_mapping_id, box_item=FolderReference(id='1234567') + ) + with pytest.raises(Exception): + user_client.integration_mappings.delete_teams_integration_mapping_by_id( + integration_mapping_id + ) + client.folders.delete_folder_by_id(folder.id) diff --git a/test/integration_new/README.md b/test/integration_new/README.md deleted file mode 100644 index f3cb2d162..000000000 --- a/test/integration_new/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Integration Tests - -## Running integration tests locally - -### Create Platform Application -To run integration tests locally you will need a `Custom App` created at https://cloud.app.box.com/developers/console -with `Server Authentication (with JWT)` selected as authentication method. -Once created you can edit properties of the application: -- In section `App Access Level` select `App + Enterprise Access`. You can enable all `Application Scopes`. -- In section `Advanced Features` enable `Make API calls using the as-user header` and `Generate user access tokens`. - -Now select `Authorization` and submit application to be reviewed by account admin. - - -### Export configuration - -There are two ways to set up JWT configuration: -1. First method: -- Select `Configuration` tab and in the bottom in the section `App Settings` - download your app configuration settings as JSON. -- Specify the path to the JWT config file in `integration_tests.cfg`, e.g. `ConfigFilePath = /Users/me/jwt-config.json` -- Specify id of admin user account in `integration_tests.cfg`, e.g. `AdminUserID = 13855142101` -2. Alternatively: -- Select `Configuration` tab and in the bottom in the section `App Settings` - download your app configuration settings as JSON. -- Encode configuration file to Base64, e.g. using command: `base64 -i path_to_json_file` -- Set environment variable: `JWT_CONFIG_BASE_64` with base64 encoded jwt configuration file -- Set environment variable: `ADMIN_USER_ID` with id of admin user account - -### Running Tests - -You can run all tests (unit in all supported python environments and integration) using command: -```bash -tox -``` - -To run only integration tests, you can run: -```bash -tox -e integration-tests -``` diff --git a/test/integration_new/__init__.py b/test/integration_new/__init__.py deleted file mode 100644 index 5044e8fd2..000000000 --- a/test/integration_new/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -import base64 -import configparser -import os -import json -from pathlib import Path -from typing import Optional - -from boxsdk.auth.jwt_auth import JWTAuth -from boxsdk.client import Client - - -JWT_CONFIG_ENV_VAR_NAME = 'JWT_CONFIG_BASE_64' -ADMIN_USER_ID_ENV_VAR_NAME = 'ADMIN_USER_ID' -CURRENT_DIR_PATH = str(Path(os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))))) -CONFIG_PATH = f'{CURRENT_DIR_PATH}/integration_tests.cfg' -CONFIG_PARSER = configparser.ConfigParser() -CONFIG_PARSER.read(CONFIG_PATH) - - -def get_jwt_config() -> JWTAuth: - jwt_config = read_jwt_config_from_env_var() or read_jwt_config_from_file() - - if not jwt_config: - raise RuntimeError( - f'JWT config cannot be loaded. Missing environment variable: {JWT_CONFIG_ENV_VAR_NAME} or JWT config path.' - ) - return jwt_config - - -def get_admin_user_id() -> str: - admin_user_id = os.getenv(ADMIN_USER_ID_ENV_VAR_NAME) or CONFIG_PARSER["JWT"].get('AdminUserID') - - if not admin_user_id: - raise RuntimeError( - f'Unknown admin user id. Missing environment variable: {ADMIN_USER_ID_ENV_VAR_NAME} or value in {CONFIG_PATH}.' - ) - - return admin_user_id - - -def read_jwt_config_from_env_var() -> Optional[JWTAuth]: - jwt_config_base64 = os.getenv(JWT_CONFIG_ENV_VAR_NAME) - if not jwt_config_base64: - return None - jwt_config_str = base64.b64decode(jwt_config_base64) - jwt_config_json = json.loads(jwt_config_str) - return JWTAuth.from_settings_dictionary(jwt_config_json) - - -def read_jwt_config_from_file() -> Optional[JWTAuth]: - jwt_config_file_path = CONFIG_PARSER["JWT"].get('ConfigFilePath') - if not jwt_config_file_path: - return None - return JWTAuth.from_settings_file(jwt_config_file_path) - - -def create_client(jwt_config: JWTAuth): - return Client(jwt_config) - - -def create_user_client(jwt_config: JWTAuth): - admin_user_id = get_admin_user_id() - jwt_config.authenticate_user(admin_user_id) - return Client(jwt_config) - - -config = get_jwt_config() -CLIENT = create_client(config) -USER_CLIENT = create_user_client(config) diff --git a/test/integration_new/conftest.py b/test/integration_new/conftest.py deleted file mode 100644 index 84785bc6f..000000000 --- a/test/integration_new/conftest.py +++ /dev/null @@ -1,62 +0,0 @@ -from test.integration_new.context_managers.box_test_user import BoxTestUser -from test.integration_new.context_managers.local_large_file import LocalLargeFile -from test.integration_new import util, CLIENT -import pytest - - -@pytest.fixture(scope='package') -def small_file_name(): - return 'small.pdf' - - -@pytest.fixture(scope='package') -def small_file_path(small_file_name): - return util.get_file_path(small_file_name) - - -@pytest.fixture(scope='package') -def small_file_v2_name(): - return 'small_v2.pdf' - - -@pytest.fixture(scope='package') -def small_file_v2_path(small_file_v2_name): - return util.get_file_path(small_file_v2_name) - - -@pytest.fixture(scope='package') -def image_name(): - return 'image.png' - - -@pytest.fixture(scope='package') -def image_path(image_name): - return util.get_file_path(image_name) - - -@pytest.fixture(scope='session') -def large_file_name(): - return f'{util.random_name()}.pdf' - - -@pytest.fixture(scope="session") -def large_file(large_file_name): - with LocalLargeFile(name=large_file_name) as large_file: - yield large_file - - -@pytest.fixture(scope="module") -def user(): - with BoxTestUser(login=f'{util.random_name()}@box.com') as user: - yield user - - -@pytest.fixture(scope="package") -def other_user(): - with BoxTestUser(login=None) as other_user: - yield other_user - - -@pytest.fixture(scope="package") -def other_client(other_user): - yield CLIENT.as_user(other_user) diff --git a/test/integration_new/context_managers/box_metadata_template.py b/test/integration_new/context_managers/box_metadata_template.py deleted file mode 100644 index 8ffc27bb0..000000000 --- a/test/integration_new/context_managers/box_metadata_template.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Any, Iterable - -from boxsdk.object.metadata_template import MetadataTemplate, MetadataField, MetadataFieldType -from test.integration_new import CLIENT - - -class BoxTestMetadataTemplate: - - def __init__(self, *, display_name: str, fields: Iterable = None): - if fields is None: - fields = [] - self._metadata_template: MetadataTemplate = CLIENT.create_metadata_template(display_name=display_name, fields=fields) - - def __enter__(self) -> MetadataTemplate: - return self._metadata_template - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - self._metadata_template.delete() diff --git a/test/integration_new/context_managers/box_retention_policy.py b/test/integration_new/context_managers/box_retention_policy.py deleted file mode 100644 index 3086ded85..000000000 --- a/test/integration_new/context_managers/box_retention_policy.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Any, Union - -from boxsdk.object.retention_policy import RetentionPolicy -from test.integration_new import USER_CLIENT - - -class BoxRetentionPolicy: - - DEFAULT_RETENTION_POLICY_NAME = "modifiable_retention_policy_for_integration_tests_extendable_by_owner" - - def __init__( - self, - *, - name: str = DEFAULT_RETENTION_POLICY_NAME, - disposition_action: str = 'permanently_delete', - retention_length: Union[float, int] = 1 - ): - - policy_type = 'finite' if retention_length < float('inf') else 'infinite' - policies_found = list(USER_CLIENT.get_retention_policies(policy_name=name, policy_type=policy_type)) - - if policies_found and policies_found[0].get().status == 'active': - self._retention_policy = policies_found[0] - else: - self._retention_policy: RetentionPolicy = USER_CLIENT.create_retention_policy( - policy_name=name, - disposition_action=disposition_action, - retention_length=retention_length, - can_owner_extend_retention=True, - retention_type='modifiable' - ) - - def __enter__(self) -> RetentionPolicy: - return self._retention_policy - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - pass diff --git a/test/integration_new/context_managers/box_retention_policy_assigment.py b/test/integration_new/context_managers/box_retention_policy_assigment.py deleted file mode 100644 index 7fb1eecc2..000000000 --- a/test/integration_new/context_managers/box_retention_policy_assigment.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Any, Union - -from boxsdk.object.enterprise import Enterprise -from boxsdk.object.folder import Folder -from boxsdk.object.metadata_template import MetadataTemplate -from boxsdk.object.retention_policy import RetentionPolicy -from boxsdk.object.retention_policy_assignment import RetentionPolicyAssignment - - -class BoxRetentionPolicyAssignment: - - def __init__( - self, - retention_policy: RetentionPolicy, - assignee: Union['Folder', 'Enterprise', 'MetadataTemplate'] - ): - self._retention_policy_assignment = retention_policy.assign(assignee) - - def __enter__(self) -> RetentionPolicyAssignment: - return self._retention_policy_assignment - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - self._retention_policy_assignment.delete() diff --git a/test/integration_new/context_managers/box_sign_request.py b/test/integration_new/context_managers/box_sign_request.py deleted file mode 100644 index adab894b8..000000000 --- a/test/integration_new/context_managers/box_sign_request.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Any, Iterable - -from test.integration_new import CLIENT -from boxsdk.object.web_link import WebLink - - -class BoxTestSignRequest: - - def __init__(self, *, files: Iterable, signers: Iterable, parent_folder_id: str): - self._sign_request = CLIENT.create_sign_request_v2(signers, files, parent_folder_id) - - def __enter__(self) -> WebLink: - return self._sign_request - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - self._sign_request.cancel() diff --git a/test/integration_new/context_managers/box_test_file.py b/test/integration_new/context_managers/box_test_file.py deleted file mode 100644 index 49d42ebbe..000000000 --- a/test/integration_new/context_managers/box_test_file.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Any, Optional - -from test.integration_new import util -from test.integration_new import CLIENT -from boxsdk.object.file import File -from boxsdk.object.folder import Folder - - -class BoxTestFile: - - def __init__(self, *, file_path: str, name: str = None, parent_folder: Optional[Folder] = None): - if name is None: - extention = file_path.split('.')[-1] - name = f'{util.random_name()}.{extention}' - if parent_folder is None: - parent_folder = CLIENT.folder('0') - self._file: File = parent_folder.upload(file_path=file_path, file_name=name) - - def __enter__(self) -> File: - return self._file - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - util.permanently_delete(self._file) diff --git a/test/integration_new/context_managers/box_test_folder.py b/test/integration_new/context_managers/box_test_folder.py deleted file mode 100644 index 077137681..000000000 --- a/test/integration_new/context_managers/box_test_folder.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Any, Optional - -from test.integration_new import util -from test.integration_new import CLIENT -from boxsdk.object.folder import Folder - - -class BoxTestFolder: - - def __init__(self, *, name: str = None, parent_folder: Optional[Folder] = None): - if name is None: - name = util.random_name() - if parent_folder is None: - parent_folder = CLIENT.root_folder() - self._folder: Folder = parent_folder.create_subfolder(name=name) - - def __enter__(self) -> Folder: - return self._folder - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - util.permanently_delete(self._folder) diff --git a/test/integration_new/context_managers/box_test_group.py b/test/integration_new/context_managers/box_test_group.py deleted file mode 100644 index 7c666a714..000000000 --- a/test/integration_new/context_managers/box_test_group.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Any - -from boxsdk.object.group import Group -from test.integration_new import util -from test.integration_new import CLIENT - - -class BoxTestGroup: - - def __init__(self, *, name: str = None): - if name is None: - name = util.random_name() - - self._group: Group = CLIENT.create_group(name=name) - - def __enter__(self) -> Group: - return self._group - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - self._group.delete() diff --git a/test/integration_new/context_managers/box_test_user.py b/test/integration_new/context_managers/box_test_user.py deleted file mode 100644 index ae8363ed8..000000000 --- a/test/integration_new/context_managers/box_test_user.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Any - -from boxsdk.object.user import User -from test.integration_new import util -from test.integration_new import CLIENT - - -class BoxTestUser: - - def __init__(self, *, name: str = None, login: str = None): - if name is None: - name = util.random_name() - - self._user: User = CLIENT.create_user(name=name, login=login) - - def __enter__(self) -> User: - return self._user - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - self._user.delete(notify=False, force=True) diff --git a/test/integration_new/context_managers/box_test_web_link.py b/test/integration_new/context_managers/box_test_web_link.py deleted file mode 100644 index cfae6f97f..000000000 --- a/test/integration_new/context_managers/box_test_web_link.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Any, Optional - -from test.integration_new import util -from test.integration_new import CLIENT -from boxsdk.object.web_link import WebLink -from boxsdk.object.folder import Folder - - -class BoxTestWebLink: - - def __init__(self, *, url: str, name: str = None, parent_folder: Optional[Folder] = None): - if name is None: - name = util.random_name() - if parent_folder is None: - parent_folder = CLIENT.folder('0') - self._web_link: WebLink = parent_folder.create_web_link(target_url=url, name=name) - - def __enter__(self) -> WebLink: - return self._web_link - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - util.permanently_delete(self._web_link) diff --git a/test/integration_new/context_managers/local_large_file.py b/test/integration_new/context_managers/local_large_file.py deleted file mode 100644 index 5c6a040e9..000000000 --- a/test/integration_new/context_managers/local_large_file.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -from typing import Any - -from test.integration_new import util - - -class LocalLargeFile: - - def __init__(self, *, name: str = None): - if name is None: - name = util.random_name() - with open(name, 'wb') as f: - f.seek(1024 * 1024 * 21) - f.write(b'0') - self.path = name - - def __enter__(self): - return self - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - if os.path.exists(self.path): - os.remove(self.path) diff --git a/test/integration_new/integration_tests.cfg b/test/integration_new/integration_tests.cfg deleted file mode 100644 index 0f662a174..000000000 --- a/test/integration_new/integration_tests.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[JWT] -ConfigFilePath = -AdminUserID = diff --git a/test/integration_new/object/ai_itest.py b/test/integration_new/object/ai_itest.py deleted file mode 100644 index 8b478d4fe..000000000 --- a/test/integration_new/object/ai_itest.py +++ /dev/null @@ -1,65 +0,0 @@ -from datetime import datetime - -import pytest - -from test.integration_new import CLIENT -from test.integration_new.context_managers.box_test_folder import BoxTestFolder -from test.integration_new.context_managers.box_test_file import BoxTestFile - -FOLDER_TESTS_DIRECTORY_NAME = 'folder-integration-tests' - - -@pytest.fixture(scope='module', autouse=True) -def parent_folder(): - with BoxTestFolder(name=f'{FOLDER_TESTS_DIRECTORY_NAME} {datetime.now()}') as folder: - yield folder - - -def test_send_ai_question(parent_folder, small_file_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - items = [{ - 'id': file.id, - 'type': 'file', - 'content': 'The sun raises in the east.' - }] - answer = CLIENT.send_ai_question( - items=items, - prompt='Which direction does the sun raise?', - mode='single_item_qa', - ) - assert 'east' in answer['answer'].lower() - assert answer['completion_reason'] == 'done' - - -def test_send_ai_text_gen(parent_folder, small_file_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - items = [{ - 'id': file.id, - 'type': 'file', - 'content': 'The sun raises in the east.' - }] - dialogue_history = [{ - 'prompt': 'How does the sun rise?', - 'answer': 'The sun raises in the east.', - 'created_at': '2013-12-12T10:53:43-08:00' - }, { - 'prompt': 'How many hours does it take for the sun to rise?', - 'answer': 'It takes 24 hours for the sun to rise.', - 'created_at': '2013-12-12T11:20:43-08:00' - }] - answer = CLIENT.send_ai_text_gen( - dialogue_history=dialogue_history, - items=items, - prompt='Which direction does the sun raise?', - ) - assert 'east' in answer['answer'].lower() - assert answer['completion_reason'] == 'done' - - -def test_get_ai_agent_default_config(): - config = CLIENT.get_ai_agent_default_config( - mode='text_gen', - language='en' - ) - assert config['type'] == 'ai_agent_text_gen' - assert config['basic_gen']['model'] != '' diff --git a/test/integration_new/object/collaboration_itest.py b/test/integration_new/object/collaboration_itest.py deleted file mode 100644 index 3a927fcca..000000000 --- a/test/integration_new/object/collaboration_itest.py +++ /dev/null @@ -1,35 +0,0 @@ -from datetime import datetime - -import pytest - -from boxsdk.object.collaboration import CollaborationRole - -from test.integration_new.context_managers.box_test_file import BoxTestFile -from test.integration_new.context_managers.box_test_folder import BoxTestFolder - -FILE_TESTS_DIRECTORY_NAME = 'collaboration-integration-tests' - - -@pytest.fixture(scope="module", autouse=True) -def parent_folder(): - with BoxTestFolder(name=f'{FILE_TESTS_DIRECTORY_NAME} {datetime.now()}') as folder: - yield folder - - -def test_collaboration(parent_folder, small_file_path, other_user,): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as test_file: - collaboration = test_file.collaborate(other_user, CollaborationRole.VIEWER) - try: - assert collaboration.item.id == test_file.id - assert collaboration.accessible_by.id == other_user.id - assert collaboration.role == CollaborationRole.VIEWER - - updated_expiration_date = '2088-01-01T00:00:00-08:00' - collaboration_update = {'role': CollaborationRole.EDITOR, 'expires_at': updated_expiration_date} - updated_collaboration = collaboration.update_info(data=collaboration_update) - - assert updated_collaboration.role == CollaborationRole.EDITOR - assert updated_collaboration.expires_at != collaboration.expires_at - assert updated_collaboration.expires_at == updated_expiration_date - finally: - collaboration.delete() diff --git a/test/integration_new/object/file_itest.py b/test/integration_new/object/file_itest.py deleted file mode 100644 index b11f58750..000000000 --- a/test/integration_new/object/file_itest.py +++ /dev/null @@ -1,268 +0,0 @@ -import io -import os -import urllib.request -from datetime import datetime, timedelta -from dateutil import parser -import pytest -import pytz - -from boxsdk import BoxAPIException -from test.integration_new.context_managers.box_retention_policy_assigment import BoxRetentionPolicyAssignment -from test.integration_new.context_managers.box_retention_policy import BoxRetentionPolicy -from test.integration_new import util -from test.integration_new.context_managers.box_test_file import BoxTestFile -from test.integration_new.context_managers.box_test_folder import BoxTestFolder - - -FILE_TESTS_DIRECTORY_NAME = 'file-integration-tests' - - -@pytest.fixture(scope="module", autouse=True) -def parent_folder(): - with BoxTestFolder(name=f'{FILE_TESTS_DIRECTORY_NAME} {datetime.now()}') as folder: - yield folder - - -@pytest.fixture(scope="module", autouse=True) -def test_file(parent_folder, small_file_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - yield file - - -def test_preflight_check(test_file): - file_size = 1213 - accelerator_url = test_file.preflight_check(size=file_size, name=util.random_name()) - - assert accelerator_url - - -def test_create_upload_session_for_file_version(test_file): - file_size = 26000000 - upload_session = test_file.create_upload_session(file_size=file_size) - - assert upload_session.id - assert upload_session.type == 'upload_session' - - -def test_get_chuncked_uploader_for_file_version(test_file, large_file): - total_size = os.stat(large_file.path).st_size - - chunked_uploader = test_file.get_chunked_uploader(file_path=large_file.path) - - assert chunked_uploader._file_size == total_size - assert chunked_uploader._upload_session - - -def test_content(test_file, small_file_path): - file_content = test_file.content() - - with open(small_file_path, 'rb') as expected_file: - assert expected_file.read() == file_content - - -def test_download_to(test_file, small_file_path): - output_file_name = util.random_name() - - with open(output_file_name, 'wb') as output_file: - test_file.download_to(writeable_stream=output_file) - - with open(output_file_name, 'rb') as downloaded_file: - with open(small_file_path, 'rb') as expected_file: - assert downloaded_file.read() == expected_file.read() - - os.remove(output_file_name) - - -def test_get_download_url(test_file, small_file_path): - download_url = test_file.get_download_url() - - with open(small_file_path, 'rb') as expected_file: - with urllib.request.urlopen(download_url) as downloaded_file: - assert downloaded_file.read() == expected_file.read() - - -def test_update_contents_with_stream(parent_folder, small_file_path, small_file_v2_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - with open(small_file_v2_path, 'rb') as file_v2: - updated_file = file.update_contents_with_stream(file_stream=file_v2) - - with open(small_file_v2_path, 'rb') as file_v2: - assert updated_file.content() == file_v2.read() - - -def test_update_contents(parent_folder, small_file_path, small_file_v2_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - updated_file = file.update_contents(small_file_v2_path) - - with open(small_file_v2_path, 'rb') as file_v2: - assert updated_file.content() == file_v2.read() - - -def test_lock_and_unlock(parent_folder, small_file_path, small_file_v2_path, other_user, other_client): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - file.collaborate(accessible_by=other_user, role='editor') - - try: - file.lock(prevent_download=True) - - assert file.get(fields=('lock',)).lock - - with pytest.raises(BoxAPIException): - other_client.file(file.object_id).update_contents(small_file_v2_path) - - with pytest.raises(BoxAPIException): - other_client.file(file.object_id).download_to(io.BytesIO()) - - finally: - file.unlock() - - assert file.get(fields=('lock',)).lock is None - - -def test_get_shared_link(parent_folder, small_file_path, other_user, other_client): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - file.collaborate(accessible_by=other_user, role='editor') - - shared_link = other_client.file(file.object_id).get_shared_link(allow_edit=True, allow_preview=True, allow_download=True) - - result_permissions = file.get().shared_link['permissions'] - assert result_permissions == {'can_preview': True, 'can_download': True, 'can_edit': True} - assert other_client.get_shared_item(shared_link).id == file.id - - -def test_get_shared_link_download_url(parent_folder, small_file_path, other_user, other_client): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - file.collaborate(accessible_by=other_user, role='editor') - - shared_link_download_url = other_client.file(file.object_id).get_shared_link_download_url() - - with open(small_file_path, 'rb') as expected_file: - with urllib.request.urlopen(shared_link_download_url) as downloaded_file: - assert downloaded_file.read() == expected_file.read() - - -def test_add_and_get_comments(parent_folder, small_file_path): - test_comment = 'this is a test comment' - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - file.add_comment(message=test_comment) - comment = list(file.get_comments())[0] - - assert comment.id - assert comment.message == test_comment - - -def test_and_get_task(parent_folder, small_file_path): - test_task_action = 'review' - test_task_message = 'this is a test task message' - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - file.create_task(message=test_task_message, action=test_task_action) - task = list(file.get_tasks())[0] - - assert task.id - assert task.message == test_task_message - assert task.action == test_task_action - - -def test_get_previous_versions(parent_folder, small_file_path, small_file_v2_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as test_file: - test_file_version = test_file.file_version - - assert not list(test_file.get_previous_versions()) - - updated_test_file = test_file.update_contents(small_file_v2_path) - updated_file_version = updated_test_file.file_version - - previous_version = list(updated_test_file.get_previous_versions())[0] - - assert previous_version != updated_file_version - assert previous_version == test_file_version - assert previous_version.type == 'file_version' - - -def test_promote_version(parent_folder, small_file_path, small_file_v2_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as test_file: - test_file_version = test_file.file_version - - updated_test_file = test_file.update_contents(small_file_v2_path) - updated_file_version = updated_test_file.file_version - - assert updated_file_version != test_file_version - - promoted_file_version = updated_test_file.promote_version(file_version=test_file_version) - - assert test_file.get(fields=('file_version',)).file_version == promoted_file_version - assert set(updated_test_file.get_previous_versions()) == {test_file_version, updated_file_version} - - with open(small_file_path, 'rb') as expected_file_v1: - assert expected_file_v1.read() == test_file.content() - - -def test_delete_version(parent_folder, small_file_path, small_file_v2_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as test_file: - updated_test_file = test_file.update_contents(small_file_v2_path) - - previous_version = list(updated_test_file.get_previous_versions())[0] - assert previous_version.trashed_at is None - - was_deletion_successs = updated_test_file.delete_version(file_version=previous_version) - - assert was_deletion_successs - assert list(test_file.get_previous_versions())[0].trashed_at - - -def test_get_embed_url(parent_folder, image_path): - with BoxTestFile(parent_folder=parent_folder, file_path=image_path) as test_file: - embed_url = test_file.get_embed_url() - - assert embed_url - - -def test_get_representation_info(parent_folder, image_path): - with BoxTestFile(parent_folder=parent_folder, file_path=image_path) as test_file: - representation_info = test_file.get_representation_info(rep_hints='[png]') - - assert representation_info - - -def test_get_thumbnail(parent_folder, image_path): - with BoxTestFile(parent_folder=parent_folder, file_path=image_path) as test_file: - thumbnail = test_file.get_thumbnail(extension='jpg', min_width=32, max_width=32, min_height=32, max_height=32) - - assert thumbnail - - -def test_get_thumbnail_representation(parent_folder, image_path): - with BoxTestFile(parent_folder=parent_folder, file_path=image_path) as test_file: - thumbnail_representation = test_file.get_thumbnail_representation(extension='jpg', dimensions='32x32') - - assert thumbnail_representation - - -def test_copy(test_file, parent_folder): - copied_file_name = util.random_name() - copied_file = test_file.copy(parent_folder=parent_folder, name=copied_file_name) - - try: - assert copied_file.id != test_file - assert copied_file.name == copied_file_name - assert test_file.content() == copied_file.content() - finally: - util.permanently_delete(copied_file) - - -def test_set_disposition_at(parent_folder, small_file_path): - with BoxRetentionPolicy(disposition_action='permanently_delete', retention_length=1) as retention_policy: - with BoxTestFolder(name=f'{FILE_TESTS_DIRECTORY_NAME} {datetime.now()}') as folder_under_retention: - with BoxRetentionPolicyAssignment(retention_policy=retention_policy, assignee=folder_under_retention): - with BoxTestFile(parent_folder=folder_under_retention, file_path=small_file_path) as file_under_retention: - old_disposition_str = file_under_retention.get(fields=('disposition_at',)).disposition_at - old_disposition_datetime = parser.parse(old_disposition_str) - - new_disposition_date = datetime.now().replace(microsecond=0).astimezone(pytz.utc) + timedelta(days=2) - file_under_retention.set_disposition_at(new_disposition_date) - - updated_disposition_str = file_under_retention.get(fields=('disposition_at',)).disposition_at - updated_disposition_datetime = parser.parse(updated_disposition_str) - - assert updated_disposition_datetime.astimezone(pytz.utc) == new_disposition_date - assert updated_disposition_datetime.astimezone(pytz.utc) != old_disposition_datetime.astimezone(pytz.utc) diff --git a/test/integration_new/object/folder_itest.py b/test/integration_new/object/folder_itest.py deleted file mode 100644 index 8da6d69b7..000000000 --- a/test/integration_new/object/folder_itest.py +++ /dev/null @@ -1,238 +0,0 @@ -from datetime import datetime - -import pytest -import hashlib -import os - -from pytest_lazyfixture import lazy_fixture - -from boxsdk.exception import BoxAPIException -from boxsdk.object.collaboration import CollaborationRole -from test.integration_new.context_managers.box_metadata_template import BoxTestMetadataTemplate -from test.integration_new.context_managers.box_test_group import BoxTestGroup -from test.integration_new.context_managers.box_test_web_link import BoxTestWebLink -from test.integration_new import CLIENT -from test.integration_new import util -from test.integration_new.context_managers.box_test_file import BoxTestFile -from test.integration_new.context_managers.box_test_folder import BoxTestFolder - -FOLDER_TESTS_DIRECTORY_NAME = 'folder-integration-tests' - - -@pytest.fixture(scope="module", autouse=True) -def parent_folder(): - with BoxTestFolder(name=f'{FOLDER_TESTS_DIRECTORY_NAME} {datetime.now()}') as folder: - yield folder - - -@pytest.fixture(scope="module") -def group(): - with BoxTestGroup() as group: - yield group - - -def test_preflight_check(parent_folder): - file_size = 1213 - accelerator_url = parent_folder.preflight_check(file_size, util.random_name()) - - assert accelerator_url - - -def test_manual_chunked_upload(parent_folder, large_file, large_file_name): - total_size = os.stat(large_file.path).st_size - sha1 = hashlib.sha1() - with open(large_file.path, 'rb') as content_stream: - upload_session = parent_folder.create_upload_session(file_size=total_size, file_name=large_file_name) - part_array = [] - for part_num in range(upload_session.total_parts): - - copied_length = 0 - chunk = b'' - while copied_length < upload_session.part_size: - bytes_read = content_stream.read(upload_session.part_size - copied_length) - if bytes_read is None: - continue - if len(bytes_read) == 0: - break - chunk += bytes_read - copied_length += len(bytes_read) - - uploaded_part = upload_session.upload_part_bytes(chunk, part_num * upload_session.part_size, total_size) - part_array.append(uploaded_part) - sha1.update(chunk) - content_sha1 = sha1.digest() - uploaded_file = upload_session.commit(content_sha1=content_sha1, parts=part_array) - - try: - assert uploaded_file.id - assert uploaded_file.name == large_file_name - assert uploaded_file.parent == parent_folder - assert uploaded_file.size == total_size - finally: - util.permanently_delete(uploaded_file) - - -def test_auto_chunked_upload_using_upload_session_urls(parent_folder, large_file, large_file_name): - total_size = os.stat(large_file.path).st_size - chunked_uploader = parent_folder.get_chunked_uploader(large_file.path, use_upload_session_urls=True) - - uploaded_file = chunked_uploader.start() - - try: - assert uploaded_file.id - assert uploaded_file.name == large_file_name - assert uploaded_file.parent == parent_folder - assert uploaded_file.size == total_size - finally: - util.permanently_delete(uploaded_file) - - -def test_auto_chunked_upload_NOT_using_upload_session_urls(parent_folder, large_file, large_file_name): - total_size = os.stat(large_file.path).st_size - chunked_uploader = parent_folder.get_chunked_uploader(large_file.path, use_upload_session_urls=False) - - uploaded_file = chunked_uploader.start() - - try: - assert uploaded_file.id - assert uploaded_file.name == large_file_name - assert uploaded_file.parent == parent_folder - assert uploaded_file.size == total_size - finally: - util.permanently_delete(uploaded_file) - - -def test_get_items(parent_folder, small_file_path): - with BoxTestFolder(parent_folder=parent_folder) as subfolder, \ - BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file, \ - BoxTestWebLink(parent_folder=parent_folder, url='https://box.com') as web_link: - - assert set(parent_folder.get_items()) == {subfolder, file, web_link} - - -def test_upload_stream_to_folder(parent_folder, small_file_name, small_file_path): - with open(small_file_path, 'rb') as stream_to_be_uploaded: - uploaded_file = parent_folder.upload_stream(file_stream=stream_to_be_uploaded, file_name=small_file_name) - - try: - assert uploaded_file.id - assert uploaded_file.parent == parent_folder - finally: - util.permanently_delete(uploaded_file) - - -def test_upload_small_file_to_folder(parent_folder, small_file_name, small_file_path): - uploaded_file = parent_folder.upload(file_path=small_file_path, file_name=small_file_name) - try: - assert uploaded_file.id - assert uploaded_file.parent == parent_folder - finally: - util.permanently_delete(uploaded_file) - - -def test_upload_small_file_to_folder_with_disabled_streaming_file_content( - parent_folder, small_file_name, small_file_path -): - uploaded_file = parent_folder.upload(file_path=small_file_path, file_name=small_file_name, stream_file_content=False) - try: - assert uploaded_file.id - assert uploaded_file.parent == parent_folder - finally: - util.permanently_delete(uploaded_file) - - -def test_create_subfolder(parent_folder): - created_subfolder = parent_folder.create_subfolder(name=util.random_name()) - try: - assert created_subfolder.id - assert created_subfolder.parent == parent_folder - finally: - util.permanently_delete(created_subfolder) - - -def test_get_shared_link(parent_folder, other_user, other_client): - with BoxTestFolder(parent_folder=parent_folder) as folder: - folder.collaborate(accessible_by=other_user, role='editor') - - shared_link = other_client.folder(folder.object_id).get_shared_link(allow_preview=True, allow_download=True) - - result_permissions = folder.get().shared_link['permissions'] - assert result_permissions == {'can_preview': True, 'can_download': True, 'can_edit': False} - assert other_client.get_shared_item(shared_link).id == folder.id - - -@pytest.mark.parametrize( - 'collaborator', [ - lazy_fixture('user'), - lazy_fixture('group'), - ] -) -def test_add_collaborator(parent_folder, collaborator): - with BoxTestFolder(parent_folder=parent_folder) as folder: - folder.add_collaborator(collaborator=collaborator, role=CollaborationRole.EDITOR) - assert list(folder.get_collaborations())[0].accessible_by == collaborator - - -def test_add_collaborator_using_email(parent_folder, user): - user_email = user.login - with BoxTestFolder(parent_folder=parent_folder) as folder: - folder.add_collaborator(collaborator=user_email, role=CollaborationRole.VIEWER) - assert list(folder.get_collaborations())[0].accessible_by == user - - -def test_invite_collaboratur_using_when_nonexistent_user_email_provided(parent_folder, user): - nonexistent_user_email = 'non-existant-user-email@box.com' - with BoxTestFolder(parent_folder=parent_folder) as folder: - folder.add_collaborator(collaborator=nonexistent_user_email, role=CollaborationRole.VIEWER) - assert list(folder.get_collaborations())[0].invite_email == nonexistent_user_email - - -def test_create_web_link(parent_folder): - created_web_link = parent_folder.create_web_link(target_url="https://box.com") - try: - assert created_web_link.id - assert created_web_link.parent == parent_folder - finally: - util.permanently_delete(created_web_link) - - -def test_delete_folder(parent_folder): - with BoxTestFolder(parent_folder=parent_folder) as folder: - created_subfolder = folder.create_subfolder(name=util.random_name()) - try: - created_subfolder.create_subfolder(name=util.random_name()) - - assert list(folder.get_items()) - created_subfolder.delete(recursive=True) - assert not list(folder.get_items()) - finally: - CLIENT.trash().permanently_delete_item(created_subfolder) - - -def test_cascade_and_get_metadata_cascade_policies(parent_folder): - with BoxTestMetadataTemplate(display_name="test_template") as metadata_template, \ - BoxTestFolder(parent_folder=parent_folder) as folder: - folder.cascade_metadata(metadata_template) - - policy_applied_to_folder = list(folder.get_metadata_cascade_policies())[0] - - assert policy_applied_to_folder.scope == metadata_template.scope - assert policy_applied_to_folder.templateKey == metadata_template.template_key - assert policy_applied_to_folder.parent == folder - - -def test_create_and_get_lock(parent_folder): - with BoxTestFolder(parent_folder=parent_folder) as folder: - lock = folder.create_lock() - - try: - assert lock.id == list(folder.get_locks())[0].id - assert lock.folder == folder - - with pytest.raises(BoxAPIException): - folder.move(parent_folder=CLIENT.root_folder()) - - with pytest.raises(BoxAPIException): - folder.delete() - finally: - lock.delete() diff --git a/test/integration_new/object/legal_hold_policy_itest.py b/test/integration_new/object/legal_hold_policy_itest.py deleted file mode 100644 index d0a3ae2c7..000000000 --- a/test/integration_new/object/legal_hold_policy_itest.py +++ /dev/null @@ -1,36 +0,0 @@ -from test.integration_new import util -from test.integration_new import CLIENT -from boxsdk.exception import BoxAPIException - - -def test_create_legal_hold_policy(): - policy_name = 'Test Legal Hold Policy ' + util.random_name() - description = 'Test Legal Hold Policy Description' - filter_started_at = '2021-12-12T10:53:43-08:00' - filter_ended_at = '2022-12-18T10:53:43-08:00' - - legal_hold_policy = CLIENT.create_legal_hold_policy( - policy_name=policy_name, - description=description, - filter_started_at=filter_started_at, - filter_ended_at=filter_ended_at - ) - - try: - assert legal_hold_policy.policy_name == policy_name - assert legal_hold_policy.description == description - assert legal_hold_policy.filter_started_at == filter_started_at - assert legal_hold_policy.filter_ended_at == filter_ended_at - - new_policy_name = 'Test Legal Hold Policy ' + util.random_name() - new_policy_description = 'Test Legal Hold Policy Description' - legal_hold_policy.update_info(data={ - 'policy_name': new_policy_name, - 'description': new_policy_description - }) - - legal_hold_policy = CLIENT.legal_hold_policy(policy_id=legal_hold_policy.object_id).get() - assert legal_hold_policy.policy_name == new_policy_name - assert legal_hold_policy.description == new_policy_description - finally: - legal_hold_policy.delete() diff --git a/test/integration_new/object/metadata_template_itest.py b/test/integration_new/object/metadata_template_itest.py deleted file mode 100644 index 49c513743..000000000 --- a/test/integration_new/object/metadata_template_itest.py +++ /dev/null @@ -1,47 +0,0 @@ -from datetime import datetime - -import pytest - -from boxsdk.object.metadata_template import MetadataField, MetadataFieldType -from test.integration_new import CLIENT -from test.integration_new.context_managers.box_test_folder import BoxTestFolder - -FILE_TESTS_DIRECTORY_NAME = 'metadata_template-integration-tests' - - -@pytest.fixture(scope="module", autouse=True) -def parent_folder(): - with BoxTestFolder(name=f'{FILE_TESTS_DIRECTORY_NAME} {datetime.now()}') as folder: - yield folder - - -def test_create_metedata_template_with_fields(parent_folder): - metadata_template_fields = [ - MetadataField( - field_type=MetadataFieldType.ENUM, - display_name='State', - key='state', - description='Which state in USA', - options=['CA', 'TX', 'NY'], - hidden=True - ) - ] - - metadata_template = CLIENT.create_metadata_template( - display_name="template_name", fields=metadata_template_fields, hidden=False - ) - - try: - assert metadata_template.displayName == 'template_name' - assert not metadata_template.hidden - - metadata_template_field = metadata_template.fields[0] - assert metadata_template_field['type'] == 'enum' - assert metadata_template_field['displayName'] == 'State' - assert metadata_template_field['key'] == 'state' - assert metadata_template_field['description'] == 'Which state in USA' - assert len(metadata_template_field['options']) == 3 - assert metadata_template_field['hidden'] - - finally: - metadata_template.delete() diff --git a/test/integration_new/object/retention_policy_assignement_itest.py b/test/integration_new/object/retention_policy_assignement_itest.py deleted file mode 100644 index a52f16274..000000000 --- a/test/integration_new/object/retention_policy_assignement_itest.py +++ /dev/null @@ -1,52 +0,0 @@ -from datetime import datetime -import pytest - -from boxsdk import BoxAPIException -from test.integration_new.context_managers.box_retention_policy import BoxRetentionPolicy -from test.integration_new.context_managers.box_test_file import BoxTestFile -from test.integration_new.context_managers.box_test_folder import BoxTestFolder -from test.integration_new.context_managers.box_metadata_template import BoxTestMetadataTemplate - - -RETENTION_POLICY_ASSIGNMENT_TESTS_DIRECTORY_NAME = 'retention-policy-assignment-integration-tests' - - -@pytest.fixture(scope="module", autouse=True) -def parent_folder(): - with BoxTestFolder(name=f'{RETENTION_POLICY_ASSIGNMENT_TESTS_DIRECTORY_NAME} {datetime.now()}') as folder: - yield folder - - -@pytest.fixture(scope="module", autouse=True) -def test_file(parent_folder, small_file_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as file: - yield file - - -def test_delete_retention_policy_assignment(parent_folder, small_file_path): - with BoxRetentionPolicy(disposition_action='permanently_delete', retention_length=1) as retention_policy: - with BoxTestFolder(name=f'{RETENTION_POLICY_ASSIGNMENT_TESTS_DIRECTORY_NAME} {datetime.now()}') as folder_under_retention: - retention_policy_assignment = retention_policy.assign(folder_under_retention) - - assignment = retention_policy_assignment.get() - assert assignment.id is not None - - retention_policy_assignment.delete() - - with pytest.raises(BoxAPIException): - retention_policy_assignment.get() - - -def test_retention_policy_assignement_to_metadata_template(): - with BoxTestMetadataTemplate(display_name="test_template") as metadata_template: - with BoxRetentionPolicy(disposition_action='permanently_delete', retention_length=1) as retention_policy: - retention_policy_assignment = retention_policy.assign(metadata_template, start_date_field='upload_date') - - assignment = retention_policy_assignment.get() - assert assignment.id is not None - assert assignment.start_date_field == 'upload_date' - - retention_policy_assignment.delete() - - with pytest.raises(BoxAPIException): - retention_policy_assignment.get() diff --git a/test/integration_new/object/retention_policy_itest.py b/test/integration_new/object/retention_policy_itest.py deleted file mode 100644 index 3fc1b504e..000000000 --- a/test/integration_new/object/retention_policy_itest.py +++ /dev/null @@ -1,40 +0,0 @@ -from test.integration_new import util -from test.integration_new import CLIENT - - -def test_create_metedata_template_with_fields(): - policy_name = 'Test Retention Policy ' + util.random_name() - disposition_action = 'permanently_delete' - retention_length = '1' - retention_type = 'modifiable' - policy_description = 'Test Retention Policy' - - retention_policy = CLIENT.create_retention_policy( - policy_name=policy_name, - disposition_action=disposition_action, - retention_length=retention_length, - retention_type=retention_type, - description=policy_description - ) - - try: - assert retention_policy.policy_name == policy_name - assert retention_policy.disposition_action == disposition_action - assert retention_policy.retention_length == retention_length - assert retention_policy.retention_type == retention_type - assert retention_policy.description == policy_description - - new_policy_name = 'Test Retention Policy ' + util.random_name() - new_policy_description = 'Test Retention Policy Updated' - retention_policy.update_info(data={ - 'policy_name': new_policy_name, - 'description': new_policy_description - }) - - retention_policy = CLIENT.retention_policy(retention_id=retention_policy.object_id).get() - assert retention_policy.policy_name == new_policy_name - assert retention_policy.description == new_policy_description - finally: - retention_policy.update_info(data={ - 'status': 'retired' - }) diff --git a/test/integration_new/object/sign_request_itest.py b/test/integration_new/object/sign_request_itest.py deleted file mode 100644 index 578c56b20..000000000 --- a/test/integration_new/object/sign_request_itest.py +++ /dev/null @@ -1,102 +0,0 @@ -from datetime import datetime - -import pytest - -from boxsdk import BoxAPIException -from test.integration_new import CLIENT -from test.integration_new.context_managers.box_sign_request import BoxTestSignRequest -from test.integration_new.context_managers.box_test_file import BoxTestFile -from test.integration_new.context_managers.box_test_folder import BoxTestFolder - -SIGN_REQUEST_TESTS_DIRECTORY_NAME = 'sign-request-integration-tests' - - -@pytest.fixture(scope="module", autouse=True) -def parent_folder(): - with BoxTestFolder(name=f'{SIGN_REQUEST_TESTS_DIRECTORY_NAME} {datetime.now()}') as folder: - yield folder - - -def test_test_sign_request(parent_folder, small_file_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as test_file: - file = { - 'id': test_file.object_id, - 'type': test_file.object_type, - } - files = [file] - signer1 = { - 'email': 'signer1@mail.com', - 'signer_group_id': 'reviewer', - } - signer2 = { - 'email': 'signer2@mail.com', - 'signer_group_id': 'reviewer', - } - signers = [signer1, signer2] - - sign_request = CLIENT.create_sign_request_v2( - files=files, - signers=signers, - parent_folder_id=parent_folder.id - ) - - try: - assert sign_request.id - assert len(sign_request.signers) == 3 - signer_group_id = None - signer_count = 0 - for signer in sign_request.signers: - if signer['role'] == 'signer': - signer_count += 1 - if signer_group_id is None: - signer_group_id = signer['signer_group_id'] - assert signer['signer_group_id'] == signer_group_id - assert signer_count == 2 - finally: - CLIENT.sign_request(sign_request.id).cancel() - - -def test_webhook_sign_request(parent_folder, small_file_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as test_file: - file = { - 'id': test_file.object_id, - 'type': test_file.object_type, - } - files = [file] - signer = { - 'email': 'signer@mail.com', - } - signers = [signer] - - with BoxTestSignRequest(files=files, signers=signers, parent_folder_id=parent_folder.id) as sign_request: - webhook_url = 'https://example.com/webhook' - new_webhook_url = 'https://example.com/new-webhook' - - assert len(sign_request.sign_files['files']) == 1 - - sign_file = sign_request.sign_files['files'][0] - webhook_file = CLIENT.file(file_id=sign_file['id']) - webhook = CLIENT.create_webhook( - webhook_file, - ['SIGN_REQUEST.COMPLETED', 'SIGN_REQUEST.DECLINED', 'SIGN_REQUEST.EXPIRED'], - webhook_url - ) - - try: - assert webhook.id - assert webhook.triggers == ['SIGN_REQUEST.COMPLETED', 'SIGN_REQUEST.DECLINED', 'SIGN_REQUEST.EXPIRED'] - assert webhook.address == webhook_url - - update_data = { - 'address': new_webhook_url, - 'triggers': ['SIGN_REQUEST.COMPLETED'] - } - webhook = webhook.update_info(data=update_data) - - assert webhook.address == new_webhook_url - assert webhook.triggers == ['SIGN_REQUEST.COMPLETED'] - finally: - webhook.delete() - - with pytest.raises(BoxAPIException): - webhook.get() diff --git a/test/integration_new/object/sign_template_itest.py b/test/integration_new/object/sign_template_itest.py deleted file mode 100644 index aeddc194f..000000000 --- a/test/integration_new/object/sign_template_itest.py +++ /dev/null @@ -1,8 +0,0 @@ -from boxsdk.pagination.box_object_collection import BoxObjectCollection - -from test.integration_new import CLIENT - - -def test_get_sign_templates(): - sign_templates = CLIENT.get_sign_templates() - assert isinstance(sign_templates, BoxObjectCollection) diff --git a/test/integration_new/object/trash_itest.py b/test/integration_new/object/trash_itest.py deleted file mode 100644 index 26b9e88e0..000000000 --- a/test/integration_new/object/trash_itest.py +++ /dev/null @@ -1,63 +0,0 @@ -from datetime import datetime - -import pytest -from boxsdk.pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection -from boxsdk.pagination.marker_based_object_collection import MarkerBasedObjectCollection - -from test.integration_new import util -from test.integration_new import CLIENT -from test.integration_new.context_managers.box_test_file import BoxTestFile -from test.integration_new.context_managers.box_test_folder import BoxTestFolder - -FILE_TESTS_DIRECTORY_NAME = 'file-integration-tests' - - -@pytest.fixture(scope="module", autouse=True) -def parent_folder(): - with BoxTestFolder(name=f'{FILE_TESTS_DIRECTORY_NAME} {datetime.now()}') as folder: - yield folder - - -def test_trash_get_items(parent_folder, small_file_path): - name = f'{util.random_name()}.pdf' - test_file = parent_folder.upload(file_path=small_file_path, file_name=name) - test_file.delete() - try: - trashed_file = test_file.get() - assert trashed_file.item_status == 'trashed' - finally: - CLIENT.trash().permanently_delete_item(test_file) - - -def test_trash_restore_item(parent_folder, small_file_path): - with BoxTestFile(parent_folder=parent_folder, file_path=small_file_path) as test_file: - test_file.delete() - folder_items = parent_folder.get_items() - assert test_file.id not in [item.id for item in folder_items] - CLIENT.trash().restore_item(test_file) - folder_items = parent_folder.get_items() - assert test_file.id in [item.id for item in folder_items] - - -def test_trash_get_items_with_offset(parent_folder, small_file_path): - name = f'{util.random_name()}.pdf' - test_file = parent_folder.upload(file_path=small_file_path, file_name=name) - test_file.delete() - try: - trash_items = CLIENT.trash().get_items() - assert isinstance(trash_items, LimitOffsetBasedObjectCollection) - assert trash_items.next() is not None - finally: - CLIENT.trash().permanently_delete_item(test_file) - - -def test_trash_get_items_with_marker(parent_folder, small_file_path): - name = f'{util.random_name()}.pdf' - test_file = parent_folder.upload(file_path=small_file_path, file_name=name) - test_file.delete() - try: - trash_items = CLIENT.trash().get_items(limit=5, use_marker=True) - assert isinstance(trash_items, MarkerBasedObjectCollection) - assert trash_items.next() is not None - finally: - CLIENT.trash().permanently_delete_item(test_file) diff --git a/test/integration_new/object/user_itest.py b/test/integration_new/object/user_itest.py deleted file mode 100644 index 330fb8e2e..000000000 --- a/test/integration_new/object/user_itest.py +++ /dev/null @@ -1,3 +0,0 @@ -def test_upload_and_delete_avatar(user, image_path): - avatar_urls = user.upload_avatar(image_path) - assert 'large' in avatar_urls and 'preview' in avatar_urls and 'small' in avatar_urls diff --git a/test/integration_new/resources/image.png b/test/integration_new/resources/image.png deleted file mode 100644 index ae6dbad7bd3629fa7610ce28764eaced3959c4e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1359 zcmds1&ui0g6n|;g(REJd#9tFJMo@7sX)6j1Nm z&b06kBju+wBySqWP6dSH4y4Y~HY(jhdbuUrqQR7)1pm#r66g*u%4B+}E zO=gTta*Qu16*gZ~rV(paRKfzmu16~nx+M^kzq)p=4IDuNTpULS$EpP4RR#H1z10~6AhWt zE;JIcX;xo$I)RAsQKh14NY`;$;ecQz_{Y~O&&H>bu9T~!i|evvgo6&H!`;ccs!0ex z7D8)gY4>&FQ?rVp;8&zcg@kM%4VUP!w-fG;XwxEn^C`(hTf|`s9GQc*xv+=M^5Urr`vk5z!C~K#;~R7M>j@=d zkx7%<{1n&+G`IsJd;T1A4g3_J-TP2o5jQ4scjw-ohM_NT@Fm@!{&E?NFkCn#u$ukG&A$H9D5ym9*uvOU{^E`2 z&8<)r00$@zu22;f0H#ZJr<17Qe|G|%q)j4H97zDokmTu1cL5MsG@zyirO~M*f(O)( z*&|*#zEOiKVr~7R46dQIDw;^))RO8xP@)}PAhy3{EU#=mbhsw$hK9UT>)OgRhqB>& z?(ipx5!{(ji;eb$Xz@r$Okk+_h;!wL3Twa4 zrR^*A4=$zbc~(Q5xBcRve&xei-s0^+G=&aFHj^9!cTMvS-)n8R&+jZYTUATI3&*Mr zp?Ao=4mg;R@-$!~mUWVDWiQ0#{8;d;Pn3I0@^}QlBeUJ&Vaud5kUMB z2{V5$&~;;i^VVM~$&(Hs!R8YH>;#EM@um_OgErX)IG4KAjw3&6}sessVVfZ-_a6soBgfk*-v z8-t@E0L<3(YH9#X$B<rGs0v)rO}bpo;%$(*>>Bg3ZhjfWlAsuf-WXe=h#R$-icU zX=)l99?{eUoCxkT5@Wajv{>nvLB~NqVVaC{;>oZ8rcLr86GBubFATKvY+aV$JhiVt~YwZ~zDJLRpU=ViK(%X3TOvLefWjn=ao@8FNRodS-l- z7*U6h^W0g7K8^)mf@>{qp6?HA5yi-xa%O z*+Yu1j#iuu{rp9cBqjk1H;TGbJK3GnTy{UB1=z5A6>z-B5iJuy%4@ za=PofX6$v#A@}Lquo?lTWpS&k`^sxmPcl__Y}XhYzHKxMFfD9jsu;Ysxc|KS>7?cn zE-B8K>1lTwbC#R@;ZKTdS!XFv>&|1pUg`L}X0CioP(A#hK0k`2Gj=Qg)=}+e&$zhL zx*RvY@@|gSW66KCH_2Yhsu!G({B+XfQwm2!v`bZ_O1MRWydyLsv}ZVUkAf7NP)Md5 zpNqF0Q*b2K_>n%_6N)Mm?jWnUr+NTO@RT(#0{W!3+NC71lopQ+_&&ZW%4u`abJ$Q* z%=F_jSG*%tB1cvzcPPTWfn`?$8~p|p9WD=Nk+t6;5{B|(9R!#n!t{7qFSE+)v5++& z_cYaBGeI>GubF+$xq30Z3J-V=Ieu2bYr75PRQ(PwPOdP#2K0QxQ8@EpIGO$I94~*4 zW>JDb9?uo6Uf#V{?EW}SVLY-5q5lI$9ru*{%dT4^>2p_9to&%KWr2n-aE%68)?^= z%&zf-owZxnf_?A2wpN0LIP=Z}Zi!-fEQLB53EX*mAIIq>p83GKJt~`!zDJ-{Xf(<) zcG$t3(Cc6T?@1@7n;Qy#RB+-&g?~GH)1l|?EeomJ>OLF_JFmvcHJjVV+gq2V-DQ1E zm{u0yZmSm>5%CInw8sYNiMa!L$kP{F6*kqhV_0(Dulmp+-|ShQ2HKoM(1BWLs7UDk zp?J~OI;jK>D~Q`!o&%Qwad&YuUSale_Kk?0jcAkb0wcQEd0zi!8wZ)YmtJdD>x)Pp zPv+AK;pt(?)m1vj95@o+NeS6lqC5BLfi~ zt^VU!gU1^F8veMOCw3HgzOpDvugtVEs?iQM2!`R4QE7{5qwOWh#ffgIQ)z(`Mk#%1 zzG-&ySyFZfLJhv(T7Sf1(xA~`8lGEbo~=7v(Wn}!ual9J>Ttm!!y($?+z6({M4~z= zJn7*sqe6{|vKJCJ4+v|$yy^eqQN^)}BX3A=;QR23d226|Ct8+7Kh-R8kR(XfW71(9z{m4;I5Lc1lNAowI2E6xUT#z-|JnOcIeFy>rsH&CovLM$k^aLzqm-nrX0N#0sf^P$Z{Q(rBkqc97YU8%`zwwg&+NmgDRzRo)I zY5jBHjeU&f)rNxXi`jLA>xA54;=!WC;_gGaR=LWpO2h|eF3zisT1%EiXX+>E$F(0b zef?_C%)YX#qKnJzql+ZE7*o}jQ})i%^0sd84Q!Ljlb~GxtKSZDs>WEvWC{!j*vZbw zn#xVdidxE8ZnN;Hm@HdudefZYW=jliJMTJ5EbqKE@bP|0q1=#9-Au4@hHcq^K-KZ8 zu4V3Ju{Fsx1e8gm}o}E*lh97m#$Bwc|1xnq0sh>ZTY1t{$xuY|e zlS^<;@Ri&iYWJDpMuoJg=CBUM!j`N9B;lOa& z@~_hN$jhQ{OHTVgI_VeU=L?Z@(9y;C)4Xg>B3&&MH zCHH(jxjRRrJJC>@X6aOwe(Ag67rj{Vs}kodIm=z|4LajVSx-%#?fkg(A>)H*$G-N( z_O%d<6UNuuxz^|V`Sk|OXE*MW$lg0PZyz7`wQZ;z{!%*??*Vw|_0M(acEpkP4tZW2 zd}VUOq^#E7rF`~dtJY&Jli?b^}I8d+@& zjonH#Jz5CwhHp1@P325h);WjTg=){}%_nx@Bk?!e8soitlwb8J7AU4DG{z;h-W@&D zM%j4(1sAxxc)}Q!`*;PSbO1X)(zYTxd!0+A-~VE8(S+1S*7AerRu`?Zd*Ag&Kgd%c z2O0(rO@zJ7+}F8JPW|d;&{FL3m5lPI&PTfvJNd6&#b)`2ENHbx&c{RxMjbgtSF5b} z@FMe0*`2|T@U+s?^N*>8Z)?`pPo@<+-@)1{2WJM?%$(nd>)l^<{j%M32zlcEtVhj+ zSNX=}(UMEG+k(eeZP#Ff%ay@nmo_u^vc_y*{VZP_xOw`W>O}D1MmJj_&uR5%0ZJP# z8^r0+94V;Lq3cEJB^x~}eNKfnOVc;{?7PY@ILDuVgoACSek(O<+SXOrby&ATcM@NM z?~45vI}mVpsb#JsHtpqTWSz|S2VYaWi?c#}*J_*{+MDOxx~HzF8w?iIVpn^YJ;ohB zeR%0hp1qZSaj>{neK~w1dyTl%mN+qOv|^N+6{qeS#Ie5MC+Xqzap-Hr;@a)~F(eVVu9N7~z@fd-4g5p8&{0q?~Q)zUZ3xNs>6_CUee!M^t2n7IkiVUvE z2!jHe;l+RcV6gw#4*h{*FlYdU#r`dnTju>9nqM5)a_Bh015_ISYed`$&Y(DBND?qM zT3gCY4uw?!09cG7sJZmPO(YXFJ)PZ2prbHN8j&IUunLL{k+byzl|v%H zlYt|6>5<4_)B!~}27sB;Ngn2m5f*=<7!(*sO&{m2-~kvVaL6%&-;XyW8Vl+th9#ib zCLzHngCkG;a=NyFpCS37y%7KcjREcdQ;91ve*cqH|5aYanOVD=o#eCi-5<+xE3e0e zOFUh}kcq>C7hZT?0Lkb709SDU-Sk}O$a}7mnG~7$i18@Ilc-^~7wp+X$n>Ikr_V}|tIG>y*7CmIJGx7t3WZXINBi>mR=8{*uZCl8y_0%XWw^8 z*gi;_t&qKK*gnT)7IkDNCq_&_R;N(qQYf`L!0J#i|KJ{eWn{b*Pv5>7gl%LHjepX_ zO94A1?fXLRQU$&?LGkPvX)TV^+YViG-kr2_cwmTCMbtOu zH!E+TOvC6*tp4*Tr;7=X>q40(uX#n^g{44y2$eapZ!0MKbc4iRXJ2>O#Ugqjo)#cD zA^~})KCu8>o`aOmseD}w+=p1&kGwG1e}P&YdIW9m^%;ruUBuk!S%lsXyzDc~v#cK= zIQ9JO!^lG`>A}^l*1e)Fp0_m*kGw9f6bosaQF#{pJi zOrYrvqu$hGNghRE8IIIBvCxsQh%$SnFrJbbzP`m98cIzyDt`o4x33jZaedzSvH(?B zy+}(Kt4br6vVt^OW{ov+4q4T`pIv~nYWsJ2NYShMUDbE$*VZF_``bNDU#!o1LEjJZ z*&W z1P};$1$i`JBdz64c6S7ja6B5JXd?rdcoV4f00683ZIzS!=rT|k4y?2>?r`bj6gr6p zPPzqvvuHC4FVfmakZ>dtjzVA%XaoX@wt~Z@z%SVE7{&2Fclo6lj2iU6 ZclnVNX>{-qY;6c*OQ8}HI!3zC{{u34GByAJ diff --git a/test/integration_new/resources/small_v2.pdf b/test/integration_new/resources/small_v2.pdf deleted file mode 100644 index 431ffe6724c146e8f198f787794ada8c8f1bac6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6235 zcmbVQ2{@GP*S8ghB!!alB>Or$%D#+5c0yT(8H{CynXzXpDcQ0mq_QNk78TjER$fw; zl*mr@B}$kqXXJ09u4Wg)?{@0jo~(16+VAf;LXWX z+)PFhIw6J(;b9`)n!7Lu_KBP0OzY^iuLwG59bmh3(Y}{;iPNyT-bhnx>|>NG>wF<< zc_4MG#d5H3;%RT6(|lm>Gdja>7nz<@E$Bm#85RfTrt8mh?SIv{#&D36p2LE^ZphJ* zNqb$Bq3c_d&ZqNYWoI{2AOOrwfb+GEsD%^+*XUaZ8_*RQy^Q-L$@kqm3E}GCdrJh z%zd}&*ieDNJV3X!i>_?o*7DJCH=FpzVRlir$e9^83jG~d8Ocex%{v!KFKfdU)>2;0 zZJM9V;Z+GeuEUMMYmMdH&(YOvYh&j~?!axWac+;)DG>g8(I9o}P95*!jh+7?$fWx000TG>$V05mo#d0;kV$ z!XT|RRnCQR#gtgM|M!Wn0&M565r_2D1Wm?Q*<*2J;Y_JRS%YD2^$c9snW=Xn$WR$5 zgOuGK{t$!*;{ZSx7IKP{@dl&JDF%Wn(<3$IcXSXn*gJafOD-OCU7`N3ne;EBJ$9dG zI$O8LgN;2zTNM&suM4Fg2qmywT;Sx+RC{opCwp&-dJpGeD;7TuHADFWj7=I5*E!== zH(rb69+bHBAVb4YY2`ST?-`5rrLT9HUjxY9Tl9fp`))G4#qN1W_avmRpuiCz#EU zrJAO_djypqJP#+LikP16?TxAo znQquKB(msRB{6Vd{^H(x%7XocV>OUq{@|m7u>#GtqSslim|QRJJ$3^SauYJ;SGBn zPT;i32Q5`?42p#A0Jg$=<5dh3#Y7GmoaWb3@6%T}U7_ly>Zftfcuy{|%i_V!XX#c3 z)tZ5)17)=n5XsBQBQFXQ^5a~SrjrAN4HA2keUfcu?ugnR3qJil=UX|0QN3!tNoZDy zc?M>v?77lq9j(;NB>SuOsrC`}mxfVIM#5F`q47_-4DwW`O5OPuh?mN?b@1Oma-3Pl|n@XGZEi(4GFk{eeLj)J4Pv>(b}4*)5YF z-EqIe=XOWBEpmJG+m+8H&m#QGzq)*B_^k9ffiIP>hVKxc8DBE88FeU?nEE1hEOi3e zQs!*M_L#|P%xdDX4KyibuFcd@xHM8Bqp)c9zI3N@_Q!msLWkT^3#Tmo()Rt<@iOP+ z)SjMyYT~1Apet)3Yg=qIpP_7Yr|?d8t#<9X;Foo;f%kTiYH9Vk8P_suvA3~VLk`Cu z#N~HNWLagMY?gO;;(2XRdE}f(NkqC%yiW9sGbZo422Aaqb(D3myN)}HAoEd`Etw_# zmX-yWp1TSSimey2{L(fg*_0wJBGY+3^4LnvN|{JcO9@ztTkf)OFZ*2bwc&kZs;jj_ zU`x2mh(l?6&d2e`g?ZA0-nFxVCsVCUKJrxRS9Yv&tO{<5Y{Hn8uJ~T5=2&}2^%koP z3+&!EJvmg~z8E#aEE*vC@U71M!F0=Z@%BCKS#0dQ3%p&@2g#kDL*@MBI2oKwl&!Bv zMwi)#q!Fv>uWe^~qbIx@-5Wo?Q|j9)-;~^|yEjfdvL*3;!USIjo6Xha;Ja__>#*WOJ7CV8d;)=5Y<6Nbs0S(y#a z%C*ajsiB^}zVf1g>>FF5^Ibd)>fbcHFDtU5*sZz0QVz;UAqzxi{L0OIgM59Mr0umd zH5w~V#kRg_)sBqj(dF^MywGma>efD^6`+-@rJHOaWm!6kiL#0Ua+G)k4s!8EbmoE(RJH;OHYZThXsxda&AZLf1Wp{SF~bV;WNh6 zn^1XWGk&63aw2-sdL92Qjww0pQCO#Da-(&GgHDkSx6TNIf!gCwUXN^-LPzrwuP0`S zBl0@){vw2xt(U!37sg&Ik+eoAiB`)tAhVzv&t4L`=gjtJs&>ZdiBT*aD{sbp*ZXoR zN+?bEk|kTI%cB7&ZTy{=Ms54XS4LAuiLZ{lSbnh?gmOgrcsbR0e-HmwkD7DkD7@TL zbpAtyzK?bNv!O3F)3NS=`>DQ#SD07P_``$5YXewB;r{sPLCe6i8z0+N!)B}Rt=`-8`Q**w)Bkbm z*|J57y`@dx>!MHAg=1|?nM+SA&(74CW6yco?IpzGV$0yUkE%}>yJ&ac|D63flrJ`Q zy!vy%)7kX*EgyPo2i=3;(nm8kF)JTEz+V;Ix@PEFsOH{;?H?*==$Ov@{H)d~*fv;m z_S9lrhxTRddo9mny}D0!^~&YSC8D24$2UJ5@oXV&efpvius?sw5Rp}}&Ln?KVR5)+ zU10t;yJDZ;wZI2cqFZ-XpS-rZW|h&?-xKj98%+q%3mBXVd7FNu{fM+m+V+K&sMVCz z(w9!U9dYg4x6%~u_yjGfH(y?ijNrX;;w)A9S=s2D^rDiYfmfl)#Wsr-&8f7OwF#qiLch1vIDEn0>@&u z(+@L7?*2L_QxmXl)2}oYIIz{poVV9TrOjV{%X!OTW+YP-Vjyw*flA?4_j<2mUiHe% z-Cnzn(yLCf;pG~#+esV61`WG9@;W3jWth*}h1wla8&MzqAFeblyoySGJ94>J{QHx& zq|W?1K|Y(+PWCSv7hF51Q&dh5Hu z_TTX?4b)TqCzwt{-d-Srh5@p=1RMp#SxeXKjkZvN9OxM7_@LP(=Q zU_R7$*q}5*0S!pQODv0VsLUja3cwIxI1r#H z#>L95c7tgTW)Dl5+VyN3%%wkyUlOyzq8Xgi&L*B? ze`;AOylNnR!SB)i50AO1nFBoWF!qqa+-zI)Znz1o7H6NQE5f+e*8rv9IwVT?@ zGiJW=-P73rDWO8ksH3La_Y0qD!qQolx1OnbQ3v?j2F#br zlIIMbMXBogj5c=8xaLqYiSvEoSgoGfdjz?zwS2Q1Z=4!(T`~?po%!TyIi7+TNw&r;9zR&B@YgwMmT)-+xNtUDr9&*^Vx!`3Z zQizr)eBw)=1*7k4R!47^CkCvGpA9%6VxM_$WJJ@l$+T#pQ>tKS*B5~X=p-ju!tRc? zcuxbrnup|lPvPR+!|Ita7weyt_Vm>;o^R2>v~UW!ZV4$Kw>p^_Ub`&u^kJCfscMr7 zGv$5QKo9I>*?Pe64XOTA7C$nhw+aS!q*59)>76oqWzd>_a%NeuV~Mfd*q58X&Ij*o zb&O-Us-&rZp8mZldjq!>JEM{&!LrT&OMpWZrv9qn$vwz9&Km`Ho8n$e?3PdEKnO{C z>byQBg{c{CP3E6^-*xrk*dt3B%dA3eUzvm4uav6RQ!Nuc_gBbxYqzbMn%bug)N@9+ zYxz|@ztmq>Y}ugI@6=;_ICt{RN|m#B;i{v#iy7g1zb*R+ubI} zm|aaNvRqESeqt%cSCJ!d0WAX?Rd%o5$qHnUss=- zg$OQDzAZuPz&w&-*|wIn!97f_?7;JS=4}dT?N|I>)f(k;{ha(4=~T|(HvQ)xavLQ1 z%tnYcjl%pAE^7I$CtTG_^0}q>`9fs2%c>JhGNo0k#*B|AlkV{&-DS?BLyj98 zhWC6zV;rg;o6{fh6c>o?mf)tCB}u;KIML_+`ixee*}E)>%SFx1DihcNLs6m3d{}!E z+7q4}F)n?pC3b^3_MuJwx9qZ8rbE5~N5g`~PfzSAvOi`f#lz+MR}ovZ2gy;^EGS6t zsNQ~!srPkI>YGOY+ry-SF=A=LQx5T#Z+a^x%~Q9XEZDjUo-Iwriv{lA`BJe{ z=3Z^H4^pB7QvTwawGY=H9JNc8xtr!wpnJ7BweMMWRGVpIdVgh?l#~ydit!{GMZGF- zo;>=1&AvLVbKs>xd_nW1JuhQ4+j@G;#92kWThba=K+$VlJMP5UL#=3mijAXYHuEvX z`bFQJx4f3ZC&{ss!Ak9w#K*nfj^^V}^zA<{*q8bCWDqi0_Vlly$EhN01nkg!sO1214dq0GSwdd z8cUu%1_KPi* zj0LZ2@=nLzgy4?{;{vvyNhA;-?A+h7Iz&eju# str: - return str(Path(os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))))) - - -def get_file_path(file_name: str) -> str: - return str(Path(f"{os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))}/resources/{file_name}")) - - -def random_name(): - return str(uuid.uuid4()) diff --git a/test/invites.py b/test/invites.py new file mode 100644 index 000000000..63689528a --- /dev/null +++ b/test/invites.py @@ -0,0 +1,33 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.invite import Invite + +from box_sdk_gen.managers.invites import CreateInviteEnterprise + +from box_sdk_gen.managers.invites import CreateInviteActionableBy + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client_with_user_subject + + +def testInvites(): + user_id: str = get_env_var('USER_ID') + client: BoxClient = get_default_client_with_user_subject(user_id) + current_user: UserFull = client.users.get_user_me(fields=['enterprise']) + email: str = get_env_var('BOX_EXTERNAL_USER_EMAIL') + invitation: Invite = client.invites.create_invite( + CreateInviteEnterprise(id=current_user.enterprise.id), + CreateInviteActionableBy(login=email), + ) + assert to_string(invitation.type) == 'invite' + assert invitation.invited_to.id == current_user.enterprise.id + assert invitation.actionable_by.login == email + get_invitation: Invite = client.invites.get_invite_by_id(invitation.id) + assert get_invitation.id == invitation.id diff --git a/test/legal_hold_policies.py b/test/legal_hold_policies.py new file mode 100644 index 000000000..fff6788dc --- /dev/null +++ b/test/legal_hold_policies.py @@ -0,0 +1,71 @@ +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.legal_hold_policy import LegalHoldPolicy + +from box_sdk_gen.schemas.legal_hold_policies import LegalHoldPolicies + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import date_time_from_string + +from box_sdk_gen.internal.utils import date_time_to_string + +from box_sdk_gen.client import BoxClient + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testCreateNotOngoingLegalHoldPolicy(): + legal_hold_policy_name: str = get_uuid() + legal_hold_description: str = 'test description' + filter_started_at: DateTime = date_time_from_string('2021-01-01T00:00:00-08:00') + filter_ended_at: DateTime = date_time_from_string('2022-01-01T00:00:00-08:00') + legal_hold_policy: LegalHoldPolicy = ( + client.legal_hold_policies.create_legal_hold_policy( + legal_hold_policy_name, + description=legal_hold_description, + filter_started_at=filter_started_at, + filter_ended_at=filter_ended_at, + is_ongoing=False, + ) + ) + assert legal_hold_policy.policy_name == legal_hold_policy_name + assert legal_hold_policy.description == legal_hold_description + assert date_time_to_string( + legal_hold_policy.filter_started_at + ) == date_time_to_string(filter_started_at) + assert date_time_to_string( + legal_hold_policy.filter_ended_at + ) == date_time_to_string(filter_ended_at) + client.legal_hold_policies.delete_legal_hold_policy_by_id(legal_hold_policy.id) + + +def testCreateUpdateGetDeleteLegalHoldPolicy(): + legal_hold_policy_name: str = get_uuid() + legal_hold_description: str = 'test description' + legal_hold_policy: LegalHoldPolicy = ( + client.legal_hold_policies.create_legal_hold_policy( + legal_hold_policy_name, description=legal_hold_description, is_ongoing=True + ) + ) + assert legal_hold_policy.policy_name == legal_hold_policy_name + assert legal_hold_policy.description == legal_hold_description + legal_hold_policy_id: str = legal_hold_policy.id + legal_hold_policy_by_id: LegalHoldPolicy = ( + client.legal_hold_policies.get_legal_hold_policy_by_id(legal_hold_policy_id) + ) + assert legal_hold_policy_by_id.id == legal_hold_policy_id + legal_hold_policies: LegalHoldPolicies = ( + client.legal_hold_policies.get_legal_hold_policies() + ) + assert len(legal_hold_policies.entries) > 0 + updated_legal_hold_policy_name: str = get_uuid() + updated_legal_hold_policy: LegalHoldPolicy = ( + client.legal_hold_policies.update_legal_hold_policy_by_id( + legal_hold_policy_id, policy_name=updated_legal_hold_policy_name + ) + ) + assert updated_legal_hold_policy.policy_name == updated_legal_hold_policy_name + client.legal_hold_policies.delete_legal_hold_policy_by_id(legal_hold_policy_id) diff --git a/test/legal_hold_policy_assignments.py b/test/legal_hold_policy_assignments.py new file mode 100644 index 000000000..81a2f2e91 --- /dev/null +++ b/test/legal_hold_policy_assignments.py @@ -0,0 +1,94 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.legal_hold_policy import LegalHoldPolicy + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.legal_hold_policy_assignment import LegalHoldPolicyAssignment + +from box_sdk_gen.managers.legal_hold_policy_assignments import ( + CreateLegalHoldPolicyAssignmentAssignTo, +) + +from box_sdk_gen.managers.legal_hold_policy_assignments import ( + CreateLegalHoldPolicyAssignmentAssignToTypeField, +) + +from box_sdk_gen.schemas.legal_hold_policy_assignments import LegalHoldPolicyAssignments + +from box_sdk_gen.schemas.files_on_hold import FilesOnHold + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import generate_byte_stream + +from test.commons import get_default_client + +from test.commons import upload_new_file + +client: BoxClient = get_default_client() + + +def testLegalHoldPolicyAssignments(): + legal_hold_policy_name: str = get_uuid() + legal_hold_description: str = 'test description' + legal_hold_policy: LegalHoldPolicy = ( + client.legal_hold_policies.create_legal_hold_policy( + legal_hold_policy_name, description=legal_hold_description, is_ongoing=True + ) + ) + legal_hold_policy_id: str = legal_hold_policy.id + file: FileFull = upload_new_file() + file_id: str = file.id + legal_hold_policy_assignment: LegalHoldPolicyAssignment = ( + client.legal_hold_policy_assignments.create_legal_hold_policy_assignment( + legal_hold_policy_id, + CreateLegalHoldPolicyAssignmentAssignTo( + type=CreateLegalHoldPolicyAssignmentAssignToTypeField.FILE, id=file_id + ), + ) + ) + assert ( + to_string(legal_hold_policy_assignment.legal_hold_policy.type) + == 'legal_hold_policy' + ) + assert legal_hold_policy_assignment.assigned_to.id == file_id + assert to_string(legal_hold_policy_assignment.assigned_to.type) == 'file' + legal_hold_policy_assignment_id: str = legal_hold_policy_assignment.id + legal_hold_policy_assignment_from_api: LegalHoldPolicyAssignment = ( + client.legal_hold_policy_assignments.get_legal_hold_policy_assignment_by_id( + legal_hold_policy_assignment_id + ) + ) + assert legal_hold_policy_assignment_from_api.id == legal_hold_policy_assignment_id + legal_policy_assignments: LegalHoldPolicyAssignments = ( + client.legal_hold_policy_assignments.get_legal_hold_policy_assignments( + legal_hold_policy_id + ) + ) + assert len(legal_policy_assignments.entries) == 1 + files_on_hold: FilesOnHold = ( + client.legal_hold_policy_assignments.get_legal_hold_policy_assignment_file_on_hold( + legal_hold_policy_assignment_id + ) + ) + assert len(files_on_hold.entries) == 1 + assert files_on_hold.entries[0].id == file_id + client.legal_hold_policy_assignments.delete_legal_hold_policy_assignment_by_id( + legal_hold_policy_assignment_id + ) + with pytest.raises(Exception): + client.legal_hold_policy_assignments.delete_legal_hold_policy_assignment_by_id( + legal_hold_policy_assignment_id + ) + client.files.delete_file_by_id(file_id) + try: + client.legal_hold_policies.delete_legal_hold_policy_by_id(legal_hold_policy_id) + except Exception: + print( + ''.join(['Could not delete Legal Policy with id: ', legal_hold_policy_id]) + ) diff --git a/test/list_collaborations.py b/test/list_collaborations.py new file mode 100644 index 000000000..b4a878e74 --- /dev/null +++ b/test/list_collaborations.py @@ -0,0 +1,89 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.group_full import GroupFull + +from box_sdk_gen.schemas.collaboration import Collaboration + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationItem + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationItemTypeField + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationAccessibleBy + +from box_sdk_gen.managers.user_collaborations import ( + CreateCollaborationAccessibleByTypeField, +) + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationRole + +from box_sdk_gen.schemas.collaborations import Collaborations + +from box_sdk_gen.schemas.collaborations_offset_paginated import ( + CollaborationsOffsetPaginated, +) + +from box_sdk_gen.managers.list_collaborations import GetCollaborationsStatus + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client + +from test.commons import create_new_folder + +from test.commons import upload_new_file + + +def testListCollaborations(): + client: BoxClient = get_default_client() + folder: FolderFull = create_new_folder() + file: FileFull = upload_new_file() + group: GroupFull = client.groups.create_group(get_uuid()) + group_collaboration: Collaboration = ( + client.user_collaborations.create_collaboration( + CreateCollaborationItem( + type=CreateCollaborationItemTypeField.FOLDER, id=folder.id + ), + CreateCollaborationAccessibleBy( + type=CreateCollaborationAccessibleByTypeField.GROUP, id=group.id + ), + CreateCollaborationRole.EDITOR, + ) + ) + file_collaboration: Collaboration = client.user_collaborations.create_collaboration( + CreateCollaborationItem(type=CreateCollaborationItemTypeField.FILE, id=file.id), + CreateCollaborationAccessibleBy( + type=CreateCollaborationAccessibleByTypeField.USER, + id=get_env_var('USER_ID'), + ), + CreateCollaborationRole.EDITOR, + ) + assert to_string(group_collaboration.role) == 'editor' + assert to_string(group_collaboration.type) == 'collaboration' + file_collaborations: Collaborations = ( + client.list_collaborations.get_file_collaborations(file.id) + ) + assert len(file_collaborations.entries) > 0 + folder_collaborations: Collaborations = ( + client.list_collaborations.get_folder_collaborations(folder.id) + ) + assert len(folder_collaborations.entries) > 0 + pending_collaborations: CollaborationsOffsetPaginated = ( + client.list_collaborations.get_collaborations(GetCollaborationsStatus.PENDING) + ) + assert len(pending_collaborations.entries) >= 0 + group_collaborations: CollaborationsOffsetPaginated = ( + client.list_collaborations.get_group_collaborations(group.id) + ) + assert len(group_collaborations.entries) > 0 + client.user_collaborations.delete_collaboration_by_id(group_collaboration.id) + client.files.delete_file_by_id(file.id) + client.folders.delete_folder_by_id(folder.id) + client.groups.delete_group_by_id(group.id) diff --git a/test/memberships.py b/test/memberships.py new file mode 100644 index 000000000..879eea5c4 --- /dev/null +++ b/test/memberships.py @@ -0,0 +1,62 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.group_memberships import GroupMemberships + +from box_sdk_gen.schemas.group_full import GroupFull + +from box_sdk_gen.schemas.group_membership import GroupMembership + +from box_sdk_gen.managers.memberships import CreateGroupMembershipUser + +from box_sdk_gen.managers.memberships import CreateGroupMembershipGroup + +from box_sdk_gen.managers.memberships import UpdateGroupMembershipByIdRole + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testMemberships(): + user: UserFull = client.users.create_user( + get_uuid(), login=''.join([get_uuid(), '@boxdemo.com']) + ) + user_memberships: GroupMemberships = client.memberships.get_user_memberships( + user.id + ) + assert user_memberships.total_count == 0 + group: GroupFull = client.groups.create_group(get_uuid()) + group_memberships: GroupMemberships = client.memberships.get_group_memberships( + group.id + ) + assert group_memberships.total_count == 0 + group_membership: GroupMembership = client.memberships.create_group_membership( + CreateGroupMembershipUser(id=user.id), CreateGroupMembershipGroup(id=group.id) + ) + assert group_membership.user.id == user.id + assert group_membership.group.id == group.id + assert to_string(group_membership.role) == 'member' + get_group_membership: GroupMembership = ( + client.memberships.get_group_membership_by_id(group_membership.id) + ) + assert get_group_membership.id == group_membership.id + updated_group_membership: GroupMembership = ( + client.memberships.update_group_membership_by_id( + group_membership.id, role=UpdateGroupMembershipByIdRole.ADMIN + ) + ) + assert updated_group_membership.id == group_membership.id + assert to_string(updated_group_membership.role) == 'admin' + client.memberships.delete_group_membership_by_id(group_membership.id) + with pytest.raises(Exception): + client.memberships.get_group_membership_by_id(group_membership.id) + client.groups.delete_group_by_id(group.id) + client.users.delete_user_by_id(user.id) diff --git a/test/metadata_cascade_policies.py b/test/metadata_cascade_policies.py new file mode 100644 index 000000000..ec7889802 --- /dev/null +++ b/test/metadata_cascade_policies.py @@ -0,0 +1,108 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.managers.metadata_templates import CreateMetadataTemplateFields + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsTypeField, +) + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.metadata_cascade_policy import MetadataCascadePolicy + +from box_sdk_gen.managers.metadata_cascade_policies import ( + CreateMetadataCascadePolicyScope, +) + +from box_sdk_gen.schemas.metadata_cascade_policies import MetadataCascadePolicies + +from box_sdk_gen.managers.metadata_cascade_policies import ( + ApplyMetadataCascadePolicyConflictResolution, +) + +from box_sdk_gen.managers.folder_metadata import CreateFolderMetadataByIdScope + +from box_sdk_gen.managers.metadata_templates import DeleteMetadataTemplateScope + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import to_string + +from test.commons import get_default_client + +from test.commons import create_new_folder + +from test.commons import upload_new_file + +client: BoxClient = get_default_client() + + +def testMetadataCascadePolicies(): + template_key: str = ''.join(['key', get_uuid()]) + client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key='testName', + display_name='testName', + ) + ], + ) + folder: FolderFull = create_new_folder() + enterprise_id: str = get_env_var('ENTERPRISE_ID') + cascade_policy: MetadataCascadePolicy = ( + client.metadata_cascade_policies.create_metadata_cascade_policy( + folder.id, CreateMetadataCascadePolicyScope.ENTERPRISE, template_key + ) + ) + assert to_string(cascade_policy.type) == 'metadata_cascade_policy' + assert to_string(cascade_policy.owner_enterprise.type) == 'enterprise' + assert to_string(cascade_policy.owner_enterprise.id) == enterprise_id + assert to_string(cascade_policy.parent.type) == 'folder' + assert cascade_policy.parent.id == folder.id + assert to_string(cascade_policy.scope) == ''.join(['enterprise_', enterprise_id]) + assert cascade_policy.template_key == template_key + cascade_policy_id: str = cascade_policy.id + policy_from_the_api: MetadataCascadePolicy = ( + client.metadata_cascade_policies.get_metadata_cascade_policy_by_id( + cascade_policy_id + ) + ) + assert cascade_policy_id == policy_from_the_api.id + policies: MetadataCascadePolicies = ( + client.metadata_cascade_policies.get_metadata_cascade_policies(folder.id) + ) + assert len(policies.entries) == 1 + with pytest.raises(Exception): + client.metadata_cascade_policies.apply_metadata_cascade_policy( + cascade_policy_id, ApplyMetadataCascadePolicyConflictResolution.OVERWRITE + ) + client.folder_metadata.create_folder_metadata_by_id( + folder.id, + CreateFolderMetadataByIdScope.ENTERPRISE, + template_key, + {'testName': 'xyz'}, + ) + client.metadata_cascade_policies.apply_metadata_cascade_policy( + cascade_policy_id, ApplyMetadataCascadePolicyConflictResolution.OVERWRITE + ) + client.metadata_cascade_policies.delete_metadata_cascade_policy_by_id( + cascade_policy_id + ) + with pytest.raises(Exception): + client.metadata_cascade_policies.get_metadata_cascade_policy_by_id( + cascade_policy_id + ) + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template_key + ) + client.folders.delete_folder_by_id(folder.id) diff --git a/test/metadata_templates.py b/test/metadata_templates.py new file mode 100644 index 000000000..0a3fbb959 --- /dev/null +++ b/test/metadata_templates.py @@ -0,0 +1,191 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.metadata_template import MetadataTemplate + +from box_sdk_gen.managers.metadata_templates import CreateMetadataTemplateFields + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsTypeField, +) + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsOptionsField, +) + +from box_sdk_gen.managers.metadata_templates import UpdateMetadataTemplateScope + +from box_sdk_gen.managers.metadata_templates import UpdateMetadataTemplateRequestBody + +from box_sdk_gen.managers.metadata_templates import ( + UpdateMetadataTemplateRequestBodyOpField, +) + +from box_sdk_gen.managers.metadata_templates import GetMetadataTemplateScope + +from box_sdk_gen.schemas.metadata_templates import MetadataTemplates + +from box_sdk_gen.managers.metadata_templates import DeleteMetadataTemplateScope + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.metadata_full import MetadataFull + +from box_sdk_gen.managers.file_metadata import CreateFileMetadataByIdScope + +from box_sdk_gen.managers.file_metadata import DeleteFileMetadataByIdScope + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +from test.commons import upload_new_file + +client: BoxClient = get_default_client() + + +def testMetadataTemplates(): + template_key: str = ''.join(['key', get_uuid()]) + template: MetadataTemplate = client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key='testName', + display_name='testName', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + key='age', + display_name='age', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.DATE, + key='birthDate', + display_name='birthDate', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.ENUM, + key='countryCode', + display_name='countryCode', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='US'), + CreateMetadataTemplateFieldsOptionsField(key='CA'), + ], + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.MULTISELECT, + key='sports', + display_name='sports', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='basketball'), + CreateMetadataTemplateFieldsOptionsField(key='football'), + CreateMetadataTemplateFieldsOptionsField(key='tennis'), + ], + ), + ], + ) + assert template.template_key == template_key + assert template.display_name == template_key + assert len(template.fields) == 5 + assert template.fields[0].key == 'testName' + assert template.fields[0].display_name == 'testName' + assert to_string(template.fields[0].type) == 'string' + assert template.fields[1].key == 'age' + assert template.fields[1].display_name == 'age' + assert to_string(template.fields[1].type) == 'float' + assert template.fields[2].key == 'birthDate' + assert template.fields[2].display_name == 'birthDate' + assert to_string(template.fields[2].type) == 'date' + assert template.fields[3].key == 'countryCode' + assert template.fields[3].display_name == 'countryCode' + assert to_string(template.fields[3].type) == 'enum' + assert template.fields[4].key == 'sports' + assert template.fields[4].display_name == 'sports' + assert to_string(template.fields[4].type) == 'multiSelect' + updated_template: MetadataTemplate = ( + client.metadata_templates.update_metadata_template( + UpdateMetadataTemplateScope.ENTERPRISE, + template_key, + [ + UpdateMetadataTemplateRequestBody( + op=UpdateMetadataTemplateRequestBodyOpField.ADDFIELD, + field_key='newfieldname', + data={'type': 'string', 'displayName': 'newFieldName'}, + ) + ], + ) + ) + assert len(updated_template.fields) == 6 + assert updated_template.fields[5].key == 'newfieldname' + assert updated_template.fields[5].display_name == 'newFieldName' + get_metadata_template: MetadataTemplate = ( + client.metadata_templates.get_metadata_template_by_id(template.id) + ) + assert get_metadata_template.id == template.id + get_metadata_template_schema: MetadataTemplate = ( + client.metadata_templates.get_metadata_template( + GetMetadataTemplateScope.ENTERPRISE, template.template_key + ) + ) + assert get_metadata_template_schema.id == template.id + enterprise_metadata_templates: MetadataTemplates = ( + client.metadata_templates.get_enterprise_metadata_templates() + ) + assert len(enterprise_metadata_templates.entries) > 0 + global_metadata_templates: MetadataTemplates = ( + client.metadata_templates.get_global_metadata_templates() + ) + assert len(global_metadata_templates.entries) > 0 + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template.template_key + ) + with pytest.raises(Exception): + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template.template_key + ) + + +def testGetMetadataTemplateByInstance(): + file: FileFull = upload_new_file() + template_key: str = ''.join(['key', get_uuid()]) + template: MetadataTemplate = client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key='testName', + display_name='testName', + ) + ], + ) + created_metadata_instance: MetadataFull = ( + client.file_metadata.create_file_metadata_by_id( + file.id, + CreateFileMetadataByIdScope.ENTERPRISE, + template_key, + {'testName': 'xyz'}, + ) + ) + metadata_templates: MetadataTemplates = ( + client.metadata_templates.get_metadata_templates_by_instance_id( + created_metadata_instance.id + ) + ) + assert len(metadata_templates.entries) == 1 + assert metadata_templates.entries[0].display_name == template_key + assert metadata_templates.entries[0].template_key == template_key + client.file_metadata.delete_file_metadata_by_id( + file.id, DeleteFileMetadataByIdScope.ENTERPRISE, template_key + ) + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template.template_key + ) + client.files.delete_file_by_id(file.id) diff --git a/test/recent_items.py b/test/recent_items.py new file mode 100644 index 000000000..2e171f1ff --- /dev/null +++ b/test/recent_items.py @@ -0,0 +1,17 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.recent_items import RecentItems + +from box_sdk_gen.internal.utils import decode_base_64 + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client_with_user_subject + + +def testRecentItems(): + client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + recent_items: RecentItems = client.recent_items.get_recent_items() + assert len(recent_items.entries) >= 0 diff --git a/test/retention_policies.py b/test/retention_policies.py new file mode 100644 index 000000000..a6e82f8de --- /dev/null +++ b/test/retention_policies.py @@ -0,0 +1,54 @@ +from box_sdk_gen.schemas.retention_policy import RetentionPolicy + +from box_sdk_gen.managers.retention_policies import CreateRetentionPolicyPolicyType + +from box_sdk_gen.managers.retention_policies import ( + CreateRetentionPolicyDispositionAction, +) + +from box_sdk_gen.managers.retention_policies import CreateRetentionPolicyRetentionType + +from box_sdk_gen.schemas.retention_policies import RetentionPolicies + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.client import BoxClient + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testCreateUpdateGetDeleteRetentionPolicy(): + retention_policy_name: str = get_uuid() + retention_description: str = 'test description' + retention_policy: RetentionPolicy = ( + client.retention_policies.create_retention_policy( + retention_policy_name, + CreateRetentionPolicyPolicyType.FINITE, + CreateRetentionPolicyDispositionAction.REMOVE_RETENTION, + description=retention_description, + retention_length='1', + retention_type=CreateRetentionPolicyRetentionType.MODIFIABLE, + can_owner_extend_retention=True, + are_owners_notified=True, + ) + ) + assert retention_policy.policy_name == retention_policy_name + assert retention_policy.description == retention_description + retention_policy_by_id: RetentionPolicy = ( + client.retention_policies.get_retention_policy_by_id(retention_policy.id) + ) + assert retention_policy_by_id.id == retention_policy.id + retention_policies: RetentionPolicies = ( + client.retention_policies.get_retention_policies() + ) + assert len(retention_policies.entries) > 0 + updated_retention_policy_name: str = get_uuid() + updated_retention_policy: RetentionPolicy = ( + client.retention_policies.update_retention_policy_by_id( + retention_policy.id, policy_name=updated_retention_policy_name + ) + ) + assert updated_retention_policy.policy_name == updated_retention_policy_name + client.retention_policies.delete_retention_policy_by_id(retention_policy.id) diff --git a/test/retention_policy_assignments.py b/test/retention_policy_assignments.py new file mode 100644 index 000000000..198042dc3 --- /dev/null +++ b/test/retention_policy_assignments.py @@ -0,0 +1,122 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.retention_policy import RetentionPolicy + +from box_sdk_gen.managers.retention_policies import CreateRetentionPolicyPolicyType + +from box_sdk_gen.managers.retention_policies import ( + CreateRetentionPolicyDispositionAction, +) + +from box_sdk_gen.managers.retention_policies import CreateRetentionPolicyRetentionType + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.managers.uploads import UploadFileVersionAttributes + +from box_sdk_gen.schemas.retention_policy_assignment import RetentionPolicyAssignment + +from box_sdk_gen.managers.retention_policy_assignments import ( + CreateRetentionPolicyAssignmentAssignTo, +) + +from box_sdk_gen.managers.retention_policy_assignments import ( + CreateRetentionPolicyAssignmentAssignToTypeField, +) + +from box_sdk_gen.schemas.retention_policy_assignments import RetentionPolicyAssignments + +from box_sdk_gen.schemas.files_under_retention import FilesUnderRetention + +from box_sdk_gen.internal.utils import decode_base_64 + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import generate_byte_stream + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testCreateUpdateGetDeleteRetentionPolicyAssignment(): + retention_policy_name: str = get_uuid() + retention_description: str = 'test description' + retention_policy: RetentionPolicy = ( + client.retention_policies.create_retention_policy( + retention_policy_name, + CreateRetentionPolicyPolicyType.FINITE, + CreateRetentionPolicyDispositionAction.REMOVE_RETENTION, + description=retention_description, + retention_length='1', + retention_type=CreateRetentionPolicyRetentionType.MODIFIABLE, + can_owner_extend_retention=True, + are_owners_notified=True, + ) + ) + folder: FolderFull = client.folders.create_folder( + get_uuid(), CreateFolderParent(id='0') + ) + files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), parent=UploadFileAttributesParentField(id=folder.id) + ), + generate_byte_stream(10), + ) + file: FileFull = files.entries[0] + new_versions: Files = client.uploads.upload_file_version( + file.id, UploadFileVersionAttributes(name=get_uuid()), generate_byte_stream(20) + ) + new_version: FileFull = new_versions.entries[0] + retention_policy_assignment: RetentionPolicyAssignment = ( + client.retention_policy_assignments.create_retention_policy_assignment( + retention_policy.id, + CreateRetentionPolicyAssignmentAssignTo( + type=CreateRetentionPolicyAssignmentAssignToTypeField.FOLDER, + id=folder.id, + ), + ) + ) + assert retention_policy_assignment.retention_policy.id == retention_policy.id + assert retention_policy_assignment.assigned_to.id == folder.id + retention_policy_assignment_by_id: RetentionPolicyAssignment = ( + client.retention_policy_assignments.get_retention_policy_assignment_by_id( + retention_policy_assignment.id + ) + ) + assert retention_policy_assignment_by_id.id == retention_policy_assignment.id + retention_policy_assignments: RetentionPolicyAssignments = ( + client.retention_policy_assignments.get_retention_policy_assignments( + retention_policy.id + ) + ) + assert len(retention_policy_assignments.entries) == 1 + files_under_retention: FilesUnderRetention = ( + client.retention_policy_assignments.get_files_under_retention_policy_assignment( + retention_policy_assignment.id + ) + ) + assert len(files_under_retention.entries) == 1 + client.retention_policy_assignments.delete_retention_policy_assignment_by_id( + retention_policy_assignment.id + ) + retention_policy_assignments_after_delete: RetentionPolicyAssignments = ( + client.retention_policy_assignments.get_retention_policy_assignments( + retention_policy.id + ) + ) + assert len(retention_policy_assignments_after_delete.entries) == 0 + client.retention_policies.delete_retention_policy_by_id(retention_policy.id) + client.files.delete_file_by_id(file.id) diff --git a/test/search.py b/test/search.py new file mode 100644 index 000000000..6846f43f0 --- /dev/null +++ b/test/search.py @@ -0,0 +1,271 @@ +from typing import Dict + +from typing import Union + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.metadata_template import MetadataTemplate + +from box_sdk_gen.managers.metadata_templates import CreateMetadataTemplateFields + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsTypeField, +) + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsOptionsField, +) + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.metadata_full import MetadataFull + +from box_sdk_gen.managers.file_metadata import CreateFileMetadataByIdScope + +from box_sdk_gen.schemas.metadata_query_results import MetadataQueryResults + +from box_sdk_gen.managers.metadata_templates import DeleteMetadataTemplateScope + +from box_sdk_gen.schemas.metadata_filter import MetadataFilter + +from box_sdk_gen.schemas.metadata_filter import MetadataFilterScopeField + +from box_sdk_gen.managers.search import SearchForContentTrashContent + +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 delay_in_seconds + +from test.commons import get_default_client + +from box_sdk_gen.schemas.metadata_field_filter_date_range import ( + MetadataFieldFilterDateRange, +) + +from box_sdk_gen.schemas.metadata_field_filter_float_range import ( + MetadataFieldFilterFloatRange, +) + +from box_sdk_gen.schemas.search_results import SearchResults + +from box_sdk_gen.schemas.search_results_with_shared_links import ( + SearchResultsWithSharedLinks, +) + +client: BoxClient = get_default_client() + + +def testCreateMetaDataQueryExecuteRead(): + template_key: str = ''.join(['key', get_uuid()]) + template: MetadataTemplate = client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key='name', + display_name='name', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + key='age', + display_name='age', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.DATE, + key='birthDate', + display_name='birthDate', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.ENUM, + key='countryCode', + display_name='countryCode', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='US'), + CreateMetadataTemplateFieldsOptionsField(key='CA'), + ], + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.MULTISELECT, + key='sports', + display_name='sports', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='basketball'), + CreateMetadataTemplateFieldsOptionsField(key='football'), + CreateMetadataTemplateFieldsOptionsField(key='tennis'), + ], + ), + ], + ) + assert template.template_key == template_key + files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(10), + ) + file: FileFull = files.entries[0] + metadata: MetadataFull = client.file_metadata.create_file_metadata_by_id( + file.id, + CreateFileMetadataByIdScope.ENTERPRISE, + template_key, + { + 'name': 'John', + 'age': 23, + 'birthDate': '2001-01-03T02:20:50.520Z', + 'countryCode': 'US', + 'sports': ['basketball', 'tennis'], + }, + ) + assert metadata.template == template_key + assert metadata.scope == template.scope + delay_in_seconds(5) + search_from: str = ''.join([template.scope, '.', template.template_key]) + query: MetadataQueryResults = client.search.search_by_metadata_query( + search_from, + '0', + query='name = :name AND age < :age AND birthDate >= :birthDate AND countryCode = :countryCode AND sports = :sports', + query_params={ + 'name': 'John', + 'age': 50, + 'birthDate': '2001-01-01T02:20:10.120Z', + 'countryCode': 'US', + 'sports': ['basketball', 'tennis'], + }, + ) + assert len(query.entries) >= 0 + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template.template_key + ) + client.files.delete_file_by_id(file.id) + + +def testMetadataFilters(): + template_key: str = ''.join(['key', get_uuid()]) + template: MetadataTemplate = client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + key='floatField', + display_name='floatField', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key='stringField', + display_name='stringField', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.DATE, + key='dateField', + display_name='dateField', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.ENUM, + key='enumField', + display_name='enumField', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='enumValue1'), + CreateMetadataTemplateFieldsOptionsField(key='enumValue2'), + ], + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.MULTISELECT, + key='multiSelectField', + display_name='multiSelectField', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='multiSelectValue1'), + CreateMetadataTemplateFieldsOptionsField(key='multiSelectValue2'), + ], + ), + ], + ) + files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(10), + ) + file: FileFull = files.entries[0] + metadata: MetadataFull = client.file_metadata.create_file_metadata_by_id( + file.id, + CreateFileMetadataByIdScope.ENTERPRISE, + template_key, + { + 'floatField': 10, + 'stringField': 'stringValue', + 'dateField': '2035-01-02T00:00:00Z', + 'enumField': 'enumValue2', + 'multiSelectField': ['multiSelectValue1', 'multiSelectValue2'], + }, + ) + search_filters: Dict[str, str] = { + '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'], + } + query: Union[SearchResults, SearchResultsWithSharedLinks] = ( + client.search.search_for_content( + ancestor_folder_ids=['0'], + mdfilters=[ + MetadataFilter( + filters=search_filters, + scope=MetadataFilterScopeField.ENTERPRISE, + template_key=template_key, + ) + ], + ) + ) + query_results: SearchResults = query + assert len(query_results.entries) >= 0 + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template.template_key + ) + client.files.delete_file_by_id(file.id) + + +def testGetSearch(): + keyword: str = 'test' + search: Union[SearchResults, SearchResultsWithSharedLinks] = ( + client.search.search_for_content( + query=keyword, + ancestor_folder_ids=['0'], + trash_content=SearchForContentTrashContent.NON_TRASHED_ONLY, + ) + ) + assert to_string(search.type) == 'search_results_items' + search_results: SearchResults = search + assert len(search_results.entries) >= 0 + search_with_shared_link: Union[SearchResults, SearchResultsWithSharedLinks] = ( + client.search.search_for_content( + query=keyword, + ancestor_folder_ids=['0'], + trash_content=SearchForContentTrashContent.NON_TRASHED_ONLY, + include_recent_shared_links=True, + ) + ) + assert to_string(search_with_shared_link.type) == 'search_results_with_shared_links' + search_results_with_shared_link: SearchResultsWithSharedLinks = ( + search_with_shared_link + ) + assert len(search_results_with_shared_link.entries) >= 0 diff --git a/test/session_termination.py b/test/session_termination.py new file mode 100644 index 000000000..92fb129e4 --- /dev/null +++ b/test/session_termination.py @@ -0,0 +1,46 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.session_termination_message import SessionTerminationMessage + +from box_sdk_gen.schemas.group_full import GroupFull + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client + +from test.commons import get_default_client_with_user_subject + +client: BoxClient = get_default_client() + + +def testSessionTerminationUser(): + admin_client: BoxClient = get_default_client_with_user_subject( + get_env_var('USER_ID') + ) + user: UserFull = admin_client.users.get_user_me() + result: SessionTerminationMessage = ( + client.session_termination.terminate_users_sessions( + [get_env_var('USER_ID')], [user.login] + ) + ) + assert ( + result.message + == 'Request is successful, please check the admin events for the status of the job' + ) + + +def testSessionTerminationGroup(): + group_name: str = get_uuid() + group: GroupFull = client.groups.create_group(group_name) + result: SessionTerminationMessage = ( + client.session_termination.terminate_groups_sessions([group.id]) + ) + assert ( + result.message + == 'Request is successful, please check the admin events for the status of the job' + ) + client.groups.delete_group_by_id(group.id) diff --git a/test/shared_links_app_items.py b/test/shared_links_app_items.py new file mode 100644 index 000000000..04db6b614 --- /dev/null +++ b/test/shared_links_app_items.py @@ -0,0 +1,20 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.app_item import AppItem + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testSharedLinksAppItems(): + app_item_shared_link: str = get_env_var('APP_ITEM_SHARED_LINK') + app_item: AppItem = client.shared_links_app_items.find_app_item_for_shared_link( + ''.join(['shared_link=', app_item_shared_link]) + ) + assert to_string(app_item.type) == 'app_item' + assert app_item.application_type == 'hubs' diff --git a/test/shared_links_files.py b/test/shared_links_files.py new file mode 100644 index 000000000..447d7f742 --- /dev/null +++ b/test/shared_links_files.py @@ -0,0 +1,100 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.managers.shared_links_files import AddShareLinkToFileSharedLink + +from box_sdk_gen.managers.shared_links_files import ( + AddShareLinkToFileSharedLinkAccessField, +) + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.managers.shared_links_files import UpdateSharedLinkOnFileSharedLink + +from box_sdk_gen.managers.shared_links_files import ( + UpdateSharedLinkOnFileSharedLinkAccessField, +) + +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 get_env_var + +from test.commons import get_default_client + +from test.commons import get_default_client_with_user_subject + +from box_sdk_gen.internal.utils import create_null + +client: BoxClient = get_default_client() + + +def testSharedLinksFiles(): + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(10), + ) + file_id: str = uploaded_files.entries[0].id + client.shared_links_files.add_share_link_to_file( + file_id, + 'shared_link', + shared_link=AddShareLinkToFileSharedLink( + access=AddShareLinkToFileSharedLinkAccessField.OPEN, password='Secret123@' + ), + ) + file_from_api: FileFull = client.shared_links_files.get_shared_link_for_file( + file_id, 'shared_link' + ) + assert to_string(file_from_api.shared_link.access) == 'open' + user_id: str = get_env_var('USER_ID') + user_client: BoxClient = get_default_client_with_user_subject(user_id) + file_from_shared_link_password: FileFull = ( + user_client.shared_links_files.find_file_for_shared_link( + ''.join( + [ + 'shared_link=', + file_from_api.shared_link.url, + '&shared_link_password=Secret123@', + ] + ) + ) + ) + assert file_id == file_from_shared_link_password.id + with pytest.raises(Exception): + user_client.shared_links_files.find_file_for_shared_link( + ''.join( + [ + 'shared_link=', + file_from_api.shared_link.url, + '&shared_link_password=incorrectPassword', + ] + ) + ) + updated_file: FileFull = client.shared_links_files.update_shared_link_on_file( + file_id, + 'shared_link', + shared_link=UpdateSharedLinkOnFileSharedLink( + access=UpdateSharedLinkOnFileSharedLinkAccessField.COLLABORATORS + ), + ) + assert to_string(updated_file.shared_link.access) == 'collaborators' + client.shared_links_files.remove_shared_link_from_file( + file_id, 'shared_link', shared_link=create_null() + ) + file_from_api_after_remove: FileFull = ( + client.shared_links_files.get_shared_link_for_file(file_id, 'shared_link') + ) + assert file_from_api_after_remove.shared_link == None + client.files.delete_file_by_id(file_id) diff --git a/test/shared_links_folders.py b/test/shared_links_folders.py new file mode 100644 index 000000000..00f2db300 --- /dev/null +++ b/test/shared_links_folders.py @@ -0,0 +1,94 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.managers.shared_links_folders import AddShareLinkToFolderSharedLink + +from box_sdk_gen.managers.shared_links_folders import ( + AddShareLinkToFolderSharedLinkAccessField, +) + +from box_sdk_gen.managers.shared_links_folders import UpdateSharedLinkOnFolderSharedLink + +from box_sdk_gen.managers.shared_links_folders import ( + UpdateSharedLinkOnFolderSharedLinkAccessField, +) + +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 get_env_var + +from test.commons import get_default_client + +from test.commons import get_default_client_with_user_subject + +from box_sdk_gen.internal.utils import create_null + +client: BoxClient = get_default_client() + + +def testSharedLinksFolders(): + folder: FolderFull = client.folders.create_folder( + get_uuid(), CreateFolderParent(id='0') + ) + client.shared_links_folders.add_share_link_to_folder( + folder.id, + 'shared_link', + shared_link=AddShareLinkToFolderSharedLink( + access=AddShareLinkToFolderSharedLinkAccessField.OPEN, password='Secret123@' + ), + ) + folder_from_api: FolderFull = ( + client.shared_links_folders.get_shared_link_for_folder(folder.id, 'shared_link') + ) + assert to_string(folder_from_api.shared_link.access) == 'open' + user_id: str = get_env_var('USER_ID') + user_client: BoxClient = get_default_client_with_user_subject(user_id) + folder_from_shared_link_password: FolderFull = ( + user_client.shared_links_folders.find_folder_for_shared_link( + ''.join( + [ + 'shared_link=', + folder_from_api.shared_link.url, + '&shared_link_password=Secret123@', + ] + ) + ) + ) + assert folder.id == folder_from_shared_link_password.id + with pytest.raises(Exception): + user_client.shared_links_folders.find_folder_for_shared_link( + ''.join( + [ + 'shared_link=', + folder_from_api.shared_link.url, + '&shared_link_password=incorrectPassword', + ] + ) + ) + updated_folder: FolderFull = ( + client.shared_links_folders.update_shared_link_on_folder( + folder.id, + 'shared_link', + shared_link=UpdateSharedLinkOnFolderSharedLink( + access=UpdateSharedLinkOnFolderSharedLinkAccessField.COLLABORATORS + ), + ) + ) + assert to_string(updated_folder.shared_link.access) == 'collaborators' + client.shared_links_folders.remove_shared_link_from_folder( + folder.id, 'shared_link', shared_link=create_null() + ) + folder_from_api_after_remove: FolderFull = ( + client.shared_links_folders.get_shared_link_for_folder(folder.id, 'shared_link') + ) + assert folder_from_api_after_remove.shared_link == None + client.folders.delete_folder_by_id(folder.id) diff --git a/test/shared_links_web_links.py b/test/shared_links_web_links.py new file mode 100644 index 000000000..2f80d0fdf --- /dev/null +++ b/test/shared_links_web_links.py @@ -0,0 +1,108 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.managers.web_links import CreateWebLinkParent + +from box_sdk_gen.managers.shared_links_web_links import AddShareLinkToWebLinkSharedLink + +from box_sdk_gen.managers.shared_links_web_links import ( + AddShareLinkToWebLinkSharedLinkAccessField, +) + +from box_sdk_gen.managers.shared_links_web_links import ( + UpdateSharedLinkOnWebLinkSharedLink, +) + +from box_sdk_gen.managers.shared_links_web_links import ( + UpdateSharedLinkOnWebLinkSharedLinkAccessField, +) + +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 get_env_var + +from test.commons import get_default_client + +from test.commons import get_default_client_with_user_subject + +from box_sdk_gen.internal.utils import create_null + +client: BoxClient = get_default_client() + + +def testSharedLinksWebLinks(): + parent: FolderFull = client.folders.get_folder_by_id('0') + web_link: WebLink = client.web_links.create_web_link( + 'https://www.box.com', + CreateWebLinkParent(id=parent.id), + name=get_uuid(), + description='Weblink description', + ) + web_link_id: str = web_link.id + client.shared_links_web_links.add_share_link_to_web_link( + web_link_id, + 'shared_link', + shared_link=AddShareLinkToWebLinkSharedLink( + access=AddShareLinkToWebLinkSharedLinkAccessField.OPEN, + password='Secret123@', + ), + ) + web_link_from_api: WebLink = ( + client.shared_links_web_links.get_shared_link_for_web_link( + web_link_id, 'shared_link' + ) + ) + assert to_string(web_link_from_api.shared_link.access) == 'open' + user_id: str = get_env_var('USER_ID') + user_client: BoxClient = get_default_client_with_user_subject(user_id) + web_link_from_shared_link_password: WebLink = ( + user_client.shared_links_web_links.find_web_link_for_shared_link( + ''.join( + [ + 'shared_link=', + web_link_from_api.shared_link.url, + '&shared_link_password=Secret123@', + ] + ) + ) + ) + assert web_link_id == web_link_from_shared_link_password.id + with pytest.raises(Exception): + user_client.shared_links_web_links.find_web_link_for_shared_link( + ''.join( + [ + 'shared_link=', + web_link_from_api.shared_link.url, + '&shared_link_password=incorrectPassword', + ] + ) + ) + updated_web_link: WebLink = ( + client.shared_links_web_links.update_shared_link_on_web_link( + web_link_id, + 'shared_link', + shared_link=UpdateSharedLinkOnWebLinkSharedLink( + access=UpdateSharedLinkOnWebLinkSharedLinkAccessField.COLLABORATORS + ), + ) + ) + assert to_string(updated_web_link.shared_link.access) == 'collaborators' + client.shared_links_web_links.remove_shared_link_from_web_link( + web_link_id, 'shared_link', shared_link=create_null() + ) + web_link_from_api_after_remove: WebLink = ( + client.shared_links_web_links.get_shared_link_for_web_link( + web_link_id, 'shared_link' + ) + ) + assert web_link_from_api_after_remove.shared_link == None + client.web_links.delete_web_link_by_id(web_link_id) diff --git a/test/shield_information_barrier_reports.py b/test/shield_information_barrier_reports.py new file mode 100644 index 000000000..ed1b9f1ca --- /dev/null +++ b/test/shield_information_barrier_reports.py @@ -0,0 +1,71 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.schemas.shield_information_barrier import ShieldInformationBarrier + +from box_sdk_gen.schemas.shield_information_barrier_reports import ( + ShieldInformationBarrierReports, +) + +from box_sdk_gen.schemas.shield_information_barrier_report import ( + ShieldInformationBarrierReport, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBaseTypeField, +) + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client_with_user_subject + +from test.commons import get_or_create_shield_information_barrier + +from box_sdk_gen.client import BoxClient + + +def testShieldInformationBarrierReports(): + client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + enterprise_id: str = get_env_var('ENTERPRISE_ID') + barrier: ShieldInformationBarrier = get_or_create_shield_information_barrier( + client, enterprise_id + ) + assert to_string(barrier.status) == 'draft' + assert to_string(barrier.type) == 'shield_information_barrier' + assert barrier.enterprise.id == enterprise_id + assert to_string(barrier.enterprise.type) == 'enterprise' + barrier_id: str = barrier.id + existing_reports: ShieldInformationBarrierReports = ( + client.shield_information_barrier_reports.get_shield_information_barrier_reports( + barrier_id + ) + ) + if len(existing_reports.entries) > 0: + return None + created_report: ShieldInformationBarrierReport = ( + client.shield_information_barrier_reports.create_shield_information_barrier_report( + shield_information_barrier=ShieldInformationBarrierBase( + id=barrier_id, + type=ShieldInformationBarrierBaseTypeField.SHIELD_INFORMATION_BARRIER, + ) + ) + ) + assert ( + created_report.shield_information_barrier.shield_information_barrier.id + == barrier_id + ) + retrieved_report: ShieldInformationBarrierReport = ( + client.shield_information_barrier_reports.get_shield_information_barrier_report_by_id( + created_report.id + ) + ) + assert retrieved_report.id == created_report.id + retrieved_reports: ShieldInformationBarrierReports = ( + client.shield_information_barrier_reports.get_shield_information_barrier_reports( + barrier_id + ) + ) + assert len(retrieved_reports.entries) > 0 diff --git a/test/shield_information_barrier_segment_members.py b/test/shield_information_barrier_segment_members.py new file mode 100644 index 000000000..fc5f27813 --- /dev/null +++ b/test/shield_information_barrier_segment_members.py @@ -0,0 +1,96 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.shield_information_barrier import ShieldInformationBarrier + +from box_sdk_gen.schemas.shield_information_barrier_segment import ( + ShieldInformationBarrierSegment, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBaseTypeField, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_member import ( + ShieldInformationBarrierSegmentMember, +) + +from box_sdk_gen.managers.shield_information_barrier_segment_members import ( + CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegment, +) + +from box_sdk_gen.managers.shield_information_barrier_segment_members import ( + CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentTypeField, +) + +from box_sdk_gen.schemas.user_base import UserBase + +from box_sdk_gen.schemas.shield_information_barrier_segment_members import ( + ShieldInformationBarrierSegmentMembers, +) + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client_with_user_subject + +from test.commons import get_or_create_shield_information_barrier + + +def testShieldInformationBarrierSegmentMembers(): + client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + enterprise_id: str = get_env_var('ENTERPRISE_ID') + barrier: ShieldInformationBarrier = get_or_create_shield_information_barrier( + client, enterprise_id + ) + barrier_id: str = barrier.id + segment_name: str = get_uuid() + segment: ShieldInformationBarrierSegment = ( + client.shield_information_barrier_segments.create_shield_information_barrier_segment( + ShieldInformationBarrierBase( + id=barrier_id, + type=ShieldInformationBarrierBaseTypeField.SHIELD_INFORMATION_BARRIER, + ), + segment_name, + ) + ) + assert segment.name == segment_name + segment_member: ShieldInformationBarrierSegmentMember = ( + client.shield_information_barrier_segment_members.create_shield_information_barrier_segment_member( + CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegment( + id=segment.id, + type=CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegmentTypeField.SHIELD_INFORMATION_BARRIER_SEGMENT, + ), + UserBase(id=get_env_var('USER_ID')), + ) + ) + assert segment_member.user.id == get_env_var('USER_ID') + assert segment_member.shield_information_barrier_segment.id == segment.id + segment_members: ShieldInformationBarrierSegmentMembers = ( + client.shield_information_barrier_segment_members.get_shield_information_barrier_segment_members( + segment.id + ) + ) + assert len(segment_members.entries) > 0 + segment_member_get: ShieldInformationBarrierSegmentMember = ( + client.shield_information_barrier_segment_members.get_shield_information_barrier_segment_member_by_id( + segment_member.id + ) + ) + assert segment_member_get.id == segment_member.id + client.shield_information_barrier_segment_members.delete_shield_information_barrier_segment_member_by_id( + segment_member.id + ) + with pytest.raises(Exception): + client.shield_information_barrier_segment_members.get_shield_information_barrier_segment_member_by_id( + segment_member.id + ) + client.shield_information_barrier_segments.delete_shield_information_barrier_segment_by_id( + segment.id + ) diff --git a/test/shield_information_barrier_segment_restrictions.py b/test/shield_information_barrier_segment_restrictions.py new file mode 100644 index 000000000..4abb37088 --- /dev/null +++ b/test/shield_information_barrier_segment_restrictions.py @@ -0,0 +1,129 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.shield_information_barrier import ShieldInformationBarrier + +from box_sdk_gen.schemas.shield_information_barrier_segment import ( + ShieldInformationBarrierSegment, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBaseTypeField, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_restriction import ( + ShieldInformationBarrierSegmentRestriction, +) + +from box_sdk_gen.managers.shield_information_barrier_segment_restrictions import ( + CreateShieldInformationBarrierSegmentRestrictionType, +) + +from box_sdk_gen.managers.shield_information_barrier_segment_restrictions import ( + CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegment, +) + +from box_sdk_gen.managers.shield_information_barrier_segment_restrictions import ( + CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegmentTypeField, +) + +from box_sdk_gen.managers.shield_information_barrier_segment_restrictions import ( + CreateShieldInformationBarrierSegmentRestrictionRestrictedSegment, +) + +from box_sdk_gen.managers.shield_information_barrier_segment_restrictions import ( + CreateShieldInformationBarrierSegmentRestrictionRestrictedSegmentTypeField, +) + +from box_sdk_gen.schemas.shield_information_barrier_segment_restrictions import ( + ShieldInformationBarrierSegmentRestrictions, +) + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client_with_user_subject + +from test.commons import get_or_create_shield_information_barrier + + +def testShieldInformationBarrierSegmentRestrictions(): + client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + enterprise_id: str = get_env_var('ENTERPRISE_ID') + barrier: ShieldInformationBarrier = get_or_create_shield_information_barrier( + client, enterprise_id + ) + barrier_id: str = barrier.id + segment: ShieldInformationBarrierSegment = ( + client.shield_information_barrier_segments.create_shield_information_barrier_segment( + ShieldInformationBarrierBase( + id=barrier_id, + type=ShieldInformationBarrierBaseTypeField.SHIELD_INFORMATION_BARRIER, + ), + get_uuid(), + description='barrier segment description', + ) + ) + segment_id: str = segment.id + segment_to_restrict: ShieldInformationBarrierSegment = ( + client.shield_information_barrier_segments.create_shield_information_barrier_segment( + ShieldInformationBarrierBase( + id=barrier_id, + type=ShieldInformationBarrierBaseTypeField.SHIELD_INFORMATION_BARRIER, + ), + get_uuid(), + description='barrier segment description', + ) + ) + segment_to_restrict_id: str = segment_to_restrict.id + segment_restriction: ShieldInformationBarrierSegmentRestriction = ( + client.shield_information_barrier_segment_restrictions.create_shield_information_barrier_segment_restriction( + CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegment( + id=segment_id, + type=CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegmentTypeField.SHIELD_INFORMATION_BARRIER_SEGMENT, + ), + CreateShieldInformationBarrierSegmentRestrictionRestrictedSegment( + id=segment_to_restrict_id, + type=CreateShieldInformationBarrierSegmentRestrictionRestrictedSegmentTypeField.SHIELD_INFORMATION_BARRIER_SEGMENT, + ), + type=CreateShieldInformationBarrierSegmentRestrictionType.SHIELD_INFORMATION_BARRIER_SEGMENT_RESTRICTION, + ) + ) + segment_restriction_id: str = segment_restriction.id + assert segment_restriction.shield_information_barrier_segment.id == segment_id + segment_restrictions: ShieldInformationBarrierSegmentRestrictions = ( + client.shield_information_barrier_segment_restrictions.get_shield_information_barrier_segment_restrictions( + segment_id + ) + ) + assert len(segment_restrictions.entries) > 0 + segment_restriction_from_api: ShieldInformationBarrierSegmentRestriction = ( + client.shield_information_barrier_segment_restrictions.get_shield_information_barrier_segment_restriction_by_id( + segment_restriction_id + ) + ) + assert segment_restriction_from_api.id == segment_restriction_id + assert ( + segment_restriction_from_api.shield_information_barrier_segment.id == segment_id + ) + assert segment_restriction_from_api.restricted_segment.id == segment_to_restrict_id + assert segment_restriction_from_api.shield_information_barrier.id == barrier_id + client.shield_information_barrier_segment_restrictions.delete_shield_information_barrier_segment_restriction_by_id( + segment_restriction_id + ) + with pytest.raises(Exception): + client.shield_information_barrier_segment_restrictions.get_shield_information_barrier_segment_restriction_by_id( + segment_restriction_id + ) + client.shield_information_barrier_segments.delete_shield_information_barrier_segment_by_id( + segment_id + ) + client.shield_information_barrier_segments.delete_shield_information_barrier_segment_by_id( + segment_to_restrict_id + ) diff --git a/test/shield_information_barrier_segments.py b/test/shield_information_barrier_segments.py new file mode 100644 index 000000000..834496487 --- /dev/null +++ b/test/shield_information_barrier_segments.py @@ -0,0 +1,84 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.shield_information_barrier import ShieldInformationBarrier + +from box_sdk_gen.schemas.shield_information_barrier_segment import ( + ShieldInformationBarrierSegment, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBase, +) + +from box_sdk_gen.schemas.shield_information_barrier_base import ( + ShieldInformationBarrierBaseTypeField, +) + +from box_sdk_gen.schemas.shield_information_barrier_segments import ( + ShieldInformationBarrierSegments, +) + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client_with_user_subject + +from test.commons import get_or_create_shield_information_barrier + + +def testShieldInformationBarrierSegments(): + client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + enterprise_id: str = get_env_var('ENTERPRISE_ID') + barrier: ShieldInformationBarrier = get_or_create_shield_information_barrier( + client, enterprise_id + ) + barrier_id: str = barrier.id + segment_name: str = get_uuid() + segment_description: str = 'barrier segment description' + segment: ShieldInformationBarrierSegment = ( + client.shield_information_barrier_segments.create_shield_information_barrier_segment( + ShieldInformationBarrierBase( + id=barrier_id, + type=ShieldInformationBarrierBaseTypeField.SHIELD_INFORMATION_BARRIER, + ), + segment_name, + description=segment_description, + ) + ) + assert segment.name == segment_name + segments: ShieldInformationBarrierSegments = ( + client.shield_information_barrier_segments.get_shield_information_barrier_segments( + barrier_id + ) + ) + assert len(segments.entries) > 0 + segment_id: str = segment.id + segment_from_api: ShieldInformationBarrierSegment = ( + client.shield_information_barrier_segments.get_shield_information_barrier_segment_by_id( + segment_id + ) + ) + assert to_string(segment_from_api.type) == 'shield_information_barrier_segment' + assert segment_from_api.id == segment_id + assert segment_from_api.name == segment_name + assert segment_from_api.description == segment_description + assert segment_from_api.shield_information_barrier.id == barrier_id + updated_segment_description: str = 'updated barrier segment description' + updated_segment: ShieldInformationBarrierSegment = ( + client.shield_information_barrier_segments.update_shield_information_barrier_segment_by_id( + segment_id, description=updated_segment_description + ) + ) + assert updated_segment.description == updated_segment_description + client.shield_information_barrier_segments.delete_shield_information_barrier_segment_by_id( + segment_id + ) + with pytest.raises(Exception): + client.shield_information_barrier_segments.get_shield_information_barrier_segment_by_id( + segment_id + ) diff --git a/test/shield_information_barriers.py b/test/shield_information_barriers.py new file mode 100644 index 000000000..56b0456d0 --- /dev/null +++ b/test/shield_information_barriers.py @@ -0,0 +1,46 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.shield_information_barrier import ShieldInformationBarrier + +from box_sdk_gen.schemas.shield_information_barriers import ShieldInformationBarriers + +from box_sdk_gen.managers.shield_information_barriers import ( + UpdateShieldInformationBarrierStatusStatus, +) + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client_with_user_subject + +from test.commons import get_or_create_shield_information_barrier + + +def testShieldInformationBarriers(): + client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + enterprise_id: str = get_env_var('ENTERPRISE_ID') + barrier: ShieldInformationBarrier = get_or_create_shield_information_barrier( + client, enterprise_id + ) + assert to_string(barrier.status) == 'draft' + assert to_string(barrier.type) == 'shield_information_barrier' + assert barrier.enterprise.id == enterprise_id + assert to_string(barrier.enterprise.type) == 'enterprise' + barrier_id: str = barrier.id + barrier_from_api: ShieldInformationBarrier = ( + client.shield_information_barriers.get_shield_information_barrier_by_id( + barrier_id + ) + ) + assert barrier_from_api.id == barrier_id + barriers: ShieldInformationBarriers = ( + client.shield_information_barriers.get_shield_information_barriers() + ) + assert len(barriers.entries) == 1 + with pytest.raises(Exception): + client.shield_information_barriers.update_shield_information_barrier_status( + barrier_id, UpdateShieldInformationBarrierStatusStatus.DISABLED + ) diff --git a/test/shield_lists.py b/test/shield_lists.py new file mode 100644 index 000000000..43baf9731 --- /dev/null +++ b/test/shield_lists.py @@ -0,0 +1,125 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.v2025_r0.shield_list_v2025_r0 import ShieldListV2025R0 + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_country_v2025_r0 import ( + ShieldListContentCountryV2025R0TypeField, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_domain_v2025_r0 import ( + ShieldListContentDomainV2025R0TypeField, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_email_v2025_r0 import ( + ShieldListContentEmailV2025R0TypeField, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_ip_v2025_r0 import ( + ShieldListContentIpV2025R0TypeField, +) + +from box_sdk_gen.schemas.v2025_r0.shield_lists_v2025_r0 import ShieldListsV2025R0 + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import upload_new_file + +from test.commons import create_new_folder + +from test.commons import get_default_client_with_user_subject + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_country_v2025_r0 import ( + ShieldListContentCountryV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_domain_v2025_r0 import ( + ShieldListContentDomainV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_email_v2025_r0 import ( + ShieldListContentEmailV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shield_list_content_ip_v2025_r0 import ( + ShieldListContentIpV2025R0, +) + +user_id: str = get_env_var('USER_ID') + +client: BoxClient = get_default_client_with_user_subject(user_id) + + +def testCreateGetUpdateDeleteShieldList(): + shield_list_country_name: str = ''.join([get_uuid(), 'shieldListCountry']) + shield_list_country: ShieldListV2025R0 = ( + client.shield_lists.create_shield_list_v2025_r0( + shield_list_country_name, + ShieldListContentCountryV2025R0( + type=ShieldListContentCountryV2025R0TypeField.COUNTRY, + country_codes=['US', 'PL'], + ), + description='A list of things that are shielded', + ) + ) + shield_list_content_domain_name: str = ''.join( + [get_uuid(), 'shieldListContentDomain'] + ) + shield_list_content_domain: ShieldListV2025R0 = ( + client.shield_lists.create_shield_list_v2025_r0( + shield_list_content_domain_name, + ShieldListContentDomainV2025R0( + type=ShieldListContentDomainV2025R0TypeField.DOMAIN, + domains=['box.com', 'example.com'], + ), + description='A list of things that are shielded', + ) + ) + shield_list_content_email_name: str = ''.join( + [get_uuid(), 'shieldListContentEmail'] + ) + shield_list_content_email: ShieldListV2025R0 = ( + client.shield_lists.create_shield_list_v2025_r0( + shield_list_content_email_name, + ShieldListContentEmailV2025R0( + type=ShieldListContentEmailV2025R0TypeField.EMAIL, + email_addresses=['test@box.com', 'test@example.com'], + ), + description='A list of things that are shielded', + ) + ) + shield_list_content_ip_name: str = ''.join([get_uuid(), 'shieldListContentIp']) + shield_list_content_ip: ShieldListV2025R0 = ( + client.shield_lists.create_shield_list_v2025_r0( + shield_list_content_ip_name, + ShieldListContentIpV2025R0( + type=ShieldListContentIpV2025R0TypeField.IP, + ip_addresses=['127.0.0.1', '80.12.12.12/24'], + ), + description='A list of things that are shielded', + ) + ) + shield_lists: ShieldListsV2025R0 = client.shield_lists.get_shield_lists_v2025_r0() + assert len(shield_lists.entries) > 0 + get_shield_list_country: ShieldListV2025R0 = ( + client.shield_lists.get_shield_list_by_id_v2025_r0(shield_list_country.id) + ) + assert get_shield_list_country.name == shield_list_country_name + assert get_shield_list_country.description == 'A list of things that are shielded' + client.shield_lists.update_shield_list_by_id_v2025_r0( + shield_list_country.id, + shield_list_country_name, + ShieldListContentCountryV2025R0( + type=ShieldListContentCountryV2025R0TypeField.COUNTRY, country_codes=['US'] + ), + description='Updated description', + ) + get_shield_list_country_updated: ShieldListV2025R0 = ( + client.shield_lists.get_shield_list_by_id_v2025_r0(shield_list_country.id) + ) + assert get_shield_list_country_updated.description == 'Updated description' + client.shield_lists.delete_shield_list_by_id_v2025_r0(shield_list_country.id) + client.shield_lists.delete_shield_list_by_id_v2025_r0(shield_list_content_domain.id) + client.shield_lists.delete_shield_list_by_id_v2025_r0(shield_list_content_email.id) + client.shield_lists.delete_shield_list_by_id_v2025_r0(shield_list_content_ip.id) diff --git a/test/sign_requests.py b/test/sign_requests.py new file mode 100644 index 000000000..753985901 --- /dev/null +++ b/test/sign_requests.py @@ -0,0 +1,137 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.sign_request import SignRequest + +from box_sdk_gen.schemas.file_base import FileBase + +from box_sdk_gen.schemas.sign_request_create_signer import SignRequestCreateSigner + +from box_sdk_gen.schemas.sign_request_create_signer import ( + SignRequestCreateSignerRoleField, +) + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.sign_request_prefill_tag import SignRequestPrefillTag + +from box_sdk_gen.schemas.sign_requests import SignRequests + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import date_from_string + +from box_sdk_gen.internal.utils import date_to_string + +from test.commons import upload_new_file + +from test.commons import create_new_folder + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testCreateGetCancelAndListSignRequest(): + signer_email: str = ''.join([get_uuid(), '@box.com']) + file_to_sign: FileFull = upload_new_file() + destination_folder: FolderFull = create_new_folder() + created_sign_request: SignRequest = client.sign_requests.create_sign_request( + [ + SignRequestCreateSigner( + email=signer_email, + suppress_notifications=True, + declined_redirect_url='https://www.box.com', + embed_url_external_user_id='123', + is_in_person=False, + login_required=False, + password='password', + role=SignRequestCreateSignerRoleField.SIGNER, + ) + ], + source_files=[FileBase(id=file_to_sign.id)], + parent_folder=FolderMini(id=destination_folder.id), + is_document_preparation_needed=False, + redirect_url='https://www.box.com', + declined_redirect_url='https://www.box.com', + are_text_signatures_enabled=True, + email_subject='Sign this document', + email_message='Please sign this document', + are_reminders_enabled=True, + name='Sign Request', + prefill_tags=[ + SignRequestPrefillTag( + date_value=date_from_string('2035-01-01'), document_tag_id='0' + ) + ], + days_valid=30, + external_id='123', + external_system_name='BoxSignIntegration', + ) + assert created_sign_request.are_reminders_enabled == True + assert created_sign_request.are_text_signatures_enabled == True + assert created_sign_request.days_valid == 30 + assert created_sign_request.declined_redirect_url == 'https://www.box.com' + assert created_sign_request.email_message == 'Please sign this document' + assert created_sign_request.email_subject == 'Sign this document' + assert created_sign_request.external_id == '123' + assert created_sign_request.external_system_name == 'BoxSignIntegration' + assert created_sign_request.is_document_preparation_needed == False + assert created_sign_request.name == 'Sign Request.pdf' + assert created_sign_request.redirect_url == 'https://www.box.com' + assert created_sign_request.sign_files.files[0].name == created_sign_request.name + assert created_sign_request.signers[1].email == signer_email + assert created_sign_request.signers[1].suppress_notifications == True + assert ( + created_sign_request.signers[1].declined_redirect_url == 'https://www.box.com' + ) + assert created_sign_request.signers[1].embed_url_external_user_id == '123' + assert created_sign_request.signers[1].is_in_person == False + assert created_sign_request.signers[1].login_required == False + assert to_string(created_sign_request.signers[1].role) == 'signer' + assert created_sign_request.parent_folder.id == destination_folder.id + assert ( + date_to_string(created_sign_request.prefill_tags[0].date_value) == '2035-01-01' + ) + new_sign_request: SignRequest = client.sign_requests.get_sign_request_by_id( + created_sign_request.id + ) + assert new_sign_request.sign_files.files[0].name == created_sign_request.name + assert new_sign_request.signers[1].email == signer_email + assert new_sign_request.parent_folder.id == destination_folder.id + cancelled_sign_request: SignRequest = client.sign_requests.cancel_sign_request( + created_sign_request.id + ) + assert to_string(cancelled_sign_request.status) == 'cancelled' + sign_requests: SignRequests = client.sign_requests.get_sign_requests() + assert to_string(sign_requests.entries[0].type) == 'sign-request' + client.folders.delete_folder_by_id(destination_folder.id, recursive=True) + client.files.delete_file_by_id(file_to_sign.id) + + +def testCreateSignRequestWithSignerGroupId(): + signer_1_email: str = ''.join([get_uuid(), '@box.com']) + signer_2_email: str = ''.join([get_uuid(), '@box.com']) + file_to_sign: FileFull = upload_new_file() + destination_folder: FolderFull = create_new_folder() + created_sign_request: SignRequest = client.sign_requests.create_sign_request( + [ + SignRequestCreateSigner(email=signer_1_email, signer_group_id='user'), + SignRequestCreateSigner(email=signer_2_email, signer_group_id='user'), + ], + source_files=[FileBase(id=file_to_sign.id)], + parent_folder=FolderMini(id=destination_folder.id), + ) + assert len(created_sign_request.signers) == 3 + assert not created_sign_request.signers[1].signer_group_id == None + assert ( + created_sign_request.signers[1].signer_group_id + == created_sign_request.signers[2].signer_group_id + ) + client.folders.delete_folder_by_id(destination_folder.id, recursive=True) + client.files.delete_file_by_id(file_to_sign.id) diff --git a/test/sign_templates.py b/test/sign_templates.py new file mode 100644 index 000000000..7009c7382 --- /dev/null +++ b/test/sign_templates.py @@ -0,0 +1,31 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.sign_templates import SignTemplates + +from box_sdk_gen.schemas.sign_template import SignTemplate + +from box_sdk_gen.internal.utils import decode_base_64 + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client_with_user_subject + + +def testGetSignTemplates(): + client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + sign_templates: SignTemplates = client.sign_templates.get_sign_templates(limit=2) + assert len(sign_templates.entries) >= 0 + + +def testGetSignTemplate(): + client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + sign_templates: SignTemplates = client.sign_templates.get_sign_templates(limit=2) + assert len(sign_templates.entries) >= 0 + if len(sign_templates.entries) > 0: + sign_template: SignTemplate = client.sign_templates.get_sign_template_by_id( + sign_templates.entries[0].id + ) + assert sign_template.id == sign_templates.entries[0].id + assert len(sign_template.source_files) > 0 + assert not sign_template.name == '' + assert not sign_template.parent_folder.id == '' diff --git a/test/skills.py b/test/skills.py new file mode 100644 index 000000000..959c871a7 --- /dev/null +++ b/test/skills.py @@ -0,0 +1,105 @@ +from typing import List + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCardTypeField + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCardSkillCardTypeField + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCardSkillCardTitleField + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCardSkillField + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCardSkillTypeField + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCardInvocationField + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCardInvocationTypeField + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCardEntriesField + +from box_sdk_gen.schemas.skill_cards_metadata import SkillCardsMetadata + +from box_sdk_gen.managers.skills import UpdateBoxSkillCardsOnFileRequestBody + +from box_sdk_gen.managers.skills import UpdateBoxSkillCardsOnFileRequestBodyOpField + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +from test.commons import upload_new_file + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCard + +client: BoxClient = get_default_client() + + +def test_skills_cards_CRUD(): + file: FileFull = upload_new_file() + skill_id: str = get_uuid() + invocation_id: str = get_uuid() + title_message: str = 'License Plates' + card_to_create: KeywordSkillCard = KeywordSkillCard( + type=KeywordSkillCardTypeField.SKILL_CARD, + skill_card_type=KeywordSkillCardSkillCardTypeField.KEYWORD, + skill_card_title=KeywordSkillCardSkillCardTitleField( + code='license-plates', message=title_message + ), + skill=KeywordSkillCardSkillField( + id=skill_id, type=KeywordSkillCardSkillTypeField.SERVICE + ), + invocation=KeywordSkillCardInvocationField( + id=invocation_id, type=KeywordSkillCardInvocationTypeField.SKILL_INVOCATION + ), + entries=[KeywordSkillCardEntriesField(text='DN86 BOX')], + ) + cards_to_create: List[KeywordSkillCard] = [card_to_create] + skill_cards_metadata: SkillCardsMetadata = ( + client.skills.create_box_skill_cards_on_file(file.id, cards_to_create) + ) + assert len(skill_cards_metadata.cards) == 1 + keyword_skill_card: KeywordSkillCard = skill_cards_metadata.cards[0] + assert keyword_skill_card.skill.id == skill_id + assert keyword_skill_card.skill_card_title.message == title_message + updated_title_message: str = 'Updated License Plates' + card_to_update: KeywordSkillCard = KeywordSkillCard( + type=KeywordSkillCardTypeField.SKILL_CARD, + skill_card_type=KeywordSkillCardSkillCardTypeField.KEYWORD, + skill_card_title=KeywordSkillCardSkillCardTitleField( + code='license-plates', message=updated_title_message + ), + skill=KeywordSkillCardSkillField( + id=skill_id, type=KeywordSkillCardSkillTypeField.SERVICE + ), + invocation=KeywordSkillCardInvocationField( + id=invocation_id, type=KeywordSkillCardInvocationTypeField.SKILL_INVOCATION + ), + entries=[KeywordSkillCardEntriesField(text='DN86 BOX')], + ) + updated_skill_cards_metadata: SkillCardsMetadata = ( + client.skills.update_box_skill_cards_on_file( + file.id, + [ + UpdateBoxSkillCardsOnFileRequestBody( + op=UpdateBoxSkillCardsOnFileRequestBodyOpField.REPLACE, + path='/cards/0', + value=card_to_update, + ) + ], + ) + ) + updated_keyword_skill_card: KeywordSkillCard = updated_skill_cards_metadata.cards[0] + assert updated_keyword_skill_card.skill.id == skill_id + assert updated_keyword_skill_card.skill_card_title.message == updated_title_message + received_skill_cards_metadata: SkillCardsMetadata = ( + client.skills.get_box_skill_cards_on_file(file.id) + ) + received_keyword_skill_card: KeywordSkillCard = received_skill_cards_metadata.cards[ + 0 + ] + assert received_keyword_skill_card.skill.id == skill_id + client.skills.delete_box_skill_cards_from_file(file.id) + client.files.delete_file_by_id(file.id) diff --git a/test/storage_policicy_assignments.py b/test/storage_policicy_assignments.py new file mode 100644 index 000000000..3cc2e5b87 --- /dev/null +++ b/test/storage_policicy_assignments.py @@ -0,0 +1,99 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.schemas.storage_policy_assignment import StoragePolicyAssignment + +from box_sdk_gen.schemas.storage_policy_assignments import StoragePolicyAssignments + +from box_sdk_gen.managers.storage_policy_assignments import ( + GetStoragePolicyAssignmentsResolvedForType, +) + +from box_sdk_gen.managers.storage_policy_assignments import ( + CreateStoragePolicyAssignmentStoragePolicy, +) + +from box_sdk_gen.managers.storage_policy_assignments import ( + CreateStoragePolicyAssignmentAssignedTo, +) + +from box_sdk_gen.managers.storage_policy_assignments import ( + CreateStoragePolicyAssignmentAssignedToTypeField, +) + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.storage_policies import StoragePolicies + +from box_sdk_gen.schemas.storage_policy import StoragePolicy + +from box_sdk_gen.managers.storage_policy_assignments import ( + UpdateStoragePolicyAssignmentByIdStoragePolicy, +) + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client_with_user_subject + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.internal.utils import get_uuid + +admin_user_id: str = get_env_var('USER_ID') + + +def get_or_create_storage_policy_assignment( + client: BoxClient, policy_id: str, user_id: str +) -> StoragePolicyAssignment: + storage_policy_assignments: StoragePolicyAssignments = ( + client.storage_policy_assignments.get_storage_policy_assignments( + GetStoragePolicyAssignmentsResolvedForType.USER, user_id + ) + ) + if len(storage_policy_assignments.entries) > 0: + if to_string(storage_policy_assignments.entries[0].assigned_to.type) == 'user': + return storage_policy_assignments.entries[0] + storage_policy_assignment: StoragePolicyAssignment = ( + client.storage_policy_assignments.create_storage_policy_assignment( + CreateStoragePolicyAssignmentStoragePolicy(id=policy_id), + CreateStoragePolicyAssignmentAssignedTo( + id=user_id, type=CreateStoragePolicyAssignmentAssignedToTypeField.USER + ), + ) + ) + return storage_policy_assignment + + +def testGetStoragePolicyAssignments(): + client: BoxClient = get_default_client_with_user_subject(admin_user_id) + user_name: str = get_uuid() + new_user: UserFull = client.users.create_user( + user_name, is_platform_access_only=True + ) + storage_policies: StoragePolicies = client.storage_policies.get_storage_policies() + storage_policy_1: StoragePolicy = storage_policies.entries[0] + storage_policy_2: StoragePolicy = storage_policies.entries[1] + storage_policy_assignment: StoragePolicyAssignment = ( + get_or_create_storage_policy_assignment( + client, storage_policy_1.id, new_user.id + ) + ) + assert to_string(storage_policy_assignment.type) == 'storage_policy_assignment' + assert to_string(storage_policy_assignment.assigned_to.type) == 'user' + assert storage_policy_assignment.assigned_to.id == new_user.id + get_storage_policy_assignment: StoragePolicyAssignment = ( + client.storage_policy_assignments.get_storage_policy_assignment_by_id( + storage_policy_assignment.id + ) + ) + assert get_storage_policy_assignment.id == storage_policy_assignment.id + updated_storage_policy_assignment: StoragePolicyAssignment = ( + client.storage_policy_assignments.update_storage_policy_assignment_by_id( + storage_policy_assignment.id, + UpdateStoragePolicyAssignmentByIdStoragePolicy(id=storage_policy_2.id), + ) + ) + assert updated_storage_policy_assignment.storage_policy.id == storage_policy_2.id + client.storage_policy_assignments.delete_storage_policy_assignment_by_id( + storage_policy_assignment.id + ) + client.users.delete_user_by_id(new_user.id) diff --git a/test/storage_policies.py b/test/storage_policies.py new file mode 100644 index 000000000..aef0edcd0 --- /dev/null +++ b/test/storage_policies.py @@ -0,0 +1,24 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.storage_policies import StoragePolicies + +from box_sdk_gen.schemas.storage_policy import StoragePolicy + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client_with_user_subject + +user_id: str = get_env_var('USER_ID') + + +def testGetStoragePolicies(): + client: BoxClient = get_default_client_with_user_subject(user_id) + storage_policies: StoragePolicies = client.storage_policies.get_storage_policies() + storage_policy: StoragePolicy = storage_policies.entries[0] + assert to_string(storage_policy.type) == 'storage_policy' + get_storage_policy: StoragePolicy = ( + client.storage_policies.get_storage_policy_by_id(storage_policy.id) + ) + assert get_storage_policy.id == storage_policy.id diff --git a/test/task_assignments.py b/test/task_assignments.py new file mode 100644 index 000000000..415ab2223 --- /dev/null +++ b/test/task_assignments.py @@ -0,0 +1,86 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.task import Task + +from box_sdk_gen.managers.tasks import CreateTaskItem + +from box_sdk_gen.managers.tasks import CreateTaskItemTypeField + +from box_sdk_gen.managers.tasks import CreateTaskAction + +from box_sdk_gen.managers.tasks import CreateTaskCompletionRule + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.task_assignment import TaskAssignment + +from box_sdk_gen.managers.task_assignments import CreateTaskAssignmentTask + +from box_sdk_gen.managers.task_assignments import CreateTaskAssignmentTaskTypeField + +from box_sdk_gen.managers.task_assignments import CreateTaskAssignmentAssignTo + +from box_sdk_gen.schemas.task_assignments import TaskAssignments + +from box_sdk_gen.managers.task_assignments import ( + UpdateTaskAssignmentByIdResolutionState, +) + +from test.commons import upload_new_file + +from test.commons import get_default_client + +from box_sdk_gen.internal.utils import date_time_from_string + +client: BoxClient = get_default_client() + + +def testCreateUpdateGetDeleteTaskAssignment(): + file: FileFull = upload_new_file() + date: DateTime = date_time_from_string('2035-01-01T00:00:00Z') + task: Task = client.tasks.create_task( + CreateTaskItem(type=CreateTaskItemTypeField.FILE, id=file.id), + action=CreateTaskAction.REVIEW, + message='test message', + due_at=date, + completion_rule=CreateTaskCompletionRule.ALL_ASSIGNEES, + ) + assert task.message == 'test message' + assert task.item.id == file.id + current_user: UserFull = client.users.get_user_me() + task_assignment: TaskAssignment = client.task_assignments.create_task_assignment( + CreateTaskAssignmentTask( + type=CreateTaskAssignmentTaskTypeField.TASK, id=task.id + ), + CreateTaskAssignmentAssignTo(id=current_user.id), + ) + assert task_assignment.item.id == file.id + assert task_assignment.assigned_to.id == current_user.id + task_assignment_by_id: TaskAssignment = ( + client.task_assignments.get_task_assignment_by_id(task_assignment.id) + ) + assert task_assignment_by_id.id == task_assignment.id + task_assignments_on_task: TaskAssignments = ( + client.task_assignments.get_task_assignments(task.id) + ) + assert task_assignments_on_task.total_count == 1 + updated_task_assignment: TaskAssignment = ( + client.task_assignments.update_task_assignment_by_id( + task_assignment.id, + message='updated message', + resolution_state=UpdateTaskAssignmentByIdResolutionState.APPROVED, + ) + ) + assert updated_task_assignment.message == 'updated message' + assert to_string(updated_task_assignment.resolution_state) == 'approved' + with pytest.raises(Exception): + client.task_assignments.delete_task_assignment_by_id(task_assignment.id) + client.files.delete_file_by_id(file.id) diff --git a/test/tasks.py b/test/tasks.py new file mode 100644 index 000000000..ca05d11f2 --- /dev/null +++ b/test/tasks.py @@ -0,0 +1,66 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.internal.utils import DateTime + +from box_sdk_gen.schemas.task import Task + +from box_sdk_gen.managers.tasks import CreateTaskItem + +from box_sdk_gen.managers.tasks import CreateTaskItemTypeField + +from box_sdk_gen.managers.tasks import CreateTaskAction + +from box_sdk_gen.managers.tasks import CreateTaskCompletionRule + +from box_sdk_gen.schemas.tasks import Tasks + +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 get_default_client + +client: BoxClient = get_default_client() + + +def testCreateUpdateGetDeleteTask(): + files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), parent=UploadFileAttributesParentField(id='0') + ), + generate_byte_stream(10), + ) + file: FileFull = files.entries[0] + date_time: DateTime = date_time_from_string('2035-01-01T00:00:00Z') + task: Task = client.tasks.create_task( + CreateTaskItem(type=CreateTaskItemTypeField.FILE, id=file.id), + action=CreateTaskAction.REVIEW, + message='test message', + due_at=date_time, + completion_rule=CreateTaskCompletionRule.ALL_ASSIGNEES, + ) + assert task.message == 'test message' + assert task.item.id == file.id + assert date_time_to_string(task.due_at) == date_time_to_string(date_time) + task_by_id: Task = client.tasks.get_task_by_id(task.id) + assert task_by_id.id == task.id + task_on_file: Tasks = client.tasks.get_file_tasks(file.id) + assert task_on_file.total_count == 1 + updated_task: Task = client.tasks.update_task_by_id( + task.id, message='updated message' + ) + assert updated_task.message == 'updated message' + client.tasks.delete_task_by_id(task.id) + client.files.delete_file_by_id(file.id) diff --git a/test/terms_of_service_user_statuses.py b/test/terms_of_service_user_statuses.py new file mode 100644 index 000000000..a9be3ca24 --- /dev/null +++ b/test/terms_of_service_user_statuses.py @@ -0,0 +1,66 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.terms_of_service import TermsOfService + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.terms_of_service_user_status import TermsOfServiceUserStatus + +from box_sdk_gen.managers.terms_of_service_user_statuses import ( + CreateTermsOfServiceStatusForUserTos, +) + +from box_sdk_gen.managers.terms_of_service_user_statuses import ( + CreateTermsOfServiceStatusForUserUser, +) + +from box_sdk_gen.schemas.terms_of_service_user_statuses import ( + TermsOfServiceUserStatuses, +) + +from test.commons import get_default_client_with_user_subject + +from test.commons import get_or_create_terms_of_services + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + + +def testGetTermsOfServiceUserStatuses(): + admin_user_id: str = get_env_var('USER_ID') + client: BoxClient = get_default_client_with_user_subject(admin_user_id) + tos: TermsOfService = get_or_create_terms_of_services() + user: UserFull = client.users.create_user( + get_uuid(), + login=''.join([get_uuid(), '@boxdemo.com']), + is_platform_access_only=True, + ) + created_tos_user_status: TermsOfServiceUserStatus = ( + client.terms_of_service_user_statuses.create_terms_of_service_status_for_user( + CreateTermsOfServiceStatusForUserTos(id=tos.id), + CreateTermsOfServiceStatusForUserUser(id=user.id), + False, + ) + ) + assert created_tos_user_status.is_accepted == False + assert to_string(created_tos_user_status.type) == 'terms_of_service_user_status' + assert to_string(created_tos_user_status.tos.type) == 'terms_of_service' + assert to_string(created_tos_user_status.user.type) == 'user' + assert created_tos_user_status.tos.id == tos.id + assert created_tos_user_status.user.id == user.id + updated_tos_user_status: TermsOfServiceUserStatus = ( + client.terms_of_service_user_statuses.update_terms_of_service_status_for_user_by_id( + created_tos_user_status.id, True + ) + ) + assert updated_tos_user_status.is_accepted == True + list_tos_user_statuses: TermsOfServiceUserStatuses = ( + client.terms_of_service_user_statuses.get_terms_of_service_user_statuses( + tos.id, user_id=user.id + ) + ) + assert list_tos_user_statuses.total_count > 0 + client.users.delete_user_by_id(user.id) diff --git a/test/terms_of_services.py b/test/terms_of_services.py new file mode 100644 index 000000000..eb7a42cd7 --- /dev/null +++ b/test/terms_of_services.py @@ -0,0 +1,35 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.terms_of_service import TermsOfService + +from box_sdk_gen.managers.terms_of_services import UpdateTermsOfServiceByIdStatus + +from box_sdk_gen.schemas.terms_of_services import TermsOfServices + +from test.commons import get_default_client + +from test.commons import get_or_create_terms_of_services + +client: BoxClient = get_default_client() + + +def testGetTermsOfServices(): + tos: TermsOfService = get_or_create_terms_of_services() + updated_tos_1: TermsOfService = ( + client.terms_of_services.update_terms_of_service_by_id( + tos.id, UpdateTermsOfServiceByIdStatus.DISABLED, 'TOS' + ) + ) + assert to_string(updated_tos_1.status) == 'disabled' + assert updated_tos_1.text == 'TOS' + updated_tos_2: TermsOfService = ( + client.terms_of_services.update_terms_of_service_by_id( + tos.id, UpdateTermsOfServiceByIdStatus.DISABLED, 'Updated TOS' + ) + ) + assert to_string(updated_tos_2.status) == 'disabled' + assert updated_tos_2.text == 'Updated TOS' + list_tos: TermsOfServices = client.terms_of_services.get_terms_of_service() + assert list_tos.total_count > 0 diff --git a/test/transfer.py b/test/transfer.py new file mode 100644 index 000000000..775bf4a1a --- /dev/null +++ b/test/transfer.py @@ -0,0 +1,27 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.transfer import TransferOwnedFolderOwnedBy + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testTransferUserContent(): + source_user_name: str = get_uuid() + source_user: UserFull = client.users.create_user( + source_user_name, is_platform_access_only=True + ) + target_user: UserFull = client.users.get_user_me() + transferred_folder: FolderFull = client.transfer.transfer_owned_folder( + source_user.id, TransferOwnedFolderOwnedBy(id=target_user.id), notify=False + ) + assert transferred_folder.owned_by.id == target_user.id + client.folders.delete_folder_by_id(transferred_folder.id, recursive=True) + client.users.delete_user_by_id(source_user.id, notify=False, force=True) diff --git a/test/trashed_files.py b/test/trashed_files.py new file mode 100644 index 000000000..cc8bbcdec --- /dev/null +++ b/test/trashed_files.py @@ -0,0 +1,57 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.trash_file import TrashFile + +from box_sdk_gen.schemas.trash_file_restored import TrashFileRestored + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import generate_byte_stream + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testTrashedFiles(): + file_size: int = 1024 * 1024 + file_name: str = get_uuid() + file_byte_stream: ByteStream = generate_byte_stream(file_size) + files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=file_name, parent=UploadFileAttributesParentField(id='0') + ), + file_byte_stream, + ) + file: FileFull = files.entries[0] + client.files.delete_file_by_id(file.id) + from_trash: TrashFile = client.trashed_files.get_trashed_file_by_id(file.id) + assert from_trash.id == file.id + assert from_trash.name == file.name + from_api_after_trashed: FileFull = client.files.get_file_by_id(file.id) + assert to_string(from_api_after_trashed.item_status) == 'trashed' + restored_file: TrashFileRestored = client.trashed_files.restore_file_from_trash( + file.id + ) + from_api_after_restore: FileFull = client.files.get_file_by_id(file.id) + assert restored_file.id == from_api_after_restore.id + assert restored_file.name == from_api_after_restore.name + assert to_string(from_api_after_restore.item_status) == 'active' + client.files.delete_file_by_id(file.id) + client.trashed_files.delete_trashed_file_by_id(file.id) + with pytest.raises(Exception): + client.trashed_files.get_trashed_file_by_id(file.id) diff --git a/test/trashed_folders.py b/test/trashed_folders.py new file mode 100644 index 000000000..c03e65fea --- /dev/null +++ b/test/trashed_folders.py @@ -0,0 +1,39 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.schemas.trash_folder import TrashFolder + +from box_sdk_gen.schemas.trash_folder_restored import TrashFolderRestored + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testTrashedFolders(): + folder: FolderFull = client.folders.create_folder( + get_uuid(), CreateFolderParent(id='0') + ) + client.folders.delete_folder_by_id(folder.id) + from_trash: TrashFolder = client.trashed_folders.get_trashed_folder_by_id(folder.id) + assert from_trash.id == folder.id + assert from_trash.name == folder.name + with pytest.raises(Exception): + client.folders.get_folder_by_id(folder.id) + restored_folder: TrashFolderRestored = ( + client.trashed_folders.restore_folder_from_trash(folder.id) + ) + from_api: FolderFull = client.folders.get_folder_by_id(folder.id) + assert restored_folder.id == from_api.id + assert restored_folder.name == from_api.name + client.folders.delete_folder_by_id(folder.id) + client.trashed_folders.delete_trashed_folder_by_id(folder.id) + with pytest.raises(Exception): + client.trashed_folders.get_trashed_folder_by_id(folder.id) diff --git a/test/trashed_items.py b/test/trashed_items.py new file mode 100644 index 000000000..feffdbc12 --- /dev/null +++ b/test/trashed_items.py @@ -0,0 +1,18 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.items import Items + +from test.commons import get_default_client + +from test.commons import upload_new_file + +client: BoxClient = get_default_client() + + +def testListTrashedItems(): + file: FileFull = upload_new_file() + client.files.delete_file_by_id(file.id) + trashed_items: Items = client.trashed_items.get_trashed_items() + assert len(trashed_items.entries) > 0 diff --git a/test/trashed_web_links.py b/test/trashed_web_links.py new file mode 100644 index 000000000..9ebcfb54e --- /dev/null +++ b/test/trashed_web_links.py @@ -0,0 +1,49 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.managers.web_links import CreateWebLinkParent + +from box_sdk_gen.schemas.trash_web_link import TrashWebLink + +from box_sdk_gen.schemas.trash_web_link_restored import TrashWebLinkRestored + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testTrashedWebLinks(): + url: str = 'https://www.box.com' + parent: FolderFull = client.folders.get_folder_by_id('0') + name: str = get_uuid() + description: str = 'Weblink description' + weblink: WebLink = client.web_links.create_web_link( + url, CreateWebLinkParent(id=parent.id), name=name, description=description + ) + client.web_links.delete_web_link_by_id(weblink.id) + from_trash: TrashWebLink = client.trashed_web_links.get_trashed_web_link_by_id( + weblink.id + ) + assert from_trash.id == weblink.id + assert from_trash.name == weblink.name + from_api_after_trashed: WebLink = client.web_links.get_web_link_by_id(weblink.id) + assert to_string(from_api_after_trashed.item_status) == 'trashed' + restored_weblink: TrashWebLinkRestored = ( + client.trashed_web_links.restore_weblink_from_trash(weblink.id) + ) + from_api: WebLink = client.web_links.get_web_link_by_id(weblink.id) + assert restored_weblink.id == from_api.id + assert restored_weblink.name == from_api.name + client.web_links.delete_web_link_by_id(weblink.id) + client.trashed_web_links.delete_trashed_web_link_by_id(weblink.id) + with pytest.raises(Exception): + client.trashed_web_links.get_trashed_web_link_by_id(weblink.id) diff --git a/test/unit/__init__.py b/test/unit/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/unit/auth/__init__.py b/test/unit/auth/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/unit/auth/test_ccg_auth.py b/test/unit/auth/test_ccg_auth.py deleted file mode 100644 index efd3d1869..000000000 --- a/test/unit/auth/test_ccg_auth.py +++ /dev/null @@ -1,281 +0,0 @@ -import json -from datetime import datetime, timedelta -from unittest import mock -from unittest.mock import Mock, MagicMock, call -from test.unit.object.conftest import mock_user # pylint:disable=unused-import - -import pytz -import pytest -from pytest_lazyfixture import lazy_fixture - -from boxsdk import BoxOAuthException -from boxsdk.auth.ccg_auth import CCGAuth -from boxsdk.config import API - - -USER_SUBJECT_TYPE = 'user' -ENTERPRISE_SUBJECT_TYPE = 'enterprise' - - -@pytest.fixture(scope='function') -def successful_token_response(successful_token_json_response): - successful_token_response_mock = MagicMock() - response = successful_token_json_response.copy() - del response['refresh_token'] - successful_token_response_mock.items = response - successful_token_response_mock.json = Mock(return_value=response) - successful_token_response_mock.access_token = response['access_token'] - successful_token_response_mock.ok = True - successful_token_response_mock.content = json.dumps(response) - successful_token_response_mock.status_code = 200 - return successful_token_response_mock - - -@pytest.fixture -def unsuccessful_ccg_response(box_datetime, status_code, error_description, include_date_header, error_code): - headers = {'Date': box_datetime.strftime('%a, %d %b %Y %H:%M:%S %Z')} if include_date_header else {} - unsuccessful_response = Mock(headers=headers) - unsuccessful_response.json.return_value = {'error_description': error_description, 'error': error_code} - unsuccessful_response.status_code = status_code - unsuccessful_response.ok = False - return unsuccessful_response - - -@pytest.fixture() -def ccg_auth(client_id, client_secret, ccg_user_id, ccg_enterprise_id) -> CCGAuth: - auth = CCGAuth(client_id=client_id, client_secret=client_secret, user=ccg_user_id, enterprise_id=ccg_enterprise_id) - auth._session.get_retry_after_time = MagicMock(return_value=0) # pylint:disable=protected-access - return auth - - -@pytest.fixture -def expect_auth_retry(status_code, error_description, include_date_header, error_code): - return status_code == 400 and 'exp' in error_description and include_date_header and error_code == 'invalid_grant' - - -@pytest.fixture -def box_datetime(): - return datetime.now(tz=pytz.utc) - timedelta(100) - - -@pytest.mark.parametrize('enterprise_id', (lazy_fixture('mock_enterprise_id'), None)) -@pytest.mark.parametrize('user, expected_normalized_user_id', - [ - (lazy_fixture('mock_user'), lazy_fixture('mock_user_id')), - (lazy_fixture('mock_user_id'), lazy_fixture('mock_user_id')), - (None, None) - ]) -def test_successful_create_ccg_auth_object_and_normalize_user_id(client_id, client_secret, enterprise_id, user, expected_normalized_user_id): - auth = CCGAuth(client_id=client_id, client_secret=client_secret, enterprise_id=enterprise_id, user=user) - assert auth._user_id == expected_normalized_user_id # pylint:disable=protected-access - - -def test_throw_type_error_when_invalid_user_object_provided(client_id, client_secret, mock_enterprise_id): - with pytest.raises(TypeError): - CCGAuth( - client_id=client_id, - client_secret=client_secret, - enterprise_id=mock_enterprise_id, - user=123123 - ) - - -@pytest.mark.parametrize('ccg_user_id, ccg_enterprise_id, user', - [ - (lazy_fixture('mock_user_id'), None, None), - (None, None, lazy_fixture('mock_user_id')), - (None, None, lazy_fixture('mock_user')) - ]) -def test_authenticate_user_success(ccg_auth, user, mock_user_id): - with mock.patch.object(CCGAuth, '_fetch_access_token', return_value='new_token') as fetch_access_token_mock: - ccg_auth.authenticate_user(user) - - fetch_access_token_mock.assert_called_once_with(mock_user_id, USER_SUBJECT_TYPE, None) - assert ccg_auth._user_id == mock_user_id # pylint:disable=protected-access - - -@pytest.mark.parametrize('ccg_user_id', (None,)) -@pytest.mark.parametrize('ccg_enterprise_id', (None,)) -def test_authenticate_user_throws_error_when_user_not_provided(ccg_auth): - with pytest.raises(ValueError): - ccg_auth.authenticate_user(None) - - -@pytest.mark.parametrize('ccg_user_id, ccg_enterprise_id, enterprise_id', - [ - (None, lazy_fixture('mock_enterprise_id'), None), - (None, None, lazy_fixture('mock_enterprise_id')) - ]) -def test_authenticate_enterprise(ccg_auth, enterprise_id, mock_enterprise_id): - with mock.patch.object(CCGAuth, '_fetch_access_token', return_value='new_token') as fetch_access_token_mock: - ccg_auth.authenticate_instance(enterprise_id) - - fetch_access_token_mock.assert_called_once_with(mock_enterprise_id, ENTERPRISE_SUBJECT_TYPE, None) - assert ccg_auth._enterprise_id == mock_enterprise_id # pylint:disable=protected-access - assert ccg_auth._user_id is None # pylint:disable=protected-access - - -@pytest.mark.parametrize('ccg_user_id, ccg_enterprise_id, enterprise_id', - [ - (None, None, None), - (None, lazy_fixture('mock_enterprise_id'), 'other_enterprise_300') - ]) -def test_authenticate_user_throws_error_when_enetrprise_not_provided_or_conflicts(ccg_auth, enterprise_id): - with pytest.raises(ValueError): - ccg_auth.authenticate_instance(enterprise_id) - - -@pytest.mark.parametrize('ccg_user_id', (lazy_fixture('mock_user_id'),)) -@pytest.mark.parametrize('ccg_enterprise_id', (lazy_fixture('mock_enterprise_id'),)) -@pytest.mark.parametrize('status_code', (400, 401)) -@pytest.mark.parametrize('error_description', ('invalid box_sub_type claim', 'invalid kid', "check the 'exp' claim")) -@pytest.mark.parametrize('error_code', ('invalid_grant', 'bad_request')) -@pytest.mark.parametrize('include_date_header', (True, False)) -def test_auth_retry_for_invalid_exp_claim( - ccg_auth, - status_code, - expect_auth_retry, - unsuccessful_ccg_response, - box_datetime, - mock_enterprise_id -): - side_effect = [BoxOAuthException(status_code, network_response=unsuccessful_ccg_response), 'jwt_token'] - with mock.patch.object(CCGAuth, '_fetch_access_token', return_value='new_token', side_effect=side_effect) as fetch_access_token_mock: - - if not expect_auth_retry: - with pytest.raises(BoxOAuthException): - ccg_auth.authenticate_instance() - else: - ccg_auth.authenticate_instance() - - expected_calls = [call(mock_enterprise_id, 'enterprise', None)] - if expect_auth_retry: - expected_calls.append(call(mock_enterprise_id, 'enterprise', box_datetime.replace(microsecond=0, tzinfo=None))) - assert len(fetch_access_token_mock.mock_calls) == len(expected_calls) - fetch_access_token_mock.assert_has_calls(expected_calls) - - -@pytest.mark.parametrize('ccg_user_id', (lazy_fixture('mock_user_id'),)) -@pytest.mark.parametrize('ccg_enterprise_id', (lazy_fixture('mock_enterprise_id'),)) -@pytest.mark.parametrize('status_code, error_description, error_code', - [(429, 'Request rate limit exceeded', 'rate_limit_exceeded'), - (500, 'Internal Server Error', 'internal_server_error')]) -@pytest.mark.parametrize('include_date_header', (False,)) -def test_auth_retry_for_rate_limit_and_server_errors( - unsuccessful_ccg_response, - ccg_auth, - status_code, - mock_enterprise_id -): - - side_effect = [] - expected_calls = [] - # Retries multiple times, but less than max retries. Then succeeds when it gets a token. - for _ in range(API.MAX_RETRY_ATTEMPTS - 2): - side_effect.append(BoxOAuthException(status_code, network_response=unsuccessful_ccg_response)) - expected_calls.append(call(mock_enterprise_id, ENTERPRISE_SUBJECT_TYPE, None)) - side_effect.append('jwt_token') - expected_calls.append(call(mock_enterprise_id, 'enterprise', None)) - with mock.patch.object(CCGAuth, '_fetch_access_token', return_value='new_token', side_effect=side_effect) as fetch_access_token_mock: - - ccg_auth.authenticate_instance(mock_enterprise_id) - - assert len(fetch_access_token_mock.mock_calls) == len(expected_calls) - fetch_access_token_mock.assert_has_calls(expected_calls) - - -@pytest.mark.parametrize('ccg_user_id', (lazy_fixture('mock_user_id'),)) -@pytest.mark.parametrize('ccg_enterprise_id', (lazy_fixture('mock_enterprise_id'),)) -@pytest.mark.parametrize('status_code, error_description, error_code', - [(429, 'Request rate limit exceeded', 'rate_limit_exceeded'), - (500, 'Internal Server Error', 'internal_server_error')]) -@pytest.mark.parametrize('include_date_header', (False,)) -def test_auth_max_retries_for_rate_limit_and_server_errors( - unsuccessful_ccg_response, - ccg_auth, - status_code, - mock_enterprise_id -): - - side_effect = [] - expected_calls = [] - # Retries multiple times, but less than max retries. Then succeeds when it gets a token. - for _ in range(API.MAX_RETRY_ATTEMPTS + 1): - side_effect.append(BoxOAuthException(status_code, network_response=unsuccessful_ccg_response)) - expected_calls.append(call(mock_enterprise_id, ENTERPRISE_SUBJECT_TYPE, None)) - with mock.patch.object(CCGAuth, '_fetch_access_token', return_value='new_token', side_effect=side_effect) as fetch_access_token_mock: - - with pytest.raises(BoxOAuthException) as error: - ccg_auth.authenticate_instance(mock_enterprise_id) - - assert error.value.status == status_code - assert len(fetch_access_token_mock.mock_calls) == len(expected_calls) - fetch_access_token_mock.assert_has_calls(expected_calls) - - -@pytest.mark.parametrize('ccg_user_id', (lazy_fixture('mock_user_id'),)) -@pytest.mark.parametrize('ccg_enterprise_id', (lazy_fixture('mock_enterprise_id'),)) -def test_extract_token_from_success_response( - successful_token_response, - successful_token_json_response, - ccg_auth, - mock_enterprise_id -): - with mock.patch.object(CCGAuth, '_execute_token_request', return_value=successful_token_response): - - ccg_auth.authenticate_instance(mock_enterprise_id) - - assert ccg_auth.access_token == successful_token_json_response['access_token'] - - -@pytest.mark.parametrize('ccg_user_id', (lazy_fixture('mock_user_id'),)) -@pytest.mark.parametrize('ccg_enterprise_id', (None,)) -def test_refresh_client_authentication_when_client_id_is_provided(ccg_auth): - ccg_auth.authenticate_instance = Mock() - ccg_auth.authenticate_user = Mock() - - ccg_auth.refresh("expired_token") - - ccg_auth.authenticate_user.assert_called_once() - ccg_auth.authenticate_instance.assert_not_called() - - -@pytest.mark.parametrize('ccg_user_id', (None,)) -@pytest.mark.parametrize('ccg_enterprise_id', (lazy_fixture('mock_enterprise_id'),)) -def test_refresh_enterprise_authentication_when_client_id_is_not_provided(ccg_auth): - ccg_auth.authenticate_instance = Mock() - ccg_auth.authenticate_user = Mock() - - ccg_auth.refresh("expired_token") - - ccg_auth.authenticate_user.assert_not_called() - ccg_auth.authenticate_instance.assert_called_once() - - -@pytest.mark.parametrize('ccg_user_id', (lazy_fixture('mock_user_id'),)) -@pytest.mark.parametrize('ccg_enterprise_id', (lazy_fixture('mock_enterprise_id'),)) -@pytest.mark.parametrize('subject_id, subject_type', - [ - (lazy_fixture('mock_user_id'), USER_SUBJECT_TYPE), - (lazy_fixture('mock_enterprise_id'), ENTERPRISE_SUBJECT_TYPE) - ]) -def test_send_authentication_request_with_correct_params( - ccg_auth, - client_id, - client_secret, - subject_id, - subject_type -): - expected_data = { - 'grant_type': 'client_credentials', - 'client_id': client_id, - 'client_secret': client_secret, - 'box_subject_id': subject_id, - 'box_subject_type': subject_type - } - ccg_auth.send_token_request = Mock(return_value=('new_token', None)) - - # pylint:disable=protected-access - ccg_auth._fetch_access_token(subject_id, subject_type, None) - - ccg_auth.send_token_request.assert_called_once_with(expected_data, access_token=None, expect_refresh_token=False) diff --git a/test/unit/auth/test_cooperatively_managed_oauth2.py b/test/unit/auth/test_cooperatively_managed_oauth2.py deleted file mode 100644 index ea48f9a5d..000000000 --- a/test/unit/auth/test_cooperatively_managed_oauth2.py +++ /dev/null @@ -1,15 +0,0 @@ -from unittest.mock import Mock - -from boxsdk.auth import cooperatively_managed_oauth2 - - -def test_cooperatively_managed_oauth2_calls_retrieve_tokens_during_refresh(access_token, refresh_token): - retrieve_tokens = Mock() - oauth2 = cooperatively_managed_oauth2.CooperativelyManagedOAuth2( - retrieve_tokens=retrieve_tokens, - client_id=None, - client_secret=None, - ) - retrieve_tokens.return_value = access_token, refresh_token - assert oauth2.refresh(None) == (access_token, refresh_token) - retrieve_tokens.assert_called_once_with() diff --git a/test/unit/auth/test_developer_token_auth.py b/test/unit/auth/test_developer_token_auth.py deleted file mode 100644 index bd67b3cd2..000000000 --- a/test/unit/auth/test_developer_token_auth.py +++ /dev/null @@ -1,24 +0,0 @@ -from unittest.mock import Mock, patch -from boxsdk.auth import developer_token_auth - - -def test_developer_token_auth_calls_callback_during_init_and_refresh(access_token): - get_new_token_callback = Mock() - get_new_token_callback.return_value = access_token - auth = developer_token_auth.DeveloperTokenAuth( - get_new_token_callback=get_new_token_callback, - ) - assert auth.access_token == access_token - get_new_token_callback.assert_called_once_with() - assert auth.refresh(access_token) == (access_token, None) - assert len(get_new_token_callback.mock_calls) == 2 - - -def test_developer_token_auth_uses_raw_input_by_default(access_token): - with patch('builtins.input', create=True) as mock_raw_input: - mock_raw_input.return_value = access_token - auth = developer_token_auth.DeveloperTokenAuth() - mock_raw_input.assert_called_once_with(auth.ENTER_TOKEN_PROMPT) - assert auth.access_token == access_token - assert auth.refresh(access_token) == (access_token, None) - assert len(mock_raw_input.mock_calls) == 2 diff --git a/test/unit/auth/test_jwt_auth.py b/test/unit/auth/test_jwt_auth.py deleted file mode 100644 index b1f3319e6..000000000 --- a/test/unit/auth/test_jwt_auth.py +++ /dev/null @@ -1,638 +0,0 @@ -from contextlib import contextmanager -from datetime import datetime, timedelta -import io -from itertools import cycle, product -import json -import random -import string -from unittest.mock import Mock, mock_open, patch, sentinel, call - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, generate_private_key as generate_rsa_private_key -from cryptography.hazmat.primitives import serialization -import pytest -import pytz - -from boxsdk.auth.jwt_auth import JWTAuth -from boxsdk.exception import BoxOAuthException -from boxsdk.config import API -from boxsdk.object.user import User - - -@pytest.fixture(params=[16, 32, 128]) -def jti_length(request): - return request.param - - -@pytest.fixture(params=('RS256', 'RS512')) -def jwt_algorithm(request): - return request.param - - -@pytest.fixture(scope='module') -def jwt_key_id(): - return 'jwt_key_id_1' - - -@pytest.fixture(scope='module') -def rsa_private_key_object(): - return generate_rsa_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) - - -@pytest.fixture(params=(None, b'strong_password')) -def rsa_passphrase(request): - return request.param - - -@pytest.fixture -def rsa_private_key_bytes(rsa_private_key_object, rsa_passphrase): - encryption = serialization.BestAvailableEncryption(rsa_passphrase) if rsa_passphrase else serialization.NoEncryption() - return rsa_private_key_object.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=encryption, - ) - - -@pytest.fixture(scope='function') -def successful_token_response(successful_token_mock, successful_token_json_response): - # pylint:disable=redefined-outer-name - response = successful_token_json_response.copy() - del response['refresh_token'] - successful_token_mock.json = Mock(return_value=response) - successful_token_mock.ok = True - successful_token_mock.content = json.dumps(response) - successful_token_mock.status_code = 200 - return successful_token_mock - - -@pytest.mark.parametrize(('key_file', 'key_data'), [(None, None), ('fake sys path', 'fake key data')]) -@pytest.mark.parametrize('rsa_passphrase', [None]) -def test_jwt_auth_init_raises_type_error_unless_exactly_one_of_rsa_private_key_file_or_data_is_given(key_file, key_data, rsa_private_key_bytes): - kwargs = dict( - rsa_private_key_data=rsa_private_key_bytes, - client_id=None, - client_secret=None, - jwt_key_id=None, - enterprise_id=None, - ) - JWTAuth(**kwargs) - kwargs.update(rsa_private_key_file_sys_path=key_file, rsa_private_key_data=key_data) - with pytest.raises(TypeError): - JWTAuth(**kwargs) - - -@pytest.mark.parametrize('key_data', [object(), 'ƒøø']) -@pytest.mark.parametrize('rsa_passphrase', [None]) -def test_jwt_auth_init_raises_type_error_if_rsa_private_key_data_has_unexpected_type(key_data, rsa_private_key_bytes): - kwargs = dict( - rsa_private_key_data=rsa_private_key_bytes, - client_id=None, - client_secret=None, - jwt_key_id=None, - enterprise_id=None, - ) - JWTAuth(**kwargs) - kwargs.update(rsa_private_key_data=key_data) - with pytest.raises(TypeError): - JWTAuth(**kwargs) - - -@pytest.mark.parametrize('rsa_private_key_data_type', [io.BytesIO, str, bytes, RSAPrivateKey]) -def test_jwt_auth_init_accepts_rsa_private_key_data(rsa_private_key_bytes, rsa_passphrase, rsa_private_key_data_type): - if rsa_private_key_data_type is str: - rsa_private_key_data = str(rsa_private_key_bytes.decode('ascii')) - elif rsa_private_key_data_type is RSAPrivateKey: - rsa_private_key_data = serialization.load_pem_private_key( - rsa_private_key_bytes, - password=rsa_passphrase, - backend=default_backend(), - ) - else: - rsa_private_key_data = rsa_private_key_data_type(rsa_private_key_bytes) - JWTAuth( - rsa_private_key_data=rsa_private_key_data, - rsa_private_key_passphrase=rsa_passphrase, - client_id=None, - client_secret=None, - jwt_key_id=None, - enterprise_id=None, - ) - - -@pytest.fixture(params=[False, True]) -def pass_private_key_by_path(request): - """For jwt_auth_init_mocks, whether to pass the private key via sys_path (True) or pass the data directly (False).""" - return request.param - - -@pytest.fixture -def jwt_auth_init_mocks( - mock_box_session, - successful_token_response, - jwt_algorithm, - jwt_key_id, - rsa_passphrase, - rsa_private_key_bytes, - pass_private_key_by_path, -): - # pylint:disable=redefined-outer-name - - @contextmanager - def _jwt_auth_init_mocks(**kwargs): - assert_authed = kwargs.pop('assert_authed', True) - fake_client_id = 'fake_client_id' - fake_client_secret = 'fake_client_secret' - assertion = Mock() - data = { - 'grant_type': JWTAuth._GRANT_TYPE, # pylint:disable=protected-access - 'client_id': fake_client_id, - 'client_secret': fake_client_secret, - 'assertion': assertion, - 'box_device_id': '0', - 'box_device_name': 'my_awesome_device', - } - mock_box_session.request.return_value = successful_token_response - with patch('boxsdk.auth.jwt_auth.open', mock_open(read_data=rsa_private_key_bytes), create=True) as jwt_auth_open: - with patch('cryptography.hazmat.primitives.serialization.load_pem_private_key') as load_pem_private_key: - oauth = JWTAuth( - client_id=fake_client_id, - client_secret=fake_client_secret, - rsa_private_key_file_sys_path=(sentinel.rsa_path if pass_private_key_by_path else None), - rsa_private_key_data=(None if pass_private_key_by_path else rsa_private_key_bytes), - rsa_private_key_passphrase=rsa_passphrase, - session=mock_box_session, - box_device_name='my_awesome_device', - jwt_algorithm=jwt_algorithm, - jwt_key_id=jwt_key_id, - enterprise_id=kwargs.pop('enterprise_id', None), - **kwargs - ) - if pass_private_key_by_path: - jwt_auth_open.assert_called_once_with(sentinel.rsa_path, 'rb') - jwt_auth_open.return_value.read.assert_called_once_with() # pylint:disable=no-member - else: - jwt_auth_open.assert_not_called() - load_pem_private_key.assert_called_once_with( - rsa_private_key_bytes, - password=rsa_passphrase, - backend=default_backend(), - ) - - yield oauth, assertion, fake_client_id, load_pem_private_key.return_value - - if assert_authed: - mock_box_session.request.assert_called_once_with( - 'POST', - f'{API.OAUTH2_API_URL}/token', - data=data, - headers={'content-type': 'application/x-www-form-urlencoded'}, - access_token=None, - ) - assert oauth.access_token == successful_token_response.json()['access_token'] - - return _jwt_auth_init_mocks - - -def test_refresh_authenticates_with_user_if_enterprise_id_and_user_both_passed_to_constructor(jwt_auth_init_and_auth_mocks): - user = 'fake_user_id' - with jwt_auth_init_and_auth_mocks(sub=user, sub_type='user', enterprise_id='fake_enterprise_id', user=user) as oauth: - oauth.refresh(None) - - -@pytest.mark.parametrize('jwt_auth_method_name', ['authenticate_user', 'authenticate_instance']) -def test_authenticate_raises_value_error_if_sub_was_never_given(jwt_auth_init_mocks, jwt_auth_method_name): - with jwt_auth_init_mocks(assert_authed=False) as params: - auth = params[0] - authenticate_method = getattr(auth, jwt_auth_method_name) - with pytest.raises(ValueError): - authenticate_method() - - -def test_jwt_auth_constructor_raises_type_error_if_user_is_unsupported_type(jwt_auth_init_mocks): - with pytest.raises(TypeError): - with jwt_auth_init_mocks(user=object()): - assert False - - -def test_authenticate_user_raises_type_error_if_user_is_unsupported_type(jwt_auth_init_mocks): - with jwt_auth_init_mocks(assert_authed=False) as params: - auth = params[0] - with pytest.raises(TypeError): - auth.authenticate_user(object()) - - -@pytest.mark.parametrize('user_id_for_init', [None, 'fake_user_id_1']) -def test_authenticate_user_saves_user_id_for_future_calls(jwt_auth_init_and_auth_mocks, user_id_for_init, jwt_encode): - - def assert_jwt_encode_call_args(user_id): - assert jwt_encode.call_args[0][0]['sub'] == user_id - assert jwt_encode.call_args[0][0]['box_sub_type'] == 'user' - jwt_encode.call_args = None - - with jwt_auth_init_and_auth_mocks(sub=None, sub_type=None, assert_authed=False, user=user_id_for_init) as auth: - for new_user_id in ['fake_user_id_2', 'fake_user_id_3']: - auth.authenticate_user(new_user_id) - assert_jwt_encode_call_args(new_user_id) - auth.authenticate_user() - assert_jwt_encode_call_args(new_user_id) - - -def test_authenticate_instance_raises_value_error_if_different_enterprise_id_is_given(jwt_auth_init_mocks): - with jwt_auth_init_mocks(enterprise_id='fake_enterprise_id_1', assert_authed=False) as params: - auth = params[0] - with pytest.raises(ValueError): - auth.authenticate_instance('fake_enterprise_id_2') - - -def test_authenticate_instance_saves_enterprise_id_for_future_calls(jwt_auth_init_and_auth_mocks): - enterprise_id = 'fake_enterprise_id' - with jwt_auth_init_and_auth_mocks(sub=enterprise_id, sub_type='enterprise', assert_authed=False) as auth: - auth.authenticate_instance(enterprise_id) - auth.authenticate_instance() - auth.authenticate_instance(enterprise_id) - with pytest.raises(ValueError): - auth.authenticate_instance('fake_enterprise_id_2') - - -@pytest.fixture -def jwt_encode(): - with patch('jwt.encode') as patched_jwt_encode: - yield patched_jwt_encode - - -@pytest.fixture -def jwt_auth_auth_mocks(jti_length, jwt_algorithm, jwt_key_id, jwt_encode): - - @contextmanager - def _jwt_auth_auth_mocks(sub, sub_type, oauth, assertion, client_id, secret, assert_authed=True): - # pylint:disable=redefined-outer-name - with patch('boxsdk.auth.jwt_auth.datetime') as mock_datetime: - with patch('boxsdk.auth.jwt_auth.random.SystemRandom') as mock_system_random: - jwt_encode.return_value = assertion - mock_datetime.utcnow.return_value = datetime(2015, 7, 6, 12, 1, 2) - mock_datetime.return_value = datetime(1970, 1, 1) - now_plus_30 = mock_datetime.utcnow.return_value + timedelta(seconds=30) - exp = int((now_plus_30 - datetime(1970, 1, 1)).total_seconds()) - system_random = mock_system_random.return_value - system_random.randint.return_value = jti_length - random_choices = [random.random() for _ in range(jti_length)] - - # Use cycle so that we can do auth more than once inside the context manager. - system_random.random.side_effect = cycle(random_choices) - - ascii_alphabet = string.ascii_letters + string.digits - ascii_len = len(ascii_alphabet) - jti = ''.join(ascii_alphabet[int(r * ascii_len)] for r in random_choices) - - yield oauth - - if assert_authed: - system_random.randint.assert_called_once_with(16, 128) - assert len(system_random.random.mock_calls) == jti_length - jwt_encode.assert_called_once_with({ - 'iss': client_id, - 'sub': sub, - 'box_sub_type': sub_type, - 'aud': 'https://api.box.com/oauth2/token', - 'jti': jti, - 'exp': exp, - }, secret, algorithm=jwt_algorithm, headers={'kid': jwt_key_id}) - - return _jwt_auth_auth_mocks - - -@pytest.fixture -def jwt_auth_init_and_auth_mocks(jwt_auth_init_mocks, jwt_auth_auth_mocks): - - @contextmanager - def _jwt_auth_init_and_auth_mocks(sub, sub_type, *jwt_auth_init_mocks_args, **jwt_auth_init_mocks_kwargs): - assert_authed = jwt_auth_init_mocks_kwargs.pop('assert_authed', True) - with jwt_auth_init_mocks(*jwt_auth_init_mocks_args, assert_authed=assert_authed, **jwt_auth_init_mocks_kwargs) as params: - with jwt_auth_auth_mocks(sub, sub_type, *params, assert_authed=assert_authed) as oauth: - yield oauth - - return _jwt_auth_init_and_auth_mocks - - -@pytest.mark.parametrize( - ('user', 'pass_in_init'), - list(product([str('fake_user_id'), User(None, 'fake_user_id')], [False, True])), -) -def test_authenticate_user_sends_post_request_with_correct_params(jwt_auth_init_and_auth_mocks, user, pass_in_init): - # pylint:disable=redefined-outer-name - if isinstance(user, User): - user_id = user.object_id - elif isinstance(user, str): - user_id = user - else: - raise NotImplementedError - init_kwargs = {} - authenticate_params = [] - if pass_in_init: - init_kwargs['user'] = user - else: - authenticate_params.append(user) - with jwt_auth_init_and_auth_mocks(user_id, 'user', **init_kwargs) as oauth: - oauth.authenticate_user(*authenticate_params) - - -@pytest.mark.parametrize(('pass_in_init', 'pass_in_auth'), [(True, False), (False, True), (True, True)]) -def test_authenticate_instance_sends_post_request_with_correct_params(jwt_auth_init_and_auth_mocks, pass_in_init, pass_in_auth): - # pylint:disable=redefined-outer-name - enterprise_id = 'fake_enterprise_id' - init_kwargs = {} - auth_params = [] - if pass_in_init: - init_kwargs['enterprise_id'] = enterprise_id - if pass_in_auth: - auth_params.append(enterprise_id) - with jwt_auth_init_and_auth_mocks(enterprise_id, 'enterprise', **init_kwargs) as oauth: - oauth.authenticate_instance(*auth_params) - - -def test_refresh_app_user_sends_post_request_with_correct_params(jwt_auth_init_and_auth_mocks): - # pylint:disable=redefined-outer-name - fake_user_id = 'fake_user_id' - with jwt_auth_init_and_auth_mocks(fake_user_id, 'user', user=fake_user_id) as oauth: - oauth.refresh(None) - - -def test_refresh_instance_sends_post_request_with_correct_params(jwt_auth_init_and_auth_mocks): - # pylint:disable=redefined-outer-name - enterprise_id = 'fake_enterprise_id' - with jwt_auth_init_and_auth_mocks(enterprise_id, 'enterprise', enterprise_id=enterprise_id) as oauth: - oauth.refresh(None) - - -@pytest.fixture() -def jwt_subclass_that_just_stores_params(): - class StoreParamJWTAuth(JWTAuth): - def __init__(self, **kwargs): - self.kwargs = kwargs - super().__init__(**kwargs) - - return StoreParamJWTAuth - - -@pytest.fixture -def fake_client_id(): - return 'fake_client_id' - - -@pytest.fixture -def fake_client_secret(): - return 'fake_client_secret' - - -@pytest.fixture -def fake_enterprise_id(): - return 'fake_enterprise_id' - - -@pytest.fixture -def app_config_json_content( - fake_client_id, - fake_client_secret, - fake_enterprise_id, - jwt_key_id, - rsa_private_key_bytes, - rsa_passphrase, -): - template = r""" -{{ - "boxAppSettings": {{ - "clientID": "{client_id}", - "clientSecret": "{client_secret}", - "appAuth": {{ - "publicKeyID": "{jwt_key_id}", - "privateKey": "{private_key}", - "passphrase": {passphrase} - }} - }}, - "enterpriseID": {enterprise_id} -}}""" - return template.format( - client_id=fake_client_id, - client_secret=fake_client_secret, - jwt_key_id=jwt_key_id, - private_key=rsa_private_key_bytes.replace(b"\n", b"\\n").decode(), - passphrase=json.dumps(rsa_passphrase and rsa_passphrase.decode()), - enterprise_id=json.dumps(fake_enterprise_id), - ) - - -@pytest.fixture() -def assert_jwt_kwargs_expected( - fake_client_id, - fake_client_secret, - fake_enterprise_id, - jwt_key_id, - rsa_private_key_bytes, - rsa_passphrase, -): - def _assert_jwt_kwargs_expected(jwt_auth): - assert jwt_auth.kwargs['client_id'] == fake_client_id - assert jwt_auth.kwargs['client_secret'] == fake_client_secret - assert jwt_auth.kwargs['enterprise_id'] == fake_enterprise_id - assert jwt_auth.kwargs['jwt_key_id'] == jwt_key_id - assert jwt_auth.kwargs['rsa_private_key_data'] == rsa_private_key_bytes.decode() - assert jwt_auth.kwargs['rsa_private_key_passphrase'] == (rsa_passphrase and rsa_passphrase.decode()) - - return _assert_jwt_kwargs_expected - - -def test_from_config_file( - jwt_subclass_that_just_stores_params, - app_config_json_content, - assert_jwt_kwargs_expected, -): - # pylint:disable=redefined-outer-name - with patch('boxsdk.auth.jwt_auth.open', mock_open(read_data=app_config_json_content), create=True): - jwt_auth_from_config_file = jwt_subclass_that_just_stores_params.from_settings_file('fake_config_file_sys_path') - assert_jwt_kwargs_expected(jwt_auth_from_config_file) - - -def test_from_settings_dictionary( - jwt_subclass_that_just_stores_params, - app_config_json_content, - assert_jwt_kwargs_expected, -): - jwt_auth_from_dictionary = jwt_subclass_that_just_stores_params.from_settings_dictionary(json.loads(app_config_json_content)) - assert_jwt_kwargs_expected(jwt_auth_from_dictionary) - - -@pytest.fixture -def expect_auth_retry(status_code, error_description, include_date_header, error_code): - return status_code == 400 and ('exp' in error_description or 'jti' in error_description) and include_date_header and error_code == 'invalid_grant' - - -@pytest.fixture -def box_datetime(): - return datetime.now(tz=pytz.utc) - timedelta(100) - - -@pytest.fixture -def unsuccessful_jwt_response(box_datetime, status_code, error_description, include_date_header, error_code): - headers = {'Date': box_datetime.strftime('%a, %d %b %Y %H:%M:%S %Z')} if include_date_header else {} - unsuccessful_response = Mock(headers=headers) - unsuccessful_response.json.return_value = {'error_description': error_description, 'error': error_code} - unsuccessful_response.status_code = status_code - unsuccessful_response.ok = False - return unsuccessful_response - - -@pytest.mark.parametrize('jwt_algorithm', ('RS512',)) -@pytest.mark.parametrize('rsa_passphrase', (None,)) -@pytest.mark.parametrize('pass_private_key_by_path', (False,)) -@pytest.mark.parametrize('status_code', (400, 401)) -@pytest.mark.parametrize('error_description', ( - 'invalid box_sub_type claim', 'invalid kid', "check the 'exp' claim", "check the 'jti' claim")) -@pytest.mark.parametrize('error_code', ('invalid_grant', 'bad_request')) -@pytest.mark.parametrize('include_date_header', (True, False)) -def test_auth_retry_for_invalid_exp_claim( - jwt_auth_init_mocks, - expect_auth_retry, - unsuccessful_jwt_response, - box_datetime, -): - # pylint:disable=redefined-outer-name - enterprise_id = 'fake_enterprise_id' - with jwt_auth_init_mocks(assert_authed=False) as params: - auth = params[0] - with patch.object(auth, '_fetch_access_token') as mock_send_jwt: - mock_send_jwt.side_effect = [BoxOAuthException(400, network_response=unsuccessful_jwt_response), 'jwt_token'] - if not expect_auth_retry: - with pytest.raises(BoxOAuthException): - auth.authenticate_instance(enterprise_id) - else: - auth.authenticate_instance(enterprise_id) - expected_calls = [call(enterprise_id, 'enterprise', None)] - if expect_auth_retry: - expected_calls.append(call(enterprise_id, 'enterprise', box_datetime.replace(microsecond=0, tzinfo=None))) - assert len(mock_send_jwt.mock_calls) == len(expected_calls) - mock_send_jwt.assert_has_calls(expected_calls) - - -@pytest.mark.parametrize('jwt_algorithm', ('RS512',)) -@pytest.mark.parametrize('rsa_passphrase', (None,)) -@pytest.mark.parametrize('pass_private_key_by_path', (False,)) -@pytest.mark.parametrize('status_code', (429,)) -@pytest.mark.parametrize('error_description', ('Request rate limit exceeded',)) -@pytest.mark.parametrize('error_code', ('rate_limit_exceeded',)) -@pytest.mark.parametrize('include_date_header', (False,)) -def test_auth_retry_for_rate_limit_error( - jwt_auth_init_mocks, - unsuccessful_jwt_response, -): - # pylint:disable=redefined-outer-name - enterprise_id = 'fake_enterprise_id' - with jwt_auth_init_mocks(assert_authed=False) as params: - auth = params[0] - with patch.object(auth, '_fetch_access_token') as mock_send_jwt: - side_effect = [] - expected_calls = [] - # Retries multiple times, but less than max retries. Then succeeds when it gets a token. - for _ in range(API.MAX_RETRY_ATTEMPTS - 2): - side_effect.append(BoxOAuthException(429, network_response=unsuccessful_jwt_response)) - expected_calls.append(call(enterprise_id, 'enterprise', None)) - side_effect.append('jwt_token') - expected_calls.append(call(enterprise_id, 'enterprise', None)) - mock_send_jwt.side_effect = side_effect - - auth.authenticate_instance(enterprise_id) - assert len(mock_send_jwt.mock_calls) == len(expected_calls) - mock_send_jwt.assert_has_calls(expected_calls) - - -@pytest.mark.parametrize('jwt_algorithm', ('RS512',)) -@pytest.mark.parametrize('rsa_passphrase', (None,)) -@pytest.mark.parametrize('pass_private_key_by_path', (False,)) -@pytest.mark.parametrize('status_code', (429,)) -@pytest.mark.parametrize('error_description', ('Request rate limit exceeded',)) -@pytest.mark.parametrize('error_code', ('rate_limit_exceeded',)) -@pytest.mark.parametrize('include_date_header', (False,)) -def test_auth_max_retries_for_rate_limit_error( - jwt_auth_init_mocks, - unsuccessful_jwt_response, -): - # pylint:disable=redefined-outer-name - enterprise_id = 'fake_enterprise_id' - with jwt_auth_init_mocks(assert_authed=False) as params: - auth = params[0] - with patch.object(auth, '_fetch_access_token') as mock_send_jwt: - side_effect = [] - expected_calls = [] - # Retries max number of times, then throws the error - for _ in range(API.MAX_RETRY_ATTEMPTS + 1): - side_effect.append(BoxOAuthException(429, network_response=unsuccessful_jwt_response)) - expected_calls.append(call(enterprise_id, 'enterprise', None)) - mock_send_jwt.side_effect = side_effect - - with pytest.raises(BoxOAuthException) as error: - auth.authenticate_instance(enterprise_id) - assert error.value.status == 429 - assert len(mock_send_jwt.mock_calls) == len(expected_calls) - mock_send_jwt.assert_has_calls(expected_calls) - - -@pytest.mark.parametrize('jwt_algorithm', ('RS512',)) -@pytest.mark.parametrize('rsa_passphrase', (None,)) -@pytest.mark.parametrize('pass_private_key_by_path', (False,)) -@pytest.mark.parametrize('status_code', (500,)) -@pytest.mark.parametrize('error_description', ('Internal Server Error',)) -@pytest.mark.parametrize('error_code', ('internal_server_error',)) -@pytest.mark.parametrize('include_date_header', (False,)) -def test_auth_retry_for_internal_server_error( - jwt_auth_init_mocks, - unsuccessful_jwt_response, -): - # pylint:disable=redefined-outer-name - enterprise_id = 'fake_enterprise_id' - with jwt_auth_init_mocks(assert_authed=False) as params: - auth = params[0] - with patch.object(auth, '_fetch_access_token') as mock_send_jwt: - side_effect = [] - expected_calls = [] - # Retries multiple times, but less than max retries. Then succeeds when it gets a token. - for _ in range(API.MAX_RETRY_ATTEMPTS - 2): - side_effect.append(BoxOAuthException(500, network_response=unsuccessful_jwt_response)) - expected_calls.append(call(enterprise_id, 'enterprise', None)) - side_effect.append('jwt_token') - expected_calls.append(call(enterprise_id, 'enterprise', None)) - mock_send_jwt.side_effect = side_effect - - auth.authenticate_instance(enterprise_id) - assert len(mock_send_jwt.mock_calls) == len(expected_calls) - mock_send_jwt.assert_has_calls(expected_calls) - - -@pytest.mark.parametrize('jwt_algorithm', ('RS512',)) -@pytest.mark.parametrize('rsa_passphrase', (None,)) -@pytest.mark.parametrize('pass_private_key_by_path', (False,)) -@pytest.mark.parametrize('status_code', (500,)) -@pytest.mark.parametrize('error_description', ('Internal Server Error',)) -@pytest.mark.parametrize('error_code', ('internal_server_error',)) -@pytest.mark.parametrize('include_date_header', (False,)) -def test_auth_max_retries_for_internal_server_error( - jwt_auth_init_mocks, - unsuccessful_jwt_response, -): - # pylint:disable=redefined-outer-name - enterprise_id = 'fake_enterprise_id' - with jwt_auth_init_mocks(assert_authed=False) as params: - auth = params[0] - with patch.object(auth, '_fetch_access_token') as mock_send_jwt: - side_effect = [] - expected_calls = [] - # Retries max number of times, then throws the error - for _ in range(API.MAX_RETRY_ATTEMPTS + 1): - side_effect.append(BoxOAuthException(500, network_response=unsuccessful_jwt_response)) - expected_calls.append(call(enterprise_id, 'enterprise', None)) - mock_send_jwt.side_effect = side_effect - - with pytest.raises(BoxOAuthException) as error: - auth.authenticate_instance(enterprise_id) - assert error.value.status == 500 - assert len(mock_send_jwt.mock_calls) == len(expected_calls) - mock_send_jwt.assert_has_calls(expected_calls) diff --git a/test/unit/auth/test_oauth2.py b/test/unit/auth/test_oauth2.py deleted file mode 100644 index f5f6e17da..000000000 --- a/test/unit/auth/test_oauth2.py +++ /dev/null @@ -1,482 +0,0 @@ -from functools import partial -import re -from threading import Thread -from unittest.mock import Mock, patch -import uuid -from urllib import parse - -import pytest - -from boxsdk.exception import BoxOAuthException -from boxsdk.network.default_network import DefaultNetworkResponse -from boxsdk.auth.oauth2 import OAuth2 -from boxsdk.config import API - - -class MyError(Exception): - pass - - -class MyBaseException(BaseException): - pass - - -@pytest.fixture(params=('https://url.com/foo?bar=baz', 'https://ȕŕľ.com/ƒőő?Ƅȁŕ=Ƅȁż', None)) -def redirect_url(request): - """A value for the `redirect_uri` query string parameter for OAuth2.""" - return request.param - - -def test_get_correct_authorization_url(redirect_url): - # pylint:disable=redefined-outer-name - fake_client_id = 'fake_client_id' - fake_client_secret = 'fake_client_secret' - oauth2 = OAuth2( - client_id=fake_client_id, - client_secret=fake_client_secret, - ) - auth_url, csrf_token = oauth2.get_authorization_url(redirect_url=redirect_url) - expected_auth_url_format = '{0}?state={1}&response_type=code&client_id={2}' - if redirect_url: - expected_auth_url_format += '&redirect_uri={3}' - assert auth_url == expected_auth_url_format.format( - API.OAUTH2_AUTHORIZE_URL, - csrf_token, - fake_client_id, - parse.quote_plus((redirect_url or '').encode('utf-8')), - ) - assert re.match('^box_csrf_token_[A-Za-z0-9]{16}$', csrf_token) - - -def test_authenticate_send_post_request_with_correct_params(mock_box_session, successful_token_response): - fake_client_id = 'fake_client_id' - fake_client_secret = 'fake_client_secret' - fake_auth_code = 'fake_auth_code' - data = { - 'grant_type': 'authorization_code', - 'code': fake_auth_code, - 'client_id': fake_client_id, - 'client_secret': fake_client_secret, - 'box_device_id': '0', - 'box_device_name': 'my_awesome_device', - } - mock_box_session.request.return_value = successful_token_response - oauth = OAuth2( - client_id=fake_client_id, - client_secret=fake_client_secret, - session=mock_box_session, - box_device_name='my_awesome_device', - ) - - oauth.authenticate(fake_auth_code) - - mock_box_session.request.assert_called_once_with( - 'POST', - f'{API.OAUTH2_API_URL}/token', - data=data, - headers={'content-type': 'application/x-www-form-urlencoded'}, - access_token=None, - ) - - assert oauth.access_token == successful_token_response.json()['access_token'] - - -@pytest.mark.parametrize('_', range(10)) -def test_refresh_send_post_request_with_correct_params_and_handles_multiple_requests( - mock_box_session, - successful_token_response, - _, -): - fake_client_id = 'fake_client_id' - fake_client_secret = 'fake_client_secret' - fake_refresh_token = 'fake_refresh_token' - fake_access_token = 'fake_access_token' - data = { - 'grant_type': 'refresh_token', - 'refresh_token': fake_refresh_token, - 'client_id': fake_client_id, - 'client_secret': fake_client_secret, - 'box_device_id': '0', - 'box_device_name': 'my_awesome_device', - } - mock_box_session.request.return_value = successful_token_response - oauth = OAuth2( - client_id=fake_client_id, - client_secret=fake_client_secret, - access_token=fake_access_token, - refresh_token=fake_refresh_token, - session=mock_box_session, - box_device_name='my_awesome_device', - ) - - # Create four threads to call refresh on oauth at the same time. - threads = [] - for _ in range(4): - threads.append(Thread(target=oauth.refresh, args=(fake_access_token,))) - - for thread in threads: - thread.start() - for thread in threads: - thread.join() - - # Assert that even four threads were trying to refresh the tokens at the same time, only one token request was made, - # and it was made with the correct params. - mock_box_session.request.assert_called_once_with( - 'POST', - f'{API.OAUTH2_API_URL}/token', - data=data, - headers={'content-type': 'application/x-www-form-urlencoded'}, - access_token=fake_access_token, - ) - - -def test_authenticate_stores_tokens_correctly(mock_box_session, successful_token_response): - fake_client_id = 'fake_client_id' - fake_client_secret = 'fake_client_secret' - fake_auth_code = 'fake_auth_code' - - mock_box_session.request.return_value = successful_token_response - mock_token_callback = Mock() - oauth = OAuth2( - client_id=fake_client_id, - client_secret=fake_client_secret, - session=mock_box_session, - store_tokens=mock_token_callback, - ) - - access_token, refresh_token = oauth.authenticate(fake_auth_code) - mock_token_callback.assert_called_once_with(access_token, refresh_token) - - assert access_token == successful_token_response.json()['access_token'] - assert refresh_token == successful_token_response.json()['refresh_token'] - - -@pytest.mark.parametrize('_', range(10)) -def test_refresh_gives_back_the_correct_response_and_handles_multiple_requests( - mock_box_session, - successful_token_response, - network_response_with_missing_tokens, - _, -): - # pylint:disable=redefined-outer-name - fake_client_id = 'fake_client_id' - fake_client_secret = 'fake_client_secret' - fake_refresh_token = 'fake_refresh_token' - fake_access_token = 'fake_access_token' - - # Setup the network layer so that if oauth makes more than one request, it will get a malformed response and failed - # the test. - mock_box_session.request.side_effect = [successful_token_response, network_response_with_missing_tokens] - oauth = OAuth2( - client_id=fake_client_id, - client_secret=fake_client_secret, - access_token=fake_access_token, - refresh_token=fake_refresh_token, - session=mock_box_session, - ) - - def refresh_tokens_and_verify_the_response(): - access_token, refresh_token = oauth.refresh(fake_access_token) - assert access_token == successful_token_response.json()['access_token'] - assert refresh_token == successful_token_response.json()['refresh_token'] - - # Creates four threads and do token refresh at the same time. Assert they all get the same new access token and - # refresh token. - threads = [] - for _ in range(4): - threads.append(Thread(target=refresh_tokens_and_verify_the_response)) - - for thread in threads: - thread.start() - for thread in threads: - thread.join() - - -@pytest.fixture() -def token_method(request): - """ Fixture that returns a partial method based on the method provided in request.param""" - if request.param == OAuth2.refresh: - return partial(OAuth2.refresh, access_token_to_refresh='fake_access_token') - if request.param == OAuth2.authenticate: - return partial(OAuth2.authenticate, auth_code='fake_code') - return None - - -@pytest.mark.parametrize( - 'token_method', - [OAuth2.refresh, OAuth2.authenticate], - indirect=True, -) -def test_token_request_raises_box_oauth_exception_when_getting_bad_network_response( - token_method, - mock_box_session, - bad_network_response, -): - with pytest.raises(BoxOAuthException): - mock_box_session.request.return_value = bad_network_response - oauth = OAuth2( - client_id='', - client_secret='', - access_token='fake_access_token', - session=mock_box_session, - ) - token_method(oauth) - - -@pytest.mark.parametrize( - 'token_method', - [OAuth2.refresh, OAuth2.authenticate], - indirect=True, -) -def test_token_request_raises_box_oauth_exception_when_no_json_object_can_be_decoded( - token_method, - mock_box_session, - non_json_response, -): - mock_box_session.request.return_value = non_json_response - oauth = OAuth2( - client_id='', - client_secret='', - access_token='fake_access_token', - session=mock_box_session, - ) - with pytest.raises(BoxOAuthException): - token_method(oauth) - - -@pytest.fixture(params=[ - ['access_token'], - ['refresh_token'], - [], -]) -def network_response_with_missing_tokens(request): - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.ok = True - json_dict = {} - for key in request.param: - json_dict[key] = 'fake_token' - mock_network_response.json.return_value = json_dict - return mock_network_response - - -@pytest.mark.parametrize('test_method', [ - partial(OAuth2.refresh, access_token_to_refresh='fake_access_token'), - partial(OAuth2.authenticate, auth_code='fake_code') -]) -def test_token_request_raises_box_oauth_exception_when_tokens_are_not_in_the_response( - test_method, - mock_box_session, - network_response_with_missing_tokens, -): - # pylint:disable=redefined-outer-name - mock_box_session.request.return_value = network_response_with_missing_tokens - oauth = OAuth2( - client_id='', - client_secret='', - access_token='fake_access_token', - session=mock_box_session, - ) - with pytest.raises(BoxOAuthException): - test_method(oauth) - - -def test_oauth_exception_error_and_error_description(mock_box_session, bad_network_response_400): - mock_box_session.request.return_value = bad_network_response_400 - oauth = OAuth2( - client_id='', - client_secret='', - access_token='fake_access_token', - session=mock_box_session, - ) - try: - oauth.authenticate('fake_auth_code') - pytest.fail('Should throw exception because of bad network response') - except BoxOAuthException as exception: - assert exception.code == 'Example Error' - assert exception.message == 'Example Error Description' - - -def test_token_request_allows_missing_refresh_token(mock_box_session): - mock_network_response = Mock() - mock_network_response.ok = True - mock_network_response.json.return_value = {'access_token': 'fake_token'} - mock_box_session.request.return_value = mock_network_response - oauth = OAuth2( - client_id='', - client_secret='', - access_token='fake_access_token', - session=mock_box_session, - ) - oauth.send_token_request({}, access_token=None, expect_refresh_token=False) - - -@pytest.fixture() -def oauth(client_id, client_secret, access_token, refresh_token, mock_box_session): - return OAuth2( - client_id=client_id, - client_secret=client_secret, - access_token=access_token, - refresh_token=refresh_token, - session=mock_box_session, - ) - - -@pytest.mark.parametrize( - 'access_token,refresh_token,expected_token_to_revoke', - ( - ('fake_access_token', 'fake_refresh_token', 'fake_access_token'), - (None, 'fake_refresh_token', 'fake_refresh_token') - ) -) -def test_revoke_sends_revoke_request( - client_id, - client_secret, - mock_box_session, - access_token, - oauth, - expected_token_to_revoke, -): - mock_network_response = Mock() - mock_network_response.ok = True - mock_box_session.request.return_value = mock_network_response - oauth.revoke() - mock_box_session.request.assert_called_once_with( - 'POST', - f'{API.OAUTH2_API_URL}/revoke', - data={ - 'client_id': client_id, - 'client_secret': client_secret, - 'token': expected_token_to_revoke, - }, - access_token=access_token, - ) - assert oauth.access_token is None - - -def test_tokens_get_updated_after_noop_refresh(client_id, client_secret, access_token, new_access_token, refresh_token, mock_box_session): - """`OAuth2` object should update its state with new tokens, after no-op refresh. - - If the protected method `_get_tokens()` returns new tokens, refresh is - skipped, and those tokens are used. - - This is a regression test for issue #128 [1]. We would return the new - tokens without updating the object state. Subsequent uses of the `OAuth2` - object would use the old tokens. - - [1] - """ - new_refresh_token = uuid.uuid4().hex - new_tokens = (new_access_token, new_refresh_token) - - class GetTokensOAuth2(OAuth2): - def _get_tokens(self): - """Return a new set of tokens, without updating any state. - - In order for the test to pass, the `OAuth2` object must be - correctly programmed to take this return value and use it to update - its state. - """ - return new_tokens - - oauth = GetTokensOAuth2( - client_id=client_id, - client_secret=client_secret, - access_token=access_token, - refresh_token=refresh_token, - session=mock_box_session, - ) - assert oauth.access_token == access_token - - assert oauth.refresh(access_token) == new_tokens - assert oauth.access_token == new_access_token - - -def test_closed_is_false_after_init(client_id, client_secret, mock_box_session): - auth = OAuth2(client_id=client_id, client_secret=client_secret, session=mock_box_session) - assert auth.closed is False - - -def test_closed_is_true_after_close(client_id, client_secret, mock_box_session): - auth = OAuth2(client_id=client_id, client_secret=client_secret, session=mock_box_session) - auth.close() - assert auth.closed is True - - -def test_token_requests_fail_after_close(client_id, client_secret, mock_box_session): - auth = OAuth2(client_id=client_id, client_secret=client_secret, session=mock_box_session) - auth.close() - with pytest.raises(ValueError): - auth.refresh(auth.access_token) - - -@pytest.mark.parametrize('raise_exception', [False, True]) -def test_context_manager_closes_auth_object(client_id, client_secret, mock_box_session, raise_exception): - auth = OAuth2(client_id=client_id, client_secret=client_secret, session=mock_box_session) - try: - with auth.closing(): - if raise_exception: - raise MyError - except MyError: - pass - assert auth.closed is True - - -def test_context_manager_fails_after_close(client_id, client_secret, mock_box_session): - auth = OAuth2(client_id=client_id, client_secret=client_secret, session=mock_box_session) - with auth.closing(): - pass - with pytest.raises(ValueError): - with auth.closing(): - assert False - - -@pytest.mark.parametrize(('close_args', 'close_kwargs'), [((), {}), ((True,), {}), ((), dict(revoke=True))]) -def test_revoke_on_close(client_id, client_secret, access_token, mock_box_session, close_args, close_kwargs): - auth = OAuth2(client_id=client_id, client_secret=client_secret, access_token=access_token, session=mock_box_session) - with patch.object(auth, 'revoke') as mock_revoke: - auth.close(*close_args, **close_kwargs) - mock_revoke.assert_called_once_with() - - -def test_auth_object_is_closed_even_if_revoke_fails(client_id, client_secret, access_token, mock_box_session): - auth = OAuth2(client_id=client_id, client_secret=client_secret, access_token=access_token, session=mock_box_session) - with patch.object(auth, 'revoke', side_effect=BoxOAuthException(status=500)): - with pytest.raises(BoxOAuthException): - auth.close(revoke=True) - assert auth.closed is True - - -@pytest.mark.parametrize(('close_args', 'close_kwargs'), [((False,), {}), ((), dict(revoke=False))]) -def test_revoke_on_close_can_be_skipped(client_id, client_secret, access_token, mock_box_session, close_args, close_kwargs): - auth = OAuth2(client_id=client_id, client_secret=client_secret, access_token=access_token, session=mock_box_session) - with patch.object(auth, 'revoke') as mock_revoke: - auth.close(*close_args, **close_kwargs) - mock_revoke.assert_not_called() - - -@pytest.mark.parametrize(('raise_from_block', 'raise_from_close', 'expected_exception'), [ - (MyError, None, MyError), - (None, BoxOAuthException(status=500), BoxOAuthException), - (MyError, BoxOAuthException(status=500), MyError), -]) -@pytest.mark.parametrize('close_kwargs', [{}, dict(revoke=False), dict(revoke=True)]) -def test_context_manager_reraises_first_exception_after_close( - client_id, client_secret, mock_box_session, close_kwargs, raise_from_block, raise_from_close, expected_exception, -): - auth = OAuth2(client_id=client_id, client_secret=client_secret, session=mock_box_session) - with patch.object(auth, 'close', side_effect=raise_from_close) as mock_close: - with pytest.raises(expected_exception): - with auth.closing(**close_kwargs): - if raise_from_block: - raise raise_from_block - mock_close.assert_called_once_with(**close_kwargs) - - -@pytest.mark.parametrize('close_kwargs', [{}, dict(revoke=False), dict(revoke=True)]) -def test_context_manager_skips_revoke_on_base_exception(client_id, client_secret, mock_box_session, close_kwargs): - auth = OAuth2(client_id=client_id, client_secret=client_secret, session=mock_box_session) - with patch.object(auth, 'close') as mock_close: - with pytest.raises(MyBaseException): - with auth.closing(**close_kwargs): - raise MyBaseException - mock_close.assert_called_once_with(revoke=False) diff --git a/test/unit/auth/test_redis_managed_oauth2.py b/test/unit/auth/test_redis_managed_oauth2.py deleted file mode 100644 index 05b65b4bf..000000000 --- a/test/unit/auth/test_redis_managed_oauth2.py +++ /dev/null @@ -1,61 +0,0 @@ -from unittest.mock import Mock, patch -import uuid - -from boxsdk.auth import redis_managed_oauth2 - - -def test_redis_managed_oauth2_gets_tokens_from_redis_on_init(access_token, refresh_token): - redis_server = Mock(redis_managed_oauth2.StrictRedis) - redis_server.hvals.return_value = access_token, refresh_token - unique_id = Mock() - oauth2 = redis_managed_oauth2.RedisManagedOAuth2( - client_id=None, - client_secret=None, - unique_id=unique_id, - redis_server=redis_server, - ) - redis_server.hvals.assert_called_once_with(unique_id) - assert oauth2.unique_id is unique_id - - -def test_redis_managed_oauth2_gets_tokens_from_redis_during_refresh(access_token, refresh_token, new_access_token): - new_refresh_token = uuid.uuid4().hex - redis_server = Mock(redis_managed_oauth2.StrictRedis) - redis_server.hvals.return_value = new_access_token, new_refresh_token - unique_id = Mock() - oauth2 = redis_managed_oauth2.RedisManagedOAuth2( - access_token=access_token, - refresh_token=refresh_token, - client_id=None, - client_secret=None, - unique_id=unique_id, - redis_server=redis_server, - ) - assert oauth2.access_token == access_token - redis_server.hvals.assert_not_called() - - assert oauth2.refresh('bogus_access_token') == (new_access_token, new_refresh_token) - assert oauth2.access_token == new_access_token - redis_server.hvals.assert_called_once_with(unique_id) - - -def test_redis_managed_oauth2_stores_tokens_to_redis_during_refresh( - access_token, - refresh_token, - mock_box_session, - successful_token_response, -): - redis_server = Mock(redis_managed_oauth2.StrictRedis) - redis_server.hvals.return_value = access_token, refresh_token - unique_id = Mock() - with patch.object(redis_managed_oauth2.RedisManagedOAuth2, '_update_current_tokens'): - oauth2 = redis_managed_oauth2.RedisManagedOAuth2( - client_id=None, - client_secret=None, - unique_id=unique_id, - redis_server=redis_server, - session=mock_box_session, - ) - mock_box_session.request.return_value = successful_token_response - oauth2.send_token_request({}, access_token=None, expect_refresh_token=True) - redis_server.hmset.assert_called_once_with(unique_id, {'access': access_token, 'refresh': refresh_token}) diff --git a/test/unit/auth/test_remote_managed_oauth2.py b/test/unit/auth/test_remote_managed_oauth2.py deleted file mode 100644 index 227afaeaa..000000000 --- a/test/unit/auth/test_remote_managed_oauth2.py +++ /dev/null @@ -1,16 +0,0 @@ -from unittest.mock import Mock - -from boxsdk.auth import remote_managed_oauth2 - - -def test_remote_managed_oauth2_calls_retrieve_tokens_during_refresh(access_token): - retrieve_access_token = Mock() - oauth2 = remote_managed_oauth2.RemoteOAuth2( - retrieve_access_token=retrieve_access_token, - client_id=None, - client_secret=None, - access_token=access_token, - ) - retrieve_access_token.return_value = access_token - assert oauth2.refresh(access_token) == (access_token, None) - retrieve_access_token.assert_called_once_with(access_token) diff --git a/test/unit/client/__init__.py b/test/unit/client/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/unit/client/test_client.py b/test/unit/client/test_client.py deleted file mode 100644 index 0c3b0e90a..000000000 --- a/test/unit/client/test_client.py +++ /dev/null @@ -1,2076 +0,0 @@ -# pylint: disable=too-many-lines -import datetime -import json -from io import BytesIO -from unittest.mock import Mock, ANY - -import pytest -import pytz - -from boxsdk.auth.oauth2 import OAuth2, TokenScope -from boxsdk.client import Client, DeveloperTokenClient, DevelopmentClient, LoggingClient -from boxsdk.config import API -from boxsdk.network.default_network import DefaultNetworkResponse -from boxsdk.object.collaboration import Collaboration -from boxsdk.object.collaboration_allowlist import CollaborationAllowlist -from boxsdk.object.email_alias import EmailAlias -from boxsdk.object.collection import Collection -from boxsdk.object.comment import Comment -from boxsdk.object.device_pinner import DevicePinner -from boxsdk.object.enterprise import Enterprise -from boxsdk.object.events import Events -from boxsdk.object.file_request import FileRequest -from boxsdk.object.folder import Folder -from boxsdk.object.file import File -from boxsdk.object.file_version import FileVersion -from boxsdk.object.group import Group -from boxsdk.object.invite import Invite -from boxsdk.object.storage_policy import StoragePolicy -from boxsdk.object.storage_policy_assignment import StoragePolicyAssignment -from boxsdk.object.terms_of_service import TermsOfService -from boxsdk.object.user import User -from boxsdk.object.upload_session import UploadSession -from boxsdk.object.trash import Trash -from boxsdk.object.group_membership import GroupMembership -from boxsdk.object.metadata_template import MetadataTemplate, MetadataField, MetadataFieldType -from boxsdk.object.retention_policy import RetentionPolicy -from boxsdk.object.retention_policy_assignment import RetentionPolicyAssignment -from boxsdk.object.file_version_retention import FileVersionRetention -from boxsdk.object.legal_hold import LegalHold -from boxsdk.object.legal_hold_policy import LegalHoldPolicy -from boxsdk.object.legal_hold_policy_assignment import LegalHoldPolicyAssignment -from boxsdk.object.metadata_cascade_policy import MetadataCascadePolicy -from boxsdk.object.sign_request import SignRequest -from boxsdk.object.sign_template import SignTemplate -from boxsdk.object.task import Task -from boxsdk.object.task_assignment import TaskAssignment -from boxsdk.object.webhook import Webhook -from boxsdk.object.web_link import WebLink -from boxsdk.pagination.marker_based_object_collection import MarkerBasedObjectCollection -from boxsdk.util.datetime_formatter import normalize_date_to_rfc3339_format - - -@pytest.fixture -def developer_token_input(monkeypatch): - monkeypatch.setattr('builtins.input', lambda prompt: 'developer_token') - - -@pytest.fixture(params=[Client, DeveloperTokenClient, DevelopmentClient, LoggingClient]) -def mock_client(mock_box_session, developer_token_input, request): - # pylint:disable=redefined-outer-name, unused-argument - mock_oauth = Mock(OAuth2) - client = request.param(mock_oauth) - # pylint:disable=protected-access - client._session = mock_box_session - return client - - -@pytest.fixture(scope='module') -def user_id_1(): - return 1 - - -@pytest.fixture(scope='module') -def user_id_2(): - return 1023 - - -@pytest.fixture(scope='module') -def file_id(): - return 100 - - -@pytest.fixture(scope='module') -def folder_id(): - return '1022' - - -@pytest.fixture() -def test_folder(mock_box_session, mock_object_id): - return Folder(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_webhook(mock_box_session, mock_object_id): - return Webhook(mock_box_session, mock_object_id) - - -@pytest.fixture(scope='function') -def mock_file_response(mock_object_id, make_mock_box_request): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={'type': 'file', 'id': mock_object_id}, - ) - return mock_box_response - - -@pytest.fixture(scope='function') -def mock_folder_response(mock_object_id, make_mock_box_request): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={'type': 'folder', 'id': mock_object_id}, - ) - return mock_box_response - - -@pytest.fixture(scope='function') -def mock_content_response(make_mock_box_request): - mock_box_response, mock_network_response = make_mock_box_request(content=b'Contents of a text file.') - mock_network_response.response_as_stream = raw = Mock() - raw.stream.return_value = (bytes((b,)) for b in mock_box_response.content) - return mock_box_response - - -@pytest.fixture(scope='module') -def marker_id(): - return 'marker_1' - - -@pytest.fixture(scope='module') -def users_response(user_id_1, user_id_2): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'entries': [ - {'type': 'user', 'id': user_id_1}, - {'type': 'user', 'id': user_id_2} - ], - 'limit': 100, - 'offset': 0, - 'total_count': 2 - } - return mock_network_response - - -@pytest.fixture(scope='module') -def user_response(user_id_1): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = {'type': 'user', 'id': user_id_1} - return mock_network_response - - -@pytest.fixture(scope='module') -def group_id_1(): - return 101 - - -@pytest.fixture(scope='module') -def group_id_2(): - return 202 - - -@pytest.fixture() -def mock_user(mock_box_session, mock_user_id): - user = User(mock_box_session, mock_user_id) - return user - - -@pytest.fixture() -def mock_user_list(mock_box_session): - first_user = User(mock_box_session, '33333') - second_user = User(mock_box_session, '44444') - user_list = [first_user, second_user] - return user_list - - -@pytest.fixture() -def mock_file(mock_box_session): - test_file = File(mock_box_session, '11111') - return test_file - - -@pytest.fixture() -def mock_retention_policy(mock_box_session): - retention_policy = RetentionPolicy(mock_box_session, '22222') - return retention_policy - - -@pytest.fixture(scope='module') -def groups_response(group_id_1, group_id_2): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'entries': [ - {'type': 'group', 'id': group_id_1, 'name': str(group_id_1)}, - {'type': 'group', 'id': group_id_2, 'name': str(group_id_2)}, - ], - 'limit': 100, - 'offset': 0, - 'total_count': 2 - } - return mock_network_response - - -@pytest.fixture(scope='module') -def legal_hold_policy_id_1(): - return 101 - - -@pytest.fixture(scope='module') -def legal_hold_policy_id_2(): - return 202 - - -@pytest.fixture(scope='module') -def legal_hold_policies_response(legal_hold_policy_id_1, legal_hold_policy_id_2): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'entries': [ - {'type': 'legal_hold_policy', 'id': legal_hold_policy_id_1, 'name': 'Test Policy 1'}, - {'type': 'legal_hold_policy', 'id': legal_hold_policy_id_2, 'name': 'Test Policy 2'}, - ], - 'limit': 5, - } - return mock_network_response - - -@pytest.fixture(scope='module') -def create_policy_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'type': 'legal_hold_policy', - 'id': 1234, - 'policy_name': 'Test Policy' - } - return mock_network_response - - -@pytest.fixture(scope='module') -def create_group_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'type': 'group', - 'id': 1234, - 'name': 'test_group_name', - } - return mock_network_response - - -@pytest.fixture(scope='module') -def tos_id_1(): - return 101 - - -@pytest.fixture(scope='module') -def tos_id_2(): - return 202 - - -@pytest.fixture(scope='module') -def terms_of_services_response(tos_id_1, tos_id_2): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'entries': [ - {'type': 'terms_of_service', 'id': tos_id_1}, - {'type': 'terms_of_service', 'id': tos_id_2}, - ], - 'total_count': 2, - } - return mock_network_response - - -@pytest.fixture(scope='module') -def create_user_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'type': 'user', - 'id': 1234, - 'name': 'Ned Stark', - } - return mock_network_response - - -@pytest.fixture(scope='module') -def create_invite_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'type': 'invite', - 'id': 1234, - } - return mock_network_response - - -@pytest.fixture(params=('file', 'folder')) -def test_item_and_response(mock_file, test_folder, mock_file_response, mock_folder_response, request): - if request.param == 'file': - return mock_file, mock_file_response - return test_folder, mock_folder_response - - -@pytest.fixture() -def create_webhook_response(test_item_and_response, test_webhook): - # pylint:disable=redefined-outer-name - test_item, _ = test_item_and_response - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'type': test_webhook.object_type, - 'id': test_webhook.object_id, - 'target': { - 'type': test_item.object_type, - 'id': test_item.object_id, - }, - 'created_at': '2016-05-09T17:41:27-07:00', - 'address': 'https://test.com', - 'triggers': [ - 'FILE.UPLOADED', - 'FOLDER.CREATED', - ], - } - return mock_network_response - - -@pytest.fixture(scope='module', params=('file', 'folder')) -def shared_item_response(request): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'type': request.param, - 'id': 1234, - 'name': 'shared_item', - } - return mock_network_response - - -@pytest.fixture(scope='module') -def search_response(file_id, folder_id): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'entries': [ - {'type': 'file', 'id': file_id}, - {'type': 'folder', 'id': folder_id} - ], - 'limit': 100, - 'offset': 0, - 'total_count': 2 - } - return mock_network_response - - -@pytest.fixture(scope='module') -def recent_items_response(file_id): - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'entries': [ - {'type': 'recent_item', 'item': {'type': 'file', 'id': file_id}} - ], - 'next_marker': None, - 'limit': 100, - } - return mock_network_response - - -@pytest.fixture(scope='module') -def device_pin_id_1(): - return 101 - - -@pytest.fixture(scope='module') -def device_pin_id_2(): - return 202 - - -@pytest.fixture(scope='module') -def device_pins_response(device_pin_id_1, device_pin_id_2): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'entries': [ - {'type': 'device_pinner', 'id': device_pin_id_1}, - {'type': 'device_pinner', 'id': device_pin_id_2}, - ], - 'limit': 2, - } - return mock_network_response - - -@pytest.mark.parametrize('test_class, factory_method_name', [ - (EmailAlias, 'email_alias'), - (Enterprise, 'enterprise'), - (Folder, 'folder'), - (File, 'file'), - (FileVersion, 'file_version'), - (Invite, 'invite'), - (User, 'user'), - (Group, 'group'), - (GroupMembership, 'group_membership'), - (Enterprise, 'enterprise'), - (Webhook, 'webhook'), - (MetadataCascadePolicy, 'metadata_cascade_policy'), - (UploadSession, 'upload_session'), - (StoragePolicy, 'storage_policy'), - (StoragePolicyAssignment, 'storage_policy_assignment'), -]) -def test_factory_returns_the_correct_object(mock_client, test_class, factory_method_name): - """ Tests the various id-only factory methods in the Client class """ - # pylint:disable=redefined-outer-name - fake_id = 'fake_id' - - factory_method = getattr(mock_client, factory_method_name) - - obj = factory_method(fake_id) - - assert isinstance(obj, test_class) - assert obj.object_id == fake_id - - -def test_root_folder(mock_client): - folder = mock_client.root_folder() - assert isinstance(folder, Folder) - assert folder.object_id == '0' - - -@pytest.fixture(scope='module', params=(None, 'user1')) -def users_filter_term(request): - return request.param - - -@pytest.fixture(scope='module', params=(0, 10)) -def users_offset(request): - return request.param - - -@pytest.fixture(scope='module', params=(0, 10)) -def users_limit(request): - return request.param - - -@pytest.fixture(scope='module', params=(None, 'all', 'external', 'managed')) -def users_type(request): - return request.param - - -def test_users_return_the_correct_user_objects( - mock_client, - mock_box_session, - users_response, - user_id_1, - user_id_2, - users_filter_term, - users_type, - users_offset, - users_limit, -): - # pylint:disable=redefined-outer-name - mock_box_session.get.return_value = users_response - users = mock_client.users(limit=users_limit, offset=users_offset, filter_term=users_filter_term, user_type=users_type) - expected_params = {'offset': users_offset} - if users_limit is not None: - expected_params['limit'] = users_limit - if users_filter_term is not None: - expected_params['filter_term'] = users_filter_term - if users_type is not None: - expected_params['user_type'] = users_type - assert users.next().object_id == user_id_1 - assert users.next().object_id == user_id_2 - mock_box_session.get.assert_called_once_with(f'{API.BASE_API_URL}/users', params=expected_params) - - -def test_users_return_the_correct_user_objects_marker( - mock_client, - mock_box_session, - user_id_1, - user_id_2, - users_filter_term, - users_type, - marker_id, - users_limit, -): - # pylint:disable=redefined-outer-name - mock_box_session.get.return_value.json.return_value = { - 'entries': [ - {'type': 'user', 'id': user_id_1}, - {'type': 'user', 'id': user_id_2} - ], - 'next_marker': 'JGWUGKNJFSAH123NDSA', - 'total_count': 2 - } - users = mock_client.users(limit=users_limit, marker=marker_id, use_marker=True, filter_term=users_filter_term, user_type=users_type) - expected_params = {'marker': marker_id, 'usemarker': True} - if users_limit is not None: - expected_params['limit'] = users_limit - if users_filter_term is not None: - expected_params['filter_term'] = users_filter_term - if users_type is not None: - expected_params['user_type'] = users_type - assert users.next().object_id == user_id_1 - assert users.next().object_id == user_id_2 - mock_box_session.get.assert_called_once_with(f'{API.BASE_API_URL}/users', params=expected_params) - - -def test_users_returns_correct_with_default_values( - mock_client, - mock_box_session, -): - expected_url = f'{API.BASE_API_URL}/users' - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'offset': 0, - 'total_count': 1, - 'entries': [ - { - 'type': 'user', - 'id': '12345', - } - ] - } - users = mock_client.users() - user = users.next() - mock_box_session.get.assert_called_once_with(expected_url, params={'offset': 0}) - assert isinstance(user, User) - assert user.type == 'user' - assert user.id == '12345' - - -def test_search_instantiates_search_and_calls_search( - mock_client, - mock_box_session, - search_response, - file_id, - folder_id, -): - # pylint:disable=redefined-outer-name - mock_box_session.get.return_value = search_response - search_term = 'lolcatz' - search_result = mock_client.search().query( - search_term, - 10, - 0, - ancestor_folders=[Folder(mock_box_session, folder_id)], - file_extensions=['.jpg'], - ) - assert search_result.next().object_id == file_id - assert search_result.next().object_id == folder_id - - -def test_events_returns_event_object(mock_client): - # pylint:disable=redefined-outer-name - assert isinstance(mock_client.events(), Events) - - -def test_collaboration_allowlist_initializer(mock_client): - collaboration_allowlist = mock_client.collaboration_allowlist() - assert isinstance(collaboration_allowlist, CollaborationAllowlist) - - -def test_get_groups_return_the_correct_group_objects( - mock_client, - mock_box_session, - groups_response, - group_id_1, - group_id_2, -): - # pylint:disable=redefined-outer-name - group_name = 'Employees' - expected_url = f'{API.BASE_API_URL}/groups' - mock_box_session.get.return_value = groups_response - groups = mock_client.get_groups(group_name) - for group, expected_id in zip(groups, [group_id_1, group_id_2]): - assert group.object_id == expected_id - assert group.name == str(expected_id) - # pylint:disable=protected-access - assert group._session == mock_box_session - mock_box_session.get.assert_called_once_with(expected_url, params={'offset': None, 'filter_term': group_name}) - - -def test_create_group_returns_the_correct_group_object(mock_client, mock_box_session, create_group_response): - # pylint:disable=redefined-outer-name - expected_url = f'{API.BASE_API_URL}/groups' - test_group_name = 'test_group_name' - value = json.dumps({ - 'name': test_group_name, - 'provenance': 'Example', - 'external_sync_identifier': 'Example-User', - 'description': 'Description of group', - 'invitability_level': 'admins_and_members', - 'member_viewability_level': 'admins_only', - }) - mock_box_session.post.return_value = create_group_response - new_group = mock_client.create_group( - name=test_group_name, - provenance='Example', - external_sync_identifier='Example-User', - description='Description of group', - invitability_level='admins_and_members', - member_viewability_level='admins_only', - fields=['name,description'], - ) - - mock_box_session.post.assert_called_once_with(expected_url, data=value, params={'fields': 'name,description'}) - assert isinstance(new_group, Group) - assert new_group.object_id == 1234 - assert new_group.name == test_group_name - - -@pytest.mark.parametrize('description', ('My test policy',)) -@pytest.mark.parametrize('filter_started_at', ('2016-01-01T00:00:00+00:00', datetime.datetime(2016, 1, 1, tzinfo=pytz.UTC))) -@pytest.mark.parametrize('filter_ended_at', ('2020-01-01T00:00:00+00:00', datetime.datetime(2020, 1, 1, tzinfo=pytz.UTC))) -@pytest.mark.parametrize('is_ongoing', ('True',)) -def test_create_legal_hold_policy_returns_the_correct_policy_object( - mock_client, - mock_box_session, - create_policy_response, - description, - filter_started_at, - filter_ended_at, - is_ongoing -): - # pylint:disable=redefined-outer-name - params = { - 'description': description, - 'filter_started_at': filter_started_at, - 'filter_ended_at': filter_ended_at, - 'is_ongoing': is_ongoing - } - - test_policy_name = 'Test Policy' - create_policy_response.json.return_value.update(params) - mock_box_session.post.return_value = create_policy_response - - new_policy = mock_client.create_legal_hold_policy(test_policy_name, **params) - - expected_url = f'{API.BASE_API_URL}/legal_hold_policies' - expected_body = { - 'policy_name': test_policy_name, - 'description': description, - 'filter_started_at': normalize_date_to_rfc3339_format(filter_started_at), - 'filter_ended_at': normalize_date_to_rfc3339_format(filter_ended_at), - 'is_ongoing': is_ongoing - } - mock_box_session.post.assert_called_once_with(expected_url, data=ANY) - assert dict(json.loads(mock_box_session.post.call_args[1]['data'])) == expected_body - assert isinstance(new_policy, LegalHoldPolicy) - assert new_policy.policy_name == test_policy_name - for param, expected_value in params.items(): - assert new_policy[param] == expected_value - - -def test_get_legal_hold_policies_return_the_correct_policy_objects( - mock_client, - mock_box_session, - legal_hold_policies_response, - legal_hold_policy_id_1, - legal_hold_policy_id_2, -): - # pylint:disable=redefined-outer-name - policy_name = 'Arbitration' - expected_url = f'{API.BASE_API_URL}/legal_hold_policies' - mock_box_session.get.return_value = legal_hold_policies_response - policies = mock_client.get_legal_hold_policies(policy_name) - for policy, expected_id in zip(policies, [legal_hold_policy_id_1, legal_hold_policy_id_2]): - assert policy.object_id == expected_id - # pylint:disable=protected-access - assert policy._session == mock_box_session - mock_box_session.get.assert_called_once_with(expected_url, params={'policy_name': policy_name}) - - -def test_trash_initializer(mock_client): - trash = mock_client.trash() - assert isinstance(trash, Trash) - - -def test_get_recent_items_returns_the_correct_items(mock_client, mock_box_session, recent_items_response, file_id): - mock_box_session.get.return_value = recent_items_response - recent_items = mock_client.get_recent_items() - assert isinstance(recent_items, MarkerBasedObjectCollection) - recent_item = recent_items.next() - assert recent_item.item.object_id == file_id - next_pointer = recent_items.next_pointer() - assert next_pointer is None - - -def test_get_recent_items_sends_get_with_correct_params(mock_client, mock_box_session, recent_items_response, marker_id): - limit = 50 - marker = marker_id - fields = ['modified_at', 'name'] - expected_params = { - 'limit': limit, - 'marker': marker_id, - 'fields': ','.join(fields), - } - mock_box_session.get.return_value = recent_items_response - object_collection = mock_client.get_recent_items(limit=limit, marker=marker, fields=fields) - object_collection.next() - mock_box_session.get.assert_called_once_with(f'{API.BASE_API_URL}/recent_items', params=expected_params) - - -@pytest.mark.parametrize('password', (None, 'p4ssw0rd')) -def test_get_shared_item_returns_the_correct_item(mock_client, mock_box_session, shared_item_response, password): - # pylint:disable=redefined-outer-name - shared_link = 'https://cloud.box.com/s/661wcw2iz6q5r7v5xxkm' - mock_box_session.request.return_value = shared_item_response - item = mock_client.get_shared_item(shared_link, password) - assert item.type == shared_item_response.json()['type'] - mock_box_session.request.assert_called_once_with( - 'GET', - f'{API.BASE_API_URL}/shared_items', - headers={ - 'BoxApi': f'shared_link={shared_link}{f"&shared_link_password={password}" if password is not None else ""}' - }, - ) - - -@pytest.mark.parametrize('test_method', [ - 'get', - 'post', - 'put', - 'delete', - 'options', -]) -def test_make_request_passes_request_on_to_session(mock_client, mock_box_session, test_method): - # pylint:disable=redefined-outer-name - mock_client.make_request(test_method, 'url') - assert mock_box_session.request.call_args[0] == (test_method, 'url') - - -def test_create_app_user_returns_the_correct_user_object(mock_client, mock_box_session, create_user_response): - # pylint:disable=redefined-outer-name - test_user_name = 'Ned Stark' - value = json.dumps({'name': test_user_name, 'is_platform_access_only': True}) - mock_box_session.post.return_value = create_user_response - new_user = mock_client.create_user(name=test_user_name) - - assert len(mock_box_session.post.call_args_list) == 1 - - assert mock_box_session.post.call_args[0] == (f'{API.BASE_API_URL}/users',) - assert mock_box_session.post.call_args[1] == {'data': value} - assert isinstance(new_user, User) - assert new_user.object_id == 1234 - assert new_user.name == test_user_name - - -def test_create_enterprise_user_returns_the_correct_user_object(mock_client, mock_box_session, create_user_response): - # pylint:disable=redefined-outer-name - test_user_name = 'Ned Stark' - test_user_login = 'eddard@box.com' - value = json.dumps({'name': test_user_name, 'login': test_user_login}) - mock_box_session.post.return_value = create_user_response - new_user = mock_client.create_user(name=test_user_name, login=test_user_login) - - assert len(mock_box_session.post.call_args_list) == 1 - - assert mock_box_session.post.call_args[0] == (f"{API.BASE_API_URL}/users",) - assert mock_box_session.post.call_args[1] == {'data': value} - assert isinstance(new_user, User) - assert new_user.object_id == 1234 - assert new_user.name == test_user_name - - -def test_get_storage_policies(mock_client, mock_box_session): - expected_url = mock_box_session.get_url('storage_policies') - mock_policy = { - 'type': 'storage_policy', - 'id': '12345', - 'name': 'Test Storage Policy' - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [mock_policy] - } - policies = mock_client.get_storage_policies() - policy = policies.next() - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(policy, StoragePolicy) - assert policy.type == 'storage_policy' - assert policy.id == '12345' - assert policy.name == 'Test Storage Policy' - - -def test_create_terms_of_service(mock_client, mock_box_session): - # pylint:disable=redefined-outer-name - expected_url = f'{API.BASE_API_URL}/terms_of_services' - test_text = 'This is a test text' - test_tos_type = 'external' - test_status = 'enabled' - value = json.dumps({ - 'status': 'enabled', - 'tos_type': 'external', - 'text': 'This is a test text', - }) - mock_box_session.post.return_value.json.return_value = { - 'type': 'terms_of_service', - 'id': '12345', - 'status': test_status, - 'tos_type': test_tos_type, - 'text': test_text, - } - new_terms_of_service = mock_client.create_terms_of_service('enabled', 'external', 'This is a test text') - mock_box_session.post.assert_called_once_with(expected_url, data=value) - assert isinstance(new_terms_of_service, TermsOfService) - assert new_terms_of_service.type == 'terms_of_service' - assert new_terms_of_service.id == '12345' - assert new_terms_of_service.status == test_status - assert new_terms_of_service.tos_type == test_tos_type - assert new_terms_of_service.text == test_text - - -def test_get_all_terms_of_services(mock_client, mock_box_session): - expected_url = f'{API.BASE_API_URL}/terms_of_services' - tos_body = { - 'type': 'terms_of_service', - 'id': '12345', - 'status': 'enabled', - 'tos_type': 'external', - } - mock_box_session.get.return_value.json.return_value = { - 'total_count': 1, - 'entries': [tos_body], - } - services = mock_client.get_terms_of_services(tos_type='external') - service = services.next() - mock_box_session.get.assert_called_once_with(expected_url, params={'tos_type': 'external'}) - assert isinstance(service, TermsOfService) - assert service.type == 'terms_of_service' - assert service.id == '12345' - assert service.status == 'enabled' - assert service.tos_type == 'external' - - -def test_create_webhook_returns_the_correct_policy_object( - test_item_and_response, - test_webhook, - mock_client, - mock_box_session, - create_webhook_response, -): - # pylint:disable=redefined-outer-name - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/webhooks' - expected_body = { - 'target': { - 'type': test_item.object_type, - 'id': test_item.object_id, - }, - 'triggers': ['FILE.UPLOADED', 'FOLDER.CREATED'], - 'address': 'https://test.com', - } - value = json.dumps(expected_body) - mock_box_session.post.return_value = create_webhook_response - new_webhook = mock_client.create_webhook(test_item, ['FILE.UPLOADED', 'FOLDER.CREATED'], 'https://test.com') - mock_box_session.post.assert_called_once_with( - expected_url, - data=value, - ) - assert isinstance(new_webhook, Webhook) - assert new_webhook.id == test_webhook.object_id - assert new_webhook.type == test_webhook.object_type - assert new_webhook.target['type'] == test_item.object_type - assert new_webhook.target['id'] == test_item.object_id - assert new_webhook.triggers == ['FILE.UPLOADED', 'FOLDER.CREATED'] - assert new_webhook.address == 'https://test.com' - - -def test_get_webhooks(mock_client, mock_box_session): - expected_url = f'{API.BASE_API_URL}/webhooks' - webhook_body = { - 'type': 'webhook', - 'id': '12345', - 'target': { - 'type': 'folder', - 'id': '11111', - }, - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [webhook_body], - } - webhooks = mock_client.get_webhooks() - webhook = webhooks.next() - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(webhook, Webhook) - assert webhook.object_id == webhook_body['id'] - assert webhook.object_type == webhook_body['type'] - assert webhook.target['id'] == webhook_body['target']['id'] - assert webhook.target['type'] == webhook_body['target']['type'] - - -def test_create_retention_policy(mock_client, mock_box_session, mock_user_list): - policy_name = 'Test Retention Policy' - policy_type = 'finite' - disposition_action = 'remove_retention' - policy_description = 'Test Retention Policy' - expected_url = f'{API.BASE_API_URL}/retention_policies' - expected_data = { - 'policy_name': policy_name, - 'disposition_action': disposition_action, - 'policy_type': 'finite', - 'retention_length': 5, - 'can_owner_extend_retention': True, - 'are_owners_notified': False, - 'custom_notification_recipients': [ - { - 'type': mock_user_list[0].object_type, - 'id': mock_user_list[0].object_id, - }, - { - 'type': mock_user_list[1].object_type, - 'id': mock_user_list[1].object_id, - }, - ], - 'retention_type': 'modifiable', - 'description': policy_description, - } - mock_policy = { - 'type': 'retention_policy', - 'id': '1234', - 'policy_name': policy_name, - 'policy_type': policy_type, - 'retention_length': 5, - 'disposition_action': disposition_action, - 'can_owner_extend_retention': False, - 'are_owners_notified': False, - 'custom_notification_recipients': [ - { - 'type': mock_user_list[0].object_type, - 'id': mock_user_list[0].object_id, - }, - { - 'type': mock_user_list[1].object_type, - 'id': mock_user_list[1].object_id, - }, - ], - 'retention_type': 'modifiable', - 'description': policy_description, - } - mock_box_session.post.return_value.json.return_value = mock_policy - policy = mock_client.create_retention_policy( - policy_name=policy_name, - disposition_action=disposition_action, - retention_length=5, - can_owner_extend_retention=True, - are_owners_notified=False, - custom_notification_recipients=mock_user_list, - retention_type='modifiable', - description=policy_description, - ) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - assert policy.object_id == mock_policy['id'] - assert policy.object_type == mock_policy['type'] - assert policy.policy_name == mock_policy['policy_name'] - assert policy.disposition_action == mock_policy['disposition_action'] - assert policy.can_owner_extend_retention == mock_policy['can_owner_extend_retention'] - assert policy.are_owners_notified == mock_policy['are_owners_notified'] - assert policy.retention_type == mock_policy['retention_type'] - assert policy.description == mock_policy['description'] - assert isinstance(policy, RetentionPolicy) - - -def test_create_infinte_retention_policy(mock_client, mock_box_session): - policy_name = 'Test Retention Policy' - policy_type = 'indefinite' - disposition_action = 'remove_retention' - policy_description = 'Test Retention Policy' - expected_url = f'{API.BASE_API_URL}/retention_policies' - expected_data = { - 'policy_name': policy_name, - 'disposition_action': disposition_action, - 'policy_type': policy_type, - 'can_owner_extend_retention': False, - 'are_owners_notified': False, - 'retention_type': 'non_modifiable', - 'description': policy_description, - } - mock_policy = { - 'type': 'retention_policy', - 'id': '1234', - 'policy_name': policy_name, - 'policy_type': policy_type, - 'disposition_action': disposition_action, - 'can_owner_extend_retention': False, - 'are_owners_notified': False, - 'retention_type': 'non_modifiable', - 'description': policy_description, - } - mock_box_session.post.return_value.json.return_value = mock_policy - policy = mock_client.create_retention_policy( - policy_name=policy_name, - disposition_action=disposition_action, - retention_length=float('inf'), - can_owner_extend_retention=False, - are_owners_notified=False, - retention_type='non_modifiable', - description=policy_description, - ) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - assert policy.object_id == mock_policy['id'] - assert policy.object_type == mock_policy['type'] - assert policy.policy_name == mock_policy['policy_name'] - assert policy.disposition_action == mock_policy['disposition_action'] - assert policy.can_owner_extend_retention == mock_policy['can_owner_extend_retention'] - assert policy.are_owners_notified == mock_policy['are_owners_notified'] - assert policy.retention_type == mock_policy['retention_type'] - assert policy.description == mock_policy['description'] - assert isinstance(policy, RetentionPolicy) - - -def test_get_retention_policies(mock_client, mock_box_session, mock_user, mock_user_id): - expected_url = f'{API.BASE_API_URL}/retention_policies' - mock_policy = { - 'type': 'retention_policy', - 'id': '12345', - 'name': 'Test Retention Policy', - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [mock_policy], - 'next_marker': 'testMarker', - } - policies = mock_client.get_retention_policies(policy_name='Test Name', policy_type='finite', user=mock_user) - policy = policies.next() - params = { - 'policy_name': 'Test Name', - 'policy_type': 'finite', - 'created_by_user_id': mock_user_id, - } - mock_box_session.get.assert_called_once_with(expected_url, params=params) - assert isinstance(policy, RetentionPolicy) - assert policy.id == mock_policy['id'] - assert policy.name == mock_policy['name'] - - -def test_get_file_version_retentions(mock_client, mock_box_session, mock_file, mock_retention_policy): - expected_url = f'{API.BASE_API_URL}/file_version_retentions' - mock_retention = { - 'type': 'file_version_retention', - 'id': '12345', - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [mock_retention], - 'next_marker': 'testMarker', - } - retentions = mock_client.get_file_version_retentions( - target_file=mock_file, - policy=mock_retention_policy, - disposition_action='remove_retention', - disposition_before='2014-09-15T13:15:35-07:00', - disposition_after='2014-09-20T13:15:35-07:00', - ) - retention = retentions.next() - params = { - 'file_id': '11111', - 'policy_id': '22222', - 'disposition_action': 'remove_retention', - 'disposition_before': '2014-09-15T13:15:35-07:00', - 'disposition_after': '2014-09-20T13:15:35-07:00', - } - mock_box_session.get.assert_called_once_with(expected_url, params=params) - assert isinstance(retention, FileVersionRetention) - assert retention.id == mock_retention['id'] - assert retention.type == mock_retention['type'] - - -def test_get_pending_collaborations(mock_client, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - expected_url = f'{API.BASE_API_URL}/collaborations' - mock_collaboration = { - 'type': 'collaboration', - 'id': '12345', - 'created_by': { - 'type': 'user', - 'id': '33333', - }, - } - mock_box_session.get.return_value.json.return_value = { - 'total_count': 1, - 'limit': 2, - 'offset': 0, - 'entries': [mock_collaboration], - } - pending_collaborations = mock_client.get_pending_collaborations(limit=2) - pending_collaboration = pending_collaborations.next() - mock_box_session.get.assert_called_once_with(expected_url, params={'limit': 2, 'status': 'pending', 'offset': None}) - assert isinstance(pending_collaboration, Collaboration) - assert pending_collaboration.id == mock_collaboration['id'] - assert pending_collaboration.type == mock_collaboration['type'] - assert pending_collaboration['created_by']['type'] == 'user' - assert pending_collaboration['created_by']['id'] == '33333' - - -@pytest.fixture -def check_downscope_token_request( - mock_client, - mock_box_session, - mock_object_id, - make_mock_box_request, -): - def do_check(scopes, item_class, additional_data, shared_link, expected_data): - temp_downscoped_token = 'temp_downscoped_token' - temp_expires_in = 1234 - mock_box_response, _ = make_mock_box_request( - response={'access_token': temp_downscoped_token, 'expires_in': temp_expires_in}, - ) - mock_box_session.post.return_value = mock_box_response - - item = item_class(mock_box_session, mock_object_id) if item_class else None - - downscoped_token_response = mock_client.downscope_token(scopes, item, additional_data, shared_link) - - assert downscoped_token_response.access_token == temp_downscoped_token - assert downscoped_token_response.expires_in == temp_expires_in - - if item: - expected_data['resource'] = item.get_url() - mock_box_session.post.assert_called_once_with(f'{API.OAUTH2_API_URL}/token', data=expected_data) - - return do_check - - -@pytest.mark.parametrize( - 'item_class,scopes,expected_scopes', - [ - (File, [TokenScope.ITEM_READWRITE], 'item_readwrite'), - (Folder, [TokenScope.ITEM_PREVIEW, TokenScope.ITEM_SHARE], 'item_preview item_share'), - (File, [TokenScope.ITEM_READ, TokenScope.ITEM_SHARE, TokenScope.ITEM_DELETE], 'item_read item_share item_delete'), - (None, [TokenScope.ITEM_DOWNLOAD], 'item_download'), - ], -) -def test_downscope_token_sends_downscope_request( - mock_client, - check_downscope_token_request, - item_class, - scopes, - expected_scopes, -): - mock_client.auth.access_token = 'existing_access_token' - expected_data = { - 'subject_token': 'existing_access_token', - 'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token', - 'scope': expected_scopes, - 'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange', - } - check_downscope_token_request(scopes, item_class, None, None, expected_data) - - -def test_downscope_token_sends_downscope_request_with_shared_link( - mock_client, - check_downscope_token_request, -): - shared_link = 'https://cloud.box.com/s/foo' - expected_data = { - 'subject_token': mock_client.auth.access_token, - 'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token', - 'scope': 'item_readwrite', - 'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange', - 'box_shared_link': shared_link - } - check_downscope_token_request([TokenScope.ITEM_READWRITE], None, None, shared_link, expected_data) - - -def test_downscope_token_sends_downscope_request_with_additional_data( - mock_client, - check_downscope_token_request, -): - additional_data = {'grant_type': 'new_grant_type', 'extra_data_key': 'extra_data_value'} - expected_data = { - 'subject_token': mock_client.auth.access_token, - 'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token', - 'scope': 'item_readwrite', - 'grant_type': 'new_grant_type', - 'extra_data_key': 'extra_data_value', - } - check_downscope_token_request([TokenScope.ITEM_READWRITE], File, additional_data, None, expected_data) - - -def test_downscope_token_sends_downscope_request_when_no_initial_token( - mock_client, - check_downscope_token_request, -): - mock_client.auth.access_token = None - mock_client.auth.refresh.return_value = 'new_access_token' - - expected_data = { - 'subject_token': 'new_access_token', - 'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token', - 'scope': 'item_readwrite', - 'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange', - } - check_downscope_token_request([TokenScope.ITEM_READWRITE], File, None, None, expected_data) - mock_client.auth.refresh.assert_called_once_with(None) - - -def test_device_pins_for_enterprise(mock_client, mock_box_session, device_pins_response, device_pin_id_1, device_pin_id_2): - # pylint:disable=redefined-outer-name - enterprise_id = '11111' - expected_url = f'{API.BASE_API_URL}/enterprises/{enterprise_id}/device_pinners' - mock_box_session.get.return_value = device_pins_response - enterprise = mock_client.enterprise(enterprise_id) - pins = mock_client.device_pinners(enterprise, direction='ASC') - for pin, expected_id in zip(pins, [device_pin_id_1, device_pin_id_2]): - assert pin.object_id == expected_id - # pylint:disable=protected-access - assert pin._session == mock_box_session - mock_box_session.get.assert_called_once_with(expected_url, params={'direction': 'ASC'}) - - -def test_metadata_template_initializer(mock_client, mock_box_session): - template = mock_client.metadata_template('enterprise', 'VendorContract') - assert isinstance(template, MetadataTemplate) - # pylint:disable=protected-access - assert template._session == mock_box_session - assert template.object_id is None - assert template.scope == 'enterprise' - assert template.template_key == 'VendorContract' - - -def test_metadata_template_by_id(mock_client, mock_box_session): - template_id = 'sdkjfhgsdg-nb34745bndfg-qw4hbsajdg' - - template = mock_client.metadata_template_by_id(template_id) - - assert isinstance(template, MetadataTemplate) - # pylint:disable=protected-access - assert template._session == mock_box_session - assert template.object_id == template_id - assert template.scope is None - assert template.template_key is None - - -def test_get_metadata_templates(mock_client, mock_box_session): - expected_url = f'{API.BASE_API_URL}/metadata_templates/enterprise' - mock_box_session.get.return_value.json.return_value = { - 'total_count': 1, - 'entries': [ - { - 'type': 'metadata_template', - 'scope': 'enterprise_33333', - 'displayName': 'Vendor Contract', - 'templateKey': 'vendorContract', - 'fields': [ - { - 'type': 'string', - 'displayName': 'Name', - 'key': 'name', - }, - ], - }, - ], - 'next_marker': None, - 'previous_marker': None, - } - - templates = mock_client.get_metadata_templates() - template = templates.next() - - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(template, MetadataTemplate) - assert template.object_id is None - assert template.displayName == 'Vendor Contract' - fields = template.fields - assert len(fields) == 1 - field = fields[0] - assert isinstance(field, dict) - assert field['type'] == 'string' - assert field['key'] == 'name' - - -def test_create_metadata_template(mock_client, mock_box_session): - expected_url = f'{API.BASE_API_URL}/metadata_templates/schema' - name = 'Vendor Contract' - key = 'vContract' - field1 = MetadataField(MetadataFieldType.DATE, 'Birthday', 'bday') - field2 = MetadataField(MetadataFieldType.ENUM, 'State', options=['CA', 'TX', 'NY']) - expected_body = { - 'scope': 'enterprise', - 'displayName': 'Vendor Contract', - 'hidden': True, - 'fields': [ - { - 'type': 'date', - 'displayName': 'Birthday', - 'key': 'bday', - }, - { - 'type': 'enum', - 'displayName': 'State', - 'options': [ - {'key': 'CA'}, - {'key': 'TX'}, - {'key': 'NY'}, - ], - }, - ], - 'copyInstanceOnItemCopy': False, - 'templateKey': 'vContract', - } - - response = { - 'type': 'metadata_template', - } - response.update(expected_body) - mock_box_session.post.return_value.json.return_value = response - - template = mock_client.create_metadata_template(name, [field1, field2], key, hidden=True) - - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_body)) - assert isinstance(template, MetadataTemplate) - assert template.object_id is None - assert template.displayName == 'Vendor Contract' - fields = template.fields - assert len(fields) == 2 - field = fields[0] - assert isinstance(field, dict) - assert field['type'] == 'date' - assert field['key'] == 'bday' - - -def test_get_current_enterprise(mock_client, mock_box_session): - expected_url = f'{API.BASE_API_URL}/users/me' - expected_params = { - 'fields': 'enterprise' - } - enterprise_id = '44444' - enterprise_name = 'Acme, Inc.' - user_json = { - 'type': 'user', - 'id': '33333', - 'enterprise': { - 'type': 'enterprise', - 'id': enterprise_id, - 'name': enterprise_name, - }, - } - mock_box_session.get.return_value.json.return_value = user_json - - enterprise = mock_client.get_current_enterprise() - - mock_box_session.get.assert_called_once_with(expected_url, params=expected_params, headers=None) - assert isinstance(enterprise, Enterprise) - assert enterprise.object_id == enterprise_id - assert enterprise._session == mock_box_session # pylint:disable=protected-access - assert enterprise.name == enterprise_name - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params={'fields': 'enterprise'}) - - -def test_comment(mock_client): - # pylint:disable=redefined-outer-name - comment_id = '12345' - comment = mock_client.comment(comment_id) - - assert isinstance(comment, Comment) - assert comment.object_id == comment_id - - -def test_collaboration(mock_client): - # pylint:disable=redefined-outer-name - collaboration_id = '12345' - collaboration = mock_client.collaboration(collaboration_id) - - assert isinstance(collaboration, Collaboration) - assert collaboration.object_id == collaboration_id - - -def test_legal_hold_policy(mock_client): - # pylint:disable=redefined-outer-name - policy_id = '12345' - policy = mock_client.legal_hold_policy(policy_id) - - assert isinstance(policy, LegalHoldPolicy) - assert policy.object_id == policy_id - - -def test_legal_hold_policy_assignment(mock_client): - # pylint:disable=redefined-outer-name - assignment_id = '12345' - assignment = mock_client.legal_hold_policy_assignment(assignment_id) - - assert isinstance(assignment, LegalHoldPolicyAssignment) - assert assignment.object_id == assignment_id - - -def test_legal_hold(mock_client): - # pylint:disable=redefined-outer-name - hold_id = '12345' - legal_hold = mock_client.legal_hold(hold_id) - - assert isinstance(legal_hold, LegalHold) - assert legal_hold.object_id == hold_id - - -def test_collection(mock_client): - # pylint:disable=redefined-outer-name - collection_id = '12345' - collection = mock_client.collection(collection_id) - - assert isinstance(collection, Collection) - assert collection.object_id == collection_id - - -def test_collections(mock_client, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - expected_url = f'{API.BASE_API_URL}/collections' - mock_collection = { - 'type': 'collection', - 'id': '12345', - 'created_by': { - 'type': 'user', - 'id': '33333', - }, - } - mock_box_session.get.return_value.json.return_value = { - 'total_count': 1, - 'limit': 2, - 'offset': 0, - 'entries': [mock_collection], - } - collections = mock_client.collections(limit=2) - collection = collections.next() - mock_box_session.get.assert_called_once_with(expected_url, params={'limit': 2, 'offset': 0}) - assert isinstance(collection, Collection) - assert collection.id == mock_collection['id'] - assert collection.type == mock_collection['type'] - assert collection['created_by']['type'] == 'user' - assert collection['created_by']['id'] == '33333' - - -def test_task(mock_client): - # pylint:disable=redefined-outer-name - task_id = '12345' - task = mock_client.task(task_id) - - assert isinstance(task, Task) - assert task.object_id == task_id - - -def test_task_assignment(mock_client): - # pylint:disable=redefined-outer-name - assignment_id = '12345' - assignment = mock_client.task_assignment(assignment_id) - - assert isinstance(assignment, TaskAssignment) - assert assignment.object_id == assignment_id - - -def test_retention_policy(mock_client): - # pylint:disable=redefined-outer-name - policy_id = '12345' - policy = mock_client.retention_policy(policy_id) - - assert isinstance(policy, RetentionPolicy) - assert policy.object_id == policy_id - - -def test_retention_policy_assignment(mock_client): - # pylint:disable=redefined-outer-name - assignment_id = '12345' - assignment = mock_client.retention_policy_assignment(assignment_id) - - assert isinstance(assignment, RetentionPolicyAssignment) - assert assignment.object_id == assignment_id - - -def test_file_version_retention(mock_client): - # pylint:disable=redefined-outer-name - retention_id = '12345' - file_version_retention = mock_client.file_version_retention(retention_id) - - assert isinstance(file_version_retention, FileVersionRetention) - assert file_version_retention.object_id == retention_id - - -def test_web_link(mock_client): - # pylint:disable=redefined-outer-name - web_link_id = '12345' - web_link = mock_client.web_link(web_link_id) - - assert isinstance(web_link, WebLink) - assert web_link.object_id == web_link_id - - -def test_device_pinner(mock_client): - # pylint:disable=redefined-outer-name - pin_id = '12345' - pin = mock_client.device_pinner(pin_id) - - assert isinstance(pin, DevicePinner) - assert pin.object_id == pin_id - - -def test_download_zip(mock_client, mock_box_session, mock_content_response): - expected_create_url = f'{API.BASE_API_URL}/zip_downloads' - name = 'test' - file_item = mock_client.file('466239504569') - folder_item = mock_client.folder('466239504580') - items = [file_item, folder_item] - mock_writeable_stream = BytesIO() - expected_create_body = { - 'download_file_name': name, - 'items': [ - { - 'type': 'file', - 'id': '466239504569' - }, - { - 'type': 'folder', - 'id': '466239504580' - } - ] - } - status_response_mock = Mock() - status_response_mock.json.return_value = { - 'total_file_count': 20, - 'downloaded_file_count': 10, - 'skipped_file_count': 10, - 'skipped_folder_count': 10, - 'state': 'succeeded' - } - mock_box_session.post.return_value.json.return_value = { - 'download_url': 'https://dl.boxcloud.com/2.0/zip_downloads/124hfiowk3fa8kmrwh/content', - 'status_url': 'https://api.box.com/2.0/zip_downloads/124hfiowk3fa8kmrwh/status', - 'expires_at': '2018-04-25T11:00:18-07:00', - 'name_conflicts': [ - [ - { - 'id': '100', - 'type': 'file', - 'original_name': 'salary.pdf', - 'download_name': 'aqc823.pdf' - }, - { - 'id': '200', - 'type': 'file', - 'original_name': 'salary.pdf', - 'download_name': 'aci23s.pdf' - } - ] - ] - } - - mock_box_session.get.side_effect = [mock_content_response, status_response_mock] - - status_returned = mock_client.download_zip(name, items, mock_writeable_stream) - mock_box_session.post.assert_called_once_with(expected_create_url, - data=json.dumps(expected_create_body), - skip_retry_codes={202}) - mock_box_session.get.assert_any_call('https://dl.boxcloud.com/2.0/zip_downloads/124hfiowk3fa8kmrwh/content', - expect_json_response=False, stream=True) - mock_box_session.get.assert_called_with('https://api.box.com/2.0/zip_downloads/124hfiowk3fa8kmrwh/status') - mock_writeable_stream.seek(0) - assert mock_writeable_stream.read() == mock_content_response.content - assert status_returned['total_file_count'] == 20 - assert status_returned['name_conflicts'][0][0]['id'] == '100' - - -@pytest.fixture(scope='module') -def mock_sign_request_response(): - # pylint:disable=redefined-outer-name - mock_sign_request = { - 'id': '42', - 'type': 'sign-request', - 'are_reminders_enabled': 'true', - 'are_text_signatures_enabled': 'true', - 'auto_expire_at': '2021-04-26T08:12:13.982Z', - 'days_valid': '2', - 'email_message': 'Hello! Please sign the document below', - 'email_subject': 'Sign Request from Acme', - 'external_id': '123', - 'is_document_preparation_needed': 'true', - 'redirect_url': 'https://www.box.com/accepted', - 'declined_redirect_url': 'https://www.box.com/declined', - 'parent_folder': { - 'id': '12345', - 'type': 'folder', - 'etag': '1', - 'name': 'Contracts', - 'sequence_id': '3' - }, - 'prefill_tags': [ - { - 'document_tag_id': '1234', - 'text_value': 'text', - 'checkbox_value': 'true', - 'date_value': '2021-04-26T08:12:13.982Z' - } - ], - 'prepare_url': 'https://prepareurl.com', - 'sign_files': { - 'files': [ - { - 'id': '12345', - 'etag': '1', - 'type': 'file', - 'sequence_id': '3', - 'name': 'Contract.pdf', - 'sha1': '85136C79CBF9FE36BB9D05D0639C70C265C18D37', - 'file_version': { - 'id': '12345', - 'type': 'file_version', - 'sha1': '134b65991ed521fcfe4724b7d814ab8ded5185dc' - } - } - ], - 'is_ready_for_download': 'true' - }, - 'signers': [ - { - 'email': 'example@gmail.com', - 'role': 'signer', - 'is_in_person': 'true', - 'order': '2', - 'embed_url_external_user_id': '1234', - 'has_viewed_document': 'true', - 'signer_decision': { - 'type': 'signed', - 'finalized_at': '2021-04-26T08:12:13.982Z' - }, - 'inputs': [ - { - 'document_tag_id': '1234', - 'text_value': 'text', - 'checkbox_value': 'true', - 'date_value': '2021-04-26T08:12:13.982Z', - 'type': 'text', - 'page_index': '4' - } - ], - 'embed_url': 'https://example.com' - } - ], - 'signing_log': { - 'id': '12345', - 'type': 'file', - 'etag': '1', - 'file_version': { - 'id': '12345', - 'type': 'file_version', - 'sha1': '134b65991ed521fcfe4724b7d814ab8ded5185dc' - }, - 'name': 'Contract.pdf', - 'sequence_id': '3', - 'sha1': '85136C79CBF9FE36BB9D05D0639C70C265C18D37' - }, - 'source_files': [ - { - 'id': '12345', - 'etag': '1', - 'type': 'file', - 'sequence_id': '3', - 'name': 'Contract.pdf', - 'sha1': '85136C79CBF9FE36BB9D05D0639C70C265C18D37', - 'file_version': { - 'id': '12345', - 'type': 'file_version', - 'sha1': '134b65991ed521fcfe4724b7d814ab8ded5185dc' - } - } - ], - 'status': 'converting', - 'template_id': '123075213-af2c8822-3ef2-4952-8557-52d69c2fe9cb' - } - return mock_sign_request - - -@pytest.fixture(scope='module') -def mock_sign_template_response(): - mock_sign_template = { - "id": "93153068-5420-467b-b8ef-8e54bfb7be42", - "type": "sign-template", - "name": "important-file.pdf", - "email_message": "Please sign this document.\n\nKind regards", - "email_subject": "Box User (boxuser@box.com) has requested your signature on a document", - "parent_folder": { - "id": "123456789", - "etag": "0", - "type": "folder", - "sequence_id": "0", - "name": "My Sign Requests" - }, - "auto_expire_days": "null", - "source_files": [ - { - "id": "123456", - "etag": "0", - "type": "file", - "sequence_id": "0", - "sha1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "file_version": { - "id": "123456", - "type": "file_version", - "sha1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - } - } - ], - "are_email_settings_locked": "false", - "are_fields_locked": "false", - "are_files_locked": "false", - "are_options_locked": "false", - "are_recipients_locked": "false", - "signers": [ - { - "email": "", - "label": "", - "public_id": "AAQXQXJZ4", - "role": "final_copy_reader", - "is_in_person": "false", - "order": 1, - "inputs": [] - }, - { - "email": "", - "label": "", - "public_id": "13XQXJZ4", - "role": "signer", - "is_in_person": "false", - "order": 1, - "inputs": [ - { - "document_tag_id": None, - "id": "0260f921-3b52-477f-ae74-0b0b0b0b0b0b", - "type": "signature", - "text_value": None, - "is_required": True, - "coordinates": { - "x": 0.27038464059712, - "y": 0.10051756244533624 - }, - "dimensions": { - "width": 0.23570031566618235, - "height": 0.04781003891921971 - }, - "date_value": None, - "page_index": 0, - "checkbox_value": None, - "document_id": "2fdf9003-d798-40ee-be7f-0b0b0b0b0b0b", - "content_type": "signature", - "dropdown_choices": None, - "group_id": None, - "label": None - } - ] - } - ], - "ready_sign_link": None, - "custom_branding": None, - "days_valid": 0, - "additional_info": { - "non_editable": [], - "required": { - "signers": [ - [ - "email" - ], - [ - "email" - ] - ] - } - } - } - return mock_sign_template - - -@pytest.fixture(scope='module') -def mock_ai_question_response(): - mock_ai_question_response = { - 'answer': 'Public APIs are important because of key and important reasons.', - 'completion_reason': 'done', - 'created_at': '2021-04-26T08:12:13.982Z', - } - return mock_ai_question_response - - -@pytest.fixture(scope='module') -def mock_ai_agent_default_config_response(): - mock_ai_agent_default_config_response = { - 'type': 'ai_agent_text_gen', - 'basic_gen': { - 'content_template': '---{content}---', - 'embeddings': { - 'model': 'openai__text_embedding_ada_002', - 'strategy': { - 'id': 'basic', - 'num_tokens_per_chunk': 64 - } - }, - 'llm_endpoint_params': { - 'type': 'openai_params', - 'frequency_penalty': 1.5, - 'presence_penalty': 1.5, - 'stop': '<|im_end|>', - 'temperature': 0, - 'top_p': 1 - }, - 'model': 'openai__gpt_3_5_turbo', - 'num_tokens_for_completion': 8400, - 'prompt_template': 'It is `{current_date}`, and I have $8000 and want to spend a week in the Azores. What ' - 'should I see?', - 'system_message': 'You are a helpful travel assistant specialized in budget travel' - } - } - return mock_ai_agent_default_config_response - - -def test_get_sign_requests(mock_client, mock_box_session, mock_sign_request_response): - expected_url = f'{API.BASE_API_URL}/sign_requests' - - mock_sign_request = mock_sign_request_response - mock_box_session.get.return_value.json.return_value = { - 'total_count': 1, - 'limit': 100, - 'entries': [mock_sign_request], - 'next_marker': None, - 'previous_marker': None, - } - - sign_requests = mock_client.get_sign_requests() - sign_request = sign_requests.next() - - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(sign_request, SignRequest) - assert sign_request.id == mock_sign_request['id'] - assert sign_request.status == mock_sign_request['status'] - - -def test_create_sign_request(mock_client, mock_box_session, mock_sign_request_response): - expected_url = f'{API.BASE_API_URL}/sign_requests' - redirect_url = 'https://www.box.com/accepted' - declined_redirect_url = 'https://www.box.com/declined' - template_id = '123075213-af2c8822-3ef2-4952-8557-52d69c2fe9cb' - source_file = { - 'id': '12345', - 'type': 'file' - } - source_file2 = { - 'id': '34567', - 'type': 'file' - } - files = [source_file, source_file2] - - signer = { - 'email': 'example@gmail.com' - } - signers = [signer] - parent_folder_id = '12345' - - data = json.dumps({ - 'signers': [ - { - 'email': signer['email'] - } - ], - 'source_files': [ - { - 'id': source_file['id'], - 'type': source_file['type'] - }, - { - 'id': source_file2['id'], - 'type': source_file2['type'] - } - ], - 'parent_folder': - { - 'id': parent_folder_id, - 'type': 'folder' - }, - 'redirect_url': redirect_url, - 'declined_redirect_url': declined_redirect_url, - 'template_id': template_id - }) - mock_box_session.post.return_value.json.return_value = mock_sign_request_response - - new_sign_request = mock_client.create_sign_request( - files, signers, parent_folder_id, - redirect_url=redirect_url, declined_redirect_url=declined_redirect_url, template_id=template_id) - - mock_box_session.post.assert_called_once_with(expected_url, data=data) - assert isinstance(new_sign_request, SignRequest) - assert new_sign_request['source_files'][0]['id'] == source_file['id'] - assert new_sign_request['signers'][0]['email'] == signer['email'] - assert new_sign_request['parent_folder']['id'] == parent_folder_id - assert new_sign_request['redirect_url'] == redirect_url - assert new_sign_request['declined_redirect_url'] == declined_redirect_url - assert new_sign_request['template_id'] == template_id - - -def test_create_sign_request_v2(mock_client, mock_box_session, mock_sign_request_response): - expected_url = f'{API.BASE_API_URL}/sign_requests' - redirect_url = 'https://www.box.com/accepted' - declined_redirect_url = 'https://www.box.com/declined' - template_id = '123075213-af2c8822-3ef2-4952-8557-52d69c2fe9cb' - - signer = { - 'email': 'example@gmail.com' - } - signers = [signer] - - data = json.dumps({ - 'signers': [ - { - 'email': signer['email'] - } - ], - 'redirect_url': redirect_url, - 'declined_redirect_url': declined_redirect_url, - 'template_id': template_id - }) - mock_box_session.post.return_value.json.return_value = mock_sign_request_response - - new_sign_request = mock_client.create_sign_request_v2( - signers, - redirect_url=redirect_url, declined_redirect_url=declined_redirect_url, template_id=template_id) - - mock_box_session.post.assert_called_once_with(expected_url, data=data) - assert isinstance(new_sign_request, SignRequest) - assert new_sign_request['signers'][0]['email'] == signer['email'] - assert new_sign_request['redirect_url'] == redirect_url - assert new_sign_request['declined_redirect_url'] == declined_redirect_url - assert new_sign_request['template_id'] == template_id - - -def test_file_request(mock_client): - # pylint:disable=redefined-outer-name - file_request_id = '12345' - file_request = mock_client.file_request(file_request_id) - - assert isinstance(file_request, FileRequest) - assert file_request.object_id == file_request_id - - -def test_get_sign_templates_from_id(mock_client, mock_box_session, mock_sign_template_response): - test_sign_template_id = '93153068-5420-467b-b8ef-8e54bfb7be42' - expected_url = f'{API.BASE_API_URL}/sign_templates/{test_sign_template_id}' - mock_box_session.get.return_value.json.return_value = mock_sign_template_response - - sign_template = mock_client.sign_template(test_sign_template_id).get() - - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - - assert isinstance(sign_template, SignTemplate) - assert sign_template.id == '93153068-5420-467b-b8ef-8e54bfb7be42' - - -def test_get_sign_template(mock_client, mock_box_session, mock_sign_template_response): - test_sign_template_id = '93153068-5420-467b-b8ef-8e54bfb7be42' - expected_url = f'{API.BASE_API_URL}/sign_templates/{test_sign_template_id}' - mock_box_session.get.return_value.json.return_value = mock_sign_template_response - - sign_template = mock_client.get_sign_template(test_sign_template_id) - - mock_box_session.get.assert_called_once_with(expected_url) - - assert isinstance(sign_template, SignTemplate) - assert sign_template.id == '93153068-5420-467b-b8ef-8e54bfb7be42' - assert sign_template.name == 'important-file.pdf' - assert sign_template.email_message == 'Please sign this document.\n\nKind regards' - - -def test_get_sign_templates(mock_client, mock_box_session, mock_sign_template_response): - expected_url = f'{API.BASE_API_URL}/sign_templates' - - mock_sign_template = mock_sign_template_response - mock_box_session.get.return_value.json.return_value = { - 'total_count': 1, - 'limit': 100, - 'entries': [mock_sign_template], - 'next_marker': None, - 'previous_marker': None, - } - - sign_templates = mock_client.get_sign_templates() - - sign_template = sign_templates.next() - - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(sign_template, SignTemplate) - assert sign_template.id == '93153068-5420-467b-b8ef-8e54bfb7be42' - assert sign_template.name == 'important-file.pdf' - - -def test_send_ai_question(mock_client, mock_box_session, mock_ai_question_response): - expected_url = f'{API.BASE_API_URL}/ai/ask' - mock_box_session.post.return_value.json.return_value = mock_ai_question_response - - items = [{ - 'type': 'file', - 'id': '12345' - }] - question = 'Why are public APIs important?' - mode = 'single_item_qa' - ai_agent = { - 'type': 'ai_agent_ask', - 'basic_text_multi': { - 'model': 'openai__gpt_3_5_turbo' - } - } - - answer = mock_client.send_ai_question(items, question, mode, ai_agent) - - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps({ - 'items': items, - 'prompt': question, - 'mode': mode, - 'ai_agent': { - 'type': 'ai_agent_ask', - 'basic_text_multi': { - 'model': 'openai__gpt_3_5_turbo' - } - } - })) - assert answer['answer'] == 'Public APIs are important because of key and important reasons.' - assert answer['completion_reason'] == 'done' - assert answer['created_at'] == '2021-04-26T08:12:13.982Z' - - -def test_send_ai_text_gen(mock_client, mock_box_session, mock_ai_question_response): - expected_url = f'{API.BASE_API_URL}/ai/text_gen' - mock_box_session.post.return_value.json.return_value = mock_ai_question_response - - items = [{ - 'type': 'file', - 'id': '12345' - }] - dialogue_history = [{ - "prompt": "Make my email about public APIs sound more professional", - "answer": "Here is the first draft of your professional email about public APIs", - "created_at": "2013-12-12T10:53:43-08:00" - }, { - "prompt": "Can you add some more information?", - "answer": "Public API schemas provide necessary information to integrate with APIs...", - "created_at": "2013-12-12T11:20:43-08:00" - }] - ai_agent = { - 'type': 'ai_agent_text_gen', - 'basic_gen': { - 'model': 'openai__gpt_3_5_turbo_16k' - } - } - answer = mock_client.send_ai_text_gen( - dialogue_history=dialogue_history, - items=items, - prompt="Write an email to a client about the importance of public APIs.", - ai_agent=ai_agent - ) - - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps({ - 'dialogue_history': dialogue_history, - 'items': items, - 'prompt': "Write an email to a client about the importance of public APIs.", - 'ai_agent': ai_agent - })) - assert answer['answer'] == 'Public APIs are important because of key and important reasons.' - assert answer['completion_reason'] == 'done' - assert answer['created_at'] == '2021-04-26T08:12:13.982Z' - - -def test_get_ai_agent_default_config(mock_client, mock_box_session, mock_ai_agent_default_config_response): - expected_url = f'{API.BASE_API_URL}/ai_agent_default' - mock_box_session.get.return_value.json.return_value = mock_ai_agent_default_config_response - - config = mock_client.get_ai_agent_default_config( - mode='text_gen', - language='en', - model='openai__gpt_3_5_turbo' - ) - - mock_box_session.get.assert_called_once_with(expected_url, params={'mode': 'text_gen', 'language': 'en', 'model': 'openai__gpt_3_5_turbo'}) - assert config['type'] == 'ai_agent_text_gen' - assert config['basic_gen']['model'] == 'openai__gpt_3_5_turbo' - assert config['basic_gen']['embeddings']['model'] == 'openai__text_embedding_ada_002' diff --git a/test/unit/conftest.py b/test/unit/conftest.py deleted file mode 100644 index 454340f90..000000000 --- a/test/unit/conftest.py +++ /dev/null @@ -1,139 +0,0 @@ -import copy -import json - -from unittest.mock import Mock, MagicMock -import pytest - -from boxsdk.config import API, Client, Proxy -from boxsdk.network import default_network -from boxsdk.network.default_network import DefaultNetworkResponse, DefaultNetwork -from boxsdk.session.box_response import BoxResponse -from boxsdk.session.session import Session -from boxsdk.util.translator import Translator - - -@pytest.fixture(scope='function', autouse=True) -def original_default_translator(): - """A reference to the default translator, before the reference is changed by `default_translator` below.""" - return Translator._default_translator # pylint:disable=protected-access - - -@pytest.yield_fixture(scope='function', autouse=True) -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 - - -@pytest.fixture(scope='function') -def translator(default_translator): # pylint:disable=unused-argument - return Translator(extend_default_translator=True, new_child=True) - - -@pytest.fixture(scope='function') -def mock_box_session(translator): - mock_session = MagicMock(Session) - # pylint:disable=protected-access - mock_session._api_config = mock_session.api_config = API() - mock_session._client_config = mock_session.client_config = Client() - mock_session._proxy_config = mock_session.proxy_config = Proxy() - # pylint:enable=protected-access - mock_session.get_url.side_effect = lambda *args, **kwargs: Session.get_url(mock_session, *args, **kwargs) - mock_session.translator = translator - return mock_session - - -@pytest.fixture() -def mock_box_session_2(translator): - mock_session = MagicMock(Session) - # pylint:disable=protected-access - mock_session._api_config = API() - mock_session._client_config = Client() - mock_session._proxy_config = Proxy() - # pylint:enable=protected-access - mock_session.get_url.side_effect = lambda *args, **kwargs: Session.get_url(mock_session, *args, **kwargs) - mock_session.translator = translator - return mock_session - - -@pytest.fixture(scope='function') -def mock_network_layer(): - mock_network = Mock(DefaultNetwork) - return mock_network - - -@pytest.fixture(autouse=True) -def prevent_tests_from_making_real_network_requests(monkeypatch): - monkeypatch.delattr(default_network.requests.Session, 'request') - - -@pytest.fixture(scope='function') -def mock_user_response(mock_user_id, make_mock_box_request): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={'type': 'user', 'id': mock_user_id}, - ) - return mock_box_response - - -@pytest.fixture(scope='function') -def mock_group_response(mock_group_id, make_mock_box_request): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={'type': 'group', 'id': mock_group_id}, - ) - return mock_box_response - - -@pytest.fixture() -def make_mock_box_request(): - def inner(status_code=200, response_ok=True, response=None, content=None): - mock_box_response = Mock(BoxResponse) - mock_network_response = Mock(DefaultNetworkResponse) - mock_box_response.network_response = mock_network_response - mock_box_response.status_code = status_code - mock_box_response.ok = response_ok - if response is not None: - mock_box_response.json.return_value = response - mock_box_response.content = json.dumps(response).encode() - else: - mock_box_response.content = content - return mock_box_response, mock_network_response - return inner - - -@pytest.fixture(scope='function') -def mock_file_response(mock_object_id, make_mock_box_request): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={'type': 'file', 'id': mock_object_id}, - ) - return mock_box_response - - -@pytest.fixture(scope='function') -def mock_folder_response(mock_object_id, make_mock_box_request): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={'type': 'folder', 'id': mock_object_id}, - ) - return mock_box_response - - -@pytest.fixture(scope='function') -def mock_web_link_response(mock_object_id, make_mock_box_request): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={'type': 'web_link', 'id': mock_object_id}, - ) - return mock_box_response diff --git a/test/unit/network/__init__.py b/test/unit/network/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/unit/network/conftest.py b/test/unit/network/conftest.py deleted file mode 100644 index 1f7252dc8..000000000 --- a/test/unit/network/conftest.py +++ /dev/null @@ -1,44 +0,0 @@ -from unittest.mock import Mock -import pytest -from requests import Session - - -@pytest.fixture(params=('GET', 'POST', 'PUT', 'DELETE', 'OPTIONS')) -def http_verb(request): - return request.param - - -@pytest.fixture() -def mock_request(monkeypatch): - mock_session_factory = Mock() - mock_session_factory.return_value = session = Mock(Session(), request=Mock()) - monkeypatch.setattr('requests.Session', mock_session_factory) - return session.request - - -@pytest.fixture(params=['generic_successful_request_response', 'server_error_request_response_502', 'server_error_request_response_503']) -def request_response(generic_successful_request_response, server_error_request_response_502, server_error_request_response_503, request): - # pylint:disable=unused-argument - return locals()[request.param] - - -@pytest.fixture -def make_request_with_request_response(mock_request, http_verb, test_url, access_token): - - def _make_request_with_request_response(network, request_response, **kwargs): - mock_request.return_value = request_response - response = network.request(http_verb, test_url, access_token, **kwargs) - mock_request.assert_called_once_with(http_verb, test_url, **kwargs) - assert response.access_token_used == access_token - return response - - return _make_request_with_request_response - - -@pytest.fixture -def make_network_request(make_request_with_request_response, request_response): - - def _make_network_request(network, **kwargs): - return make_request_with_request_response(network, request_response, **kwargs) - - return _make_network_request diff --git a/test/unit/network/test_network.py b/test/unit/network/test_network.py deleted file mode 100644 index 9a820a666..000000000 --- a/test/unit/network/test_network.py +++ /dev/null @@ -1,307 +0,0 @@ -from functools import partial -import json -from logging import Logger -from operator import attrgetter -from pprint import pformat -from unittest.mock import DEFAULT, Mock, patch, ANY - -import pytest -from requests import Response - -from boxsdk.network import default_network -from boxsdk.network.default_network import DefaultNetworkResponse, DefaultNetwork - - -class ExceptionSubclass(Exception): - pass - - -@pytest.fixture -def logger(): - return Mock(Logger) - - -@pytest.fixture -def network(): - return DefaultNetwork() - - -@pytest.fixture(autouse=True) -def mocked_logger(logger): - with patch.object(default_network, 'getLogger') as get_logger: - get_logger.return_value = logger - yield - - -@pytest.fixture(scope='module') -def logger_method_names(): - return ['critical', 'debug', 'error', 'exception', 'fatal', 'info', 'log', 'warn', 'warning'] - - -@pytest.fixture -def logger_methods(logger, logger_method_names): - return list(map(partial(getattr, logger), logger_method_names)) - - -@pytest.fixture -def assert_logger_called_once_with(logger, logger_method_names): - - def _assert_logger_called_once_with(method_name, *args, **kwargs): - getattr(logger, method_name).assert_called_once_with(*args, **kwargs) - method_names = set(logger_method_names) - method_names.discard(method_name) - for name in method_names: - getattr(logger, name).assert_not_called() - - return _assert_logger_called_once_with - - -@pytest.fixture -def assert_logger_not_called(logger_methods): - - def _assert_logger_not_called(): - for method in logger_methods: - method.assert_not_called() - - return _assert_logger_not_called - - -@pytest.fixture -def logger_call_count(logger_methods): - - def _logger_call_count(): - return sum(map(attrgetter('call_count'), logger_methods)) - - return _logger_call_count - - -@pytest.fixture -def make_network_request_and_assert_response(make_network_request, request_response): - # pylint:disable=unused-argument - - def _make_network_request(*args, **kwargs): - response = make_network_request(*args, **kwargs) - assert response.request_response is request_response - return response - - return _make_network_request - - -@pytest.fixture -def construct_network_response(access_token): - - def _construct_network_response(request_response, log_response_content=True): - return DefaultNetworkResponse( - request_response=request_response, - access_token_used=access_token, - log_response_content=log_response_content - ) - - return _construct_network_response - - -@pytest.fixture -def network_response(construct_network_response, request_response): - return construct_network_response(request_response) - - -@pytest.fixture(params=['content', 'json']) -def get_content_from_response(request): - - def _get_content_from_response(response): - content = getattr(response, request.param) - if request.param in ['json']: - try: - content = content() - except ValueError: - pass - - return _get_content_from_response - - -@pytest.fixture(params=['response_as_stream', 'request_response', 'log']) -def do_not_get_content_from_response(request): - - def _do_not_get_content_from_response(response): - attribute = getattr(response, request.param) - if request.param in ['log']: - attribute() - - return _do_not_get_content_from_response - - -# BEGIN Tests for DefaultNetwork. - - -def test_default_network_response_properties_pass_through_to_session_response_properties(access_token): - mock_session_response = Mock(Response) - mock_session_response.status_code = 200 - mock_session_response.headers = {} - mock_session_response.raw = Mock() - mock_session_response.content = json.dumps('content') - mock_session_response.request = Mock() - network_reponse = DefaultNetworkResponse(mock_session_response, access_token) - assert network_reponse.json() == mock_session_response.json() - assert network_reponse.content == mock_session_response.content - assert network_reponse.ok == mock_session_response.ok - assert network_reponse.status_code == mock_session_response.status_code - assert network_reponse.headers == mock_session_response.headers - assert network_reponse.response_as_stream == mock_session_response.raw - assert network_reponse.access_token_used == access_token - - -def test_default_network_request(make_network_request_and_assert_response): - # pylint:disable=redefined-outer-name - default_network = DefaultNetwork() - make_network_request_and_assert_response(default_network, custom_kwargs='test') - - -@pytest.mark.parametrize('delay', (0, 1)) -def test_default_network_retry_after_sleeps(delay): - default_network = DefaultNetwork() - retry_call = Mock() - mock_sleep = Mock() - with patch('boxsdk.network.default_network.time.sleep', mock_sleep): - default_network.retry_after(delay, retry_call, DEFAULT, kwarg=DEFAULT) - mock_sleep.assert_called_once_with(delay) - retry_call.assert_called_once_with(DEFAULT, kwarg=DEFAULT) - - -def test_network_response_constructor(make_network_request_and_assert_response): - assert DefaultNetwork().network_response_constructor is DefaultNetworkResponse - - class DefaultNetworkResponseSubclass(DefaultNetworkResponse): - pass - - class DefaultNetworkSubclass(DefaultNetwork): - @property - def network_response_constructor(self): - return DefaultNetworkResponseSubclass - - network = DefaultNetworkSubclass() - response = make_network_request_and_assert_response(network) - assert isinstance(response, DefaultNetworkResponseSubclass) - - -def test_network_logs_requests(make_network_request, http_verb, test_url, network, logger): - kwargs = dict(custom_kwarg='foo') - make_network_request(network, **kwargs) - logger.info.assert_any_call( - network.REQUEST_FORMAT, - {'method': http_verb, 'url': test_url, 'request_kwargs': pformat(kwargs)}, - ) - - -def test_network_logs_request_exception(make_network_request, mock_request, http_verb, test_url, logger, network): - mock_request.side_effect = expected_exception = ExceptionSubclass('exception raised from request()') - with pytest.raises(ExceptionSubclass) as pytest_exc_info: - make_network_request(network) - assert pytest_exc_info.value is expected_exception - logger.warning.assert_called_once_with( - network.EXCEPTION_FORMAT, - {'method': http_verb, 'url': test_url, 'exc_type_name': ExceptionSubclass.__name__, 'exc_value': expected_exception}, - ) - - -def test_network_request_returns_network_response(make_network_request, request_response, network): - response = make_network_request(network) - assert response.request_response is request_response - assert isinstance(response, DefaultNetworkResponse) - - -# BEGIN Tests for NetworkResponse. - - -def test_all_network_responses_log_immediately(network_response, logger_call_count): - # pylint:disable=unused-argument - # Need to load the `network_response` fixture for this test to be - # meaningful. - assert logger_call_count() == 1 - - -def test_network_response_only_logs_once(network_response, logger_call_count): - network_response.log() - assert logger_call_count() == 1 - network_response.log() - assert logger_call_count() == 1 - - -@pytest.mark.parametrize('content_length_header', [False, True]) -def test_network_logs_successful_json_responses( - construct_network_response, generic_successful_request_response, assert_logger_called_once_with, - get_content_from_response, http_verb, test_url, content_length_header, -): - expected_content_length = str(len(generic_successful_request_response.content)) - if content_length_header: - generic_successful_request_response.headers['Content-Length'] = expected_content_length - else: - generic_successful_request_response.headers.pop('Content-Length', None) - generic_successful_request_response.request.method = http_verb - generic_successful_request_response.request.url = test_url - network_response = construct_network_response(generic_successful_request_response) - get_content_from_response(network_response) - assert_logger_called_once_with( - 'info', - DefaultNetworkResponse.SUCCESSFUL_RESPONSE_FORMAT, - { - 'method': http_verb, - 'url': test_url, - 'status_code': generic_successful_request_response.status_code, - 'content_length': expected_content_length, - 'headers': pformat(generic_successful_request_response.headers), - 'content': pformat(generic_successful_request_response.json()), - } - ) - - -@pytest.mark.parametrize('content_length_header', [False, True]) -def test_network_logs_successful_responses_with_file_content_placeholder( - construct_network_response, generic_successful_request_response, assert_logger_called_once_with, - logger, content_length_header, -): - if content_length_header: - expected_content_length = str(len(generic_successful_request_response.content)) - generic_successful_request_response.headers['Content-Length'] = expected_content_length - else: - generic_successful_request_response.headers.pop('Content-Length', None) - expected_content_length = '?' - network_response = construct_network_response(generic_successful_request_response, log_response_content=False) - assert_logger_called_once_with('info', DefaultNetworkResponse.SUCCESSFUL_RESPONSE_FORMAT, ANY) - assert logger.info.call_args[0][1]['content'] == network_response.CONTENT_NOT_LOGGED - assert logger.info.call_args[0][1]['content_length'] == expected_content_length - - -def test_network_logs_successful_responses_with_non_json_content( - construct_network_response, generic_successful_request_response, assert_logger_called_once_with, - logger, get_content_from_response, -): - generic_successful_request_response.content = content = (b''.join(chr(i).encode('utf-8') for i in range(128)) * 4) - generic_successful_request_response.json.side_effect = lambda: json.loads(content.decode('utf-8')) - network_response = construct_network_response(generic_successful_request_response, log_response_content=False) - get_content_from_response(network_response) - assert_logger_called_once_with('info', DefaultNetworkResponse.SUCCESSFUL_RESPONSE_FORMAT, ANY) - assert logger.info.call_args[0][1]['content'] == network_response.CONTENT_NOT_LOGGED - - -def test_network_logs_non_successful_responses( - construct_network_response, server_error_request_response, assert_logger_called_once_with, - http_verb, test_url, -): - server_error_request_response.request.method = http_verb - server_error_request_response.request.url = test_url - construct_network_response(server_error_request_response) - assert_logger_called_once_with( - 'warning', - DefaultNetworkResponse.ERROR_RESPONSE_FORMAT, - { - 'method': http_verb, - 'url': test_url, - 'status_code': server_error_request_response.status_code, - 'content_length': str(len(server_error_request_response.content)), - 'headers': pformat(server_error_request_response.headers), - 'content': pformat(server_error_request_response.json()), - } - ) - - -# END Tests for NetworkResponse. diff --git a/test/unit/object/__init__.py b/test/unit/object/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/unit/object/conftest.py b/test/unit/object/conftest.py deleted file mode 100644 index a8c4fbd33..000000000 --- a/test/unit/object/conftest.py +++ /dev/null @@ -1,428 +0,0 @@ -import os -from datetime import datetime -from unittest.mock import Mock -import pytest -import pytz - -from boxsdk.object.collaboration import Collaboration -from boxsdk.object.collection import Collection -from boxsdk.object.comment import Comment -from boxsdk.object.device_pinner import DevicePinner -from boxsdk.object.file import File -from boxsdk.object.file_request import FileRequest -from boxsdk.object.file_version import FileVersion -from boxsdk.object.file_version_retention import FileVersionRetention -from boxsdk.object.legal_hold import LegalHold -from boxsdk.object.folder import Folder -from boxsdk.object.folder_lock import FolderLock -from boxsdk.object.group import Group -from boxsdk.object.group_membership import GroupMembership -from boxsdk.object.legal_hold_policy import LegalHoldPolicy -from boxsdk.object.legal_hold_policy_assignment import LegalHoldPolicyAssignment -from boxsdk.object.metadata_template import MetadataTemplate -from boxsdk.object.user import User -from boxsdk.object.retention_policy import RetentionPolicy -from boxsdk.object.retention_policy_assignment import RetentionPolicyAssignment -from boxsdk.object.search import Search -from boxsdk.object.sign_request import SignRequest -from boxsdk.object.sign_template import SignTemplate -from boxsdk.object.storage_policy import StoragePolicy -from boxsdk.object.storage_policy_assignment import StoragePolicyAssignment -from boxsdk.object.terms_of_service import TermsOfService -from boxsdk.object.terms_of_service_user_status import TermsOfServiceUserStatus -from boxsdk.object.collaboration_allowlist import CollaborationAllowlist -from boxsdk.object.collaboration_allowlist_entry import CollaborationAllowlistEntry -from boxsdk.object.collaboration_allowlist_exempt_target import CollaborationAllowlistExemptTarget -from boxsdk.object.webhook import Webhook -from boxsdk.object.task import Task -from boxsdk.object.task_assignment import TaskAssignment -from boxsdk.object.web_link import WebLink -from boxsdk.util.default_arg_value import SDK_VALUE_NOT_SET - -# pylint:disable=redefined-outer-name - - -@pytest.fixture(scope='module') -def mock_group_membership_id(): - return 'fake-group-membership-5' - - -@pytest.fixture(scope='module') -def mock_collaboration_id(): - return 'collab_id1' - - -@pytest.fixture(scope='module') -def mock_collection_id(): - return 'collection_id1' - - -@pytest.fixture(scope='module') -def mock_file_path(): - return os.path.join('path', 'to', 'file') - - -@pytest.fixture(scope='function') -def mock_image_path(image_extension): - return os.path.join('path', 'to', f'image.{image_extension}') - - -@pytest.fixture(scope='function') -def mock_content_response(make_mock_box_request): - mock_box_response, mock_network_response = make_mock_box_request(content=b'Contents of a text file.') - mock_network_response.response_as_stream = raw = Mock() - raw.stream.return_value = (bytes((b,)) for b in mock_box_response.content) - return mock_box_response - - -@pytest.fixture(scope='function', params=[False, True]) -def mock_upload_response_contains_entries(request): - """Is the upload response formatted as {"type": "file", "id": "123", ...}, or as {"entries": [{...}]}. - - The v2.0 API does the latter, but future versions might do the former. So - we'll test both. - """ - return request.param - - -@pytest.fixture(scope='function') -def mock_upload_response(mock_object_id, make_mock_box_request, mock_upload_response_contains_entries): - response = {'type': 'file', 'id': mock_object_id, 'description': 'Test File Description', } - if mock_upload_response_contains_entries: - response = {'entries': [response]} - mock_box_response, _ = make_mock_box_request(response=response) - return mock_box_response - - -@pytest.fixture() -def test_collaboration(mock_box_session, mock_collaboration_id): - return Collaboration(mock_box_session, mock_collaboration_id) - - -@pytest.fixture() -def test_file(mock_box_session, mock_object_id): - return File(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_file_version(mock_box_session): - return FileVersion(mock_box_session, 'file_version_id') - - -@pytest.fixture() -def test_comment(mock_box_session, mock_object_id): - return Comment(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_collaboration_allowlist(mock_box_session): - return CollaborationAllowlist(mock_box_session) - - -@pytest.fixture() -def test_collaboration_allowlist_entry(mock_box_session, mock_object_id): - return CollaborationAllowlistEntry(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_collaboration_allowlist_exemption(mock_box_session, mock_object_id): - return CollaborationAllowlistExemptTarget(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_folder(mock_box_session, mock_object_id): - return Folder(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_group(mock_box_session, mock_group_id): - return Group(mock_box_session, mock_group_id) - - -@pytest.fixture() -def test_retention_policy(mock_box_session, mock_object_id): - return RetentionPolicy(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_file_version_retention(mock_box_session, mock_object_id): - return FileVersionRetention(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_retention_policy_assignment(mock_box_session, mock_object_id): - return RetentionPolicyAssignment(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_group_membership(mock_box_session, mock_object_id): - return GroupMembership(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_legal_hold_policy(mock_box_session, mock_object_id): - return LegalHoldPolicy(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_legal_hold_policy_assignment(mock_box_session, mock_object_id): - return LegalHoldPolicyAssignment(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_legal_hold(mock_box_session, mock_object_id): - return LegalHold(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_search(mock_box_session): - return Search(mock_box_session) - - -@pytest.fixture() -def test_storage_policy(mock_box_session, mock_object_id): - return StoragePolicy(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_storage_policy_assignment(mock_box_session, mock_object_id): - return StoragePolicyAssignment(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_terms_of_service(mock_box_session, mock_object_id): - return TermsOfService(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_terms_of_service_user_status(mock_box_session, mock_object_id): - return TermsOfServiceUserStatus(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_webhook(mock_box_session, mock_object_id): - return Webhook(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_task(mock_box_session, mock_object_id): - return Task(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_task_assignment(mock_box_session, mock_object_id): - return TaskAssignment(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_web_link(mock_box_session, mock_object_id): - return WebLink(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_device_pin(mock_box_session, mock_object_id): - return DevicePinner(mock_box_session, mock_object_id) - - -@pytest.fixture(scope='function') -def mock_collab_response(make_mock_box_request, mock_collaboration_id): - mock_box_response, _ = make_mock_box_request( - response={'type': 'collaboration', 'id': mock_collaboration_id}, - ) - return mock_box_response - - -@pytest.fixture() -def mock_user(mock_box_session, mock_user_id): - user = User(mock_box_session, mock_user_id) - return user - - -@pytest.fixture() -def mock_collection(mock_box_session, mock_collection_id): - return Collection(mock_box_session, mock_collection_id) - - -@pytest.fixture(scope='function') -def mock_precondition_failed_response(mock_object_id, make_mock_box_request): - mock_box_response, _ = make_mock_box_request( - status_code=412, - response_ok=False, - response={'type': 'folder', 'id': mock_object_id}, - ) - return mock_box_response - - -@pytest.fixture() -def mock_group_membership_dict(mock_group_membership_id, mock_user_id, mock_group_id): - data = { - 'type': 'group_membership', - 'id': mock_group_membership_id, - 'role': 'member', - 'user': {'type': 'user', 'id': mock_user_id}, - 'group': {'type': 'group', 'id': mock_group_id}, - } - return data - - -@pytest.fixture() -def mock_add_member_response(mock_group_membership_dict, make_mock_box_request): - mock_box_response, _ = make_mock_box_request( - status_code=200, - response_ok=True, - response=mock_group_membership_dict, - ) - return mock_box_response - - -@pytest.fixture() -def if_match_header(etag): - return {'If-Match': etag} if etag is not None else None - - -@pytest.fixture() -def if_none_match_header(etag): - return {'If-None-Match': etag} if etag is not None else None - - -@pytest.fixture() -def if_match_sha1_header(etag, sha1): - headers = {} - if etag is not None: - headers['If-Match'] = etag - if sha1 is not None: - headers['Content-MD5'] = sha1 - if not headers: - headers = None - return headers - - -@pytest.fixture(params=[None, 'etag']) -def etag(request): - return request.param - - -@pytest.fixture(params=[None, 'cf23df2207d99a74fbe169e3eba035e633b65d94']) -def sha1(request): - return request.param - - -@pytest.fixture(params=[True, False]) -def preflight_check(request): - return request.param - - -@pytest.fixture(params=[True, False]) -def upload_using_accelerator(request): - return request.param - - -@pytest.fixture(params=[True, False]) -def preflight_fails(preflight_check, request): - return preflight_check and request.param - - -@pytest.fixture(params=[True, False]) -def upload_using_accelerator_fails(upload_using_accelerator, request): - return upload_using_accelerator and request.param - - -@pytest.fixture(params=[0, 100]) -def file_size(request): - return request.param - - -@pytest.fixture() -def mock_group(mock_box_session, mock_group_id): - group = Group(mock_box_session, mock_group_id) - return group - - -@pytest.fixture(params=(True, False, None)) -def shared_link_can_download(request): - return request.param - - -@pytest.fixture(params=(True, False, None)) -def shared_link_can_preview(request): - return request.param - - -@pytest.fixture(params=(True, False, None)) -def shared_link_can_edit(request): - return request.param - - -@pytest.fixture(params=('open', None)) -def shared_link_access(request): - return request.param - - -@pytest.fixture(params=('hunter2', None)) -def shared_link_password(request): - return request.param - - -@pytest.fixture( - params=( - '2018-10-31', - '2018-10-31T00:00:00+14:00', - None, - SDK_VALUE_NOT_SET, - datetime(2018, 10, 31, 23, 59, 59, tzinfo=pytz.timezone('US/Alaska')) - ) -) -def shared_link_unshared_at(request): - return request.param - - -@pytest.fixture(params=('my-custom-vanity-name', None)) -def shared_link_vanity_name(request): - return request.param - - -@pytest.fixture(params=[ - # Test case for plain message - ( - 'message', - 'Hello there!' - ), - - # Test case for tagged message - ( - 'tagged_message', - '@[22222:Test User] Hi!' - ) -]) -def comment_params(request): - return request.param - - -@pytest.fixture() -def test_metadata_template(mock_box_session): - fake_response = { - 'type': 'metadata_template', - 'scope': 'enterprise', - 'templateKey': 'vContract', - } - return MetadataTemplate(mock_box_session, None, fake_response) - - -@pytest.fixture() -def test_folder_lock(mock_box_session, mock_object_id): - return FolderLock(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_sign_request(mock_box_session, mock_object_id): - return SignRequest(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_file_request(mock_box_session, mock_object_id): - return FileRequest(mock_box_session, mock_object_id) - - -@pytest.fixture() -def test_sign_template(mock_box_session, mock_object_id): - return SignTemplate(mock_box_session, mock_object_id) diff --git a/test/unit/object/test_api_json_object.py b/test/unit/object/test_api_json_object.py deleted file mode 100644 index 9637fd910..000000000 --- a/test/unit/object/test_api_json_object.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest - -from boxsdk.object.api_json_object import APIJSONObject - - -@pytest.fixture(params=[{'foo': 'bar'}, {'a': {'b': 'c'}}]) -def api_json_object(request): - return request.param, APIJSONObject(request.param) - - -def test_len(api_json_object): - dictionary, test_object = api_json_object - assert len(dictionary) == len(test_object) - - -def test_api_json_object_dict(api_json_object): - dictionary, test_object = api_json_object - assert dictionary == test_object diff --git a/test/unit/object/test_base_api_json_object.py b/test/unit/object/test_base_api_json_object.py deleted file mode 100644 index a9cf2fb39..000000000 --- a/test/unit/object/test_base_api_json_object.py +++ /dev/null @@ -1,70 +0,0 @@ -import pytest - -from boxsdk.object.base_api_json_object import BaseAPIJSONObject -from boxsdk.object.base_object import BaseObject -from boxsdk.object.folder import Folder - - -@pytest.fixture(params=[{'foo': 'bar'}, {'a': {'b': 'c'}}]) -def response(request): - return request.param - - -@pytest.fixture() -def base_api_json_object(response): - dictionary_response = response - return dictionary_response, BaseAPIJSONObject(dictionary_response) - - -def test_getitem(base_api_json_object): - dictionary_response, test_object = base_api_json_object - assert isinstance(test_object, BaseAPIJSONObject) - for key in dictionary_response: - assert test_object[key] == dictionary_response[key] - - -def test_contains(base_api_json_object): - dictionary_response, test_object = base_api_json_object - for key in dictionary_response: - assert key in test_object - assert 'some_key_that_doesnt_exist' not in test_object - - -def test_iter(base_api_json_object): - dictionary_response, test_object = base_api_json_object - all_test_object_keys = list(test_object) - all_dictionary_response_keys = list(dictionary_response) - assert set(all_test_object_keys) == set(all_dictionary_response_keys) - - -def test_meta_registers_new_item_type_in_default_translator(default_translator, original_default_translator): - item_type = 'ƒøø' - - class Foo(BaseAPIJSONObject): - _item_type = item_type - - assert default_translator.get(item_type) is Foo - assert (set(default_translator) - set(original_default_translator)) == {item_type} - - -@pytest.mark.parametrize('subclass', [BaseAPIJSONObject, BaseObject, Folder]) -def test_meta_does_not_register_new_subclass_in_default_translator_if_item_type_is_not_defined_in_namespace( - subclass, - default_translator, - original_default_translator, -): - - class Foo(subclass): - pass - - assert Foo not in default_translator.values() - assert default_translator == original_default_translator - - -def test_meta_overrides_registration_if_subclass_redefines_item_type(default_translator, original_default_translator): - - class FolderSubclass(Folder): - _item_type = 'folder' - - assert default_translator.get('folder') is FolderSubclass - assert set(default_translator.keys()) == set(original_default_translator.keys()) diff --git a/test/unit/object/test_base_item.py b/test/unit/object/test_base_item.py deleted file mode 100644 index b873b6bfc..000000000 --- a/test/unit/object/test_base_item.py +++ /dev/null @@ -1,139 +0,0 @@ -import json -import pytest - - -@pytest.fixture(params=('file', 'folder', 'web_link')) -def test_base_item_and_response( - test_file, test_folder, test_web_link, mock_file_response, mock_folder_response, mock_web_link_response, request): - if request.param == 'file': - return test_file, mock_file_response - if request.param == 'folder': - return test_folder, mock_folder_response - - return test_web_link, mock_web_link_response - - -@pytest.fixture(params=('empty', 'same', 'other')) -def test_collections_for_addition(mock_collection_id, request): - """Fixture returning a tuple of the expected collections values before and after addition""" - other_collection_id = mock_collection_id + '2' - if request.param == 'empty': - return [], [{'id': mock_collection_id}] - if request.param == 'same': - # Adding a second instance of the same collection is handled correctly by the API, - # so for simplicity we do not check for an existing copy of the collection and just append - return [{'id': mock_collection_id}], [{'id': mock_collection_id}, {'id': mock_collection_id}] - if request.param == 'other': - return [{'id': other_collection_id}], [{'id': other_collection_id}, {'id': mock_collection_id}] - - raise NotImplementedError(f"Forgot to implement {request.param}") - - -@pytest.fixture(params=('empty', 'only_removed', 'only_other', 'other_and_removed')) -def test_collections_for_removal(mock_collection_id, request): - """Fixture returning a tuple of the expected collections values before and after removal""" - other_collection_id = mock_collection_id + '2' - if request.param == 'empty': - return [], [] - if request.param == 'only_removed': - return [{'id': mock_collection_id}], [] - if request.param == 'only_other': - return [{'id': other_collection_id}], [{'id': other_collection_id}] - if request.param == 'other_and_removed': - return [{'id': mock_collection_id}, {'id': other_collection_id}], [{'id': other_collection_id}] - - raise NotImplementedError(f"Forgot to implement {request.param}") - - -@pytest.mark.parametrize('params, expected_data', [ - ({}, {}), - ({'name': 'New name.pdf'}, {'name': 'New name.pdf'}) -]) -def test_copy_base_item(test_base_item_and_response, mock_box_session, test_folder, mock_object_id, params, expected_data): - # pylint:disable=redefined-outer-name, protected-access - test_item, mock_item_response = test_base_item_and_response - expected_url = test_item.get_url('copy') - expected_body = { - 'parent': {'id': mock_object_id}, - } - expected_body.update(expected_data) - mock_box_session.post.return_value = mock_item_response - copy_response = test_item.copy(parent_folder=test_folder, **params) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_body)) - assert isinstance(copy_response, test_item.__class__) - - -@pytest.mark.parametrize( - 'params, expected_data', [ - ({}, {}), - ({'name': 'New name.pdf'}, {'name': 'New name.pdf'}) - ] -) -def test_move_base_item(test_base_item_and_response, mock_box_session, test_folder, mock_object_id, params, expected_data): - # pylint:disable=redefined-outer-name, protected-access - test_item, mock_item_response = test_base_item_and_response - expected_url = test_item.get_url() - expected_body = { - 'parent': {'id': mock_object_id}, - } - expected_body.update(expected_data) - mock_box_session.put.return_value = mock_item_response - move_response = test_item.move(test_folder, **params) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(expected_body), params=None, headers=None) - assert isinstance(move_response, test_item.__class__) - - -def test_rename_base_item(test_base_item_and_response, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - test_item, mock_item_response = test_base_item_and_response - expected_url = test_item.get_url() - mock_box_session.put.return_value = mock_item_response - rename_response = test_item.rename('new name') - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps({'name': 'new name'}), params=None, headers=None) - assert isinstance(rename_response, test_item.__class__) - - -def test_add_to_collection(test_base_item_and_response, mock_box_session, mock_collection, test_collections_for_addition): - # pylint:disable=redefined-outer-name, protected-access - test_item, mock_item_response = test_base_item_and_response - current_collections, expected_collections = test_collections_for_addition - expected_url = test_item.get_url() - expected_params = {'fields': 'collections'} - expected_data = { - 'collections': expected_collections - } - mock_response = { - 'type': test_item.object_type, - 'id': test_item.object_id, - 'collections': current_collections, - } - mock_box_session.get.return_value.json.return_value = mock_response - mock_box_session.put.return_value = mock_item_response - - test_item.add_to_collection(mock_collection) - - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=expected_params) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(expected_data), headers=None, params=None) - - -def test_remove_from_collection(test_base_item_and_response, mock_box_session, mock_collection, test_collections_for_removal): - # pylint:disable=redefined-outer-name, protected-access - test_item, mock_item_response = test_base_item_and_response - current_collections, expected_collections = test_collections_for_removal - expected_url = test_item.get_url() - expected_params = {'fields': 'collections'} - expected_data = { - 'collections': expected_collections - } - mock_response = { - 'type': test_item.object_type, - 'id': test_item.object_id, - 'collections': current_collections, - } - mock_box_session.get.return_value.json.return_value = mock_response - mock_box_session.put.return_value = mock_item_response - - test_item.remove_from_collection(mock_collection) - - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=expected_params) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(expected_data), headers=None, params=None) diff --git a/test/unit/object/test_base_object.py b/test/unit/object/test_base_object.py deleted file mode 100644 index ba30afe66..000000000 --- a/test/unit/object/test_base_object.py +++ /dev/null @@ -1,121 +0,0 @@ -from itertools import product -import json -import pytest -from boxsdk.config import API -from boxsdk.object.base_object import BaseObject -from boxsdk.object.file import File -from boxsdk.object.folder import Folder -from boxsdk.object.user import User - - -@pytest.fixture(params=('file', 'folder', 'user')) -def test_object_and_response( - test_file, test_folder, mock_user, - mock_file_response, mock_folder_response, mock_user_response, - request): - test_objects_and_responses = { - 'file': (test_file, mock_file_response), - 'folder': (test_folder, mock_folder_response), - 'user': (mock_user, mock_user_response), - } - return test_objects_and_responses[request.param] - - -@pytest.fixture(params=('same_file', 'same_folder', 'same_user', 'against_none', 'different_ids', 'different_types')) -def objects_for_comparison(test_file, test_folder, mock_user, request, mock_box_session): - cases = { - 'identical_object': (test_file, test_file, True), - 'same_file': (test_file, File(mock_box_session, test_file.object_id), True), - 'same_folder': (test_folder, Folder(mock_box_session, test_folder.object_id), True), - 'same_user': (mock_user, User(mock_box_session, mock_user.object_id), True), - 'against_none': (test_file, None, False), - 'different_ids': (File(mock_box_session, '1'), File(mock_box_session, '2'), False), - 'different_types': (File(mock_box_session, '1'), Folder(mock_box_session, '1'), False), - 'not_api_object': (test_file, {'id': test_file.object_id, 'type': 'file'}, False), - } - return cases[request.param] - - -@pytest.mark.parametrize('params,headers', product(*([[None, {}, {'foo': 'bar'}, {'foo': 'bar', 'num': 4}]] * 2))) -def test_update_info(test_object_and_response, mock_box_session, params, headers): - # pylint:disable=redefined-outer-name, protected-access - test_object, mock_object_response = test_object_and_response - expected_url = test_object.get_url() - mock_box_session.put.return_value = mock_object_response - data = {'foo': 'bar', 'baz': {'foo': 'bar'}, 'num': 4} - update_response = BaseObject.update_info(test_object, data=data, params=params, headers=headers) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), params=params, headers=headers) - assert isinstance(update_response, test_object.__class__) - assert update_response.object_id == test_object.object_id - - -@pytest.mark.parametrize('params, headers, success', [ - (None, None, True), - ({'a': 'b'}, {'10': '20'}, True), - ({'a': 'b'}, None, False), -]) -def test_delete_handles_params_and_headers_correctly(mock_box_session, make_mock_box_request, params, headers, success): - # pylint:disable=redefined-outer-name, protected-access - fake_id = 'a_fake_id' - base_object = BaseObject(mock_box_session, fake_id) - - mock_box_response, _ = make_mock_box_request(response_ok=success) - mock_box_session.delete.return_value = mock_box_response - expected_url = f'{API.BASE_API_URL}/{None}s/{fake_id}' - update_response = base_object.delete(params=params, headers=headers) - mock_box_session.delete.assert_called_once_with( - expected_url, - expect_json_response=False, - params=params or {}, - headers=headers, - ) - assert update_response is success - - -def test_getattr_and_getitem(test_object_and_response, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - test_object, mock_object_response = test_object_and_response - mock_box_session.put.return_value = mock_object_response - update_response = BaseObject.update_info(test_object, data={}) - assert isinstance(update_response, test_object.__class__) - assert update_response.object_id == update_response.id == update_response['id'] # pylint:disable=no-member - - -def test_get_url(test_object_and_response): - # pylint:disable=redefined-outer-name, protected-access - test_object, _ = test_object_and_response - url = test_object.get_url() - assert test_object.object_id in url - assert test_object._item_type in url - - -def test_get_type_url(test_object_and_response): - # pylint:disable=redefined-outer-name, protected-access - test_object, _ = test_object_and_response - url = test_object.get_type_url() - assert url.endswith(f'{test_object._item_type}s') - - -def test_eq(objects_for_comparison): - # pylint:disable=redefined-outer-name - obj1, obj2, expected_value = objects_for_comparison - assert (obj1 == obj2) == expected_value - - -def test_ne(objects_for_comparison): - # pylint:disable=redefined-outer-name - obj1, obj2, are_equal = objects_for_comparison - expected_value = not are_equal - assert (obj1 != obj2) == expected_value - - -def test_hash(objects_for_comparison): - # pylint:disable=redefined-outer-name - obj1, obj2, are_equal = objects_for_comparison - - # Set a value in the dict such that it will be overridden to True iff the objects hash identically (are equal) - test_dict = {} - test_dict[obj1] = False - test_dict[obj2] = True - - assert test_dict[obj1] == are_equal diff --git a/test/unit/object/test_chunked_upload.py b/test/unit/object/test_chunked_upload.py deleted file mode 100644 index 67984d523..000000000 --- a/test/unit/object/test_chunked_upload.py +++ /dev/null @@ -1,427 +0,0 @@ -# pylint: disable-msg=too-many-locals -import time -from random import randint -from unittest.mock import MagicMock, Mock, call -import io -import json -import pytest - -from boxsdk.config import API -from boxsdk.exception import BoxAPIException -from boxsdk.exception import BoxException -from boxsdk.object.file import File -from boxsdk.pagination.limit_offset_based_dict_collection import LimitOffsetBasedDictCollection -from boxsdk.object.upload_session import UploadSession -from boxsdk.util.chunked_uploader import ChunkedUploader - - -@pytest.fixture() -def test_upload_session(mock_box_session): - upload_session_response_object = { - 'total_parts': 4, - 'part_size': 2, - 'session_expires_at': '2017-11-09T21:59:16Z', - 'id': 'D5E3F7ADA11A38A0A66AD0B64AACA658', - 'type': 'upload_session', - 'num_parts_processed': 0 - } - return UploadSession(mock_box_session, 'D5E3F7ADA11A38A0A66AD0B64AACA658', upload_session_response_object) - - -@pytest.fixture() -def mock_upload_session(): - upload_session_mock_object = Mock(UploadSession) - upload_session_mock_object.total_parts = 4 - upload_session_mock_object.part_size = 2 - upload_session_mock_object.id = 'F971964745A5CD0C001BZ4E58196BFD' - upload_session_mock_object.type = 'upload_session' - upload_session_mock_object.num_parts_processed = 0 - return upload_session_mock_object - - -def test_start(test_upload_session, mock_box_session): - expected_put_url = f'{API.UPLOAD_URL}/files/upload_sessions/{test_upload_session.object_id}' - expected_post_url = f'{API.UPLOAD_URL}/files/upload_sessions/{test_upload_session.object_id}/commit' - file_size = 7 - first_sha1 = '2iNhTgJGmg18e9G9q1ycR0sZBNw=' - second_sha1 = 'A0d4GYoEXB7YC+JxzdApt2h09vw=' - third_sha1 = '+CIFFHGVe3u+u4qwiP6b1tFPQmE=' - fourth_sha1 = 'VP0XESCfscB4EJI3QTLGbnniJBs=' - part_one = { - 'part_id': 'CFEB4BA9', - 'offset': 0, - 'size': 2, - 'sha1': first_sha1, - } - part_two = { - 'part_id': '4DBB872D', - 'offset': 2, - 'size': 2, - 'sha1': second_sha1, - } - part_three = { - 'part_id': '6F2D3486', - 'offset': 4, - 'size': 2, - 'sha1': third_sha1, - } - part_four = { - 'part_id': '4DBC872D', - 'offset': 6, - 'size': 1, - 'sha1': fourth_sha1, - } - parts = [part_one, part_two, part_three, part_four] - expected_data = { - 'parts': parts, - } - expected_headers = { - 'Content-Type': 'application/json', - 'Digest': 'SHA=L7XhNBn8iSRoZeejJPR27GJOh0A=', - } - - expected_headers_first_upload = { - 'Content-Type': 'application/octet-stream', - 'Digest': f'SHA={first_sha1}', - 'Content-Range': 'bytes 0-1/7', - } - expected_headers_second_upload = { - 'Content-Type': 'application/octet-stream', - 'Digest': f'SHA={second_sha1}', - 'Content-Range': 'bytes 2-3/7', - } - expected_headers_third_upload = { - 'Content-Type': 'application/octet-stream', - 'Digest': f'SHA={third_sha1}', - 'Content-Range': 'bytes 4-5/7', - } - expected_headers_fourth_upload = { - 'Content-Type': 'application/octet-stream', - 'Digest': f'SHA={fourth_sha1}', - 'Content-Range': 'bytes 6-6/7', - } - - first_response_mock = Mock() - second_response_mock = Mock() - third_response_mock = Mock() - fourth_response_mock = Mock() - - first_response_mock.json.return_value = { - 'part': part_one - } - second_response_mock.json.return_value = { - 'part': part_two - } - third_response_mock.json.return_value = { - 'part': part_three - } - fourth_response_mock.json.return_value = { - 'part': part_four - } - mock_box_session.put.side_effect = [ - first_response_mock, - second_response_mock, - third_response_mock, - fourth_response_mock - ] - mock_box_session.post.return_value.json.return_value = { - 'entries': [ - { - 'type': 'file', - 'id': '12345', - 'description': 'This is a test description', - } - ] - } - flaky_stream = Mock() - flaky_stream.read.side_effect = [b'ab', None, b'c', b'd', b'ef', b'g', b''] - - chunked_uploader = ChunkedUploader(test_upload_session, flaky_stream, file_size) - uploaded_file = chunked_uploader.start() - calls = [ - call(expected_put_url, data=b'ab', headers=expected_headers_first_upload), - call(expected_put_url, data=b'cd', headers=expected_headers_second_upload), - call(expected_put_url, data=b'ef', headers=expected_headers_third_upload), - call(expected_put_url, data=b'g', headers=expected_headers_fourth_upload), - ] - flaky_stream.read.assert_has_calls([call(2), call(2), call(2), call(1), call(2), call(2), call(1)], any_order=False) - mock_box_session.put.assert_has_calls(calls, any_order=True) - mock_box_session.post.assert_called_once_with( - expected_post_url, - data=json.dumps(expected_data), - headers=expected_headers - ) - assert uploaded_file.type == 'file' - assert uploaded_file.id == '12345' - assert uploaded_file.description == 'This is a test description' - assert isinstance(uploaded_file, File) - assert uploaded_file._session == mock_box_session # pylint:disable=protected-access - - -def test_resume_cross_process(test_file, mock_upload_session): - file_size = 7 - part_bytes = b'abcdefg' - stream = io.BytesIO(part_bytes) - part_one = { - 'part_id': 'CFEB4BA9', - 'offset': 0, - 'size': 2, - 'sha1': None, - } - part_two = { - 'part_id': '4DBB872D', - 'offset': 2, - 'size': 2, - 'sha1': None, - } - part_three = { - 'part_id': '6F2D3486', - 'offset': 4, - 'size': 2, - 'sha1': None, - } - part_four = { - 'part_id': '4DBC872D', - 'offset': 6, - 'size': 1, - 'sha1': None, - } - parts = [part_one, part_two, part_three, part_four] - mock_upload_session.commit.return_value = test_file - mock_iterator = MagicMock(LimitOffsetBasedDictCollection) - mock_iterator.__iter__.return_value = [part_one, part_four] - mock_upload_session.get_parts.return_value = mock_iterator - mock_upload_session.upload_part_bytes.side_effect = [part_two, part_three] - chunked_uploader = ChunkedUploader(mock_upload_session, stream, file_size) - uploaded_file = chunked_uploader.resume() - calls = [ - call(offset=2, part_bytes=b'cd', total_size=7), - call(offset=4, part_bytes=b'ef', total_size=7), - ] - mock_upload_session.upload_part_bytes.assert_has_calls(calls, any_order=True) - mock_upload_session.commit.assert_called_once_with( - content_sha1=b'/\xb5\xe14\x19\xfc\x89$he\xe7\xa3$\xf4v\xecbN\x87@', - parts=parts - ) - assert uploaded_file is test_file - - -def test_resume_in_process(test_file, mock_upload_session): - file_size = 7 - part_bytes = b'abcdefg' - stream = io.BytesIO(part_bytes) - first_part = { - 'part_id': 'CFEB4BA9', - 'offset': 0, - 'size': 2, - 'sha1': '2iNhTgJGmg18e9G9q1ycR0sZBNw=', - } - second_part = { - 'part_id': '4DBB872D', - 'offset': 2, - 'size': 2, - 'sha1': 'A0d4GYoEXB7YC+JxzdApt2h09vw=', - } - third_part = { - 'part_id': '6F2D3486', - 'offset': 4, - 'size': 2, - 'sha1': '+CIFFHGVe3u+u4qwiP6b1tFPQmE=', - } - fourth_part = { - 'part_id': '4DBC872D', - 'offset': 6, - 'size': 1, - 'sha1': 'VP0XESCfscB4EJI3QTLGbnniJBs=', - } - parts = [first_part, second_part, third_part, fourth_part] - mock_iterator = MagicMock(LimitOffsetBasedDictCollection) - mock_iterator.__iter__.return_value = [first_part, second_part, fourth_part] - mock_upload_session.get_parts.return_value = mock_iterator - mock_upload_session.commit.return_value = test_file - chunked_uploader = ChunkedUploader(mock_upload_session, stream, file_size) - mock_upload_session.upload_part_bytes.side_effect = [ - first_part, - second_part, - BoxAPIException(502), - fourth_part - ] - uploaded_file = None - calls = [ - call(offset=0, part_bytes=b'ab', total_size=7), - call(offset=2, part_bytes=b'cd', total_size=7), - call(offset=4, part_bytes=b'ef', total_size=7), - call(offset=6, part_bytes=b'g', total_size=7), - call(offset=4, part_bytes=b'ef', total_size=7), - ] - try: - chunked_uploader.start() - except BoxAPIException: - mock_upload_session.upload_part_bytes.side_effect = [third_part] - uploaded_file = chunked_uploader.resume() - mock_upload_session.upload_part_bytes.assert_has_calls(calls, any_order=True) - mock_upload_session.commit.assert_called_once_with( - content_sha1=b'/\xb5\xe14\x19\xfc\x89$he\xe7\xa3$\xf4v\xecbN\x87@', - parts=parts - ) - assert uploaded_file is test_file - - -def test_resume_with_upload_random_duration(test_file, mock_upload_session): - # pylint: disable-msg=unused-argument - def upload_mock_func(part_bytes, offset, total_size): - value = upload_results.pop(0) - time.sleep(randint(0, 1)) - if isinstance(value, Exception): - raise value - return value - - file_size = 7 - part_bytes = b'abcdefg' - stream = io.BytesIO(part_bytes) - first_part = { - 'part_id': 'CFEB4BA9', - 'offset': 0, - 'size': 2, - 'sha1': '2iNhTgJGmg18e9G9q1ycR0sZBNw=', - } - second_part = { - 'part_id': '4DBB872D', - 'offset': 2, - 'size': 2, - 'sha1': 'A0d4GYoEXB7YC+JxzdApt2h09vw=', - } - third_part = { - 'part_id': '6F2D3486', - 'offset': 4, - 'size': 2, - 'sha1': '+CIFFHGVe3u+u4qwiP6b1tFPQmE=', - } - fourth_part = { - 'part_id': '4DBC872D', - 'offset': 6, - 'size': 1, - 'sha1': 'VP0XESCfscB4EJI3QTLGbnniJBs=', - } - parts = [first_part, second_part, third_part, fourth_part] - mock_iterator = MagicMock(LimitOffsetBasedDictCollection) - mock_iterator.__iter__.return_value = [first_part, second_part, fourth_part] - mock_upload_session.get_parts.return_value = mock_iterator - mock_upload_session.commit.return_value = test_file - chunked_uploader = ChunkedUploader(mock_upload_session, stream, file_size) - upload_results = [ - first_part, - second_part, - BoxAPIException(502), - fourth_part, - third_part - ] - mock_upload_session.upload_part_bytes.side_effect = upload_mock_func - uploaded_file = None - try: - chunked_uploader.start() - except BoxAPIException: - uploaded_file = chunked_uploader.resume() - calls = [ - call(offset=0, part_bytes=b'ab', total_size=7), - call(offset=2, part_bytes=b'cd', total_size=7), - call(offset=4, part_bytes=b'ef', total_size=7), - call(offset=6, part_bytes=b'g', total_size=7), - call(offset=4, part_bytes=b'ef', total_size=7), - ] - mock_upload_session.upload_part_bytes.assert_has_calls(calls, any_order=True) - mock_upload_session.commit.assert_called_once_with( - content_sha1=b'/\xb5\xe14\x19\xfc\x89$he\xe7\xa3$\xf4v\xecbN\x87@', - parts=parts - ) - assert uploaded_file is test_file - - -def test_abort_with_start(mock_upload_session): - file_size = 7 - part_bytes = b'abcdefg' - stream = io.BytesIO(part_bytes) - chunked_uploader = ChunkedUploader(mock_upload_session, stream, file_size) - mock_upload_session.abort.return_value = True - is_aborted = chunked_uploader.abort() - try: - chunked_uploader.start() - except BoxException: - pass - mock_upload_session.abort.assert_called_once_with() - assert is_aborted is True - - -def test_abort_with_resume(mock_upload_session): - file_size = 7 - part_bytes = b'abcdefg' - stream = io.BytesIO(part_bytes) - chunked_uploader = ChunkedUploader(mock_upload_session, stream, file_size) - mock_upload_session.abort.return_value = True - is_aborted = chunked_uploader.abort() - try: - chunked_uploader.resume() - except BoxException: - pass - mock_upload_session.abort.assert_called_once_with() - assert is_aborted is True - - -def test_resume_after_commit_failed(test_file, mock_upload_session): - # given - file_size = 7 - part_bytes = b'abcdefg' - stream = io.BytesIO(part_bytes) - first_part = { - 'part_id': 'CFEB4BA9', - 'offset': 0, - 'size': 2, - 'sha1': '2iNhTgJGmg18e9G9q1ycR0sZBNw=', - } - second_part = { - 'part_id': '4DBB872D', - 'offset': 2, - 'size': 2, - 'sha1': 'A0d4GYoEXB7YC+JxzdApt2h09vw=', - } - third_part = { - 'part_id': '6F2D3486', - 'offset': 4, - 'size': 2, - 'sha1': '+CIFFHGVe3u+u4qwiP6b1tFPQmE=', - } - fourth_part = { - 'part_id': '4DBC872D', - 'offset': 6, - 'size': 1, - 'sha1': 'VP0XESCfscB4EJI3QTLGbnniJBs=', - } - parts = [first_part, second_part, third_part, fourth_part] - mock_iterator = MagicMock(LimitOffsetBasedDictCollection) - mock_iterator.__iter__.return_value = [first_part, second_part, third_part, fourth_part] - mock_upload_session.get_parts.return_value = mock_iterator - mock_upload_session.commit.side_effect = [None, test_file] - chunked_uploader = ChunkedUploader(mock_upload_session, stream, file_size) - mock_upload_session.upload_part_bytes.side_effect = [ - first_part, - second_part, - third_part, - fourth_part - ] - - # when - uploaded_file = chunked_uploader.start() - - # then - assert uploaded_file is None - - # when - uploaded_file = chunked_uploader.resume() - - # then - expected_call = call( - content_sha1=b'/\xb5\xe14\x19\xfc\x89$he\xe7\xa3$\xf4v\xecbN\x87@', - parts=parts - ) - mock_upload_session.commit.assert_has_calls([expected_call, expected_call]) - assert uploaded_file is test_file diff --git a/test/unit/object/test_collaboration.py b/test/unit/object/test_collaboration.py deleted file mode 100644 index c461a86e9..000000000 --- a/test/unit/object/test_collaboration.py +++ /dev/null @@ -1,124 +0,0 @@ -import json -import pytest - -from boxsdk.config import API -from boxsdk.object.collaboration import CollaborationRole, CollaborationStatus - - -@pytest.mark.parametrize('data', [ - {}, - {'role': CollaborationRole.EDITOR}, - {'role': CollaborationRole.VIEWER}, - {'status': CollaborationStatus.ACCEPTED}, - {'status': CollaborationStatus.REJECTED}, - {'role': CollaborationRole.EDITOR, 'status': CollaborationStatus.ACCEPTED}, -]) -def test_update_info_returns_the_correct_response( - test_collaboration, - mock_box_session, - mock_collab_response, - data): - # pylint:disable=protected-access - expected_url = test_collaboration.get_url() - mock_box_session.put.return_value = mock_collab_response - update_response = test_collaboration.update_info(**data) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(data), - headers=None, - params=None, - ) - assert isinstance(update_response, test_collaboration.__class__) - assert update_response.object_id == test_collaboration.object_id - - -@pytest.mark.parametrize('data', [ - {}, - {'role': CollaborationRole.EDITOR, 'status': CollaborationStatus.REJECTED}, - {'role': CollaborationRole.EDITOR, 'status': CollaborationStatus.ACCEPTED}, - {'role': CollaborationRole.EDITOR, 'expires_at': '2025-08-29T23:59:00-07:00'}, - {'role': CollaborationRole.EDITOR, 'can_view_path': True}, -]) -def test_update_info_returns_the_correct_response_with_data_param( - test_collaboration, - mock_box_session, - mock_collab_response, - data): - # pylint:disable=protected-access - expected_url = test_collaboration.get_url() - mock_box_session.put.return_value = mock_collab_response - update_response = test_collaboration.update_info(data=data) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(data), - headers=None, - params=None, - ) - assert isinstance(update_response, test_collaboration.__class__) - assert update_response.object_id == test_collaboration.object_id - - -def test_update_info_returns_204( - test_collaboration, - mock_box_session): - # pylint:disable=protected-access - data = {'role': CollaborationRole.OWNER, 'status': CollaborationStatus.ACCEPTED} - expected_url = test_collaboration.get_url() - mock_box_session.put.return_value.ok = True - is_success = test_collaboration.update_info(data=data) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(data), - expect_json_response=False, - headers=None, - params=None, - ) - assert is_success is True - - -def test_accept_pending_collaboration(test_collaboration, mock_box_session): - # pylint:disable=protected-access - new_status = 'accepted' - expected_url = f'{API.BASE_API_URL}/collaborations/{test_collaboration.object_id}' - mock_collab_response = { - 'type': 'collaboration', - 'id': '1234', - 'status': 'accepted', - } - mock_box_session.put.return_value.json.return_value = mock_collab_response - response = test_collaboration.accept() - update_body = { - 'status': 'accepted' - } - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(update_body), - headers=None, - params=None, - ) - assert isinstance(response, test_collaboration.__class__) - assert response.status == new_status - - -def test_reject_pending_collaboration(test_collaboration, mock_box_session): - # pylint:disable=protected-access - new_status = 'rejected' - expected_url = f'{API.BASE_API_URL}/collaborations/{test_collaboration.object_id}' - mock_collab_response = { - 'type': 'collaboration', - 'id': '1234', - 'status': 'rejected', - } - mock_box_session.put.return_value.json.return_value = mock_collab_response - response = test_collaboration.reject() - update_body = { - 'status': 'rejected' - } - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(update_body), - headers=None, - params=None, - ) - assert isinstance(response, test_collaboration.__class__) - assert response.status == new_status diff --git a/test/unit/object/test_collaboration_allowlist.py b/test/unit/object/test_collaboration_allowlist.py deleted file mode 100644 index 2cebeee70..000000000 --- a/test/unit/object/test_collaboration_allowlist.py +++ /dev/null @@ -1,97 +0,0 @@ -import json -import pytest - -from boxsdk.config import API -from boxsdk.object.collaboration_allowlist_entry import CollaborationAllowlistEntry -from boxsdk.object.collaboration_allowlist_exempt_target import CollaborationAllowlistExemptTarget - - -def test_get_entries(mock_box_session, test_collaboration_allowlist): - expected_url = f'{API.BASE_API_URL}/collaboration_whitelist_entries' - mock_entry = { - 'type': 'collaboration_whitelist_entry', - 'id': '12345', - 'domain': 'box.com', - 'direction': 'both' - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [mock_entry] - } - entries = test_collaboration_allowlist.get_entries() - entry = entries.next() - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(entry, CollaborationAllowlistEntry) - assert entry.id == mock_entry['id'] - assert entry.direction == mock_entry['direction'] - assert entry.domain == mock_entry['domain'] - - -def test_get_exemptions(mock_box_session, test_collaboration_allowlist): - expected_url = f'{API.BASE_API_URL}/collaboration_whitelist_exempt_targets' - mock_exemption = { - 'type': 'collaboration_whitelist_exempt_target', - 'id': '12345', - 'user': { - 'type': 'user', - 'id': '33333' - } - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [mock_exemption] - } - exemptions = test_collaboration_allowlist.get_exemptions() - exemption = exemptions.next() - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(exemption, CollaborationAllowlistExemptTarget) - assert exemption.id == mock_exemption['id'] - assert exemption.user['id'] == mock_exemption['user']['id'] - - -@pytest.mark.parametrize( - 'direction', - ['inbound', 'outbound', 'both'] -) -def test_add_domain(mock_box_session, test_collaboration_allowlist, direction): - expected_url = f'{API.BASE_API_URL}/collaboration_whitelist_entries' - domain = 'example.com' - mock_entry = { - 'type': 'collaboration_whitelist_entry', - 'id': '12345', - 'domain': domain, - 'direction': direction - } - expected_data = { - 'domain': domain, - 'direction': direction - } - mock_box_session.post.return_value.json.return_value = mock_entry - entry = test_collaboration_allowlist.add_domain(domain, direction) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - assert isinstance(entry, CollaborationAllowlistEntry) - assert entry.id == mock_entry['id'] - assert entry.domain == domain - assert entry.direction == direction - - -def test_add_exemption(mock_box_session, test_collaboration_allowlist, mock_user): - expected_url = f'{API.BASE_API_URL}/collaboration_whitelist_exempt_targets' - expected_data = { - 'user': { - 'id': mock_user.object_id - } - } - mock_exemption = { - 'type': 'collaboration_whitelist_exempt_target', - 'id': '12345', - 'user': { - 'type': 'user', - 'id': mock_user.object_id - } - } - mock_box_session.post.return_value.json.return_value = mock_exemption - exemption = test_collaboration_allowlist.add_exemption(mock_user) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - assert exemption.id == mock_exemption['id'] - assert exemption.user['id'] == mock_exemption['user']['id'] diff --git a/test/unit/object/test_collaboration_allowlist_entry.py b/test/unit/object/test_collaboration_allowlist_entry.py deleted file mode 100644 index e3c24267c..000000000 --- a/test/unit/object/test_collaboration_allowlist_entry.py +++ /dev/null @@ -1,25 +0,0 @@ -from boxsdk.config import API - - -def test_get(mock_box_session, test_collaboration_allowlist_entry): - entry_id = test_collaboration_allowlist_entry.object_id - expected_url = f'{API.BASE_API_URL}/collaboration_whitelist_entries/{entry_id}' - mock_entry = { - 'type': 'collaboration_whitelist_entry', - 'id': '98765', - 'domain': 'example.com', - 'direction': 'inbound' - } - mock_box_session.get.return_value.json.return_value = mock_entry - entry = test_collaboration_allowlist_entry.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert entry.id == mock_entry['id'] - assert entry.domain == mock_entry['domain'] - assert entry.direction == mock_entry['direction'] - - -def test_delete(mock_box_session, test_collaboration_allowlist_entry): - entry_id = test_collaboration_allowlist_entry.object_id - expected_url = f'{API.BASE_API_URL}/collaboration_whitelist_entries/{entry_id}' - test_collaboration_allowlist_entry.delete() - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=None, params={}) diff --git a/test/unit/object/test_collaboration_allowlist_exempt_target.py b/test/unit/object/test_collaboration_allowlist_exempt_target.py deleted file mode 100644 index aa587f008..000000000 --- a/test/unit/object/test_collaboration_allowlist_exempt_target.py +++ /dev/null @@ -1,25 +0,0 @@ -from boxsdk.config import API - - -def test_get(mock_box_session, test_collaboration_allowlist_exemption): - exemption_id = test_collaboration_allowlist_exemption.object_id - expected_url = f'{API.BASE_API_URL}/collaboration_whitelist_exempt_targets/{exemption_id}' - mock_exemption = { - 'type': 'collaboration_whitelist_entry', - 'id': '98765', - 'domain': 'example.com', - 'direction': 'inbound' - } - mock_box_session.get.return_value.json.return_value = mock_exemption - exemption = test_collaboration_allowlist_exemption.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert exemption.id == mock_exemption['id'] - assert exemption.domain == mock_exemption['domain'] - assert exemption.direction == mock_exemption['direction'] - - -def test_delete(mock_box_session, test_collaboration_allowlist_exemption): - exemption_id = test_collaboration_allowlist_exemption.object_id - expected_url = mock_box_session.get_url('collaboration_whitelist_exempt_targets', exemption_id) - test_collaboration_allowlist_exemption.delete() - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=None, params={}) diff --git a/test/unit/object/test_collection.py b/test/unit/object/test_collection.py deleted file mode 100644 index 3c13fbe13..000000000 --- a/test/unit/object/test_collection.py +++ /dev/null @@ -1,48 +0,0 @@ -def test_get(mock_collection, mock_box_session): - expected_url = mock_collection.get_url() - collection_name = 'Favorites' - - mock_box_session.get.return_value.json.return_value = { - 'type': 'collection', - 'id': mock_collection.object_id, - 'name': collection_name, - 'collection_type': 'favorites' - } - - fetched_collection = mock_collection.get() - - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert fetched_collection.name == collection_name - - -def test_get_items(mock_collection, mock_box_session): - expected_url = mock_collection.get_url('items') - item_id1 = '12345' - item_id2 = '56789' - - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'offset': 0, - 'total_count': 2, - 'entries': [ - { - 'type': 'folder', - 'id': item_id1 - }, - { - 'type': 'file', - 'id': item_id2 - } - ] - } - - items = mock_collection.get_items() - - item1 = items.next() - mock_box_session.get.assert_called_once_with(expected_url, params={'offset': 0}) - assert item1.type == 'folder' - assert item1.object_id == item_id1 - - item2 = items.next() - assert item2.type == 'file' - assert item2.object_id == item_id2 diff --git a/test/unit/object/test_comment.py b/test/unit/object/test_comment.py deleted file mode 100644 index a078dd6a8..000000000 --- a/test/unit/object/test_comment.py +++ /dev/null @@ -1,64 +0,0 @@ -import json - -from boxsdk.object.comment import Comment - - -# pylint:disable=protected-access -# pylint:disable=redefined-outer-name - -def test_reply(test_comment, mock_box_session, comment_params): - expected_url = mock_box_session.get_url('comments') - message_type, message = comment_params - expected_data = { - message_type: message, - 'item': { - 'type': 'comment', - 'id': test_comment.object_id - } - } - mock_box_session.post.return_value.json.return_value = { - 'type': 'comment', - 'id': '12345', - message_type: message - } - reply_comment = test_comment.reply(message) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - assert isinstance(reply_comment, Comment) - assert reply_comment.object_id == '12345' - - -def test_edit(test_comment, mock_box_session, comment_params): - expected_url = mock_box_session.get_url('comments', test_comment.object_id) - message_type, message = comment_params - expected_data = { - message_type: message, - } - mock_box_session.put.return_value.json.return_value = { - 'type': 'comment', - 'id': '12345', - message_type: message - } - updated_comment = test_comment.edit(message) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(expected_data), headers=None, params=None) - assert isinstance(updated_comment, Comment) - assert updated_comment[message_type] == message - - -def test_get(mock_box_session): - comment_id = '1235' - expected_url = mock_box_session.get_url('comments', comment_id) - mock_box_session.get.return_value.json.return_value = { - 'type': 'comment', - 'id': comment_id, - 'message': 'Hi!' - } - comment = Comment(mock_box_session, comment_id).get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert comment.object_id == comment_id - assert comment.message == 'Hi!' # pylint:disable=no-member - - -def test_delete(test_comment, mock_box_session): - expected_url = test_comment.get_url() - test_comment.delete() - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=None, params={}) diff --git a/test/unit/object/test_device_pin.py b/test/unit/object/test_device_pin.py deleted file mode 100644 index 001fd6876..000000000 --- a/test/unit/object/test_device_pin.py +++ /dev/null @@ -1,43 +0,0 @@ -from unittest.mock import Mock -import pytest - -from boxsdk.config import API -from boxsdk.object.device_pinner import DevicePinner -from boxsdk.network.default_network import DefaultNetworkResponse - - -@pytest.fixture(scope='module') -def delete_device_pin_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.ok = True - return mock_network_response - - -def test_get(test_device_pin, mock_box_session): - created_at = '2016-05-18T17:38:03-07:00' - expected_url = f'{API.BASE_API_URL}/device_pinners/{test_device_pin.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': 'device_pinner', - 'id': test_device_pin.object_id, - 'created_at': created_at - } - device_pin = test_device_pin.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(device_pin, DevicePinner) - assert device_pin.created_at == created_at - - -def test_delete_device_pin_return_the_correct_response( - test_device_pin, - mock_box_session, - delete_device_pin_response, -): - # pylint:disable=redefined-outer-name - mock_box_session.delete.return_value = delete_device_pin_response - response = test_device_pin.delete() - # pylint:disable=protected-access - expected_url = test_device_pin.get_url() - # pylint:enable = protected-access - mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None) - assert response is True diff --git a/test/unit/object/test_enterprise.py b/test/unit/object/test_enterprise.py deleted file mode 100644 index 5f063c9af..000000000 --- a/test/unit/object/test_enterprise.py +++ /dev/null @@ -1,40 +0,0 @@ -import json -import pytest - -from boxsdk.config import API -from boxsdk.object.enterprise import Enterprise -from boxsdk.object.invite import Invite - - -@pytest.fixture() -def test_enterprise(mock_box_session): - return Enterprise( - session=mock_box_session, - object_id='test_enterprise_id', - ) - - -def test_invite_user(test_enterprise, mock_box_session): - # pylint:disable=redefined-outer-name - expected_url = f'{API.BASE_API_URL}/invites' - test_user_login = 'test@user.com' - expected_body = json.dumps({ - 'enterprise': { - 'id': test_enterprise.object_id, - }, - 'actionable_by': { - 'login': test_user_login, - }, - }) - invite_json = { - 'type': 'invite', - 'id': '11111', - 'status': 'pending', - } - mock_box_session.post.return_value.json.return_value = invite_json - new_invite = test_enterprise.invite_user(test_user_login) - mock_box_session.post.assert_called_once_with(expected_url, data=expected_body) - assert isinstance(new_invite, Invite) - assert new_invite.object_id == invite_json['id'] - assert new_invite._session == mock_box_session # pylint: disable=protected-access - assert new_invite.status == invite_json['status'] diff --git a/test/unit/object/test_event.py b/test/unit/object/test_event.py deleted file mode 100644 index a006c7433..000000000 --- a/test/unit/object/test_event.py +++ /dev/null @@ -1,16 +0,0 @@ -from boxsdk.object.event import Event - - -def test_init_event(): - event = Event( - { - "type": "event", - "event_id": "f82c3ba03e41f7e8a7608363cc6c0390183c3f83", - "source": - { - "type": "folder", - "id": "11446498", - }, - }) - assert event['type'] == 'event' - assert event['event_id'] == 'f82c3ba03e41f7e8a7608363cc6c0390183c3f83' diff --git a/test/unit/object/test_events.py b/test/unit/object/test_events.py deleted file mode 100644 index 4dde702c0..000000000 --- a/test/unit/object/test_events.py +++ /dev/null @@ -1,351 +0,0 @@ -from collections import OrderedDict -from datetime import datetime -from itertools import chain -import json -from typing import Optional, Union -from unittest.mock import Mock -from urllib.parse import urlunsplit, urlencode - -import pytest -import pytz -from requests.exceptions import Timeout - -from boxsdk.network.default_network import DefaultNetworkResponse -from boxsdk.object.events import Events, EventsStreamType, UserEventsStreamType -from boxsdk.object.event import Event -from boxsdk.object.folder import Folder -from boxsdk.session.box_response import BoxResponse - - -@pytest.fixture() -def test_events(mock_box_session): - return Events(mock_box_session) - - -@pytest.fixture() -def final_stream_position(): - return 1348790499820 - - -@pytest.fixture() -def initial_stream_position(): - return 1348790499819 - - -# pylint:disable=no-member -# pylint isn't currently smart enough to recognize the class member that was -# added by the metaclass, when the metaclass was added by @add_metaclass() / -# with_metaclass(). -STREAM_TYPES_AS_ENUM_INSTANCES = list(EventsStreamType.__members__.values()) -# pylint:enable=no-member -STREAM_TYPES_AS_STRINGS = list(map(str, STREAM_TYPES_AS_ENUM_INSTANCES)) - - -def test_events_stream_type_extended_enum_class_has_expected_members(): - assert len(STREAM_TYPES_AS_ENUM_INSTANCES) >= 4 - assert len(STREAM_TYPES_AS_STRINGS) >= 4 - assert 'all' in STREAM_TYPES_AS_STRINGS - assert 'changes' in STREAM_TYPES_AS_STRINGS - assert 'sync' in STREAM_TYPES_AS_STRINGS - assert 'admin_logs' in STREAM_TYPES_AS_STRINGS - - -@pytest.fixture( - scope='session', - params=list(chain( - [None], # Default behavior of not passing any stream_type - STREAM_TYPES_AS_ENUM_INSTANCES, # Passing an enum instance - STREAM_TYPES_AS_STRINGS, # Passing an enum value - - # For forwards compatibility, make sure that it works to pass a string - # value that is not a member of the enum. - ['future_stream_type'], - )), -) -def stream_type_param(request) -> Optional[Union[str, EventsStreamType]]: - """The value to pass as an Event method's stream_type parameter. - - :return: - The parameter value, or `None` if no value should be passed. - """ - return request.param - - -@pytest.fixture() -def expected_stream_type(stream_type_param) -> str: - """The stream type we expect to use. - """ - if stream_type_param is None: - return UserEventsStreamType.ALL - return stream_type_param - - -@pytest.fixture() -def stream_type_kwargs(stream_type_param) -> dict: - """The kwargs for stream_type to pass when invoking a method on `Events`. - """ - if stream_type_param: - return {'stream_type': stream_type_param} - return {} - - -@pytest.fixture() -def expected_stream_type_params(expected_stream_type) -> OrderedDict: - """The stream_type-related params that we expect to pass to request methods. - """ - return OrderedDict(stream_type=expected_stream_type) - - -@pytest.fixture() -def empty_events_response(final_stream_position): - # pylint:disable=redefined-outer-name - mock_box_response = Mock(BoxResponse) - mock_network_response = Mock(DefaultNetworkResponse) - mock_box_response.network_response = mock_network_response - mock_box_response.json.return_value = mock_json = {'next_stream_position': final_stream_position, 'entries': []} - mock_box_response.content = json.dumps(mock_json).encode() - mock_box_response.status_code = 200 - mock_box_response.ok = True - return mock_box_response - - -@pytest.fixture() -def long_poll_url(test_url, expected_stream_type_params): - return urlunsplit(('', '', test_url, urlencode(expected_stream_type_params), '')) - - -@pytest.fixture() -def retry_timeout(): - return 610 - - -@pytest.fixture() -def options_response_entry(long_poll_url, retry_timeout): - return {'url': long_poll_url, 'retry_timeout': retry_timeout} - - -@pytest.fixture() -def options_response(options_response_entry, make_mock_box_request): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={'entries': [options_response_entry]}, - ) - return mock_box_response - - -@pytest.fixture() -def new_change_long_poll_response(make_mock_box_request): - mock_box_response, _ = make_mock_box_request( - response={'message': 'new_change'}, - ) - return mock_box_response - - -@pytest.fixture() -def reconnect_long_poll_response(make_mock_box_request): - mock_box_response, _ = make_mock_box_request( - response={'message': 'reconnect'}, - ) - return mock_box_response - - -@pytest.fixture() -def max_retries_long_poll_response(make_mock_box_request): - mock_box_response, _ = make_mock_box_request( - response={'message': 'max_retries'}, - ) - return mock_box_response - - -@pytest.fixture() -def mock_event_json(): - return { - "type": "event", - "event_id": "f82c3ba03e41f7e8a7608363cc6c0390183c3f83", - "source": { - "type": "folder", - "id": "11446498", - }, - } - - -@pytest.fixture() -def events_response(initial_stream_position, mock_event_json, make_mock_box_request): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={"next_stream_position": initial_stream_position, "entries": [mock_event_json]}, - ) - return mock_box_response - - -def test_get_events( - test_events, - mock_box_session, - events_response, - stream_type_kwargs, - expected_stream_type_params, -): - # pylint:disable=redefined-outer-name - expected_url = test_events.get_url() - mock_box_session.get.return_value = events_response - events = test_events.get_events(**stream_type_kwargs) - assert 'next_stream_position' in events - mock_box_session.get.assert_any_call( - expected_url, - params={'limit': 100, 'stream_position': 0, **expected_stream_type_params}, - ) - for event, json in zip(events['entries'], events_response.json.return_value['entries']): - assert isinstance(event, Event) - assert event.event_id == json['event_id'] - - -@pytest.mark.parametrize("limit", [100, None]) -@pytest.mark.parametrize('created_after', [ - '2019-07-01T22:02:24+14:00', - datetime(2019, 7, 1, 22, 2, 24, tzinfo=pytz.timezone('US/Alaska')) -]) -@pytest.mark.parametrize('created_before', [ - '2019-08-07T22:02:24+14:00', - datetime(2019, 8, 7, 22, 2, 24, tzinfo=pytz.timezone('US/Alaska')) -]) -def test_get_admin_events( - test_events, - mock_box_session, - events_response, - limit, - created_after, - created_before -): - # pylint:disable=redefined-outer-name - expected_url = test_events.get_url() - mock_box_session.get.return_value = events_response - events = test_events.get_admin_events( - limit=limit, - stream_position=0, - created_after=created_after, - created_before=created_before, - event_types=['ITEM_CREATE', "LOGIN"], - ) - expected_params = { - 'stream_position': 0, - 'created_after': '2019-07-01T22:02:24+14:00', - 'created_before': '2019-08-07T22:02:24+14:00', - 'event_type': 'ITEM_CREATE,LOGIN', - 'stream_type': 'admin_logs' - } - if limit: - expected_params['limit'] = limit - - mock_box_session.get.assert_called_with( - expected_url, - params=expected_params - ) - for event, json in zip(events['entries'], events_response.json.return_value['entries']): - assert isinstance(event, Event) - assert event.event_id == json['event_id'] - - -@pytest.mark.parametrize("limit", [100, None]) -def test_get_admin_events_streaming( - test_events, - mock_box_session, - events_response, - limit, -): - # pylint:disable=redefined-outer-name - expected_url = test_events.get_url() - mock_box_session.get.return_value = events_response - events = test_events.get_admin_events_streaming( - limit=limit, - stream_position=100, - event_types=['ITEM_CREATE', "LOGIN"], - ) - expected_params = { - 'stream_type': 'admin_logs_streaming', - 'stream_position': 100, - 'event_type': 'ITEM_CREATE,LOGIN' - } - if limit: - expected_params['limit'] = limit - - mock_box_session.get.assert_called_with( - expected_url, - params=expected_params - ) - for event, json in zip(events['entries'], events_response.json.return_value['entries']): - assert isinstance(event, Event) - assert event.event_id == json['event_id'] - - -def test_get_long_poll_options( - mock_box_session, - test_events, - stream_type_kwargs, - expected_stream_type_params, - options_response, - options_response_entry, -): - expected_url = test_events.get_url() - mock_box_session.options.return_value = options_response - long_poll_options = test_events.get_long_poll_options(**stream_type_kwargs) - mock_box_session.options.assert_called_with(expected_url, params=expected_stream_type_params) - assert long_poll_options == options_response_entry - - -def test_generate_events_with_long_polling( - test_events, - mock_box_session, - events_response, - empty_events_response, - initial_stream_position, - long_poll_url, - retry_timeout, - options_response, - new_change_long_poll_response, - reconnect_long_poll_response, - max_retries_long_poll_response, - mock_event_json, - stream_type_kwargs, - expected_stream_type, - expected_stream_type_params, -): - # pylint:disable=redefined-outer-name - expected_url = test_events.get_url() - mock_box_session.options.return_value = options_response - mock_box_session.get.side_effect = [ - events_response, # initial call to get now stream position - Timeout, - reconnect_long_poll_response, - max_retries_long_poll_response, - new_change_long_poll_response, - events_response, - new_change_long_poll_response, - empty_events_response, - EscapeGenerator("A fake exception for the session to throw so that the generator won't block forever"), - ] - events = test_events.generate_events_with_long_polling(**stream_type_kwargs) - - event = next(events) - assert isinstance(event, Event) - assert event.event_id == mock_event_json['event_id'] - assert isinstance(event.source, Folder) - assert event.source.id == mock_event_json['source']['id'] - with pytest.raises(EscapeGenerator): - next(events) - events.close() - mock_box_session.options.assert_called_with(expected_url, params=expected_stream_type_params) - mock_box_session.get.assert_any_call(expected_url, params={'stream_position': 'now', 'limit': 0, 'stream_type': expected_stream_type}) - assert '/events' in expected_url - mock_box_session.get.assert_any_call( - expected_url, - params={'limit': 100, 'stream_position': initial_stream_position, **expected_stream_type_params}, - ) - mock_box_session.get.assert_any_call( - long_poll_url, - timeout=retry_timeout, - params={'stream_position': initial_stream_position}, - ) - - -class EscapeGenerator(RuntimeError): - pass diff --git a/test/unit/object/test_file.py b/test/unit/object/test_file.py deleted file mode 100644 index 272ad0ade..000000000 --- a/test/unit/object/test_file.py +++ /dev/null @@ -1,1081 +0,0 @@ -import json -from datetime import datetime -from io import BytesIO -from unittest.mock import mock_open, patch, Mock - -import pytest -import pytz -from pytest_lazyfixture import lazy_fixture - -from boxsdk.config import API -from boxsdk.exception import BoxAPIException -from boxsdk.object.comment import Comment -from boxsdk.object.file import File -from boxsdk.object.file_version import FileVersion -from boxsdk.object.task import Task -from boxsdk.object.upload_session import UploadSession -from boxsdk.util.chunked_uploader import ChunkedUploader -from boxsdk.util.datetime_formatter import normalize_date_to_rfc3339_format -from boxsdk.util.default_arg_value import SDK_VALUE_NOT_SET - - -# pylint:disable=protected-access -# pylint:disable=too-many-lines -# pylint:disable=redefined-outer-name - -@pytest.fixture() -def mock_accelerator_upload_url_for_update(): - return 'https://upload.box.com/api/2.0/files/fake_file_id/content?upload_session_id=123' - - -@pytest.fixture(scope='function') -def mock_accelerator_response_for_update(make_mock_box_request, mock_accelerator_upload_url_for_update): - mock_response, _ = make_mock_box_request( - response={ - 'upload_url': mock_accelerator_upload_url_for_update, - 'download_url': None, - } - ) - return mock_response - - -def test_delete_file(test_file, mock_box_session, etag, if_match_header): - test_file.delete(etag=etag) - expected_url = test_file.get_url() - mock_box_session.delete.assert_called_once_with( - expected_url, - expect_json_response=False, - params={}, - headers=if_match_header, - ) - - -@pytest.mark.parametrize('use_upload_session_urls', [True, False]) -def test_create_upload_session(test_file, mock_box_session, use_upload_session_urls): - expected_url = f'{API.UPLOAD_URL}/files/{test_file.object_id}/upload_sessions' - file_size = 197520 - part_size = 12345 - total_parts = 16 - num_parts_processed = 0 - upload_session_type = 'upload_session' - upload_session_id = 'F971964745A5CD0C001BBE4E58196BFD' - file_name = 'test_file.pdf' - expected_data = { - 'file_id': test_file.object_id, - 'file_size': file_size, - 'file_name': file_name - } - mock_box_session.post.return_value.json.return_value = { - 'id': upload_session_id, - 'type': upload_session_type, - 'num_parts_processed': num_parts_processed, - 'total_parts': total_parts, - 'part_size': part_size, - } - upload_session = test_file.create_upload_session( - file_size, file_name, use_upload_session_urls=use_upload_session_urls - ) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - assert isinstance(upload_session, UploadSession) - assert upload_session._session == mock_box_session - assert upload_session.part_size == part_size - assert upload_session.total_parts == total_parts - assert upload_session.num_parts_processed == num_parts_processed - assert upload_session.type == upload_session_type - assert upload_session.id == upload_session_id - assert upload_session._use_upload_session_urls == use_upload_session_urls - - -@pytest.mark.parametrize('use_upload_session_urls', [True, False]) -def test_get_chunked_uploader( - mock_box_session, mock_content_response, mock_file_path, test_file, use_upload_session_urls -): - expected_url = f'{API.UPLOAD_URL}/files/{test_file.object_id}/upload_sessions' - mock_file_stream = BytesIO(mock_content_response.content) - file_size = 197520 - part_size = 12345 - total_parts = 16 - num_parts_processed = 0 - upload_session_type = 'upload_session' - upload_session_id = 'F971964745A5CD0C001BBE4E58196BFD' - expected_data = { - 'file_id': test_file.object_id, - 'file_size': file_size, - } - mock_box_session.post.return_value.json.return_value = { - 'id': upload_session_id, - 'type': upload_session_type, - 'num_parts_processed': num_parts_processed, - 'total_parts': total_parts, - 'part_size': part_size, - } - with patch('os.stat') as stat: - stat.return_value.st_size = file_size - with patch('boxsdk.object.file.open', return_value=mock_file_stream): - chunked_uploader = test_file.get_chunked_uploader( - mock_file_path, use_upload_session_urls=use_upload_session_urls - ) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - upload_session = chunked_uploader._upload_session - assert upload_session.part_size == part_size - assert upload_session.total_parts == total_parts - assert upload_session.num_parts_processed == num_parts_processed - assert upload_session.type == upload_session_type - assert upload_session.id == upload_session_id - assert upload_session._use_upload_session_urls == use_upload_session_urls - assert isinstance(chunked_uploader, ChunkedUploader) - - -@pytest.mark.parametrize( - 'due_at', - [ - '2014-04-03T11:09:43+14:00', - datetime(2014, 4, 3, 11, 9, 43, tzinfo=pytz.timezone('US/Alaska')) - ] -) -def test_create_task(test_file, test_task, mock_box_session, due_at): - # pylint:disable=redefined-outer-name - expected_url = f"{API.BASE_API_URL}/tasks" - action = 'review' - message = 'Test Message' - mock_box_session.post.return_value.json.return_value = { - 'type': test_task.object_type, - 'id': test_task.object_id, - 'due_at': '2014-04-03T11:09:43+14:00', - 'action': action, - 'message': message, - } - - new_task = test_file.create_task(message=message, due_at=due_at) - - expected_body = { - 'item': { - 'type': 'file', - 'id': '42', - }, - 'action': action, - 'message': message, - 'due_at': '2014-04-03T11:09:43+14:00' - } - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_body)) - assert isinstance(new_task, Task) - assert new_task.object_type == test_task.object_type - assert new_task.object_id == test_task.object_id - assert new_task.action == action - assert new_task.message == message - assert new_task.due_at == '2014-04-03T11:09:43+14:00' - - -def test_create_task_with_review(test_file, test_task, mock_box_session): - # pylint:disable=redefined-outer-name - expected_url = f"{API.BASE_API_URL}/tasks" - due_at = '2020-09-18T12:09:43+00:00' - action = 'complete' - message = 'Test Message' - completion_rule = 'any_assignee' - expected_body = { - 'item': { - 'type': 'file', - 'id': '42', - }, - 'action': action, - 'message': message, - 'due_at': due_at, - 'completion_rule': completion_rule, - } - mock_box_session.post.return_value.json.return_value = { - 'type': test_task.object_type, - 'id': test_task.object_id, - 'due_at': due_at, - 'action': action, - 'message': message, - 'completion_rule': completion_rule, - } - value = json.dumps(expected_body) - new_task = test_file.create_task( - message=message, - due_at=due_at, - action=action, - completion_rule=completion_rule, - ) - mock_box_session.post.assert_called_once_with(expected_url, data=value) - assert isinstance(new_task, Task) - assert new_task.object_type == test_task.object_type - assert new_task.object_id == test_task.object_id - assert new_task.action == action - assert new_task.message == message - assert new_task.due_at == due_at - assert new_task.completion_rule == completion_rule - - -def test_get_tasks(test_file, mock_box_session): - expected_url = test_file.get_url('tasks') - task_body = { - 'type': 'task', - 'id': '12345', - 'item': { - 'type': 'file', - 'id': '33333', - }, - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [task_body], - } - tasks = test_file.get_tasks() - task = tasks.next() - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(task, Task) - assert task.id == task_body['id'] - assert task.object_type == task_body['type'] - assert task.item['id'] == task_body['item']['id'] - - -def test_get_download_url(test_file, mock_box_session): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}/content' - download_url = 'https://dl.boxcloud.com/sdjhfgksdjfgshdbg' - mock_box_session.get.return_value.headers = { - 'location': download_url - } - url = test_file.get_download_url() - mock_box_session.get.assert_called_once_with( - expected_url, - params=None, - expect_json_response=False, - allow_redirects=False - ) - assert url == download_url - - -def test_get_download_url_failed(test_file, mock_box_session): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}/content' - mock_box_session.get.return_value.headers = {} - with pytest.raises(BoxAPIException) as exc_info: - test_file.get_download_url() - assert exc_info.value.message == 'Download URL is not present in the response.' - mock_box_session.get.assert_called_once_with( - expected_url, - params=None, - expect_json_response=False, - allow_redirects=False - ) - - -def test_get_download_url_file_version(test_file, test_file_version, mock_box_session): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}/content' - download_url = 'https://dl.boxcloud.com/sdjhfgksdjfgshdbg' - mock_box_session.get.return_value.headers = { - 'location': download_url - } - url = test_file.get_download_url(file_version=test_file_version) - mock_box_session.get.assert_called_once_with( - expected_url, - params={'version': test_file_version.object_id}, - expect_json_response=False, - allow_redirects=False - ) - assert url == download_url - - -@pytest.mark.parametrize('params,expected_query,expected_headers', [ - ({}, None, None), - ({'byte_range': (100, 199)}, None, {'Range': 'bytes=100-199'}), - ({'byte_range': (100,)}, None, {'Range': 'bytes=100-'}), -]) -def test_download_to(test_file, mock_box_session, mock_content_response, params, expected_query, expected_headers): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}/content' - mock_box_session.get.return_value = mock_content_response - mock_writeable_stream = BytesIO() - test_file.download_to(mock_writeable_stream, **params) - mock_writeable_stream.seek(0) - assert mock_writeable_stream.read() == mock_content_response.content - mock_box_session.get.assert_called_once_with( - expected_url, - expect_json_response=False, - stream=True, - params=expected_query, - headers=expected_headers - ) - - -def test_download_to_file_version(test_file, test_file_version, mock_box_session, mock_content_response): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}/content' - mock_box_session.get.return_value = mock_content_response - mock_writeable_stream = BytesIO() - test_file.download_to(mock_writeable_stream, file_version=test_file_version) - mock_writeable_stream.seek(0) - assert mock_writeable_stream.read() == mock_content_response.content - mock_box_session.get.assert_called_once_with( - expected_url, - expect_json_response=False, - stream=True, - headers=None, - params={'version': test_file_version.object_id} - ) - - -@pytest.mark.parametrize('params,expected_query,expected_headers', [ - ({}, None, None), - ({'byte_range': (100, 199)}, None, {'Range': 'bytes=100-199'}), -]) -def test_get_content(test_file, mock_box_session, mock_content_response, params, expected_query, expected_headers): - expected_url = test_file.get_url('content') - mock_box_session.get.return_value = mock_content_response - file_content = test_file.content(**params) - assert file_content == mock_content_response.content - mock_box_session.get.assert_called_once_with( - expected_url, - expect_json_response=False, - params=expected_query, - headers=expected_headers - ) - - -def test_get_content_file_version(test_file, mock_box_session, mock_content_response, test_file_version): - expected_url = test_file.get_url('content') - mock_box_session.get.return_value = mock_content_response - file_content = test_file.content(file_version=test_file_version) - assert file_content == mock_content_response.content - mock_box_session.get.assert_called_once_with( - expected_url, - expect_json_response=False, - params={'version': test_file_version.object_id}, - headers=None - ) - - -@pytest.mark.parametrize('content_modified_at', [ - '1970-01-01T11:11:11.500+14:00', - datetime(1970, 1, 1, 11, 11, 11, microsecond=500, tzinfo=pytz.timezone('US/Alaska')) -]) -@pytest.mark.parametrize('is_stream', (True, False)) -def test_update_contents( - test_file, - mock_box_session, - mock_content_response, - mock_upload_response, - mock_file_path, - etag, - sha1, - upload_using_accelerator, - mock_accelerator_response_for_update, - mock_accelerator_upload_url_for_update, - upload_using_accelerator_fails, - if_match_sha1_header, - is_stream, - content_modified_at -): - # pylint:disable=too-many-locals - file_new_name = 'new_file_name' - additional_attributes = {'attr': 123} - expected_url = test_file.get_url('content').replace(API.BASE_API_URL, API.UPLOAD_URL) - if upload_using_accelerator: - if upload_using_accelerator_fails: - mock_box_session.options.side_effect = BoxAPIException(400) - else: - mock_box_session.options.return_value = mock_accelerator_response_for_update - expected_url = mock_accelerator_upload_url_for_update - - mock_box_session.post.return_value = mock_upload_response - - if is_stream: - mock_file_stream = BytesIO(mock_content_response.content) - new_file = test_file.update_contents_with_stream( - mock_file_stream, - etag=etag, - upload_using_accelerator=upload_using_accelerator, - file_name=file_new_name, - content_modified_at=content_modified_at, - additional_attributes=additional_attributes, - sha1=sha1, - ) - else: - mock_file = mock_open(read_data=mock_content_response.content) - mock_file_stream = mock_file.return_value - with patch('boxsdk.object.file.open', mock_file, create=True): - new_file = test_file.update_contents( - mock_file_path, - etag=etag, - upload_using_accelerator=upload_using_accelerator, - file_name=file_new_name, - content_modified_at=content_modified_at, - additional_attributes=additional_attributes, - sha1=sha1, - ) - - mock_files = {'file': ('unused', mock_file_stream)} - attributes = { - 'name': file_new_name, - 'content_modified_at': '1970-01-01T11:11:11+14:00', - } - # Using `update` to mirror the actual impl, since the attributes could otherwise come through in a different order - # in Python 2 tests - attributes.update(additional_attributes) - data = {'attributes': json.dumps(attributes)} - mock_box_session.post.assert_called_once_with( - expected_url, - expect_json_response=False, - files=mock_files, - data=data, - headers=if_match_sha1_header, - ) - assert isinstance(new_file, File) - assert new_file.object_id == test_file.object_id - assert 'id' in new_file - assert new_file['id'] == test_file.object_id - assert not hasattr(new_file, 'entries') - assert 'entries' not in new_file - - -@pytest.mark.parametrize('is_stream', (True, False)) -def test_update_contents_combines_preflight_and_accelerator_calls_if_both_are_requested( - test_file, - mock_box_session, - mock_file_path, - mock_content_response, - mock_accelerator_response_for_update, - is_stream -): - mock_box_session.options.return_value = mock_accelerator_response_for_update - - if is_stream: - mock_file_stream = BytesIO(mock_content_response.content) - test_file.update_contents_with_stream( - mock_file_stream, - preflight_check=True, - upload_using_accelerator=True, - ) - else: - mock_file = mock_open(read_data=mock_content_response.content) - with patch('boxsdk.object.file.open', mock_file, create=True): - test_file.update_contents( - mock_file_path, - preflight_check=True, - upload_using_accelerator=True, - ) - - mock_box_session.options.assert_called_once() - - -def test_update_contents_with_stream_does_preflight_check_if_specified( - test_file, - preflight_check, - file_size, - preflight_fails, - mock_box_session, -): - with patch.object(File, 'preflight_check', return_value=None): - kwargs = {'file_stream': BytesIO(b'some bytes')} - if preflight_check: - kwargs['preflight_check'] = preflight_check - kwargs['preflight_expected_size'] = file_size - if preflight_fails: - test_file.preflight_check.side_effect = BoxAPIException(400) - with pytest.raises(BoxAPIException): - test_file.update_contents_with_stream(**kwargs) - else: - test_file.update_contents_with_stream(**kwargs) - - if preflight_check: - test_file.preflight_check.assert_called_once_with(size=file_size) - if preflight_fails: - assert not mock_box_session.post.called - else: - assert mock_box_session.post.called - else: - assert not test_file.preflight_check.called - - -@patch('boxsdk.object.file.open', mock_open(read_data=b'some bytes'), create=True) -def test_update_contents_does_preflight_check_if_specified( - test_file, - mock_file_path, - preflight_check, - file_size, - preflight_fails, - mock_box_session, -): - with patch.object(File, 'preflight_check', return_value=None): - kwargs = {'file_path': mock_file_path} - if preflight_check: - kwargs['preflight_check'] = preflight_check - kwargs['preflight_expected_size'] = file_size - if preflight_fails: - test_file.preflight_check.side_effect = BoxAPIException(400) - with pytest.raises(BoxAPIException): - test_file.update_contents(**kwargs) - else: - test_file.update_contents(**kwargs) - - if preflight_check: - test_file.preflight_check.assert_called_once_with(size=file_size) - if preflight_fails: - assert not mock_box_session.post.called - else: - assert mock_box_session.post.called - else: - assert not test_file.preflight_check.called - - -@pytest.mark.parametrize('params,expected_data', [ - ({}, {'is_download_prevented': False}), - ({'prevent_download': False}, {'is_download_prevented': False}), - ({'prevent_download': True}, {'is_download_prevented': True}), - ({'expire_time': '2018-11-06T19:40:00-08:00'}, - { - 'is_download_prevented': False, - 'expires_at': '2018-11-06T19:40:00-08:00' - }), - ({'expire_time': datetime(2018, 11, 6, 19, 40, 00, tzinfo=pytz.timezone('US/Alaska'))}, - { - 'is_download_prevented': False, - 'expires_at': '2018-11-06T19:40:00+14:00' - }), -]) -def test_lock(test_file, mock_box_session, mock_file_response, params, expected_data): - expected_url = test_file.get_url() - expected_body = { - 'lock': { - 'type': 'lock' - } - } - - if 'is_download_prevented' in expected_data.keys(): - expected_body['lock']['is_download_prevented'] = expected_data['is_download_prevented'] - if 'expires_at' in expected_data.keys(): - expected_body['lock']['expires_at'] = expected_data['expires_at'] - - mock_box_session.put.return_value = mock_file_response - test_file.lock(**params) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(expected_body), - params=None, - headers=None, - ) - - -def test_unlock(test_file, mock_box_session, mock_file_response): - expected_url = test_file.get_url() - mock_box_session.put.return_value = mock_file_response - test_file.unlock() - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps({'lock': None}), - params=None, - headers=None, - ) - - -@pytest.mark.parametrize( - 'size, name, expected_data', - [ - # Test case for specifying the name of the file for preflight - ( - 100, - 'foo.txt', - json.dumps({'size': 100, 'name': 'foo.txt'}), - ), - - # Test case for omitting the name of the file for preflight - ( - 200, - None, - json.dumps({'size': 200}) - ), - ] -) -def test_preflight_check( - test_file, - mock_object_id, - mock_box_session, - mock_accelerator_response_for_update, - mock_accelerator_upload_url_for_update, - size, - name, - expected_data, -): - mock_box_session.options.return_value = mock_accelerator_response_for_update - kwargs = {'size': size} - if name: - kwargs['name'] = name - - accelerator_url = test_file.preflight_check(**kwargs) - - mock_box_session.options.assert_called_once_with( - url=f'{API.BASE_API_URL}/files/{mock_object_id}/content', - expect_json_response=True, - data=expected_data, - ) - assert accelerator_url == mock_accelerator_upload_url_for_update - - -def test_get_shared_link( - test_file, - mock_box_session, - shared_link_access, - shared_link_unshared_at, - shared_link_password, - shared_link_can_download, - shared_link_can_preview, - shared_link_can_edit, - shared_link_vanity_name, - test_url, - etag, - if_match_header, -): - # pylint:disable=redefined-outer-name, protected-access - expected_url = test_file.get_url() - mock_box_session.put.return_value.json.return_value = { - 'type': test_file.object_type, - 'id': test_file.object_id, - 'shared_link': { - 'url': test_url, - }, - } - expected_data = {'shared_link': {}} - if shared_link_access is not None: - expected_data['shared_link']['access'] = shared_link_access - if shared_link_unshared_at is not SDK_VALUE_NOT_SET: - expected_data['shared_link']['unshared_at'] = normalize_date_to_rfc3339_format(shared_link_unshared_at) - permissions = {} - if shared_link_can_download is not None: - permissions['can_download'] = shared_link_can_download - if shared_link_can_preview is not None: - permissions['can_preview'] = shared_link_can_preview - if shared_link_can_edit is not None: - permissions['can_edit'] = shared_link_can_edit - if permissions: - expected_data['shared_link']['permissions'] = permissions - if shared_link_password is not None: - expected_data['shared_link']['password'] = shared_link_password - if shared_link_vanity_name is not None: - expected_data['shared_link']['vanity_name'] = shared_link_vanity_name - - url = test_file.get_shared_link( - etag=etag, - access=shared_link_access, - unshared_at=shared_link_unshared_at, - password=shared_link_password, - allow_download=shared_link_can_download, - allow_preview=shared_link_can_preview, - allow_edit=shared_link_can_edit, - vanity_name=shared_link_vanity_name, - ) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(expected_data), - headers=if_match_header, - params=None, - ) - assert url == test_url - - -def test_get_shared_link_download_url( - test_file, - mock_box_session, - shared_link_access, - shared_link_unshared_at, - shared_link_password, - shared_link_can_preview, - shared_link_vanity_name, - test_url, - etag, - if_match_header, -): - # pylint:disable=redefined-outer-name, protected-access - expected_url = test_file.get_url() - mock_box_session.put.return_value.json.return_value = { - 'type': test_file.object_type, - 'id': test_file.object_id, - 'shared_link': { - 'url': None, - 'download_url': test_url, - }, - } - expected_data = { - 'shared_link': {}, - } - if shared_link_access is not None: - expected_data['shared_link']['access'] = shared_link_access - if shared_link_unshared_at is not SDK_VALUE_NOT_SET: - expected_data['shared_link']['unshared_at'] = normalize_date_to_rfc3339_format(shared_link_unshared_at) - if shared_link_can_preview is not None: - expected_data['shared_link']['permissions'] = permissions = {} - permissions['can_preview'] = shared_link_can_preview - if shared_link_password is not None: - expected_data['shared_link']['password'] = shared_link_password - if shared_link_vanity_name is not None: - expected_data['shared_link']['vanity_name'] = shared_link_vanity_name - - url = test_file.get_shared_link_download_url( - etag=etag, - access=shared_link_access, - unshared_at=shared_link_unshared_at, - password=shared_link_password, - allow_preview=shared_link_can_preview, - vanity_name=shared_link_vanity_name, - ) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(expected_data), - headers=if_match_header, - params=None, - ) - assert url == test_url - - -def test_get_comments(test_file, mock_box_session): - expected_url = test_file.get_url('comments') - mock_comment1 = { - 'type': 'comment', - 'id': '11111', - 'message': 'Foo' - } - mock_comment2 = { - 'type': 'comment', - 'id': '22222', - 'tagged_message': 'Well hello there, @[33333:friend]!' - } - mock_box_session.get.return_value.json.return_value = { - 'total_count': 2, - 'offset': 0, - 'limit': 100, - 'entries': [mock_comment1, mock_comment2] - } - comments = test_file.get_comments() - comment1 = comments.next() - mock_box_session.get.assert_called_once_with(expected_url, params={'offset': 0}) - assert comment1.object_id == mock_comment1['id'] - assert comment1.message == mock_comment1['message'] - - comment2 = comments.next() - assert comment2.object_id == mock_comment2['id'] - assert comment2.tagged_message == mock_comment2['tagged_message'] - - -def test_add_comment(test_file, mock_box_session, comment_params): - expected_url = 'https://api.box.com/2.0/comments' - comment_id = '12345' - (message_type, message) = comment_params - expected_data = { - message_type: message, - 'item': { - 'type': 'file', - 'id': test_file.object_id - } - } - mock_box_session.post.return_value.json.return_value = { - 'type': 'comment', - 'id': comment_id, - message_type: message - } - comment = test_file.add_comment(message) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - assert isinstance(comment, Comment) - assert comment.object_id == comment_id - - -def test_get_previous_versions(test_file, mock_box_session): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}/versions' - mock_version1 = { - 'type': 'file_version', - 'id': '11111', - 'sha1': '4788db35f85f87acaaa5ba82cc99d72c9323281f', - } - mock_version2 = { - 'type': 'comment', - 'id': '22222', - 'sha1': '4788db35f85f87acaaa5ba82cc99d72c9323281f', - } - mock_box_session.get.return_value.json.return_value = { - 'total_count': 2, - 'offset': 0, - 'limit': 100, - 'entries': [mock_version1, mock_version2] - } - versions = test_file.get_previous_versions() - version1 = versions.next() - mock_box_session.get.assert_called_once_with(expected_url, params={'offset': None}) - assert version1.object_id == mock_version1['id'] - assert version1.sha1 == mock_version1['sha1'] - - version2 = versions.next() - assert version2.object_id == mock_version2['id'] - assert version2.sha1 == mock_version2['sha1'] - - -def test_promote_version(test_file, test_file_version, mock_box_session): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}/versions/current' - sha1 = '12039d6dd9a7e6eefc78846802e' - expected_body = { - 'type': 'file_version', - 'id': test_file_version.object_id, - } - mock_box_session.post.return_value.json.return_value = { - 'type': 'file_version', - 'id': '77777', - 'sha1': sha1, - } - new_version = test_file.promote_version(test_file_version) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_body)) - assert isinstance(new_version, FileVersion) - assert new_version.object_id == '77777' - assert new_version.sha1 == sha1 - - -@pytest.mark.parametrize('params,expected_headers', [ - ({}, None), - ({'etag': 'foobar'}, {'If-Match': 'foobar'}), -]) -def test_delete_version(test_file, test_file_version, mock_box_session, params, expected_headers): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}/versions/{test_file_version.object_id}' - mock_box_session.delete.return_value.ok = True - is_success = test_file.delete_version(test_file_version, **params) - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=expected_headers) - assert is_success is True - - -def test_get_embed_url(test_file, mock_box_session): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}' - expected_params = { - 'fields': 'expiring_embed_link' - } - embed_url = 'https://app.box.com/preview/mystuff' - mock_box_session.get.return_value.json.return_value = { - 'type': 'file', - 'id': test_file.object_id, - 'expiring_embed_link': { - 'url': embed_url, - }, - } - - url = test_file.get_embed_url() - mock_box_session.get.assert_called_once_with(expected_url, params=expected_params) - assert url == embed_url - - -@pytest.mark.parametrize('rep_hints,expected_headers', [ - (None, None), - ('[pdf]', {'X-Rep-Hints': '[pdf]'}), -]) -def test_get_representation_info(test_file, mock_box_session, rep_hints, expected_headers): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}' - expected_params = {'fields': 'representations'} - - info_url = 'https://api.box.com/2.0/representations/pdf' - mock_box_session.get.return_value.json.return_value = { - 'type': 'file', - 'id': test_file.object_id, - 'representations': { - 'total_count': 1, - 'entries': [ - { - 'representation': 'pdf', - 'info': { - 'url': info_url, - }, - }, - ], - }, - } - - reps = test_file.get_representation_info(rep_hints=rep_hints) - mock_box_session.get.assert_called_once_with(expected_url, params=expected_params, headers=expected_headers) - assert isinstance(reps, list) - assert len(reps) == 1 - rep = reps[0] - assert rep['representation'] == 'pdf' - assert rep['info']['url'] == info_url - - -@pytest.mark.parametrize('extension,min_width,min_height,max_width,max_height,expected_params', [ - ('png', None, None, None, None, {}), - ('png', None, None, None, None, {}), - ('jpg', None, None, None, None, {}), - ('png', 1, 2, None, None, {'min_width': 1, 'min_height': 2}), - ('png', 1, 2, 3, 4, {'min_width': 1, 'min_height': 2, 'max_width': 3, 'max_height': 4}), -]) -def test_get_thumbnail( - test_file, - mock_box_session, - mock_content_response, - extension, - min_width, - min_height, - max_width, - max_height, - expected_params, -): - expected_url = f'{API.BASE_API_URL}/files/{test_file.object_id}/thumbnail.{extension}' - mock_box_session.get.return_value = mock_content_response - - thumb = test_file.get_thumbnail( - extension=extension, - min_width=min_width, - min_height=min_height, - max_width=max_width, - max_height=max_height, - ) - - mock_box_session.get.assert_called_once_with(expected_url, expect_json_response=False, params=expected_params) - assert thumb == mock_content_response.content - - -@pytest.mark.parametrize('dimensions,extension', [ - ('92x92', 'png'), - ('92x92', 'jpg'), -]) -def test_get_thumbnail_representation( - test_file, - mock_box_session, - mock_content_response, - dimensions, - extension, -): - representation_url = f'{API.BASE_API_URL}/files/{test_file.object_id}' - content_url = 'https://dl.boxcloud.com/api/2.0/internal_files/123/versions/345/representations/jpg/content/' - - mock_representations_response = Mock() - mock_representations_response.json.return_value = { - 'etag': '1', - 'id': test_file.object_id, - 'representations': { - 'entries': [ - { - 'content': { - 'url_template': content_url + '{+asset_path}' - }, - 'info': { - 'url': 'https://api.box.com/2.0/internal_files/123/versions/345/representations/jpg' - }, - 'properties': {}, - 'representation': 'pdf', - 'status': { - 'state': 'success' - } - } - ] - }, - 'type': 'file' - } - - mock_box_session.get.side_effect = [mock_representations_response, mock_content_response] - - thumb = test_file.get_thumbnail_representation( - dimensions=dimensions, - extension=extension, - ) - - mock_box_session.get.assert_any_call(representation_url, headers={'X-Rep-Hints': f'[{extension}?dimensions=92x92]'}, - params={'fields': 'representations'}) - mock_box_session.get.assert_any_call(content_url, expect_json_response=False) - assert thumb == mock_content_response.content - - -def test_get_thumbnail_representation_not_found( - test_file, - mock_box_session, - mock_content_response, -): - representation_url = f'{API.BASE_API_URL}/files/{test_file.object_id}' - dimensions = '100x100' - extension = 'jpg' - - mock_representations_response = Mock() - mock_representations_response.json.return_value = { - 'etag': '1', - 'id': test_file.object_id, - 'representations': { - 'entries': [], - }, - 'type': 'file' - } - - mock_box_session.get.side_effect = [mock_representations_response, mock_content_response] - - thumb = test_file.get_thumbnail_representation( - dimensions=dimensions, - extension=extension, - ) - - mock_box_session.get.assert_any_call( - representation_url, - headers={'X-Rep-Hints': f'[{extension}?dimensions={dimensions}]'}, - params={'fields': 'representations'}, - ) - assert thumb == b'' - - -def test_get_thumbnail_representation_not_available( - test_file, - mock_box_session, - mock_content_response, -): - representation_url = f'{API.BASE_API_URL}/files/{test_file.object_id}' - dimensions = '100x100' - extension = 'jpg' - - mock_representations_response = Mock() - mock_representations_response.json.return_value = { - 'etag': '1', - 'id': test_file.object_id, - 'representations': { - 'entries': [ - { - 'content': { - 'url_template': 'content_url {+asset_path}' - }, - 'info': { - 'url': 'https://api.box.com/2.0/internal_files/123/versions/345/representations/jpg' - }, - 'properties': {}, - 'representation': 'pdf', - 'status': {'state': 'error', 'code': 'error_password_protected'} - } - ] - }, - 'type': 'file' - } - - mock_box_session.get.side_effect = [mock_representations_response, mock_content_response] - - thumb = test_file.get_thumbnail_representation( - dimensions=dimensions, - extension=extension, - ) - - mock_box_session.get.assert_any_call( - representation_url, - headers={'X-Rep-Hints': f'[{extension}?dimensions={dimensions}]'}, - params={'fields': 'representations'}, - ) - assert thumb == b'' - - -@pytest.mark.parametrize( - 'disposition_at', - ( - lazy_fixture('mock_datetime_rfc3339_str'), - "2035-03-04T10:14:24.000+14:00", - "2035/03/04 10:14:24.000+14:00", - lazy_fixture('mock_timezone_aware_datetime_obj'), - ) -) -def test_set_diposition_at( - test_file, - mock_box_session, - disposition_at, - mock_datetime_rfc3339_str, -): - expected_url = test_file.get_url() - expected_data = {'disposition_at': mock_datetime_rfc3339_str} - - test_file.set_disposition_at(disposition_at) - - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(expected_data), - headers=None, - params=None, - ) diff --git a/test/unit/object/test_file_request.py b/test/unit/object/test_file_request.py deleted file mode 100644 index a79c81814..000000000 --- a/test/unit/object/test_file_request.py +++ /dev/null @@ -1,90 +0,0 @@ -import json -from datetime import datetime -import pytest -import pytz - -from boxsdk.config import API -from boxsdk.object.folder import Folder -from boxsdk.object.file_request import FileRequest -from boxsdk.object.file_request import StatusState - - -def test_get(test_file_request, mock_box_session): - expected_url = f'{API.BASE_API_URL}/file_requests/{test_file_request.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': test_file_request.object_type, - 'id': test_file_request.object_id, - 'title': 'File Request' - } - file_request = test_file_request.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(file_request, FileRequest) - assert file_request['type'] == file_request.object_type - assert file_request['id'] == file_request.object_id - assert file_request['title'] == 'File Request' - - -def test_update(test_file_request, mock_box_session): - new_title = 'New File Request Title' - new_status = StatusState.INACTIVE - expected_url = f'{API.BASE_API_URL}/file_requests/{test_file_request.object_id}' - mock_box_session.put.return_value.json.return_value = { - 'type': test_file_request.object_type, - 'id': test_file_request.object_id, - 'title': new_title, - 'status': new_status, - } - data = { - 'title': new_title, - 'status': StatusState.INACTIVE, - } - file_request = test_file_request.update_info(data=data) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None) - assert isinstance(file_request, FileRequest) - assert file_request['type'] == test_file_request.object_type - assert file_request['id'] == test_file_request.object_id - assert file_request['title'] == new_title - assert file_request['status'] == StatusState.INACTIVE - - -@pytest.mark.parametrize('expires_at', [ - '2019-07-01T22:02:24+14:00', - datetime(2019, 7, 1, 22, 2, 24, tzinfo=pytz.timezone('US/Alaska')) -]) -def test_copy(test_file_request, mock_box_session, expires_at): - new_folder_id = '100' - expected_url = f'{API.BASE_API_URL}/file_requests/{test_file_request.object_id}/copy' - expected_expires_at = '2019-07-01T22:02:24+14:00' - mock_box_session.post.return_value.json.return_value = { - 'type': test_file_request.object_type, - 'id': test_file_request.object_id, - 'title': 'File Request Copied', - 'folder': { - 'type': 'folder', - 'id': new_folder_id, - }, - 'expires_at': expected_expires_at, - } - new_title = 'File Request Copied' - new_folder = Folder(mock_box_session, object_id=new_folder_id) - file_request = test_file_request.copy(title=new_title, folder=new_folder, expires_at=expires_at) - data = { - 'folder': { - 'id': new_folder_id, - 'type': 'folder', - }, - 'title': new_title, - 'expires_at': expected_expires_at, - } - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(data)) - assert isinstance(file_request, FileRequest) - assert file_request['type'] == test_file_request.object_type - assert file_request['title'] == 'File Request Copied' - assert file_request['folder']['id'] == '100' - assert file_request['folder']['type'] == 'folder' - - -def test_delete(test_file_request, mock_box_session): - expected_url = f'{API.BASE_API_URL}/file_requests/{test_file_request.object_id}' - test_file_request.delete() - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=None, params={}) diff --git a/test/unit/object/test_file_version_retention.py b/test/unit/object/test_file_version_retention.py deleted file mode 100644 index 8e24b76ea..000000000 --- a/test/unit/object/test_file_version_retention.py +++ /dev/null @@ -1,13 +0,0 @@ -from boxsdk.config import API -from boxsdk.object.file_version_retention import FileVersionRetention - - -def test_get(test_file_version_retention, mock_box_session): - expected_url = f'{API.BASE_API_URL}/file_version_retentions/{test_file_version_retention.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': 'file_version_retention', - 'id': test_file_version_retention.object_id, - } - file_version_retention = test_file_version_retention.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(file_version_retention, FileVersionRetention) diff --git a/test/unit/object/test_folder.py b/test/unit/object/test_folder.py deleted file mode 100644 index f5a97adb8..000000000 --- a/test/unit/object/test_folder.py +++ /dev/null @@ -1,767 +0,0 @@ -import json -from datetime import datetime -from io import BytesIO -from os.path import basename -from unittest.mock import mock_open, patch, Mock, MagicMock, ANY -import pytest -import pytz - -from boxsdk.config import API -from boxsdk.exception import BoxAPIException -from boxsdk.network.default_network import DefaultNetworkResponse -from boxsdk.object.enterprise import Enterprise -from boxsdk.object.file import File -from boxsdk.object.metadata_cascade_policy import MetadataCascadePolicy -from boxsdk.object.web_link import WebLink -from boxsdk.object.collaboration import Collaboration, CollaborationRole -from boxsdk.object.folder import Folder, FolderSyncState -from boxsdk.object.upload_session import UploadSession -from boxsdk.session.box_response import BoxResponse -from boxsdk.util.chunked_uploader import ChunkedUploader - - -# pylint:disable=protected-access -# pylint:disable=redefined-outer-name -from boxsdk.util.datetime_formatter import normalize_date_to_rfc3339_format -from boxsdk.util.default_arg_value import SDK_VALUE_NOT_SET - - -@pytest.fixture() -def mock_new_upload_accelerator_url(): - return 'https://upload.box.com/api/2.0/files/content?upload_session_id=123' - - -@pytest.fixture(scope='function') -def mock_accelerator_response_for_new_uploads(make_mock_box_request, mock_new_upload_accelerator_url): - mock_response, _ = make_mock_box_request( - response={ - 'upload_url': mock_new_upload_accelerator_url, - 'upload_token': None, - } - ) - return mock_response - - -@pytest.fixture() -def mock_items(mock_box_session, mock_object_id): - return [ - {'type': 'file', 'id': mock_object_id}, - {'type': 'folder', 'id': mock_object_id}, - {'type': 'file', 'id': mock_object_id}, - ], [ - File(mock_box_session, mock_object_id), - Folder(mock_box_session, mock_object_id), - File(mock_box_session, mock_object_id), - ] - - -@pytest.fixture() -def mock_items_response(mock_items): - # pylint:disable=redefined-outer-name - def get_response(limit, offset): - items_json, items = mock_items - entries = items_json[offset:limit + offset] - mock_box_response = Mock(BoxResponse) - mock_network_response = Mock(DefaultNetworkResponse) - mock_box_response.network_response = mock_network_response - mock_box_response.json.return_value = mock_json = { - 'entries': entries, - 'total_count': len(entries), - 'limit': limit, - 'offset': offset, - } - mock_box_response.content = json.dumps(mock_json).encode() - mock_box_response.status_code = 200 - mock_box_response.ok = True - return mock_box_response, items[offset:limit + offset] - return get_response - - -@pytest.mark.parametrize('use_upload_session_urls', [True, False]) -def test_get_chunked_uploader( - mock_box_session, mock_content_response, mock_file_path, test_folder, use_upload_session_urls -): - expected_url = f'{API.UPLOAD_URL}/files/upload_sessions' - mock_file_stream = BytesIO(mock_content_response.content) - file_size = 197520 - file_name = 'file' - part_size = 12345 - total_parts = 16 - num_parts_processed = 0 - upload_session_type = 'upload_session' - upload_session_id = 'F971964745A5CD0C001BBE4E58196BFD' - expected_data = { - 'folder_id': test_folder.object_id, - 'file_size': file_size, - 'file_name': file_name, - } - mock_box_session.post.return_value.json.return_value = { - 'id': upload_session_id, - 'type': upload_session_type, - 'num_parts_processed': num_parts_processed, - 'total_parts': total_parts, - 'part_size': part_size, - } - with patch('os.stat') as stat: - stat.return_value.st_size = file_size - with patch('boxsdk.object.folder.open', return_value=mock_file_stream): - chunked_uploader = test_folder.get_chunked_uploader( - mock_file_path, use_upload_session_urls=use_upload_session_urls - ) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - upload_session = chunked_uploader._upload_session - assert upload_session.part_size == part_size - assert upload_session.total_parts == total_parts - assert upload_session.num_parts_processed == num_parts_processed - assert upload_session.type == upload_session_type - assert upload_session.id == upload_session_id - assert upload_session._use_upload_session_urls is use_upload_session_urls - assert isinstance(chunked_uploader, ChunkedUploader) - - -@pytest.mark.parametrize('use_upload_session_urls', [True, False]) -def test_create_upload_session(test_folder, mock_box_session, use_upload_session_urls): - expected_url = f'{API.UPLOAD_URL}/files/upload_sessions' - file_size = 197520 - file_name = 'test_file.pdf' - upload_session_id = 'F971964745A5CD0C001BBE4E58196BFD' - upload_session_type = 'upload_session' - num_parts_processed = 0 - total_parts = 16 - part_size = 12345 - expected_data = { - 'folder_id': test_folder.object_id, - 'file_size': file_size, - 'file_name': file_name, - } - mock_box_session.post.return_value.json.return_value = { - 'id': upload_session_id, - 'type': upload_session_type, - 'num_parts_processed': num_parts_processed, - 'total_parts': total_parts, - 'part_size': part_size, - } - upload_session = test_folder.create_upload_session( - file_size, file_name, use_upload_session_urls=use_upload_session_urls - ) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - assert isinstance(upload_session, UploadSession) - assert upload_session.part_size == part_size - assert upload_session.total_parts == total_parts - assert upload_session.num_parts_processed == num_parts_processed - assert upload_session.type == upload_session_type - assert upload_session.id == upload_session_id - assert upload_session._use_upload_session_urls == use_upload_session_urls - - -@pytest.fixture() -def mock_items_response_with_marker(mock_items): - # pylint:disable=redefined-outer-name - def get_response(limit, offset): - items_json, items = mock_items - entries = items_json[offset:limit + offset] - mock_box_response = Mock(BoxResponse) - mock_network_response = Mock(DefaultNetworkResponse) - mock_box_response.network_response = mock_network_response - mock_box_response.json.return_value = mock_json = { - 'entries': entries, - 'total_count': len(entries), - 'limit': limit, - 'offset': offset, - } - mock_box_response.content = json.dumps(mock_json).encode() - mock_box_response.status_code = 200 - mock_box_response.ok = True - return mock_box_response, items[offset:limit + offset] - return get_response - - -def _assert_collaborator_added(test_folder, collaborator, mock_box_session, mock_collab_response, notify, role, can_view_path, data): - mock_box_session.post.return_value = mock_collab_response - collaboration = test_folder.add_collaborator(collaborator, role, notify, can_view_path) - assert isinstance(collaboration, Collaboration) - expected_url = API.BASE_API_URL + '/collaborations' - params = {'notify': notify} - mock_box_session.post.assert_called_once_with(expected_url, expect_json_response=True, data=data, params=params) - - -@pytest.mark.parametrize('accessible_by', ['user', 'group', 'email']) -@pytest.mark.parametrize('notify', [True, False]) -@pytest.mark.parametrize('role', iter(CollaborationRole)) -@pytest.mark.parametrize('can_view_path', [True, False]) -def test_add_collaborator(test_folder, mock_user, mock_group, mock_box_session, mock_collab_response, accessible_by, notify, role, can_view_path): - accessible_dict = { - 'user': (mock_user, {'id': mock_user.object_id, 'type': 'user'}), - 'group': (mock_group, {'id': mock_group.object_id, 'type': 'group'}), - 'email': ('foo@example.com', {'login': 'foo@example.com', 'type': 'user'}), - } - - invitee, mock_accessible_by = accessible_dict[accessible_by] - - body_params = { - 'item': {'id': test_folder.object_id, 'type': 'folder'}, - 'accessible_by': mock_accessible_by, - 'role': role, - } - if can_view_path: - body_params['can_view_path'] = True - data = json.dumps(body_params) - _assert_collaborator_added(test_folder, invitee, mock_box_session, mock_collab_response, notify, role, can_view_path, data) - - -def test_add_collaborator_raises_for_bad_type(test_folder): - with pytest.raises(TypeError): - test_folder.add_collaborator(b'byte string', CollaborationRole.EDITOR) - - -@pytest.mark.parametrize('recursive', [True, False]) -def test_delete_folder(test_folder, mock_box_session, recursive, etag, if_match_header): - test_folder.delete(recursive=recursive, etag=etag) - expected_url = test_folder.get_url() - mock_box_session.delete.assert_called_once_with( - expected_url, - expect_json_response=False, - params={'recursive': recursive}, - headers=if_match_header, - ) - - -@pytest.mark.parametrize('limit,offset,fields,sort,direction', [ - (1, 0, None, None, None), - (100, 0, ['foo', 'bar'], None, None), - (1, 1, None, None, None), - (1, 0, None, 'name', 'ASC'), - (1, 1, None, 'date', 'DESC') -]) -def test_get_items(test_folder, mock_box_session, mock_items_response, limit, offset, fields, sort, direction): - # pylint:disable=redefined-outer-name - expected_url = test_folder.get_url('items') - mock_box_session.get.return_value, expected_items = mock_items_response(limit, offset) - items = test_folder.get_items(limit, offset, fields=fields, sort=sort, direction=direction) - expected_params = {'limit': limit, 'offset': offset} - if fields: - expected_params['fields'] = ','.join(fields) - if sort: - expected_params['sort'] = sort - if direction: - expected_params['direction'] = direction - for actual, expected in zip(items, expected_items): - assert actual == expected - mock_box_session.get.assert_called_once_with(expected_url, params=expected_params) - assert all(i.id == e.object_id for (i, e) in zip(items, expected_items)) - - -@pytest.mark.parametrize('content_created_at', [ - '1970-01-01T00:00:00+00:00', - datetime(1970, 1, 1, 0, 0, 0, tzinfo=pytz.UTC), - None -]) -@pytest.mark.parametrize('content_modified_at', [ - '1970-01-01T11:11:11+00:00', - datetime(1970, 1, 1, 11, 11, 11, tzinfo=pytz.UTC), - None -]) -@pytest.mark.parametrize('is_stream', (True, False)) -def test_upload( - test_folder, - mock_box_session, - mock_content_response, - mock_upload_response, - mock_file_path, - mock_object_id, - upload_using_accelerator, - mock_accelerator_response_for_new_uploads, - mock_new_upload_accelerator_url, - upload_using_accelerator_fails, - is_stream, - etag, - sha1, - if_match_sha1_header, - content_created_at, - content_modified_at -): - # pylint:disable=too-many-locals - # pylint:disable=too-many-arguments - file_description = 'Test File Description' - additional_attributes = {'attr': 123} - expected_url = f'{API.UPLOAD_URL}/files/content' - if upload_using_accelerator: - if upload_using_accelerator_fails: - mock_box_session.options.side_effect = BoxAPIException(400) - else: - mock_box_session.options.return_value = mock_accelerator_response_for_new_uploads - expected_url = mock_new_upload_accelerator_url - - mock_box_session.post.return_value = mock_upload_response - - if is_stream: - mock_file_stream = BytesIO(mock_content_response.content) - new_file = test_folder.upload_stream( - mock_file_stream, - basename(mock_file_path), - file_description, - upload_using_accelerator=upload_using_accelerator, - content_created_at=content_created_at, - content_modified_at=content_modified_at, - additional_attributes=additional_attributes, - sha1=sha1, - etag=etag, - ) - else: - mock_file = mock_open(read_data=mock_content_response.content) - mock_file_stream = mock_file.return_value - with patch('boxsdk.object.folder.open', mock_file, create=True): - new_file = test_folder.upload( - mock_file_path, - file_description=file_description, - upload_using_accelerator=upload_using_accelerator, - content_created_at=content_created_at, - content_modified_at=content_modified_at, - additional_attributes=additional_attributes, - sha1=sha1, - etag=etag, - ) - - mock_files = {'file': ('unused', mock_file_stream)} - attributes = { - 'name': basename(mock_file_path), - 'parent': {'id': mock_object_id}, - 'description': file_description, - 'content_created_at': normalize_date_to_rfc3339_format(content_created_at), - 'content_modified_at': normalize_date_to_rfc3339_format(content_modified_at), - } - # Using `update` to mirror the actual impl, since the attributes could otherwise come through in a different order - # in Python 2 tests - attributes.update(additional_attributes) - data = {'attributes': json.dumps(attributes)} - mock_box_session.post.assert_called_once_with( - expected_url, - expect_json_response=False, - files=mock_files, - data=data, - headers=if_match_sha1_header, - stream_file_content=True - ) - assert isinstance(new_file, File) - assert new_file.object_id == mock_object_id - assert 'id' in new_file - assert new_file['id'] == mock_object_id - assert new_file.description == file_description - assert not hasattr(new_file, 'entries') - assert 'entries' not in new_file - - -@pytest.mark.parametrize('is_stream', (True, False)) -def test_upload_combines_preflight_and_accelerator_calls_if_both_are_requested( - test_folder, - mock_box_session, - mock_file_path, - mock_content_response, - mock_accelerator_response_for_new_uploads, - is_stream -): - mock_box_session.options.return_value = mock_accelerator_response_for_new_uploads - - if is_stream: - mock_file_stream = BytesIO(mock_content_response.content) - test_folder.upload_stream( - mock_file_stream, - basename(mock_file_path), - preflight_check=True, - upload_using_accelerator=True, - ) - else: - mock_file = mock_open(read_data=mock_content_response.content) - with patch('boxsdk.object.folder.open', mock_file, create=True): - test_folder.upload( - mock_file_path, - preflight_check=True, - upload_using_accelerator=True, - ) - - mock_box_session.options.assert_called_once() - - -def test_upload_stream_does_preflight_check_if_specified( - mock_box_session, - test_folder, - preflight_check, - preflight_fails, - file_size, -): - with patch.object(Folder, 'preflight_check', return_value=None): - kwargs = {'file_stream': BytesIO(b'some bytes'), 'file_name': 'foo.txt'} - mock_box_session.post = MagicMock() - if preflight_check: - kwargs['preflight_check'] = preflight_check - kwargs['preflight_expected_size'] = file_size - if preflight_fails: - test_folder.preflight_check.side_effect = BoxAPIException(400) - with pytest.raises(BoxAPIException): - test_folder.upload_stream(**kwargs) - else: - test_folder.upload_stream(**kwargs) - - if preflight_check: - test_folder.preflight_check.assert_called_once_with(size=file_size, name='foo.txt') - _assert_post_called_correctly(mock_box_session, preflight_fails) - else: - assert not test_folder.preflight_check.called - - -def _assert_post_called_correctly(mock_box_session, preflight_fails): - if preflight_fails: - assert not mock_box_session.post.called - else: - assert mock_box_session.post.called - - -@patch('boxsdk.object.folder.open', mock_open(read_data=b'some bytes'), create=True) -def test_upload_does_preflight_check_if_specified( - mock_box_session, - test_folder, - mock_file_path, - preflight_check, - preflight_fails, - file_size, -): - with patch.object(Folder, 'preflight_check', return_value=None): - kwargs = {'file_path': mock_file_path, 'file_name': 'foo.txt'} - mock_box_session.post = MagicMock() - if preflight_check: - kwargs['preflight_check'] = preflight_check - kwargs['preflight_expected_size'] = file_size - if preflight_fails: - test_folder.preflight_check.side_effect = BoxAPIException(400) - with pytest.raises(BoxAPIException): - test_folder.upload(**kwargs) - else: - test_folder.upload(**kwargs) - - if preflight_check: - test_folder.preflight_check.assert_called_once_with(size=file_size, name='foo.txt') - _assert_post_called_correctly(mock_box_session, preflight_fails) - else: - assert not test_folder.preflight_check.called - - -@patch('boxsdk.object.folder.open', mock_open(read_data=b'some bytes'), create=True) -@pytest.mark.parametrize('stream_file_content', (True, False)) -def test_upload_if_flag_stream_file_content_is_passed_to_session( - mock_box_session, - test_folder, - stream_file_content, -): - expected_url = f'{API.UPLOAD_URL}/files/content' - - test_folder.upload('foo.txt', file_name='foo.txt', stream_file_content=stream_file_content) - - mock_files = {'file': ('unused', ANY)} - mock_box_session.post.assert_called_once_with( - expected_url, - data=ANY, - files=mock_files, - expect_json_response=False, - headers=None, - stream_file_content=stream_file_content) - - -def test_create_subfolder(test_folder, mock_box_session, mock_object_id, mock_folder_response): - expected_url = test_folder.get_type_url() - mock_box_session.post.return_value = mock_folder_response - new_folder = test_folder.create_subfolder('name') - data = json.dumps({'name': 'name', 'parent': {'id': mock_object_id}}) - mock_box_session.post.assert_called_once_with(expected_url, data=data) - assert isinstance(new_folder, Folder) - assert new_folder.object_id == mock_object_id - - -def test_get_shared_link( - test_folder, - mock_box_session, - shared_link_access, - shared_link_unshared_at, - shared_link_password, - shared_link_can_download, - shared_link_can_preview, - shared_link_vanity_name, - test_url, - etag, - if_match_header, -): - # pylint:disable=redefined-outer-name, protected-access - expected_url = test_folder.get_url() - mock_box_session.put.return_value.json.return_value = { - 'type': test_folder.object_type, - 'id': test_folder.object_id, - 'shared_link': { - 'url': test_url, - }, - } - expected_data = {'shared_link': {}} - if shared_link_access is not None: - expected_data['shared_link']['access'] = shared_link_access - if shared_link_unshared_at is not SDK_VALUE_NOT_SET: - expected_data['shared_link']['unshared_at'] = normalize_date_to_rfc3339_format(shared_link_unshared_at) - if shared_link_can_download is not None or shared_link_can_preview is not None: - expected_data['shared_link']['permissions'] = permissions = {} - if shared_link_can_download is not None: - permissions['can_download'] = shared_link_can_download - if shared_link_can_preview is not None: - permissions['can_preview'] = shared_link_can_preview - if shared_link_password is not None: - expected_data['shared_link']['password'] = shared_link_password - if shared_link_vanity_name is not None: - expected_data['shared_link']['vanity_name'] = shared_link_vanity_name - - url = test_folder.get_shared_link( - etag=etag, - access=shared_link_access, - unshared_at=shared_link_unshared_at, - password=shared_link_password, - allow_download=shared_link_can_download, - allow_preview=shared_link_can_preview, - vanity_name=shared_link_vanity_name, - ) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(expected_data), - headers=if_match_header, - params=None, - ) - assert url == test_url - - -@pytest.mark.parametrize('sync_state', iter(FolderSyncState)) -def test_update_sync_state(test_folder, mock_folder_response, mock_box_session, sync_state): - expected_url = test_folder.get_url() - mock_box_session.put.return_value = mock_folder_response - data = {'sync_state': sync_state} - update_response = test_folder.update_sync_state(sync_state) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), params=None, headers=None) - assert isinstance(update_response, Folder) - assert update_response.object_id == test_folder.object_id - - -def test_preflight( - test_folder, - mock_object_id, - mock_box_session, - mock_accelerator_response_for_new_uploads, - mock_new_upload_accelerator_url, -): - new_file_size, new_file_name = 100, 'foo.txt' - mock_box_session.options.return_value = mock_accelerator_response_for_new_uploads - - accelerator_url = test_folder.preflight_check(size=new_file_size, name=new_file_name) - - mock_box_session.options.assert_called_once_with( - url=f'{API.BASE_API_URL}/files/content', - expect_json_response=True, - data=json.dumps( - { - 'size': new_file_size, - 'name': new_file_name, - 'parent': {'id': mock_object_id}, - } - ), - ) - assert accelerator_url == mock_new_upload_accelerator_url - - -def test_create_web_link_returns_the_correct_web_link_object(test_folder, mock_box_session): - expected_url = f"{API.BASE_API_URL}/web_links" - expected_name = 'Test WebLink' - description = 'Test Description' - test_web_link_url = 'https://test.com' - mock_box_session.post.return_value.json.return_value = { - 'type': 'web_link', - 'id': '42', - 'url': test_web_link_url, - 'name': expected_name, - 'description': description - } - new_web_link = test_folder.create_web_link(test_web_link_url, expected_name, description) - data = { - 'url': test_web_link_url, - 'parent': { - 'id': '42', - }, - 'name': expected_name, - 'description': description, - } - mock_box_session.post.assert_called_once_with( - expected_url, - data=json.dumps(data), - ) - assert isinstance(new_web_link, WebLink) - assert new_web_link.object_id == '42' - assert new_web_link.url == test_web_link_url - assert new_web_link.name == expected_name - assert new_web_link.description == description - - -def test_get_metadata_cascade_policies(test_folder, mock_box_session): - expected_url = f'{API.BASE_API_URL}/metadata_cascade_policies' - params = {'folder_id': test_folder.object_id} - mock_box_session.get.return_value.json.return_value = { - 'entries': [ - { - 'id': '84113349-794d-445c-b93c-d8481b223434', - 'type': 'metadata_cascade_policy', - 'parent': { - 'type': 'folder', - 'id': test_folder.object_id, - }, - 'scope': 'enterprise_11111', - 'templateKey': 'testTemplate', - } - ], - 'next_marker': None, - 'prev_marker': None, - } - - cascade_policies = test_folder.get_metadata_cascade_policies() - policy = cascade_policies.next() - - mock_box_session.get.assert_called_once_with(expected_url, params=params) - assert isinstance(policy, MetadataCascadePolicy) - assert policy.object_id == '84113349-794d-445c-b93c-d8481b223434' - assert policy.scope == 'enterprise_11111' - assert policy.templateKey == 'testTemplate' - # pylint: disable=protected-access - assert policy._session == mock_box_session - - -def test_cascade_metadata(test_folder, mock_box_session, test_metadata_template): - expected_url = f'{API.BASE_API_URL}/metadata_cascade_policies' - expected_body = { - 'folder_id': test_folder.object_id, - 'scope': test_metadata_template.scope, - 'templateKey': test_metadata_template.template_key, - } - mock_box_session.post.return_value.json.return_value = { - 'id': '84113349-794d-445c-b93c-d8481b223434', - 'type': 'metadata_cascade_policy', - 'owner_enterprise': { - 'type': 'enterprise', - 'id': '11111', - }, - 'parent': { - 'type': 'folder', - 'id': test_folder.object_id, - }, - 'scope': test_metadata_template.scope, - 'templateKey': test_metadata_template.template_key, - } - - cascade_policy = test_folder.cascade_metadata(test_metadata_template) - - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_body)) - assert isinstance(cascade_policy, MetadataCascadePolicy) - assert cascade_policy.object_id == '84113349-794d-445c-b93c-d8481b223434' - enterprise = cascade_policy.owner_enterprise - assert isinstance(enterprise, Enterprise) - assert enterprise.object_id == '11111' - folder = cascade_policy.parent - assert isinstance(folder, Folder) - assert folder.object_id == test_folder.object_id - assert cascade_policy.scope == test_metadata_template.scope - assert cascade_policy.templateKey == test_metadata_template.template_key - - -def test_get_folder_locks(test_folder, mock_box_session): - expected_url = f'{API.BASE_API_URL}/folder_locks' - params = {'folder_id': test_folder.object_id} - mock_box_session.get.return_value.json.return_value = { - "entries": [ - { - "folder": { - "id": "12345", - "etag": "1", - "type": "folder", - "sequence_id": "3", - "name": "Contracts" - }, - "id": "12345678", - "type": "folder_lock", - "created_by": { - "id": "11446498", - "type": "user" - }, - "created_at": "2020-09-14T23:12:53Z", - "locked_operations": { - "move": True, - "delete": True - }, - "lock_type": "freeze" - } - ], - "limit": 1000, - "next_marker": None - } - - folder_locks = test_folder.get_locks() - lock = folder_locks.next() - - mock_box_session.get.assert_called_once_with(expected_url, params=params) - assert lock.id == '12345678' - assert lock.folder.id == '12345' - assert lock.locked_operations['move'] - # pylint: disable=protected-access - assert lock._session == mock_box_session - - -def test_create_folder_lock(test_folder, mock_box_session): - expected_url = f'{API.BASE_API_URL}/folder_locks' - expected_body = { - "folder": { - "type": "folder", - "id": test_folder.object_id - }, - "locked_operations": { - "move": True, - "delete": True - } - } - mock_box_session.post.return_value.json.return_value = { - "id": "12345678", - "type": "folder_lock", - "created_at": "2020-09-14T23:12:53Z", - "created_by": { - "id": "11446498", - "type": "user" - }, - "folder": { - "id": "12345", - "type": "folder", - "etag": "1", - "name": "Contracts", - "sequence_id": "3" - }, - "lock_type": "freeze", - "locked_operations": { - "delete": True, - "move": True - } - } - - lock = test_folder.create_lock() - - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_body)) - assert lock.id == '12345678' - assert lock.folder.id == '12345' - assert lock.locked_operations['move'] - # pylint: disable=protected-access - assert lock._session == mock_box_session - - -def test_delete_folder_lock(test_folder_lock, mock_box_session): - expected_url = f'{API.BASE_API_URL}/folder_locks/{test_folder_lock.object_id}' - test_folder_lock.delete() - mock_box_session.delete.assert_called_once_with( - expected_url, - expect_json_response=False, - headers=None, - params={} - ) diff --git a/test/unit/object/test_group.py b/test/unit/object/test_group.py deleted file mode 100644 index f525d878c..000000000 --- a/test/unit/object/test_group.py +++ /dev/null @@ -1,259 +0,0 @@ -from itertools import chain, islice, repeat, count -import json -from operator import sub -from unittest.mock import Mock - -import pytest - -from boxsdk.network.default_network import DefaultNetworkResponse -from boxsdk.object.collaboration import Collaboration -from boxsdk.object.group_membership import GroupMembership -from boxsdk.object.user import User -from boxsdk.config import API -from boxsdk.session.box_response import BoxResponse - - -@pytest.fixture(scope='module') -def delete_group_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.ok = True - return mock_network_response - - -def test_delete_group_return_the_correct_response( - mock_group, - mock_box_session, - delete_group_response, -): - # pylint:disable=redefined-outer-name - mock_box_session.delete.return_value = delete_group_response - response = mock_group.delete() - - # pylint:disable=protected-access - expected_url = mock_group.get_url() - # pylint:enable=protected-access - mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None) - - assert response is True - - -@pytest.mark.parametrize('role', ['member', 'admin']) -def test_add_member(test_group, mock_box_session, mock_add_member_response, mock_user, role): - expected_url = f'{API.BASE_API_URL}/group_memberships' - mock_box_session.post.return_value = mock_add_member_response - new_group_membership = test_group.add_member(mock_user, role, configurable_permissions={'can_run_reports': True}) - data = json.dumps({ - 'user': {'id': mock_user.object_id}, - 'group': {'id': test_group.object_id}, - 'role': role, - 'configurable_permissions': {'can_run_reports': True} - }) - mock_box_session.post.assert_called_once_with(expected_url, data=data) - assert isinstance(new_group_membership, GroupMembership) - - -def test_add_member_default_permission(test_group, mock_box_session, mock_add_member_response, mock_user): - expected_url = f'{API.BASE_API_URL}/group_memberships' - mock_box_session.post.return_value = mock_add_member_response - new_group_membership = test_group.add_member(mock_user, 'member') - data = json.dumps({ - 'user': {'id': mock_user.object_id}, - 'group': {'id': test_group.object_id}, - 'role': 'member', - }) - mock_box_session.post.assert_called_once_with(expected_url, data=data) - assert isinstance(new_group_membership, GroupMembership) - - -def test_add_member_none_permission(test_group, mock_box_session, mock_add_member_response, mock_user): - expected_url = f'{API.BASE_API_URL}/group_memberships' - mock_box_session.post.return_value = mock_add_member_response - new_group_membership = test_group.add_member(mock_user, 'member', configurable_permissions=None) - data = json.dumps({ - 'user': {'id': mock_user.object_id}, - 'group': {'id': test_group.object_id}, - 'role': 'member', - 'configurable_permissions': None - }) - mock_box_session.post.assert_called_once_with(expected_url, data=data) - assert isinstance(new_group_membership, GroupMembership) - - -@pytest.fixture() -def mock_membership_dict_stream(): - def gen_data(some_id): - return { - 'type': 'group_membership', - 'id': f"membership_id_{some_id}", - 'role': 'member', - 'user': {'type': 'user', 'id': f"user_id_{some_id}"}, - 'group': {'type': 'group', 'id': f"group_id_{some_id}"}, - } - - return map(gen_data, count()) - - -@pytest.fixture() -def mock_membership_responses(mock_membership_dict_stream): - """ - Returns a generator method that takes params: total, page_size. - The generator generates a sequence of 'group membership' mock_box_responses each containing page_size - items, until 'total' entries have been returned - """ - # pylint:disable=redefined-outer-name - def number_entries_per_response(total, page_size, hidden_in_batch): - if not hidden_in_batch: - hidden_in_batch = repeat(0) - quotient, remainder = divmod(total, page_size) - max_items_in_batch = chain(repeat(page_size, quotient), (remainder,)) - return map(sub, max_items_in_batch, hidden_in_batch) - - def take(iterable, number): - return list(islice(iterable, number)) - - def membership_responses(total, page_size, hidden_in_batch=None): - offset = 0 - for number_entries in number_entries_per_response(total, page_size, hidden_in_batch): - entries = take(mock_membership_dict_stream, number_entries) - - mock_box_response = Mock(BoxResponse) - mock_network_response = Mock(DefaultNetworkResponse) - mock_box_response.network_response = mock_network_response - mock_box_response.json.return_value = { - 'entries': entries, - 'total_count': total, - 'offset': offset, - 'limit': page_size, - } - offset += page_size - mock_box_response.status_code = 200 - mock_box_response.ok = True - yield mock_box_response - - return membership_responses - - -@pytest.mark.parametrize('total, page_size', [ - (0, 6), - (5, 6), - (6, 6), - (5, 4), - (9, 4), -]) -def test_get_memberships(test_group, mock_box_session, mock_membership_responses, total, page_size): - # pylint:disable=redefined-outer-name - # Each call the 'get' (the GET next page call) will return the next response - mock_box_session.get.side_effect = mock_membership_responses(total, page_size) - - # Get all the members - all_members = test_group.get_memberships() - - # Assert we got the expected number of membership instances - count = 0 - for membership in all_members: - count += 1 - assert isinstance(membership, GroupMembership) - assert count == total - - -@pytest.mark.parametrize('hidden_in_batch', [ - (1, 0, 0), - (0, 0, 1), - (10, 10, 9), - (10, 10, 10), -]) -def test_get_memberships_with_hidden_results(test_group, mock_box_session, mock_membership_responses, hidden_in_batch): - """ - This test verifies that the SDK properly deals with missing (aka hidden) data potentially present in a paged - API. The API might indicate that the total_size is X, but in actuality the pages API could return less than X - because the auth'd user might not have access to all X of the resources. - """ - # pylint:disable=redefined-outer-name - total = 30 - page_size = 10 - - total_hidden = sum(hidden_in_batch) - - # Each call the 'get' (the GET next page call) will return the next response - mock_box_session.get.side_effect = mock_membership_responses(total, page_size, hidden_in_batch=hidden_in_batch) - - # Get all the members - all_members = test_group.get_memberships(limit=page_size, offset=0) - - # Assert we got the expected number of membership instances - count = 0 - for membership in all_members: - count += 1 - assert isinstance(membership, GroupMembership) - assert count == total - total_hidden - - -def test_get_group_collaborations(test_group, mock_box_session): - expected_url = f'{API.BASE_API_URL}/groups/{test_group.object_id}/collaborations' - mock_collaboration = { - 'type': 'collaboration', - 'id': '12345', - 'created_by': { - 'type': 'user', - 'id': '33333' - } - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [mock_collaboration], - 'offset': 0, - 'total_count': 1 - } - collaborations = test_group.get_collaborations(fields=['type', 'id', 'created_by']) - collaboration = collaborations.next() - mock_box_session.get.assert_called_once_with(expected_url, params={'offset': None, 'fields': 'type,id,created_by'}) - assert isinstance(collaboration, Collaboration) - assert collaboration.id == mock_collaboration['id'] - assert collaboration.created_by['id'] == mock_collaboration['created_by']['id'] - - -def test_base_api_json_object_returns_correctly(test_group_membership, mock_box_session): - expected_data = { - 'type': 'group_membership', - 'id': '12345', - 'test': [ - { - 'user': { - 'type': 'user' - } - }, - { - 'group': { - 'type': 'group' - } - }, - ], - 'user': { - 'type': 'user', - 'id': '5678', - 'name': 'Test User', - 'login': 'test@example.com', - }, - 'group': { - 'type': 'group', - 'id': '54321', - 'name': 'Test' - }, - 'role': 'admin', - 'configurable_permissions': { - 'can_run_reports': False, - 'can_instant_login': True, - 'can_create_accounts': False, - 'can_edit_accounts': True, - }, - 'created_at': '2013-05-16T15:27:57-07:00', - 'modified_at': '2013-05-16T15:27:57-07:00', - } - mock_box_session.get.return_value.json.return_value = expected_data - membership = test_group_membership.get() - membership_response = membership.response_object - assert isinstance(membership.user, User) - assert isinstance(membership_response, dict) - assert membership_response is not expected_data - assert membership_response == expected_data diff --git a/test/unit/object/test_item.py b/test/unit/object/test_item.py deleted file mode 100644 index a540f7396..000000000 --- a/test/unit/object/test_item.py +++ /dev/null @@ -1,460 +0,0 @@ -import json -import pytest - -from boxsdk.exception import BoxAPIException -from boxsdk.config import API -from boxsdk.object.watermark import Watermark -from boxsdk.object.collaboration import Collaboration -from boxsdk.exception import BoxValueError - - -@pytest.fixture(params=('file', 'folder')) -def test_item_and_response(test_file, test_folder, mock_file_response, mock_folder_response, request): - if request.param == 'file': - return test_file, mock_file_response - return test_folder, mock_folder_response - - -def test_update_info(test_item_and_response, mock_box_session, etag, if_match_header): - # pylint:disable=redefined-outer-name, protected-access - test_item, mock_item_response = test_item_and_response - expected_url = test_item.get_url() - mock_box_session.put.return_value = mock_item_response - data = {'foo': 'bar', 'baz': {'foo': 'bar'}, 'num': 4} - update_response = test_item.update_info(data=data, etag=etag) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=if_match_header, params=None) - assert isinstance(update_response, test_item.__class__) - assert update_response.object_id == test_item.object_id - - -def test_update_info_with_default_request_kwargs(test_item_and_response, mock_box_session, mock_box_session_2): - # pylint:disable=redefined-outer-name, protected-access - test_item, mock_item_response = test_item_and_response - expected_url = test_item.get_url() - mock_box_session.with_default_network_request_kwargs.return_value = mock_box_session_2 - mock_box_session_2.put.return_value = mock_item_response - data = {'foo': 'bar', 'baz': {'foo': 'bar'}, 'num': 4} - extra_network_parameters = {'timeout': 1} - update_response = test_item.update_info(data=data, extra_network_parameters=extra_network_parameters) - mock_box_session.with_default_network_request_kwargs.assert_called_once_with({'timeout': 1}) - mock_box_session_2.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None) - assert isinstance(update_response, test_item.__class__) - assert update_response.object_id == test_item.object_id - - -def test_clear_unshared_at_for_shared_link( - test_item_and_response, - mock_box_session, - test_url, -): - test_item, _ = test_item_and_response - expected_url = test_item.get_url() - mock_box_session.put.return_value.json.return_value = { - 'type': test_item.object_type, - 'id': test_item.object_id, - 'shared_link': { - 'url': test_url, - 'unshared_at': None, - }, - } - expected_data = {'shared_link': {'unshared_at': None, }, } - shared_link = test_item.get_shared_link(unshared_at=None) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(expected_data), - headers=None, - params=None, - ) - assert shared_link is test_url - - -def test_remove_shared_link(test_item_and_response, mock_box_session, etag, if_match_header): - # pylint:disable=redefined-outer-name, protected-access - test_item, _ = test_item_and_response - expected_url = test_item.get_url() - mock_box_session.put.return_value.json.return_value = { - 'type': test_item.object_type, - 'id': test_item.object_id, - 'shared_link': None, - } - removed = test_item.remove_shared_link(etag=etag) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps({'shared_link': None}), - headers=if_match_header, - params=None, - ) - assert removed is True - - -@pytest.mark.parametrize('fields', (None, ['name', 'created_at'])) -def test_get(test_item_and_response, mock_box_session, fields, mock_object_id, etag, if_none_match_header): - # pylint:disable=redefined-outer-name, protected-access - test_item, mock_item_response = test_item_and_response - expected_url = test_item.get_url() - mock_box_session.get.return_value = mock_item_response - expected_params = {'fields': ','.join(fields)} if fields else None - info = test_item.get(fields=fields, etag=etag) - mock_box_session.get.assert_called_once_with(expected_url, params=expected_params, headers=if_none_match_header) - assert isinstance(info, test_item.__class__) - assert info.id == mock_object_id - - -def test_get_watermark(test_item_and_response, mock_box_session): - test_item, _ = test_item_and_response - created_at = '2016-10-31T15:33:33-07:00' - modified_at = '2016-10-31T15:33:33-07:00' - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}/watermark' - mock_box_session.get.return_value.json.return_value = { - 'watermark': { - 'created_at': created_at, - 'modified_at': modified_at, - }, - } - watermark = test_item.get_watermark() - mock_box_session.get.assert_called_once_with(expected_url) - assert isinstance(watermark, Watermark) - assert watermark['created_at'] == created_at - assert watermark['modified_at'] == modified_at - - -def test_apply_watermark(test_item_and_response, mock_box_session): - test_item, _ = test_item_and_response - created_at = '2016-10-31T15:33:33-07:00' - modified_at = '2016-10-31T15:33:33-07:00' - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}/watermark' - mock_box_session.put.return_value.json.return_value = { - 'watermark': { - 'created_at': created_at, - 'modified_at': modified_at, - }, - } - watermark = test_item.apply_watermark() - mock_box_session.put.assert_called_once_with(expected_url, data='{"watermark": {"imprint": "default"}}') - assert isinstance(watermark, Watermark) - assert watermark['created_at'] == created_at - assert watermark['modified_at'] == modified_at - - -def test_delete_watermark(test_item_and_response, mock_box_session): - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}/watermark' - mock_box_session.delete.return_value.ok = True - is_watermark_deleted = test_item.delete_watermark() - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False) - assert is_watermark_deleted is True - - -def test_collaborate_with_group(test_item_and_response, test_group, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/collaborations' - expected_data = { - 'item': { - 'type': test_item.object_type, - 'id': test_item.object_id, - }, - 'accessible_by': { - 'type': test_group.object_type, - 'id': test_group.object_id, - }, - 'role': 'editor', - } - mock_collaboration = { - 'type': 'collaboration', - 'id': '1234', - 'created_by': { - 'type': 'user', - 'id': '1111', - } - } - mock_box_session.post.return_value.json.return_value = mock_collaboration - collaboration = test_item.collaborate(test_group, 'editor') - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data), params={}) - assert collaboration.id == mock_collaboration['id'] - assert collaboration['type'] == mock_collaboration['type'] - assert collaboration['created_by']['id'] == mock_collaboration['created_by']['id'] - - -@pytest.mark.parametrize('can_view_path,fields,notify,data,params', [ - (None, None, None, {}, {}), - (True, None, None, {'can_view_path': True}, {}), - (False, None, None, {'can_view_path': False}, {}), - (None, ['type', 'id', 'created_by'], None, {}, {'fields': 'type,id,created_by'}), - (None, None, True, {}, {'notify': True}), - (None, None, False, {}, {'notify': False}), - (True, ['type', 'id', 'created_by'], False, {'can_view_path': True}, {'fields': 'type,id,created_by', 'notify': False}) -]) -def test_collaborate_with_user(test_item_and_response, mock_user, mock_box_session, can_view_path, fields, notify, data, params): - # pylint:disable=redefined-outer-name, protected-access - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/collaborations' - expected_data = { - 'item': { - 'type': test_item.object_type, - 'id': test_item.object_id, - }, - 'accessible_by': { - 'type': mock_user.object_type, - 'id': mock_user.object_id, - }, - 'role': 'editor', - } - expected_data.update(data) - mock_collaboration = { - 'type': 'collaboration', - 'id': '1234', - 'created_by': { - 'type': 'user', - 'id': '1111', - } - } - expected_params = params - mock_box_session.post.return_value.json.return_value = mock_collaboration - collaboration = test_item.collaborate(mock_user, 'editor', can_view_path=can_view_path, fields=fields, notify=notify) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data), params=expected_params) - assert collaboration.id == mock_collaboration['id'] - assert collaboration['type'] == mock_collaboration['type'] - assert collaboration['created_by']['id'] == mock_collaboration['created_by']['id'] - - -@pytest.mark.parametrize('can_view_path,fields,notify,data,params', [ - (None, None, None, {}, {}), - (True, None, None, {'can_view_path': True}, {}), - (False, None, None, {'can_view_path': False}, {}), - (None, ['type', 'id', 'created_by'], None, {}, {'fields': 'type,id,created_by'}), - (None, None, True, {}, {'notify': True}), - (None, None, False, {}, {'notify': False}), - (True, ['type', 'id', 'created_by'], False, {'can_view_path': True}, {'fields': 'type,id,created_by', 'notify': False}) -]) -def test_collaborate_with_login(test_item_and_response, mock_box_session, can_view_path, fields, notify, data, params): - # pylint:disable=redefined-outer-name, protected-access - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/collaborations' - expected_data = { - 'item': { - 'type': test_item.object_type, - 'id': test_item.object_id, - }, - 'accessible_by': { - 'type': 'user', - 'login': 'test@example.com', - }, - 'role': 'editor', - } - expected_data.update(data) - mock_collaboration = { - 'type': 'collaboration', - 'id': '1234', - 'created_by': { - 'type': 'user', - 'id': '1111', - } - } - expected_params = params - mock_box_session.post.return_value.json.return_value = mock_collaboration - collaboration = test_item.collaborate_with_login('test@example.com', 'editor', can_view_path=can_view_path, fields=fields, notify=notify) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data), params=expected_params) - assert collaboration.id == mock_collaboration['id'] - assert collaboration['type'] == mock_collaboration['type'] - assert collaboration['created_by']['id'] == mock_collaboration['created_by']['id'] - - -def test_collaborations(test_item_and_response, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}/collaborations' - mock_collaboration = { - 'type': 'collaboration', - 'id': '12345', - 'created_by': { - 'type': 'user', - 'id': '33333', - }, - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 500, - 'entries': [mock_collaboration] - } - collaborations = test_item.get_collaborations(limit=500) - collaboration = collaborations.next() - mock_box_session.get.assert_called_once_with(expected_url, params={'limit': 500}) - assert isinstance(collaboration, Collaboration) - assert collaboration.id == mock_collaboration['id'] - assert collaboration.type == mock_collaboration['type'] - assert collaboration['created_by']['type'] == 'user' - assert collaboration['created_by']['id'] == '33333' - - -def test_get_all_metadata(test_item_and_response, mock_box_session): - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}/metadata' - mock_metadata = { - 'currentDocumentStage': 'prioritization', - 'needsApprovalFrom': 'planning team', - '$type': 'documentFlow-452b4c9d-c3ad-4ac7-b1ad-9d5192f2fc5f', - '$parent': 'folder_998951261', - '$id': 'e57f90ff-0044-48c2-807d-06b908765baf', - '$version': 1, - '$typeVersion': 2, - 'maximumDaysAllowedInCurrentStage': 5, - '$template': 'documentFlow', - '$scope': 'enterprise_12345', - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [mock_metadata] - } - - all_metadata = test_item.get_all_metadata() - metadata = all_metadata.next() - - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(metadata, dict) - for key in metadata: - assert mock_metadata[key] == mock_metadata[key] - - -def test_add_classification(test_item_and_response, mock_box_session): - # pylint:disable=redefined-outer-name - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}' \ - f'/metadata/enterprise/securityClassification-6VMVochwUWo' - metadata_response = { - 'Box__Security__Classification__Key': 'Public', - } - metadata_response = mock_box_session.post.return_value.json.return_value = metadata_response - data = { - 'Box__Security__Classification__Key': 'Public' - } - headers = { - b'Content-Type': b'application/json' - } - metadata = test_item.add_classification('Public') - mock_box_session.post.assert_called_once_with(expected_url, headers=headers, data=json.dumps(data)) - assert metadata is metadata_response['Box__Security__Classification__Key'] - - -def test_update_classification(test_item_and_response, mock_box_session): - # pylint:disable=redefined-outer-name - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}' \ - f'/metadata/enterprise/securityClassification-6VMVochwUWo' - metadata_response = { - 'Box__Security__Classification__Key': 'Internal', - } - metadata_response = mock_box_session.put.return_value.json.return_value = metadata_response - data = [{ - 'op': 'add', - 'path': '/Box__Security__Classification__Key', - 'value': 'Internal', - }] - headers = { - b'Content-Type': b'application/json-patch+json' - } - metadata = test_item.update_classification('Internal') - mock_box_session.put.assert_called_once_with(expected_url, headers=headers, data=json.dumps(data)) - assert metadata is metadata_response['Box__Security__Classification__Key'] - - -def test_set_classification_succeeds(test_item_and_response, mock_box_session): - # pylint:disable=redefined-outer-name - test_item, _ = test_item_and_response - metadata_response = { - 'Box__Security__Classification__Key': 'Public', - } - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}' \ - f'/metadata/enterprise/securityClassification-6VMVochwUWo' - post_data = { - 'Box__Security__Classification__Key': 'Public', - } - put_data = [{ - 'op': 'add', - 'path': '/Box__Security__Classification__Key', - 'value': 'Public', - }] - post_headers = { - b'Content-Type': b'application/json' - } - put_headers = { - b'Content-Type': b'application/json-patch+json' - } - mock_box_session.post.side_effect = [BoxAPIException(status=409)] - mock_box_session.put.return_value.json.return_value = metadata_response - metadata = test_item.set_classification('Public') - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(post_data), headers=post_headers) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(put_data), headers=put_headers) - assert metadata is metadata_response['Box__Security__Classification__Key'] - - -def test_set_classification_fails(test_item_and_response, mock_box_session): - # pylint:disable=redefined-outer-name - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}' \ - f'/metadata/enterprise/securityClassification-6VMVochwUWo' - post_data = { - 'Box__Security__Classification__Key': 'Public', - } - post_headers = { - b'Content-Type': b'application/json' - } - mock_box_session.post.side_effect = [BoxAPIException(status=500)] - with pytest.raises(BoxAPIException): - test_item.set_classification('Public') - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(post_data), headers=post_headers) - - -def test_get_classification_succeeds(test_item_and_response, mock_box_session): - # pylint:disable=redefined-outer-name - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}' \ - f'/metadata/enterprise/securityClassification-6VMVochwUWo' - metadata_response = { - 'Box__Security__Classification__Key': 'Public' - } - mock_box_session.get.return_value.json.return_value = metadata_response - metadata = test_item.get_classification() - assert metadata is metadata_response['Box__Security__Classification__Key'] - mock_box_session.get.assert_called_once_with(expected_url) - - -def test_get_classification_not_found(test_item_and_response, mock_box_session): - # pylint:disable=redefined-outer-name - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}' \ - f'/metadata/enterprise/securityClassification-6VMVochwUWo' - mock_box_session.get.side_effect = [BoxAPIException(status=404, code="instance_not_found")] - metadata = test_item.get_classification() - assert metadata is None - mock_box_session.get.assert_called_once_with(expected_url) - - -def test_get_classification_raises_exception(test_item_and_response, mock_box_session): - # pylint:disable=redefined-outer-name - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}' \ - f'/metadata/enterprise/securityClassification-6VMVochwUWo' - mock_box_session.get.side_effect = [BoxAPIException(status=500)] - with pytest.raises(BoxAPIException): - test_item.get_classification() - mock_box_session.get.assert_called_once_with(expected_url) - - -def test_remove_classification(test_item_and_response, mock_box_session, make_mock_box_request): - # pylint:disable=redefined-outer-name - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type}s/{test_item.object_id}' \ - f'/metadata/enterprise/securityClassification-6VMVochwUWo' - mock_box_session.delete.return_value, _ = make_mock_box_request(response_ok='success') - is_removed = test_item.remove_classification() - mock_box_session.delete.assert_called_once_with(expected_url) - assert is_removed == 'success' - - -def test_sanitize_item_id(test_item_and_response): - # pylint:disable=redefined-outer-name, protected-access - test_item, _ = test_item_and_response - assert test_item.validate_item_id(test_item._object_id) is None - test_item._object_id = "foo" - with pytest.raises(BoxValueError): - test_item.validate_item_id(test_item._object_id) diff --git a/test/unit/object/test_legal_hold.py b/test/unit/object/test_legal_hold.py deleted file mode 100644 index c4ee7916d..000000000 --- a/test/unit/object/test_legal_hold.py +++ /dev/null @@ -1,21 +0,0 @@ -from boxsdk.object.legal_hold import LegalHold -from boxsdk.config import API - - -def test_get(test_legal_hold, mock_box_session): - file_version_id = '1234' - expected_url = f'{API.BASE_API_URL}/file_version_legal_holds/{test_legal_hold.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': 'legal_hold', - 'id': test_legal_hold.object_id, - 'file_version': { - 'type': 'file_version', - 'id': file_version_id, - }, - } - file_version_legal_hold = test_legal_hold.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(file_version_legal_hold, LegalHold) - assert file_version_legal_hold['file_version']['id'] == file_version_id - assert file_version_legal_hold.type == 'legal_hold' - assert file_version_legal_hold.id == test_legal_hold.object_id diff --git a/test/unit/object/test_legal_hold_assignment.py b/test/unit/object/test_legal_hold_assignment.py deleted file mode 100644 index 9cec5bfef..000000000 --- a/test/unit/object/test_legal_hold_assignment.py +++ /dev/null @@ -1,48 +0,0 @@ -from unittest.mock import Mock -import pytest - -from boxsdk.object.legal_hold_policy_assignment import LegalHoldPolicyAssignment -from boxsdk.config import API -from boxsdk.network.default_network import DefaultNetworkResponse - - -def test_get(test_legal_hold_policy_assignment, mock_box_session): - expected_url = f'{API.BASE_API_URL}/legal_hold_policy_assignments/{test_legal_hold_policy_assignment.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': 'legal_hold_policy_assignment', - 'id': test_legal_hold_policy_assignment.object_id, - 'assigned_to': { - 'type': 'user', - 'id': '1234', - }, - } - legal_hold_policy_assignment = test_legal_hold_policy_assignment.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(legal_hold_policy_assignment, LegalHoldPolicyAssignment) - assert legal_hold_policy_assignment.type == 'legal_hold_policy_assignment' - assert legal_hold_policy_assignment.object_id == test_legal_hold_policy_assignment.object_id - assert legal_hold_policy_assignment['assigned_to']['type'] == 'user' - assert legal_hold_policy_assignment['assigned_to']['id'] == '1234' - - -@pytest.fixture(scope='module') -def delete_assignment_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.ok = True - return mock_network_response - - -def test_delete_policy_return_the_correct_response( - test_legal_hold_policy_assignment, - mock_box_session, - delete_assignment_response, -): - # pylint:disable=redefined-outer-name - mock_box_session.delete.return_value = delete_assignment_response - response = test_legal_hold_policy_assignment.delete() - # pylint:disable=protected-access - expected_url = test_legal_hold_policy_assignment.get_url() - # pylint:enable = protected-access - mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None) - assert response is True diff --git a/test/unit/object/test_legal_hold_policy.py b/test/unit/object/test_legal_hold_policy.py deleted file mode 100644 index 9d96c8380..000000000 --- a/test/unit/object/test_legal_hold_policy.py +++ /dev/null @@ -1,160 +0,0 @@ -from unittest.mock import Mock -import json -import pytest - -from boxsdk.object.legal_hold_policy import LegalHoldPolicy -from boxsdk.object.legal_hold_policy_assignment import LegalHoldPolicyAssignment -from boxsdk.config import API -from boxsdk.network.default_network import DefaultNetworkResponse - - -@pytest.fixture(scope='module') -def policy_id_1(): - return 101 - - -@pytest.fixture(scope='module') -def policy_id_2(): - return 202 - - -@pytest.fixture(scope='module') -def legal_hold_id_1(): - return 101 - - -@pytest.fixture(scope='module') -def legal_hold_id_2(): - return 202 - - -@pytest.fixture(scope='module') -def policies_response(policy_id_1, policy_id_2): - # pylint disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'entries': [ - {'type': 'legal_hold_policy', 'id': policy_id_1, 'name': 'Test Policy 1'}, - {'type': 'legal_hold_policy', 'id': policy_id_2, 'name': 'Test Policy 2'} - ], - 'limit': 5, - } - return mock_network_response - - -@pytest.fixture(scope='module') -def legal_hold_response(legal_hold_id_1, legal_hold_id_2): - # pylint disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'entries': [ - {'type': 'legal_hold', 'id': legal_hold_id_1, 'name': 'Test Legal Hold 1'}, - {'type': 'legal_hold', 'id': legal_hold_id_2, 'name': 'Test Legal Hold 2'} - ], - 'limit': 5, - } - return mock_network_response - - -def test_assign(test_legal_hold_policy, mock_box_session, test_file): - assignment_id = '12345' - assigned_at = '2016-05-18T17:38:03-07:00' - expected_url = f'{API.BASE_API_URL}/legal_hold_policy_assignments' - expected_body = { - 'policy_id': test_legal_hold_policy.object_id, - 'assign_to': { - 'type': 'file', - 'id': test_file.object_id - } - } - mock_box_session.post.return_value.json.return_value = { - 'type': 'legal_hold_policy_assignment', - 'id': assignment_id, - 'assigned_at': assigned_at, - } - new_legal_hold_assignment = test_legal_hold_policy.assign(test_file) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_body)) - assert isinstance(new_legal_hold_assignment, LegalHoldPolicyAssignment) - assert new_legal_hold_assignment.assigned_at == assigned_at - assert new_legal_hold_assignment.id == assignment_id - - -def test_get(test_legal_hold_policy, mock_box_session): - created_at = '2016-05-18T17:38:03-07:00' - expected_url = f'{API.BASE_API_URL}/legal_hold_policies/{test_legal_hold_policy.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': 'legal_hold_policy', - 'id': test_legal_hold_policy.object_id, - 'created_at': created_at - } - legal_hold_policy = test_legal_hold_policy.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(legal_hold_policy, LegalHoldPolicy) - assert legal_hold_policy.created_at == created_at - - -@pytest.fixture(scope='module') -def delete_policy_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.ok = True - return mock_network_response - - -def test_delete_policy_return_the_correct_response( - test_legal_hold_policy, - mock_box_session, - delete_policy_response, -): - # pylint:disable=redefined-outer-name - mock_box_session.delete.return_value = delete_policy_response - response = test_legal_hold_policy.delete() - # pylint:disable=protected-access - expected_url = test_legal_hold_policy.get_url() - # pylint:enable = protected-access - mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None) - assert response is True - - -@pytest.mark.parametrize('assign_to_type, assign_to_id, params', [ - (None, None, {}), - ('file', None, {'assign_to_type': 'file'}), - ('folder', '22222', {'assign_to_type': 'folder', 'assign_to_id': '22222'}) -]) -def test_get_assignments( - test_legal_hold_policy, - mock_box_session, - policies_response, - policy_id_1, - policy_id_2, - assign_to_type, - assign_to_id, - params, -): - # pylint:disable=redefined-outer-name - expected_url = f'{API.BASE_API_URL}/legal_hold_policy_assignments' - expected_params = {'policy_id': test_legal_hold_policy.object_id} - expected_params.update(params) - mock_box_session.get.return_value = policies_response - assignments = test_legal_hold_policy.get_assignments(assign_to_type=assign_to_type, assign_to_id=assign_to_id) - for assignment, expected_id in zip(assignments, [policy_id_1, policy_id_2]): - assert assignment.object_id == expected_id - # pylint:disable=protected-access - assert assignment._session == mock_box_session - mock_box_session.get.assert_called_once_with(expected_url, params=expected_params) - - -def test_get_file_version_legal_holds( - test_legal_hold_policy, - mock_box_session, - legal_hold_response, - legal_hold_id_1, - legal_hold_id_2 -): - # pylint:disable=redefined-outer-name - mock_box_session.get.return_value = legal_hold_response - legal_holds = test_legal_hold_policy.get_file_version_legal_holds() - for legal_hold, expected_id in zip(legal_holds, [legal_hold_id_1, legal_hold_id_2]): - assert legal_hold.object_id == expected_id - # pylint:disable=protected-access - assert legal_hold._session == mock_box_session diff --git a/test/unit/object/test_metadata.py b/test/unit/object/test_metadata.py deleted file mode 100644 index 592296e9d..000000000 --- a/test/unit/object/test_metadata.py +++ /dev/null @@ -1,142 +0,0 @@ -import json -import pytest -from boxsdk.object.metadata import MetadataUpdate -from boxsdk.exception import BoxAPIException - - -@pytest.fixture -def metadata_response(): - return { - '$id': 'c79896a0-a33f-11e3-a5e2-0800200c9a66', - '$type': 'properties', - '$parent': 'file_552345101', - 'client_number': '820183', - 'client_name': 'Biomedical Corp', - 'case_reference': 'A83JAA', - 'case_type': 'Employment Litigation', - 'assigned_attorney': 'Francis Burke', - 'case_status': 'in-progress', - } - - -@pytest.fixture(params=['enterprise', 'global']) -def metadata_scope(request): - return request.param - - -@pytest.fixture(params=['properties', 'custom']) -def metadata_template(request): - return request.param - - -@pytest.fixture(params=['file', 'folder']) -def test_object(test_file, test_folder, request): - if request.param == 'file': - return test_file - return test_folder - - -@pytest.mark.parametrize('success', [True, False]) -def test_delete(mock_box_session, make_mock_box_request, test_object, metadata_scope, metadata_template, success): - # pylint:disable=redefined-outer-name - mock_box_session.delete.return_value, _ = make_mock_box_request(response_ok=success) - metadata = test_object.metadata(metadata_scope, metadata_template) - assert metadata.delete() is success - mock_box_session.delete.assert_called_once_with(metadata.get_url()) - - -def test_create( - mock_box_session, - make_mock_box_request, - test_object, - metadata_scope, - metadata_template, - metadata_response, -): - # pylint:disable=redefined-outer-name - mock_box_session.post.return_value, _ = make_mock_box_request(response=metadata_response) - metadata = test_object.metadata(metadata_scope, metadata_template) - response = metadata.create(metadata_response) - assert response is metadata_response - mock_box_session.post.assert_called_once_with( - metadata.get_url(), - data=json.dumps(metadata_response), - headers={b'Content-Type': b'application/json'}, - ) - - -def test_set( - mock_box_session, - test_object, - metadata_scope, - metadata_template, - metadata_response, -): - post_data = { - 'case_status': 'in-progress', - } - post_value = json.dumps(post_data) - put_value = json.dumps([{ - 'op': 'add', - 'path': '/case_status', - 'value': 'in-progress', - }]) - mock_box_session.post.side_effect = [BoxAPIException(status=409, message="Conflict")] - mock_box_session.put.return_value.json.return_value = metadata_response - metadata = test_object.metadata(metadata_scope, metadata_template) - response = metadata.set(post_data) - assert response is metadata_response - mock_box_session.post.assert_called_once_with(metadata.get_url(), data=post_value, headers={b'Content-Type': b'application/json'}) - mock_box_session.put.assert_called_once_with(metadata.get_url(), data=put_value, headers={b'Content-Type': b'application/json-patch+json'}) - - -def test_get( - mock_box_session, - make_mock_box_request, - test_object, - metadata_scope, - metadata_template, - metadata_response, -): - # pylint:disable=redefined-outer-name - mock_box_session.get.return_value, _ = make_mock_box_request(response=metadata_response) - metadata = test_object.metadata(metadata_scope, metadata_template) - response = metadata.get() - assert response is metadata_response - mock_box_session.get.assert_called_once_with(metadata.get_url()) - - -@pytest.fixture -def metadata_update(): - update = MetadataUpdate() - update.add('path', 'value') - update.remove('path', 'value') - update.test('path', 'value') - update.update('path', 'value', 'value') - return update - - -def test_update( - mock_box_session, - make_mock_box_request, - test_object, - metadata_scope, - metadata_template, - metadata_response, - metadata_update, -): - # pylint:disable=redefined-outer-name - mock_box_session.put.return_value, _ = make_mock_box_request(response=metadata_response) - metadata = test_object.metadata(metadata_scope, metadata_template) - response = metadata.update(metadata_update) - assert response is metadata_response - mock_box_session.put.assert_called_once_with( - metadata.get_url(), - data=json.dumps(metadata_update.ops), - headers={b'Content-Type': b'application/json-patch+json'}, - ) - - -def test_start_update(test_object): - update = test_object.metadata().start_update() - assert isinstance(update, MetadataUpdate) diff --git a/test/unit/object/test_metadata_cascade_policy.py b/test/unit/object/test_metadata_cascade_policy.py deleted file mode 100644 index 8e23a44d6..000000000 --- a/test/unit/object/test_metadata_cascade_policy.py +++ /dev/null @@ -1,70 +0,0 @@ -import json -import pytest - -from boxsdk.config import API -from boxsdk.object.enterprise import Enterprise -from boxsdk.object.folder import Folder -from boxsdk.object.metadata_cascade_policy import MetadataCascadePolicy, CascadePolicyConflictResolution - - -@pytest.fixture() -def test_cascade_policy(mock_box_session): - return MetadataCascadePolicy(mock_box_session, 'test_cascade_policy_id') - - -def test_get(test_cascade_policy, mock_box_session): - expected_url = f'{API.BASE_API_URL}/metadata_cascade_policies/{test_cascade_policy.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'id': '84113349-794d-445c-b93c-d8481b223434', - 'type': 'metadata_cascade_policy', - 'owner_enterprise': { - 'type': 'enterprise', - 'id': '11111', - }, - 'parent': { - 'type': 'folder', - 'id': '22222', - }, - 'scope': 'enterprise_11111', - 'templateKey': 'testTemplate', - } - - cascade_policy = test_cascade_policy.get() - - mock_box_session.get.assert_called_once_with(expected_url, params=None, headers=None) - assert isinstance(cascade_policy, MetadataCascadePolicy) - enterprise = cascade_policy.owner_enterprise - assert isinstance(enterprise, Enterprise) - assert enterprise.object_id == '11111' - folder = cascade_policy.parent - assert isinstance(folder, Folder) - assert folder.object_id == '22222' - assert cascade_policy.scope == 'enterprise_11111' - assert cascade_policy.templateKey == 'testTemplate' - - -def test_delete(test_cascade_policy, mock_box_session): - expected_url = f'{API.BASE_API_URL}/metadata_cascade_policies/{test_cascade_policy.object_id}' - mock_box_session.delete.return_value.ok = True - - result = test_cascade_policy.delete() - - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=None, params={}) - assert result is True - - -def test_force_apply(test_cascade_policy, mock_box_session): - expected_url = f'{API.BASE_API_URL}/metadata_cascade_policies/{test_cascade_policy.object_id}/apply' - expected_body = { - 'conflict_resolution': 'overwrite', - } - mock_box_session.post.return_value.ok = True - - result = test_cascade_policy.force_apply(CascadePolicyConflictResolution.OVERWRITE) - - mock_box_session.post.assert_called_once_with( - expected_url, - data=json.dumps(expected_body), - expect_json_response=False - ) - assert result is True diff --git a/test/unit/object/test_metadata_template.py b/test/unit/object/test_metadata_template.py deleted file mode 100644 index 2c0c079a9..000000000 --- a/test/unit/object/test_metadata_template.py +++ /dev/null @@ -1,165 +0,0 @@ -import json - -from boxsdk.config import API -from boxsdk.object.metadata_template import MetadataTemplate, MetadataField, MetadataFieldType - - -def test_get(test_metadata_template, mock_box_session): - expected_url = f'{API.BASE_API_URL}/metadata_templates/{test_metadata_template.scope}/{test_metadata_template.template_key}/schema' - mock_box_session.get.return_value.json.return_value = { - 'type': 'metadata_template', - 'scope': 'enterprise', - 'displayName': 'Vendor Contract', - 'hidden': True, - 'fields': [ - { - 'type': 'date', - 'displayName': 'Birthday', - 'key': 'bday', - }, - { - 'type': 'enum', - 'displayName': 'State', - 'options': [ - {'key': 'CA'}, - {'key': 'TX'}, - {'key': 'NY'}, - ], - }, - ], - 'templateKey': 'vContract', - } - - template = test_metadata_template.get() - - mock_box_session.get.assert_called_once_with(expected_url, params=None, headers=None) - assert isinstance(template, MetadataTemplate) - assert template.object_id is None - assert template.displayName == 'Vendor Contract' - fields = template.fields - assert len(fields) == 2 - field = fields[0] - assert isinstance(field, dict) - assert field['type'] == 'date' - assert field['key'] == 'bday' - - -def test_delete(test_metadata_template, mock_box_session): - expected_url = f'{API.BASE_API_URL}/metadata_templates/{test_metadata_template.scope}/{test_metadata_template.template_key}/schema' - mock_box_session.delete.return_value.ok = True - - result = test_metadata_template.delete() - - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=None, params={}) - assert result is True - - -def test_update_info(test_metadata_template, mock_box_session): - expected_url = f'{API.BASE_API_URL}/metadata_templates/{test_metadata_template.scope}/{test_metadata_template.template_key}/schema' - updates = test_metadata_template.start_update() - updates.add_enum_option('state', 'WI') - updates.add_field( - MetadataField(field_type=MetadataFieldType.STRING, display_name='Name', description="Description", hidden=False) - ) - updates.reorder_enum_options('state', ['CA', 'NY', 'TX', 'WI']) - updates.reorder_fields(['bday', 'name', 'state']) - updates.edit_field('state', MetadataField(None, 'State of Residency')) - updates.edit_enum_option_key('state', 'WI', 'WY') - updates.remove_enum_option('state', 'NY') - updates.remove_field('bday') - updates.edit_template({'hidden': False}) - - expected_body = [ - { - 'op': 'addEnumOption', - 'fieldKey': 'state', - 'data': {'key': 'WI'}, - }, - { - 'op': 'addField', - 'data': { - 'type': 'string', - 'displayName': 'Name', - "description": "Description", - "hidden": False - }, - }, - { - 'op': 'reorderEnumOptions', - 'fieldKey': 'state', - 'enumOptionKeys': ['CA', 'NY', 'TX', 'WI'], - }, - { - 'op': 'reorderFields', - 'fieldKeys': ['bday', 'name', 'state'], - }, - { - 'op': 'editField', - 'fieldKey': 'state', - 'data': { - 'displayName': 'State of Residency', - }, - }, - { - 'op': 'editEnumOption', - 'fieldKey': 'state', - 'enumOptionKey': 'WI', - 'data': { - 'key': 'WY', - }, - }, - { - 'op': 'removeEnumOption', - 'fieldKey': 'state', - 'enumOptionKey': 'NY', - }, - { - 'op': 'removeField', - 'fieldKey': 'bday', - }, - { - 'op': 'editTemplate', - 'data': {'hidden': False}, - }, - ] - - mock_box_session.put.return_value.json.return_value = { - 'type': 'metadata_template', - 'scope': 'enterprise', - 'displayName': 'Vendor Contract', - 'hidden': False, - 'fields': [ - { - 'type': 'string', - 'key': 'name', - 'displayName': 'Name', - 'description': 'Description', - 'hidden': False - }, - { - 'type': 'enum', - 'key': 'state', - 'displayName': 'State of Residency', - 'options': [ - {'key': 'CA'}, - {'key': 'TX'}, - {'key': 'WY'}, - ], - }, - ], - 'templateKey': 'vContract', - } - - updated_template = test_metadata_template.update_info(updates=updates) - - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(expected_body), headers=None, params=None) - assert isinstance(updated_template, MetadataTemplate) - assert updated_template.hidden is False - assert updated_template.object_id is None - fields = updated_template.fields - assert len(fields) == 2 - field = fields[1] - assert field['type'] == 'enum' - assert field['displayName'] == 'State of Residency' - assert len(field['options']) == 3 - assert field['options'][2]['key'] == 'WY' diff --git a/test/unit/object/test_retention_policy.py b/test/unit/object/test_retention_policy.py deleted file mode 100644 index 0c50639fb..000000000 --- a/test/unit/object/test_retention_policy.py +++ /dev/null @@ -1,105 +0,0 @@ -import json - -from boxsdk.config import API -from boxsdk.object.retention_policy import RetentionPolicy -from boxsdk.object.retention_policy_assignment import RetentionPolicyAssignment - - -def test_get(test_retention_policy, mock_box_session): - expected_url = f'{API.BASE_API_URL}/retention_policies/{test_retention_policy.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': test_retention_policy.object_type, - 'id': test_retention_policy.object_id, - 'policy_name': 'Policy Name', - 'policy_type': 'finite', - 'retention_length': '10', - 'disposition_action': 'permanently_delete', - 'description': 'Description', - } - retention_policy = test_retention_policy.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(retention_policy, RetentionPolicy) - assert retention_policy['type'] == test_retention_policy.object_type - assert retention_policy['id'] == test_retention_policy.object_id - assert retention_policy['policy_name'] == 'Policy Name' - assert retention_policy['description'] == 'Description' - - -def test_update(test_retention_policy, mock_box_session): - new_policy_name = 'New Name' - new_retention_type = 'modifiable' - new_retention_length = 60 - new_description = 'New Description' - expected_url = f'{API.BASE_API_URL}/retention_policies/{test_retention_policy.object_id}' - mock_box_session.put.return_value.json.return_value = { - 'type': test_retention_policy.object_type, - 'id': test_retention_policy.object_id, - 'policy_name': new_policy_name, - 'policy_type': 'finite', - 'retention_length': new_retention_length, - 'retention_type': new_retention_type, - 'description': new_description, - } - data = { - 'policy_name': new_policy_name, - 'retention_type': new_retention_type, - 'retention_length': new_retention_length, - 'description': new_description, - } - retention_policy = test_retention_policy.update_info(data=data) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None) - assert isinstance(retention_policy, RetentionPolicy) - assert retention_policy['type'] == test_retention_policy.object_type - assert retention_policy['id'] == test_retention_policy.object_id - assert retention_policy['policy_name'] == new_policy_name - assert retention_policy['retention_type'] == new_retention_type - assert retention_policy['retention_length'] == new_retention_length - assert retention_policy['description'] == new_description - - -def test_assign(test_retention_policy, test_folder, mock_box_session): - policy_id = '42' - expected_url = mock_box_session.get_url('retention_policy_assignments') - expected_data = { - 'policy_id': policy_id, - 'assign_to': { - 'type': test_folder.object_type, - 'id': test_folder.object_id, - }, - "start_date_field": "upload_date" - } - mock_assignment = { - 'type': 'retention_policy_assignment', - 'id': '1234', - 'retention_policy': { - 'type': 'retention_policy', - 'id': policy_id, - }, - "start_date_field": "upload_date" - } - mock_box_session.post.return_value.json.return_value = mock_assignment - assignment = test_retention_policy.assign(test_folder, start_date_field='upload_date') - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data), params={}) - assert assignment.id == mock_assignment['id'] - assert assignment.retention_policy['id'] == mock_assignment['retention_policy']['id'] - assert assignment.start_date_field == mock_assignment['start_date_field'] - assert isinstance(assignment, RetentionPolicyAssignment) - - -def test_get_assignments(test_retention_policy, mock_box_session): - expected_url = test_retention_policy.get_url('assignments') - mock_assignment = { - 'type': 'retention_policy_assignment', - 'id': '12345', - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [mock_assignment], - 'next_marker': 'testMarker', - } - assignments = test_retention_policy.assignments() - assignment = assignments.next() - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(assignment, RetentionPolicyAssignment) - assert assignment.id == mock_assignment['id'] - assert assignment.type == mock_assignment['type'] diff --git a/test/unit/object/test_retention_policy_assignment.py b/test/unit/object/test_retention_policy_assignment.py deleted file mode 100644 index fbab67418..000000000 --- a/test/unit/object/test_retention_policy_assignment.py +++ /dev/null @@ -1,85 +0,0 @@ -from boxsdk.config import API -from boxsdk.object.file import File -from boxsdk.object.file_version import FileVersion -from boxsdk.object.retention_policy_assignment import RetentionPolicyAssignment - - -def test_get_assignment(test_retention_policy_assignment, mock_box_session): - expected_url = f'{API.BASE_API_URL}/retention_policy_assignments/{test_retention_policy_assignment.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': test_retention_policy_assignment.object_type, - 'id': test_retention_policy_assignment.object_id, - } - retention_policy_assignment = test_retention_policy_assignment.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(retention_policy_assignment, RetentionPolicyAssignment) - assert retention_policy_assignment['type'] == test_retention_policy_assignment.object_type - assert retention_policy_assignment['id'] == test_retention_policy_assignment.object_id - - -def test_get_files_under_retention(test_retention_policy_assignment, test_file, mock_box_session): - # given: - test_marker = 'test_marker' - test_limit = 50 - target_url = f"{API.BASE_API_URL}/retention_policy_assignments/{test_retention_policy_assignment.object_id}/files_under_retention" - - mock_box_session.get.return_value.json.return_value = { - 'limit': test_limit, - 'entries': [test_file], - 'next_marker': test_marker, - } - - # when: - files_under_retention = test_retention_policy_assignment.get_files_under_retention( - limit=test_limit, - marker=test_marker - ) - file_under_retention = files_under_retention.next() - - # then: - params = { - 'limit': test_limit, - 'marker': test_marker, - } - mock_box_session.get.assert_called_once_with(target_url, params=params) - assert isinstance(file_under_retention, File) - assert file_under_retention.object_id == test_file.object_id - - -def test_get_file_versions_under_retention(test_retention_policy_assignment, test_file_version, mock_box_session): - # given: - test_marker = 'test_marker' - test_limit = 50 - target_url = f"{API.BASE_API_URL}/retention_policy_assignments/{test_retention_policy_assignment.object_id}/file_versions_under_retention" - - mock_box_session.get.return_value.json.return_value = { - 'limit': test_limit, - 'entries': [test_file_version], - 'next_marker': test_marker, - } - - # when: - file_versions_under_retention = test_retention_policy_assignment.get_file_versions_under_retention( - limit=test_limit, - marker=test_marker - ) - file_version_under_retention = file_versions_under_retention.next() - - # then: - params = { - 'limit': test_limit, - 'marker': test_marker, - } - mock_box_session.get.assert_called_once_with(target_url, params=params) - assert isinstance(file_version_under_retention, FileVersion) - assert file_version_under_retention.object_id == test_file_version.object_id - - -def test_delete_assignment(test_retention_policy_assignment, mock_box_session): - expected_url = f'{API.BASE_API_URL}/retention_policy_assignments/{test_retention_policy_assignment.object_id}' - mock_box_session.delete.return_value.ok = True - - is_assignment_deleted = test_retention_policy_assignment.delete() - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, params={}, headers=None) - - assert is_assignment_deleted diff --git a/test/unit/object/test_search.py b/test/unit/object/test_search.py deleted file mode 100644 index 8c5e9d5fa..000000000 --- a/test/unit/object/test_search.py +++ /dev/null @@ -1,438 +0,0 @@ -from unittest.mock import ANY -import json -import pytest - -from boxsdk.config import API -from boxsdk.object.file import File -from boxsdk.object.user import User -from boxsdk.object.search import MetadataSearchFilters, MetadataSearchFilter, SearchScope, TrashContent - - -@pytest.fixture -def search_query(): - return 'myquery' - - -@pytest.fixture(params=(1, 20, 100)) -def search_limit(request): - return request.param - - -@pytest.fixture(params=(0, 10)) -def search_offset(request): - return request.param - - -@pytest.fixture(params=(None, 'file', 'folder')) -def search_result_type(request): - return request.param - - -@pytest.fixture(params=(None, ('name',), ('name', 'description'))) -def search_content_types(request): - return request.param - - -@pytest.fixture -def search_value_based_filters(): - metadata_filters = MetadataSearchFilters() - metadata_filter = MetadataSearchFilter(template_key='mytemplate', scope='enterprise') - metadata_filter.add_value_based_filter(field_key='myfield', value='myvalue') - metadata_filters.add_filter(metadata_filter) - return metadata_filters - - -@pytest.fixture(params=( - {'gt_value': 'mygtvalue'}, - {'lt_value': 'myltvalue'}, - {'gt_value': 'mygtvalue', 'lt_value': 'myltvalue'} -)) -def search_range_filters(request): - metadata_filters = MetadataSearchFilters() - metadata_filter = MetadataSearchFilter(template_key='mytemplate', scope='enterprise') - filter_params = {'field_key': 'myfield'} - filter_params.update(request.param) - metadata_filter.add_range_filter(**filter_params) - metadata_filters.add_filter(metadata_filter) - return metadata_filters - - -@pytest.fixture -def search_entries(): - return [ - {'id': '1234', 'type': 'file'} - ] - - -@pytest.fixture -def search_response(search_entries): - return { - 'entries': search_entries, - 'total_count': len(search_entries), - 'limit': 20, - 'offset': 0 - } - - -@pytest.fixture -def metadata_query_response(): - return { - 'entries': [ - { - 'type': 'file', - 'id': '1244738582', - 'name': 'Very Important.docx', - 'metadata': { - 'enterprise_67890': { - 'catalogImages': { - '$parent': 'file_50347290', - '$version': 2, - '$template': 'catalogImages', - '$scope': 'enterprise_67890', - 'photographer': 'Bob Dylan' - } - } - } - }, - { - 'type': 'folder', - 'id': '124242482', - 'name': 'Also Important.docx', - 'metadata': { - 'enterprise_67890': { - 'catalogImages': { - '$parent': 'file_50427291', - '$version': 2, - '$template': 'catalogImages', - '$scope': 'enterprise_67890', - 'photographer': 'Bob Dylan' - } - } - } - } - ], - 'limit': 2, - 'next_marker': '' - } - - -@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(search_with_shared_links_entries): - entries = search_with_shared_links_entries - return { - 'entries': entries, - 'total_count': len(entries), - 'limit': 20, - 'offset': 0 - } - - -class Matcher: - def __init__(self, compare, some_obj): - self.compare = compare - self.some_obj = some_obj - - def __eq__(self, other): - return self.compare(self.some_obj, other) - - -def compare_params(self, other): - if not isinstance(self, dict) or not isinstance(other, dict): - return False - for key in self: - # We need to ensure that the JSON-encoded mdfilters matches regardless of key order - if key == 'mdfilters': - if json.loads(self['mdfilters']) != json.loads(other['mdfilters']): - return False - # For other keys, just ensure that they are equal - elif key in ('type', 'content_types'): - return self[key] is None or self[key] == other[key] - else: - if self[key] != other[key]: - return False - return True - - -def test_query_with_value_based_filters( - mock_box_session, - make_mock_box_request, - test_search, - search_query, - search_limit, - search_offset, - search_value_based_filters, - search_response, - search_entries, - search_result_type, - search_content_types, -): - # pylint:disable=redefined-outer-name - mock_box_session.get.return_value, _ = make_mock_box_request(response=search_response) - response = test_search.query( - 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, item['id'], item) for item in search_entries]): - assert actual == expected - - mock_box_session.get.assert_called_once_with( - test_search.get_url(), - params=Matcher(compare_params, { - 'query': search_query, - '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, - }) - ) - - -def test_query_with_range_filters( - mock_box_session, - make_mock_box_request, - test_search, - search_query, - search_limit, - search_offset, - search_range_filters, - search_response, - search_entries, - search_result_type, - search_content_types, -): - # pylint:disable=redefined-outer-name - mock_box_session.get.return_value, _ = make_mock_box_request(response=search_response) - response = test_search.query( - search_query, - limit=search_limit, - offset=search_offset, - metadata_filters=search_range_filters, - result_type=search_result_type, - content_types=search_content_types, - ) - for actual, expected in zip(response, [File(mock_box_session, item['id'], item) for item in search_entries]): - assert actual == expected - - mock_box_session.get.assert_called_once_with( - test_search.get_url(), - params=Matcher(compare_params, { - 'query': search_query, - 'limit': search_limit, - 'mdfilters': json.dumps(search_range_filters.as_list()), - 'offset': search_offset, - 'type': search_result_type, - 'content_types': ','.join(search_content_types) if search_content_types else search_content_types, - }) - ) - - -@pytest.mark.parametrize('kwargs, params', [ - ({'scope': SearchScope.ENTERPRISE}, {'scope': 'enterprise_content'}), - ({'scope': SearchScope.USER}, {'scope': 'user_content'}), - ({'created_at_range': (None, '2018-01-01T00:00:00Z')}, {'created_at_range': ',2018-01-01T00:00:00Z'}), - ({'created_at_range': ('2015-02-03T12:00:00-08:00', None)}, {'created_at_range': '2015-02-03T12:00:00-08:00,'}), - ({'created_at_range': ('2012-01-01T00:00:00Z', '2012-12-31T11:59:59Z')}, {'created_at_range': '2012-01-01T00:00:00Z,2012-12-31T11:59:59Z'}), - ({'updated_at_range': (None, '2018-01-01T00:00:00Z')}, {'updated_at_range': ',2018-01-01T00:00:00Z'}), - ({'updated_at_range': ('2015-02-03T12:00:00-08:00', None)}, {'updated_at_range': '2015-02-03T12:00:00-08:00,'}), - ({'updated_at_range': ('2012-01-01T00:00:00Z', '2012-12-31T11:59:59Z')}, {'updated_at_range': '2012-01-01T00:00:00Z,2012-12-31T11:59:59Z'}), - ({'size_range': (None, 123)}, {'size_range': ',123'}), - ({'size_range': (123, None)}, {'size_range': '123,'}), - ({'size_range': (123, 456)}, {'size_range': '123,456'}), - ({'trash_content': TrashContent.NONE}, {'trash_content': 'non_trashed_only'}), - ({'trash_content': TrashContent.ONLY}, {'trash_content': 'trashed_only'}), - ({'sort': 'modified_at'}, {'sort': 'modified_at'}), - ({'direction': 'DESC'}, {'direction': 'DESC'}), -]) -def test_query_with_optional_parameters( - mock_box_session, - test_search, - make_mock_box_request, - search_query, - search_response, - search_entries, - kwargs, - params -): - # pylint:disable=redefined-outer-name - mock_box_session.get.return_value, _ = make_mock_box_request(response=search_response) - response = test_search.query( - search_query, - **kwargs - ) - for actual, expected in zip(response, [File(mock_box_session, item['id'], item) for item in search_entries]): - assert actual == expected - - expected_params = { - 'query': search_query, - } - expected_params.update(params) - - mock_box_session.get.assert_called_once_with( - test_search.get_url(), - params=Matcher(compare_params, expected_params) - ) - - -def test_query_with_owner_users( - mock_box_session, - test_search, - make_mock_box_request, - search_query, - search_response, - search_entries, -): - # pylint:disable=redefined-outer-name - user1 = User(mock_box_session, '987') - user2 = User(mock_box_session, '654') - mock_box_session.get.return_value, _ = make_mock_box_request(response=search_response) - response = test_search.query( - search_query, - owner_users=[user1, user2], - ) - for actual, expected in zip(response, [File(mock_box_session, item['id'], item) for item in search_entries]): - assert actual == expected - - mock_box_session.get.assert_called_once_with( - test_search.get_url(), - params=Matcher(compare_params, { - 'query': search_query, - 'owner_user_ids': '987,654' - }) - ) - - -def test_metadata_query( - mock_box_session, - make_mock_box_request, - test_search, - metadata_query_response -): - # pylint:disable=redefined-outer-name - expected_url = f'{API.BASE_API_URL}/metadata_queries/execute_read' - from_template = 'enterprise_12345.someTemplate' - ancestor_folder_id = '5555' - query = 'amount >= :arg' - query_params = {'arg': 100} - order_by = [ - { - 'field_key': 'amount', - 'direction': 'asc' - } - ] - fields = ['type', 'id', 'name', 'metadata.enterprise_67890.catalogImages.$parent'] - limit = 2 - marker = 'AAAAAmVYB1FWec8GH6yWu2nwmanfMh07IyYInaa7DZDYjgO1H4KoLW29vPlLY173OKs' - expected_data = { - 'limit': limit, - 'from': from_template, - 'ancestor_folder_id': ancestor_folder_id, - 'query': query, - 'query_params': query_params, - 'order_by': order_by, - 'marker': marker, - 'fields': fields - } - expected_headers = {b'Content-Type': b'application/json'} - mock_box_session.post.return_value, _ = make_mock_box_request(response=metadata_query_response) - items = test_search.metadata_query( - from_template=from_template, - ancestor_folder_id=ancestor_folder_id, - query=query, - query_params=query_params, - order_by=order_by, - marker=marker, - limit=limit, - fields=fields - ) - item1 = items.next() - item2 = items.next() - mock_box_session.post.assert_called_once_with(expected_url, data=ANY, headers=expected_headers) - assert dict(json.loads(mock_box_session.post.call_args[1]['data'])) == expected_data - assert mock_box_session.post.call_args[1]['headers'] == expected_headers - assert item1['type'] == 'file' - assert item1['metadata']['enterprise_67890']['catalogImages']['$parent'] == 'file_50347290' - assert item2['type'] == 'folder' - assert item2['metadata']['enterprise_67890']['catalogImages']['$parent'] == 'file_50427291' - - -def test_range_filter_without_gt_and_lt_will_fail_validation(): - metadata_filter = MetadataSearchFilter(template_key='mytemplate', scope='enterprise') - with pytest.raises(ValueError): - metadata_filter.add_range_filter(field_key='mykey') - - -def test_start_search_filters(test_search): - filters = test_search.start_metadata_filters() - assert isinstance(filters, MetadataSearchFilters) - - -def test_make_single_metadata_filter(test_search): - template_key = 'mytemplate' - scope = 'myscope' - metadata_filter = test_search.make_single_metadata_filter(template_key, scope) - assert isinstance(metadata_filter, MetadataSearchFilter) - 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, - }) - ) diff --git a/test/unit/object/test_sign_request.py b/test/unit/object/test_sign_request.py deleted file mode 100644 index b3a7909e1..000000000 --- a/test/unit/object/test_sign_request.py +++ /dev/null @@ -1,146 +0,0 @@ -import pytest - -from boxsdk.config import API -from boxsdk.object.sign_request import SignRequest - - -@pytest.fixture(scope='module') -def mock_sign_request_response(): - # pylint:disable=redefined-outer-name - mock_sign_request = { - 'id': '42', - 'type': 'sign-request', - 'are_reminders_enabled': 'true', - 'are_text_signatures_enabled': 'true', - 'auto_expire_at': '2021-04-26T08:12:13.982Z', - 'days_valid': '2', - 'email_message': 'Hello! Please sign the document below', - 'email_subject': 'Sign Request from Acme', - 'external_id': '123', - 'is_document_preparation_needed': 'true', - 'redirect_url': 'https://www.box.com/accepted', - 'declined_redirect_url': 'https://www.box.com/declined', - 'parent_folder': { - 'id': '12345', - 'type': 'folder', - 'etag': '1', - 'name': 'Contracts', - 'sequence_id': '3' - }, - 'prefill_tags': [ - { - 'document_tag_id': '1234', - 'text_value': 'text', - 'checkbox_value': 'true', - 'date_value': '2021-04-26T08:12:13.982Z' - } - ], - 'prepare_url': 'https://prepareurl.com', - 'sign_files': { - 'files': [ - { - 'id': '12345', - 'etag': '1', - 'type': 'file', - 'sequence_id': '3', - 'name': 'Contract.pdf', - 'sha1': '85136C79CBF9FE36BB9D05D0639C70C265C18D37', - 'file_version': { - 'id': '12345', - 'type': 'file_version', - 'sha1': '134b65991ed521fcfe4724b7d814ab8ded5185dc' - } - } - ], - 'is_ready_for_download': 'true' - }, - 'signers': [ - { - 'email': 'example@gmail.com', - 'role': 'signer', - 'is_in_person': 'true', - 'order': '2', - 'embed_url_external_user_id': '1234', - 'has_viewed_document': 'true', - 'signer_decision': { - 'type': 'signed', - 'finalized_at': '2021-04-26T08:12:13.982Z' - }, - 'inputs': [ - { - 'document_tag_id': '1234', - 'text_value': 'text', - 'checkbox_value': 'true', - 'date_value': '2021-04-26T08:12:13.982Z', - 'type': 'text', - 'page_index': '4' - } - ], - 'embed_url': 'https://example.com' - } - ], - 'signing_log': { - 'id': '12345', - 'type': 'file', - 'etag': '1', - 'file_version': { - 'id': '12345', - 'type': 'file_version', - 'sha1': '134b65991ed521fcfe4724b7d814ab8ded5185dc' - }, - 'name': 'Contract.pdf', - 'sequence_id': '3', - 'sha1': '85136C79CBF9FE36BB9D05D0639C70C265C18D37' - }, - 'source_files': [ - { - 'id': '12345', - 'etag': '1', - 'type': 'file', - 'sequence_id': '3', - 'name': 'Contract.pdf', - 'sha1': '85136C79CBF9FE36BB9D05D0639C70C265C18D37', - 'file_version': { - 'id': '12345', - 'type': 'file_version', - 'sha1': '134b65991ed521fcfe4724b7d814ab8ded5185dc' - } - } - ], - 'status': 'cancelled', - 'template_id': '123075213-af2c8822-3ef2-4952-8557-52d69c2fe9cb' - } - return mock_sign_request - - -def test_get_sign_request(test_sign_request, mock_box_session, mock_sign_request_response): - expected_url = f'{API.BASE_API_URL}/sign_requests/{test_sign_request.object_id}' - mock_box_session.get.return_value.json.return_value = mock_sign_request_response - - sign_request = test_sign_request.get() - - mock_box_session.get.assert_called_once_with( - expected_url, headers=None, params=None) - assert isinstance(sign_request, SignRequest) - assert sign_request['id'] == test_sign_request.object_id - - -def test_cancel_sign_request(test_sign_request, mock_box_session, mock_sign_request_response): - expected_url = f'{API.BASE_API_URL}/sign_requests/{test_sign_request.object_id}/cancel' - mock_box_session.post.return_value.json.return_value = mock_sign_request_response - - sign_request = test_sign_request.cancel() - - mock_box_session.post.assert_called_once_with(expected_url) - assert isinstance(sign_request, SignRequest) - assert sign_request['id'] == test_sign_request.object_id - assert sign_request['status'] == mock_sign_request_response['status'] - - -def test_resend_sign_request(test_sign_request, mock_box_session): - expected_url = f'{API.BASE_API_URL}/sign_requests/{test_sign_request.object_id}/resend' - - test_sign_request.resend() - - mock_box_session.post.assert_called_once_with( - expected_url, expect_json_response=False, skip_retry_codes={202}) diff --git a/test/unit/object/test_sign_templates.py b/test/unit/object/test_sign_templates.py deleted file mode 100644 index 4742d2cf2..000000000 --- a/test/unit/object/test_sign_templates.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest - -from boxsdk.config import API -from boxsdk.object.sign_template import SignTemplate - - -@pytest.fixture(scope='module') -def mock_sign_template_response(): - mock_sign_template = { - "id": "93153068-5420-467b-b8ef-8e54bfb7be42", - "type": "sign-template", - "name": "important-file.pdf", - "email_message": "Please sign this document.\n\nKind regards", - "email_subject": "Box User (boxuser@box.com) has requested your signature on a document", - "parent_folder": { - "id": "123456789", - "etag": "0", - "type": "folder", - "sequence_id": "0", - "name": "My Sign Requests" - }, - "auto_expire_days": "null", - "source_files": [ - { - "id": "123456", - "etag": "0", - "type": "file", - "sequence_id": "0", - "sha1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "file_version": { - "id": "123456", - "type": "file_version", - "sha1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - } - } - ], - "are_email_settings_locked": "false", - "are_fields_locked": "false", - "are_files_locked": "false", - "are_options_locked": "false", - "are_recipients_locked": "false", - "signers": [ - { - "email": "", - "label": "", - "public_id": "AAQXQXJZ4", - "role": "final_copy_reader", - "is_in_person": "false", - "order": 1, - "inputs": [] - }, - { - "email": "", - "label": "", - "public_id": "13XQXJZ4", - "role": "signer", - "is_in_person": "false", - "order": 1, - "inputs": [ - { - "document_tag_id": None, - "id": "0260f921-3b52-477f-ae74-0b0b0b0b0b0b", - "type": "signature", - "text_value": None, - "is_required": True, - "coordinates": { - "x": 0.27038464059712, - "y": 0.10051756244533624 - }, - "dimensions": { - "width": 0.23570031566618235, - "height": 0.04781003891921971 - }, - "date_value": None, - "page_index": 0, - "checkbox_value": None, - "document_id": "2fdf9003-d798-40ee-be7f-0b0b0b0b0b0b", - "content_type": "signature", - "dropdown_choices": None, - "group_id": None, - "label": None - } - ] - } - ], - "ready_sign_link": None, - "custom_branding": None, - "days_valid": 0, - "additional_info": { - "non_editable": [], - "required": { - "signers": [ - [ - "email" - ], - [ - "email" - ] - ] - } - } - } - return mock_sign_template - - -def test_get_sign_template(test_sign_template, mock_box_session, mock_sign_template_response): - expected_url = f'{API.BASE_API_URL}/sign_templates/{test_sign_template.object_id}' - mock_box_session.get.return_value.json.return_value = mock_sign_template_response - - sign_template = test_sign_template.get() - - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - - assert isinstance(sign_template, SignTemplate) - - assert sign_template.id == '93153068-5420-467b-b8ef-8e54bfb7be42' - assert sign_template.name == 'important-file.pdf' - assert sign_template.email_message == 'Please sign this document.\n\nKind regards' diff --git a/test/unit/object/test_storage_policy.py b/test/unit/object/test_storage_policy.py deleted file mode 100644 index 009f52c5b..000000000 --- a/test/unit/object/test_storage_policy.py +++ /dev/null @@ -1,172 +0,0 @@ -import json -from boxsdk.config import API -from boxsdk.object.storage_policy import StoragePolicy -from boxsdk.object.storage_policy_assignment import StoragePolicyAssignment - - -def test_get(test_storage_policy, mock_box_session): - expected_url = f'{API.BASE_API_URL}/storage_policies/{test_storage_policy.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': test_storage_policy.object_type, - 'id': test_storage_policy.object_id, - } - storage_policy = test_storage_policy.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(storage_policy, StoragePolicy) - - -def test_assign_with_same_assignment(test_storage_policy, test_storage_policy_assignment, mock_user, mock_box_session): - expected_url = f'{API.BASE_API_URL}/storage_policy_assignments' - additional_params = { - 'resolved_for_type': mock_user.object_type, - 'resolved_for_id': mock_user.object_id, - } - mock_box_session.get.return_value.json.return_value = { - 'next_marker': None, - 'limit': 1, - 'entries': [ - { - 'type': test_storage_policy_assignment.object_type, - 'id': test_storage_policy_assignment.object_id, - 'storage_policy': { - 'type': test_storage_policy.object_type, - 'id': test_storage_policy.object_id, - }, - }, - ], - } - assignment = test_storage_policy.assign(mock_user) - mock_box_session.get.assert_called_once_with(expected_url, params=additional_params) - assert isinstance(assignment, StoragePolicyAssignment) - assert assignment.type == test_storage_policy_assignment.object_type - assert assignment.id == test_storage_policy_assignment.object_id - assert assignment.storage_policy['type'] == test_storage_policy.object_type - assert assignment.storage_policy['id'] == test_storage_policy.object_id - - -def test_assign_with_assigned_enterprise(test_storage_policy, test_storage_policy_assignment, mock_user, mock_box_session): - expected_url = f'{API.BASE_API_URL}/storage_policy_assignments' - additional_params = { - 'resolved_for_type': mock_user.object_type, - 'resolved_for_id': mock_user.object_id, - } - expected_data = { - 'storage_policy': { - 'type': test_storage_policy.object_type, - 'id': test_storage_policy.object_id, - }, - 'assigned_to': { - 'type': mock_user.object_type, - 'id': mock_user.object_id, - }, - } - mock_box_session.get.return_value.json.return_value = { - 'next_marker': None, - 'limit': 1, - 'entries': [ - { - 'type': test_storage_policy_assignment.object_type, - 'id': '11111', - 'storage_policy': { - 'type': test_storage_policy.object_type, - 'id': '22222', - }, - 'assigned_to': { - 'type': 'enterprise', - 'id': '12345', - }, - }, - ], - } - mock_box_session.post.return_value.json.return_value = { - 'type': test_storage_policy_assignment.object_type, - 'id': test_storage_policy_assignment.object_id, - 'storage_policy': { - 'type': test_storage_policy.object_type, - 'id': test_storage_policy.object_id - } - } - assignment = test_storage_policy.assign(mock_user) - mock_box_session.get.assert_called_once_with(expected_url, params=additional_params) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - assert isinstance(assignment, StoragePolicyAssignment) - assert assignment.type == test_storage_policy_assignment.object_type - assert assignment.id == test_storage_policy_assignment.object_id - assert assignment.storage_policy['type'] == test_storage_policy.object_type - assert assignment.storage_policy['id'] == test_storage_policy.object_id - - -def test_assign_with_update(test_storage_policy, test_storage_policy_assignment, mock_user, mock_box_session): - expected_url = f'{API.BASE_API_URL}/storage_policy_assignments' - expected_put_url = f'{API.BASE_API_URL}/storage_policy_assignments/11111' - additional_params = { - 'resolved_for_type': mock_user.object_type, - 'resolved_for_id': mock_user.object_id, - } - expected_data = { - 'storage_policy': { - 'type': test_storage_policy.object_type, - 'id': '42', - }, - } - mock_box_session.get.return_value.json.return_value = { - 'next_marker': None, - 'limit': 1, - 'entries': [ - { - 'type': test_storage_policy_assignment.object_type, - 'id': '11111', - 'storage_policy': { - 'type': test_storage_policy.object_type, - 'id': '22222', - }, - 'assigned_to': { - 'type': 'user', - 'id': '12345', - }, - }, - ], - } - mock_box_session.put.return_value.json.return_value = { - 'type': test_storage_policy_assignment.object_type, - 'id': test_storage_policy_assignment.object_id, - 'storage_policy': { - 'type': test_storage_policy.object_type, - 'id': '42' - } - } - assignment = test_storage_policy.assign(mock_user) - mock_box_session.get.assert_called_once_with(expected_url, params=additional_params) - mock_box_session.put.assert_called_once_with(expected_put_url, data=json.dumps(expected_data), headers=None, params=None) - assert isinstance(assignment, StoragePolicyAssignment) - assert assignment.type == test_storage_policy_assignment.object_type - assert assignment.id == test_storage_policy_assignment.object_id - assert assignment.storage_policy['type'] == test_storage_policy.object_type - assert assignment.storage_policy['id'] == '42' - - -def test_create_assignment(test_storage_policy, mock_user, mock_box_session): - expected_url = f'{API.BASE_API_URL}/storage_policy_assignments' - expected_data = { - 'storage_policy': { - 'type': 'storage_policy', - 'id': '42', - }, - 'assigned_to': { - 'type': 'user', - 'id': 'fake-user-100', - }, - } - mock_assignment = { - 'type': 'storage_policy_assignment', - 'id': '1234', - 'storage_policy': { - 'type': test_storage_policy.object_type, - 'id': test_storage_policy.object_id, - }, - } - mock_box_session.post.return_value.json.return_value = mock_assignment - assignment = test_storage_policy.create_assignment(mock_user) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data)) - assert assignment.id == mock_assignment['id'] - assert assignment.storage_policy['id'] == mock_assignment['storage_policy']['id'] diff --git a/test/unit/object/test_storage_policy_assignment.py b/test/unit/object/test_storage_policy_assignment.py deleted file mode 100644 index 56ddcb57f..000000000 --- a/test/unit/object/test_storage_policy_assignment.py +++ /dev/null @@ -1,57 +0,0 @@ -from unittest.mock import Mock -import json -import pytest - -from boxsdk.config import API -from boxsdk.object.storage_policy_assignment import StoragePolicyAssignment -from boxsdk.network.default_network import DefaultNetworkResponse - - -@pytest.fixture(scope='module') -def delete_assignment_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.ok = True - return mock_network_response - - -def test_get(test_storage_policy_assignment, mock_box_session): - expected_url = f'{API.BASE_API_URL}/storage_policy_assignments/{test_storage_policy_assignment.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': 'storage_policy_assignment', - 'id': test_storage_policy_assignment.object_id, - } - storage_policy_assignment = test_storage_policy_assignment.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(storage_policy_assignment, StoragePolicyAssignment) - - -def test_update(test_storage_policy_assignment, mock_box_session): - new_policy_id = '1234' - expected_url = f'{API.BASE_API_URL}/storage_policy_assignments/{test_storage_policy_assignment.object_id}' - mock_box_session.put.return_value.json.return_value = { - 'type': 'storage_policy_assignment', - 'id': new_policy_id, - } - storage_policy_assignment = test_storage_policy_assignment.update_info(data={ - 'storage_policy': { - 'type': 'storage_policy', - 'id': new_policy_id, - } - }) - data = { - 'storage_policy': { - 'type': 'storage_policy', - 'id': new_policy_id, - } - } - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None) - assert isinstance(storage_policy_assignment, StoragePolicyAssignment) - - -def test_delete(test_storage_policy_assignment, delete_assignment_response, mock_box_session): - mock_box_session.delete.return_value = delete_assignment_response - response = test_storage_policy_assignment.delete() - expected_url = test_storage_policy_assignment.get_url() - mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None) - assert response is True diff --git a/test/unit/object/test_task.py b/test/unit/object/test_task.py deleted file mode 100644 index 6e5da14e4..000000000 --- a/test/unit/object/test_task.py +++ /dev/null @@ -1,152 +0,0 @@ -from unittest.mock import Mock -import json -import pytest - -from boxsdk.config import API -from boxsdk.object.task import Task -from boxsdk.object.task_assignment import TaskAssignment -from boxsdk.network.default_network import DefaultNetworkResponse - - -@pytest.fixture(scope='module') -def delete_task_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.ok = True - return mock_network_response - - -def test_get(test_task, mock_box_session): - expected_url = f'{API.BASE_API_URL}/tasks/{test_task.object_id}' - due_at = '2014-04-03T11:09:43-07:00' - action = 'review' - message = 'Test Message' - mock_box_session.get.return_value.json.return_value = { - 'type': test_task.object_type, - 'id': test_task.object_id, - 'due_at': due_at, - 'action': action, - 'message': message, - } - retrieved_task = test_task.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(retrieved_task, Task) - assert retrieved_task.object_type == test_task.object_type - assert retrieved_task.object_id == test_task.object_id - assert retrieved_task.due_at == due_at - assert retrieved_task.action == action - assert retrieved_task.message == message - - -def test_update(test_task, mock_box_session): - new_message = 'New Message' - expected_url = f'{API.BASE_API_URL}/tasks/{test_task.object_id}' - mock_box_session.put.return_value.json.return_value = { - 'type': test_task.object_type, - 'id': test_task.object_id, - 'message': new_message, - } - expected_body = { - 'message': new_message, - } - updated_task = test_task.update_info(data={'message': new_message}) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(expected_body), headers=None, params=None) - assert isinstance(updated_task, Task) - assert updated_task.message == new_message - assert updated_task.object_type == test_task.object_type - assert updated_task.object_id == test_task.object_id - - -def test_delete_policy_return_the_correct_response( - test_task, - mock_box_session, - delete_task_response, -): - # pylint:disable=redefined-outer-name - mock_box_session.delete.return_value = delete_task_response - response = test_task.delete() - # pylint:disable=protected-access - expected_url = f'{API.BASE_API_URL}/tasks/{test_task.object_id}' - # pylint:enable = protected-access - mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None) - assert response is True - - -def test_assign(test_task, mock_user, mock_box_session): - expected_url = f'{API.BASE_API_URL}/task_assignments' - expected_body = { - 'task': { - 'type': test_task.object_type, - 'id': test_task.object_id, - }, - 'assign_to': { - 'id': mock_user.object_id, - }, - } - mock_box_session.post.return_value.json.return_value = { - 'type': 'task_assignment', - 'id': '42', - 'assigned_to': { - 'type': 'user', - 'id': '1234', - }, - 'assigned_at': '2013-05-10T11:43:41-07:00', - } - new_task_assignment = test_task.assign(assignee=mock_user) - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_body)) - assert isinstance(new_task_assignment, TaskAssignment) - assert new_task_assignment.object_type == 'task_assignment' - assert new_task_assignment.object_id == '42' - assert new_task_assignment.assigned_to['type'] == 'user' - assert new_task_assignment.assigned_at == '2013-05-10T11:43:41-07:00' - - -def test_assign_with_login(test_task, mock_box_session): - expected_url = f'{API.BASE_API_URL}/task_assignments' - expected_body = { - 'task': { - 'type': test_task.object_type, - 'id': test_task.object_id, - }, - 'assign_to': { - 'login': 'test_user@example.com', - }, - } - mock_box_session.post.return_value.json.return_value = { - 'type': 'task_assignment', - 'id': '42', - 'assigned_to': { - 'type': 'user', - 'id': '1234', - }, - 'assigned_at': '2013-05-10T11:43:41-07:00', - } - new_task_assignment = test_task.assign_with_login(assignee_login='test_user@example.com') - mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_body)) - assert isinstance(new_task_assignment, TaskAssignment) - assert new_task_assignment.object_type == 'task_assignment' - assert new_task_assignment.object_id == '42' - assert new_task_assignment.assigned_to['type'] == 'user' - assert new_task_assignment.assigned_at == '2013-05-10T11:43:41-07:00' - - -def test_get_assignments(test_task, mock_box_session): - expected_url = f'{API.BASE_API_URL}/tasks/{test_task.object_id}/assignments' - mock_assignment = { - 'type': 'task_assignment', - 'id': '12345', - 'item': { - 'type': 'file', - 'id': '33333' - } - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'entries': [mock_assignment] - } - assignments = test_task.get_assignments() - assignment = assignments.next() - mock_box_session.get.assert_called_once_with(expected_url, params={}) - assert isinstance(assignment, TaskAssignment) - assert assignment.id == mock_assignment['id'] - assert assignment.item['id'] == mock_assignment['item']['id'] diff --git a/test/unit/object/test_task_assignment.py b/test/unit/object/test_task_assignment.py deleted file mode 100644 index 9473734c1..000000000 --- a/test/unit/object/test_task_assignment.py +++ /dev/null @@ -1,72 +0,0 @@ -from unittest.mock import Mock -import json -import pytest - -from boxsdk.config import API -from boxsdk.object.task_assignment import TaskAssignment, ResolutionState -from boxsdk.network.default_network import DefaultNetworkResponse - - -@pytest.fixture(scope='module') -def delete_task_assignment_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.ok = True - return mock_network_response - - -def test_get_assignment(test_task_assignment, mock_box_session): - expected_url = f'{API.BASE_API_URL}/task_assignments/{test_task_assignment.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': test_task_assignment.object_type, - 'id': test_task_assignment.object_id, - 'assigned_to': { - 'type': 'user', - 'id': '11111', - }, - } - retrieved_task = test_task_assignment.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(retrieved_task, TaskAssignment) - assert retrieved_task.object_type == test_task_assignment.object_type - assert retrieved_task.object_id == test_task_assignment.object_id - assert retrieved_task.assigned_to['type'] == 'user' - assert retrieved_task.assigned_to['id'] == '11111' - - -def test_delete_policy_return_the_correct_response( - test_task_assignment, - mock_box_session, - delete_task_assignment_response, -): - # pylint:disable=redefined-outer-name - mock_box_session.delete.return_value = delete_task_assignment_response - response = test_task_assignment.delete() - # pylint:disable=protected-access - expected_url = f'{API.BASE_API_URL}/task_assignments/{test_task_assignment.object_id}' - # pylint:enable = protected-access - mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None) - assert response is True - - -def test_update(test_task_assignment, mock_box_session): - new_message = 'New Message' - resolution_state = ResolutionState.APPROVED - expected_url = f'{API.BASE_API_URL}/task_assignments/{test_task_assignment.object_id}' - mock_box_session.put.return_value.json.return_value = { - 'type': 'task_assignment', - 'id': test_task_assignment.object_id, - 'message': new_message, - 'resolution_state': resolution_state, - } - expected_body = { - 'message': new_message, - 'resolution_state': resolution_state, - } - updated_task_assignment = test_task_assignment.update_info(data={'message': new_message, 'resolution_state': resolution_state}) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(expected_body), headers=None, params=None) - assert isinstance(updated_task_assignment, TaskAssignment) - assert updated_task_assignment.message == new_message - assert updated_task_assignment.object_type == test_task_assignment.object_type - assert updated_task_assignment.object_id == test_task_assignment.object_id - assert updated_task_assignment.resolution_state == resolution_state diff --git a/test/unit/object/test_terms_of_service.py b/test/unit/object/test_terms_of_service.py deleted file mode 100644 index 928a1eeee..000000000 --- a/test/unit/object/test_terms_of_service.py +++ /dev/null @@ -1,176 +0,0 @@ -import json - -from boxsdk.exception import BoxAPIException -from boxsdk.config import API -from boxsdk.object.terms_of_service import TermsOfService -from boxsdk.object.terms_of_service_user_status import TermsOfServiceUserStatus - - -def test_get(test_terms_of_service, mock_box_session): - created_at = '2016-05-18T17:38:03-07:00' - expected_url = f'{API.BASE_API_URL}/terms_of_services/{test_terms_of_service.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': 'terms_of_service', - 'id': test_terms_of_service.object_id, - 'created_at': created_at, - } - terms_of_service = test_terms_of_service.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(terms_of_service, TermsOfService) - assert terms_of_service.created_at == created_at - assert terms_of_service.id == test_terms_of_service.object_id - assert terms_of_service.type == test_terms_of_service.object_type - - -def test_update(test_terms_of_service, mock_box_session): - new_text = 'This is new text' - expected_url = f'{API.BASE_API_URL}/terms_of_services/{test_terms_of_service.object_id}' - mock_box_session.put.return_value.json.return_value = { - 'type': 'terms_of_service', - 'id': test_terms_of_service.object_id, - 'text': new_text, - } - data = { - 'text': new_text, - } - updated_terms_of_service = test_terms_of_service.update_info(data=data) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None) - assert isinstance(updated_terms_of_service, TermsOfService) - assert updated_terms_of_service.text == new_text - assert updated_terms_of_service.id == test_terms_of_service.object_id - assert updated_terms_of_service.type == test_terms_of_service.object_type - - -def test_accept_terms_of_service(test_terms_of_service, test_terms_of_service_user_status, mock_user, mock_box_session): - # pylint:disable=redefined-outer-name - expected_url = f"{API.BASE_API_URL}/terms_of_service_user_statuses" - created_at = '2016-05-18T17:38:03-07:00' - value = json.dumps({ - 'tos': { - 'type': 'terms_of_service', - 'id': test_terms_of_service.object_id, - }, - 'is_accepted': True, - 'user': { - 'type': mock_user.object_type, - 'id': mock_user.object_id, - }, - }) - mock_box_session.post.return_value.json.return_value = { - 'type': 'terms_of_service_user_status', - 'id': test_terms_of_service_user_status.object_id, - 'created_at': created_at, - } - new_terms_of_service_user_status = test_terms_of_service.accept(mock_user) - mock_box_session.post.assert_called_once_with(expected_url, data=value) - assert isinstance(new_terms_of_service_user_status, TermsOfServiceUserStatus) - assert new_terms_of_service_user_status.id == test_terms_of_service_user_status.object_id - assert new_terms_of_service_user_status.type == test_terms_of_service_user_status.object_type - assert new_terms_of_service_user_status.created_at == created_at - - -def test_reject_terms_of_service(test_terms_of_service, test_terms_of_service_user_status, mock_user, mock_box_session): - # pylint:disable=redefined-outer-name - expected_url = f"{API.BASE_API_URL}/terms_of_service_user_statuses" - created_at = '2016-05-18T17:38:03-07:00' - value = json.dumps({ - 'tos': { - 'type': 'terms_of_service', - 'id': test_terms_of_service.object_id, - }, - 'is_accepted': False, - 'user': { - 'type': mock_user.object_type, - 'id': mock_user.object_id, - }, - }) - mock_box_session.post.return_value.json.return_value = { - 'type': 'terms_of_service_user_status', - 'id': test_terms_of_service_user_status.object_id, - 'created_at': created_at, - } - new_terms_of_service_user_status = test_terms_of_service.reject(mock_user) - mock_box_session.post.assert_called_once_with(expected_url, data=value) - assert isinstance(new_terms_of_service_user_status, TermsOfServiceUserStatus) - assert new_terms_of_service_user_status.id == test_terms_of_service_user_status.object_id - assert new_terms_of_service_user_status.type == test_terms_of_service_user_status.object_type - assert new_terms_of_service_user_status.created_at == created_at - - -def test_get_user_status(test_terms_of_service, mock_user, test_terms_of_service_user_status, mock_box_session): - expected_url = f"{API.BASE_API_URL}/terms_of_service_user_statuses" - created_at = '2016-05-18T17:38:03-07:00' - expected_params = { - 'tos_id': test_terms_of_service.object_id, - 'user_id': mock_user.object_id, - } - mock_user_status = { - 'type': 'terms_of_service_user_status', - 'id': test_terms_of_service_user_status.object_id, - 'created_at': created_at, - } - mock_box_session.get.return_value.json.return_value = { - 'total_count': 2, - 'entries': [mock_user_status], - } - new_terms_of_service_user_status = test_terms_of_service.get_user_status(user=mock_user) - mock_box_session.get.assert_called_once_with(expected_url, params=expected_params) - assert isinstance(new_terms_of_service_user_status, TermsOfServiceUserStatus) - assert new_terms_of_service_user_status.type == 'terms_of_service_user_status' - assert new_terms_of_service_user_status.id == test_terms_of_service_user_status.object_id - assert new_terms_of_service_user_status.created_at == created_at - - -def test_get_empty_user_status(test_terms_of_service, mock_user, mock_box_session): - mock_box_session.get.return_value.json.return_value = { - 'total_count': 1, - 'entries': [], - } - new_terms_of_service_user_status = test_terms_of_service.get_user_status(user=mock_user) - assert new_terms_of_service_user_status is None - - -def test_set_user_status(test_terms_of_service, mock_user, mock_box_session): - expected_post_url = f"{API.BASE_API_URL}/terms_of_service_user_statuses" - expected_put_url = f"{API.BASE_API_URL}/terms_of_service_user_statuses/{test_terms_of_service.object_id}" - post_value = json.dumps({ - 'tos': { - 'type': test_terms_of_service.object_type, - 'id': test_terms_of_service.object_id, - }, - 'is_accepted': True, - 'user': { - 'type': mock_user.object_type, - 'id': mock_user.object_id, - }, - }) - put_value = json.dumps({ - 'is_accepted': True - }) - mock_box_session.post.side_effect = [BoxAPIException(status=409, message="Conflict")] - mock_box_session.get.return_value.json.return_value = { - 'entries': [ - { - 'type': 'terms_of_service_user_status', - 'id': '42', - }, - ], - } - mock_box_session.put.return_value.json.return_value = { - 'type': 'terms_of_service_user_status', - 'id': '12345', - 'tos': { - 'type': test_terms_of_service.object_type, - 'id': test_terms_of_service.object_id, - }, - 'is_accepted': True, - } - new_terms_of_service_user_status = test_terms_of_service.set_user_status(True, mock_user) - mock_box_session.post.assert_called_once_with(expected_post_url, data=post_value) - mock_box_session.put.assert_called_once_with(expected_put_url, data=put_value, headers=None, params=None) - assert isinstance(new_terms_of_service_user_status, TermsOfServiceUserStatus) - assert new_terms_of_service_user_status.type == 'terms_of_service_user_status' - assert new_terms_of_service_user_status.id == '12345' - assert new_terms_of_service_user_status.tos['type'] == test_terms_of_service.object_type - assert new_terms_of_service_user_status.tos['id'] == test_terms_of_service.object_id - assert new_terms_of_service_user_status.is_accepted is True diff --git a/test/unit/object/test_terms_of_service_user_status.py b/test/unit/object/test_terms_of_service_user_status.py deleted file mode 100644 index 0c4362dd2..000000000 --- a/test/unit/object/test_terms_of_service_user_status.py +++ /dev/null @@ -1,52 +0,0 @@ -import json - -from boxsdk.object.terms_of_service_user_status import TermsOfServiceUserStatus -from boxsdk.config import API - - -def test_get(test_terms_of_service_user_status, mock_box_session): - created_at = '2016-05-18T17:38:03-07:00' - expected_url = f'{API.BASE_API_URL}/terms_of_service_user_statuses/{test_terms_of_service_user_status.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': 'terms_of_service_user_status', - 'id': test_terms_of_service_user_status.object_id, - 'created_at': created_at - } - terms_of_service = test_terms_of_service_user_status.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(terms_of_service, TermsOfServiceUserStatus) - assert terms_of_service.type == test_terms_of_service_user_status.object_type - assert terms_of_service.id == test_terms_of_service_user_status.object_id - assert terms_of_service.created_at == created_at - - -def test_accept(test_terms_of_service_user_status, mock_box_session): - expected_url = f'{API.BASE_API_URL}/terms_of_service_user_statuses/{test_terms_of_service_user_status.object_id}' - mock_box_session.put.return_value.json.return_value = { - 'type': test_terms_of_service_user_status.object_type, - 'id': test_terms_of_service_user_status.object_id, - 'is_accepted': True - } - data = {'is_accepted': True} - terms_of_service_user_status = test_terms_of_service_user_status.accept() - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None) - assert isinstance(terms_of_service_user_status, TermsOfServiceUserStatus) - assert terms_of_service_user_status.type == test_terms_of_service_user_status.object_type - assert terms_of_service_user_status.id == test_terms_of_service_user_status.object_id - assert terms_of_service_user_status.is_accepted is True - - -def test_reject(test_terms_of_service_user_status, mock_box_session): - expected_url = f'{API.BASE_API_URL}/terms_of_service_user_statuses/{test_terms_of_service_user_status.object_id}' - mock_box_session.put.return_value.json.return_value = { - 'type': 'terms_of_service_user_status', - 'id': test_terms_of_service_user_status.object_id, - 'is_accepted': False - } - data = {'is_accepted': False} - terms_of_service_user_status = test_terms_of_service_user_status.reject() - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None) - assert isinstance(terms_of_service_user_status, TermsOfServiceUserStatus) - assert terms_of_service_user_status.type == test_terms_of_service_user_status.object_type - assert terms_of_service_user_status.id == test_terms_of_service_user_status.object_id - assert terms_of_service_user_status.is_accepted is False diff --git a/test/unit/object/test_trash.py b/test/unit/object/test_trash.py deleted file mode 100644 index 83ee7d979..000000000 --- a/test/unit/object/test_trash.py +++ /dev/null @@ -1,171 +0,0 @@ -import json -import pytest - -from boxsdk.config import API -from boxsdk.object.trash import Trash - - -@pytest.fixture() -def test_trash(mock_box_session): - return Trash(mock_box_session) - - -@pytest.fixture(params=('file', 'folder', 'web_link')) -def test_item_and_response( - test_file, - mock_file_response, - test_folder, - mock_folder_response, - test_web_link, - mock_web_link_response, - request, -): - if request.param == 'file': - return test_file, mock_file_response - if request.param == 'web_link': - return test_web_link, mock_web_link_response - return test_folder, mock_folder_response - - -def test_get_from_trash(test_item_and_response, test_trash, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type + "s"}/{test_item.object_id}/trash' - mock_box_session.get.return_value.json.return_value = { - 'type': test_item.object_type, - 'id': test_item.object_id, - 'created_at': '2015-05-07T14:31:16-07:00', - 'modified_at': '2015-05-07T14:31:16-07:00', - 'created_by': { - 'type': 'user', - 'id': '11111', - }, - } - trashed_item_info = test_trash.get_item( - item=test_item, fields=['created_at', 'modified_at']) - mock_box_session.get.assert_called_once_with( - expected_url, params={'fields': 'created_at,modified_at'}) - assert isinstance(trashed_item_info, test_item.__class__) - assert trashed_item_info.object_type == test_item.object_type - assert trashed_item_info.object_id == test_item.object_id - assert trashed_item_info.created_at == '2015-05-07T14:31:16-07:00' - assert trashed_item_info.modified_at == '2015-05-07T14:31:16-07:00' - assert trashed_item_info.created_by['type'] == 'user' - assert trashed_item_info.created_by['id'] == '11111' - - -def test_restore_from_trash(test_item_and_response, test_trash, test_folder, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - test_item, _ = test_item_and_response - new_name = 'New Name' - expected_url = f'{API.BASE_API_URL}/{test_item.object_type + "s"}/{test_item.object_id}' - value = json.dumps({ - 'name': new_name, - 'parent': { - 'id': test_folder.object_id, - }, - }) - mock_box_session.post.return_value.json.return_value = { - 'type': test_item.object_type, - 'id': test_item.object_id, - 'created_at': '2015-05-07T14:31:16-07:00', - 'modified_at': '2015-05-07T14:31:16-07:00', - 'created_by': { - 'type': 'user', - 'id': '11111', - }, - } - restored_item = test_trash.restore_item(test_item, new_name, test_folder, [ - 'created_at', 'modified_at']) - mock_box_session.post.assert_called_once_with(expected_url, data=value, params={ - 'fields': 'created_at,modified_at'}) - assert isinstance(restored_item, test_item.__class__) - assert restored_item.object_type == test_item.object_type - assert restored_item.object_id == test_item.object_id - assert restored_item.created_at == '2015-05-07T14:31:16-07:00' - assert restored_item.modified_at == '2015-05-07T14:31:16-07:00' - assert restored_item.created_by['type'] == 'user' - assert restored_item.created_by['id'] == '11111' - - -def test_permanently_delete(test_item_and_response, test_trash, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/{test_item.object_type + "s"}/{test_item.object_id}/trash' - mock_box_session.delete.return_value.ok = True - info = test_trash.permanently_delete_item(test_item) - mock_box_session.delete.assert_called_once_with( - expected_url, expect_json_response=False) - assert info is True - - -def test_get_trashed_items(test_item_and_response, test_trash, mock_box_session): - test_item, _ = test_item_and_response - item_name = 'Test Trashed Item' - expected_url = f'{API.BASE_API_URL}/folders/trash/items' - mock_trash = { - 'type': test_item.object_type, - 'id': test_item.object_id, - 'name': 'Test Trashed Item' - } - mock_box_session.get.return_value.json.return_value = { - 'total_count': 5, - 'offset': 0, - 'limit': 100, - 'entries': [mock_trash] - } - trashed_items = test_trash.get_items(fields=['name']) - trashed_item = trashed_items.next() - mock_box_session.get.assert_called_once_with( - expected_url, params={'fields': 'name', 'offset': None}) - assert isinstance(trashed_item, test_item.__class__) - assert trashed_item.type == mock_trash['type'] - assert trashed_item.id == mock_trash['id'] - assert trashed_item.name == item_name - - -def test_get_trashed_items_with_sort(test_item_and_response, test_trash, mock_box_session): - test_item, _ = test_item_and_response - expected_url = f'{API.BASE_API_URL}/folders/trash/items' - mock_trash = { - 'type': test_item.object_type, - 'id': test_item.object_id, - 'name': 'Test Trashed Item' - } - mock_box_session.get.return_value.json.return_value = { - 'total_count': 5, - 'offset': 0, - 'limit': 100, - 'entries': [mock_trash] - } - trashed_items = test_trash.get_items(fields=['name'], sort='name', direction='ASC') - trashed_item = trashed_items.next() - mock_box_session.get.assert_called_once_with( - expected_url, params={'direction': 'ASC', 'sort': 'name', 'offset': None, 'fields': 'name'}) - assert isinstance(trashed_item, test_item.__class__) - - -def test_get_trashed_items_with_marker(test_item_and_response, test_trash, mock_box_session): - test_item, _ = test_item_and_response - item_name = 'Test Trashed Item' - expected_url = f'{API.BASE_API_URL}/folders/trash/items' - mock_trash = { - 'type': test_item.object_type, - 'id': test_item.object_id, - 'name': 'Test Trashed Item' - } - mock_box_session.get.return_value.json.return_value = { - 'limit': 100, - 'next_marker': 2345, - 'entries': [mock_trash] - } - trashed_items = test_trash.get_items( - fields=['name'], limit=100, marker=1234, use_marker=True) - trashed_item = trashed_items.next() - - mock_box_session.get.assert_called_once_with(expected_url, params={ - 'limit': 100, 'usemarker': True, 'marker': 1234, 'fields': 'name'}) - assert isinstance(trashed_item, test_item.__class__) - assert trashed_item.type == mock_trash['type'] - assert trashed_item.id == mock_trash['id'] - assert trashed_item.name == item_name diff --git a/test/unit/object/test_upload_session.py b/test/unit/object/test_upload_session.py deleted file mode 100644 index ff66e90c1..000000000 --- a/test/unit/object/test_upload_session.py +++ /dev/null @@ -1,348 +0,0 @@ -from unittest.mock import patch -import base64 -import hashlib -import io -import json -import pytest -from pytest_lazyfixture import lazy_fixture - -from boxsdk.exception import BoxAPIException -from boxsdk.config import API -from boxsdk.util.chunked_uploader import ChunkedUploader -from boxsdk.object.file import File -from boxsdk.object.upload_session import UploadSession - - -UPLOAD_SESSION_ID = 'F971964745A5CD0C001BBE4E58196BFD' -SESSION_ENDPOINTS = { - "abort": f"https://changed.box.com/api/2.0/files/upload_sessions/{UPLOAD_SESSION_ID}", - "commit": f"https://changed.box.com/api/2.0/files/upload_sessions/{UPLOAD_SESSION_ID}/commit", - "list_parts": f"https://changed.box.com/api/2.0/files/upload_sessions/{UPLOAD_SESSION_ID}/parts", - "log_event": f"https://changed.box.com/api/2.0/files/upload_sessions/{UPLOAD_SESSION_ID}/log", - "status": f"https://changed.box.com/api/2.0/files/upload_sessions/{UPLOAD_SESSION_ID}", - "upload_part": f"https://changed.box.com/api/2.0/files/upload_sessions/{UPLOAD_SESSION_ID}" -} - - -@pytest.fixture() -def upload_session_using_upload_session_urls(mock_box_session): - upload_session_response_object = { - 'part_size': 8, - 'total_parts': 10, - 'session_endpoints': SESSION_ENDPOINTS, - } - return UploadSession( - mock_box_session, - UPLOAD_SESSION_ID, - upload_session_response_object, - use_upload_session_urls=True - ) - - -@pytest.fixture() -def upload_session_not_using_upload_session_urls(mock_box_session): - upload_session_response_object = { - 'part_size': 8, - 'total_parts': 10, - 'session_endpoints': SESSION_ENDPOINTS, - } - return UploadSession( - mock_box_session, - UPLOAD_SESSION_ID, - upload_session_response_object, - use_upload_session_urls=False - ) - - -@pytest.mark.parametrize( - 'test_upload_session, expected_url', - [ - (lazy_fixture('upload_session_using_upload_session_urls'), SESSION_ENDPOINTS['list_parts']), - (lazy_fixture('upload_session_not_using_upload_session_urls'), - f'{API.UPLOAD_URL}/files/upload_sessions/{UPLOAD_SESSION_ID}/parts') - ]) -def test_get_parts(mock_box_session, test_upload_session, expected_url): - mock_entry = { - 'part_id': '8F0966B1', - 'offset': 0, - 'size': 8, - 'sha1': None, - } - mock_box_session.get.return_value.json.return_value = { - 'entries': [mock_entry], - 'offset': 0, - 'total_count': 1, - 'limit': 1000, - } - test_parts = test_upload_session.get_parts() - part = test_parts.next() - mock_box_session.get.assert_called_once_with(expected_url, params={'offset': None}) - assert isinstance(part, dict) - assert part['part_id'] == mock_entry['part_id'] - assert part['size'] == mock_entry['size'] - assert part['offset'] == mock_entry['offset'] - - -@pytest.mark.parametrize( - 'test_upload_session, expected_url', - [ - (lazy_fixture('upload_session_using_upload_session_urls'), SESSION_ENDPOINTS['abort']), - (lazy_fixture('upload_session_not_using_upload_session_urls'), - f'{API.UPLOAD_URL}/files/upload_sessions/{UPLOAD_SESSION_ID}') - ]) -def test_abort(mock_box_session, test_upload_session, expected_url): - mock_box_session.delete.return_value.ok = True - result = test_upload_session.abort() - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False) - assert result is True - - -@pytest.mark.parametrize( - 'test_upload_session, expected_url', - [ - (lazy_fixture('upload_session_using_upload_session_urls'), SESSION_ENDPOINTS['upload_part']), - (lazy_fixture('upload_session_not_using_upload_session_urls'), - f'{API.UPLOAD_URL}/files/upload_sessions/{UPLOAD_SESSION_ID}') - ]) -def test_upload_part_bytes(mock_box_session, test_upload_session, expected_url): - part_bytes = b'abcdefgh' - offset = 32 - total_size = 80 - expected_sha1 = 'QlrxKgdDUCsyLpOgFbz4aOMk1Wo=' - expected_headers = { - 'Content-Type': 'application/octet-stream', - 'Digest': f'SHA={expected_sha1}', - 'Content-Range': 'bytes 32-39/80', - } - mock_box_session.put.return_value.json.return_value = { - 'part': { - 'part_id': 'ABCDEF123', - 'offset': offset, - 'size': 8, - 'sha1': expected_sha1, - }, - } - part = test_upload_session.upload_part_bytes(part_bytes, offset, total_size) - - mock_box_session.put.assert_called_once_with(expected_url, data=part_bytes, headers=expected_headers) - assert isinstance(part, dict) - assert part['sha1'] == expected_sha1 - assert part['size'] == 8 - assert part['part_id'] == 'ABCDEF123' - - -@pytest.mark.parametrize( - 'test_upload_session, expected_url', - [ - (lazy_fixture('upload_session_using_upload_session_urls'), SESSION_ENDPOINTS['commit']), - (lazy_fixture('upload_session_not_using_upload_session_urls'), - f'{API.UPLOAD_URL}/files/upload_sessions/{UPLOAD_SESSION_ID}/commit') - ]) -def test_commit(mock_box_session, test_upload_session, expected_url): - sha1 = hashlib.sha1() - sha1.update(b'fake_file_data') - file_id = '12345' - file_type = 'file' - file_etag = '7' - file_attributes = {'description': 'This is a test description.'} - parts = [ - { - 'part_id': 'ABCDEF123', - 'offset': 0, - 'size': 8, - 'sha1': 'fake_sha1', - }, - { - 'part_id': 'ABCDEF456', - 'offset': 8, - 'size': 8, - 'sha1': 'fake_sha1', - }, - ] - expected_data = { - 'attributes': file_attributes, - 'parts': parts, - } - expected_headers = { - 'Content-Type': 'application/json', - 'Digest': f'SHA={base64.b64encode(sha1.digest()).decode("utf-8")}', - 'If-Match': '7', - } - mock_box_session.post.return_value.json.return_value = { - 'entries': [ - { - 'type': file_type, - 'id': file_id, - 'description': 'This is a test description.', - }, - ], - } - created_file = test_upload_session.commit( - content_sha1=sha1.digest(), parts=parts, file_attributes=file_attributes, etag=file_etag - ) - mock_box_session.post.assert_called_once_with( - expected_url, data=json.dumps(expected_data), headers=expected_headers - ) - assert isinstance(created_file, File) - assert created_file.id == file_id - assert created_file.type == file_type - assert created_file.description == 'This is a test description.' - - -@pytest.mark.parametrize( - 'test_upload_session, expected_get_url, expected_commit_url', - [ - ( - lazy_fixture('upload_session_using_upload_session_urls'), - SESSION_ENDPOINTS['list_parts'], - SESSION_ENDPOINTS['commit'] - ), - ( - lazy_fixture('upload_session_not_using_upload_session_urls'), - f'{API.UPLOAD_URL}/files/upload_sessions/{UPLOAD_SESSION_ID}/parts', - f'{API.UPLOAD_URL}/files/upload_sessions/{UPLOAD_SESSION_ID}/commit' - ) - ]) -def test_commit_with_missing_params(mock_box_session, test_upload_session, expected_get_url, expected_commit_url): - sha1 = hashlib.sha1() - sha1.update(b'fake_file_data') - file_id = '12345' - file_type = 'file' - parts = [ - { - 'part_id': '8F0966B1', - 'offset': 0, - 'size': 8, - 'sha1': None, - }, - ] - expected_data = { - 'parts': parts, - } - expected_headers = { - 'Content-Type': 'application/json', - 'Digest': f'SHA={base64.b64encode(sha1.digest()).decode("utf-8")}', - } - mock_entry = { - 'part_id': '8F0966B1', - 'offset': 0, - 'size': 8, - 'sha1': None, - } - mock_box_session.get.return_value.json.return_value = { - 'entries': [mock_entry], - 'offset': 0, - 'total_count': 1, - 'limit': 1000, - } - mock_box_session.post.return_value.json.return_value = { - 'entries': [ - { - 'type': file_type, - 'id': file_id, - }, - ], - } - created_file = test_upload_session.commit(content_sha1=sha1.digest()) - mock_box_session.get.assert_called_once_with(expected_get_url, params={'offset': None}) - mock_box_session.post.assert_called_once_with( - expected_commit_url, data=json.dumps(expected_data), headers=expected_headers - ) - assert isinstance(created_file, File) - assert created_file.id == file_id - assert created_file.type == file_type - - -@pytest.mark.parametrize( - 'test_upload_session, expected_url', - [ - (lazy_fixture('upload_session_using_upload_session_urls'), SESSION_ENDPOINTS['commit']), - (lazy_fixture('upload_session_not_using_upload_session_urls'), - f'{API.UPLOAD_URL}/files/upload_sessions/{UPLOAD_SESSION_ID}/commit') - ]) -def test_commit_returns_none_when_202_is_returned(mock_box_session, test_upload_session, expected_url): - sha1 = hashlib.sha1() - sha1.update(b'fake_file_data') - file_etag = '7' - file_attributes = {'description': 'This is a test description.'} - parts = [ - { - 'part_id': 'ABCDEF123', - 'offset': 0, - 'size': 8, - 'sha1': 'fake_sha1', - }, - { - 'part_id': 'ABCDEF456', - 'offset': 8, - 'size': 8, - 'sha1': 'fake_sha1', - }, - ] - expected_data = { - 'attributes': file_attributes, - 'parts': parts, - } - expected_headers = { - 'Content-Type': 'application/json', - 'Digest': f'SHA={base64.b64encode(sha1.digest()).decode("utf-8")}', - 'If-Match': '7', - } - mock_box_session.post.side_effect = BoxAPIException(status=202) - - created_file = test_upload_session.commit( - content_sha1=sha1.digest(), parts=parts, file_attributes=file_attributes, etag=file_etag - ) - - mock_box_session.post.assert_called_once_with( - expected_url, data=json.dumps(expected_data), headers=expected_headers - ) - assert created_file is None - - -@pytest.mark.parametrize( - 'test_upload_session', - [ - lazy_fixture('upload_session_using_upload_session_urls'), - lazy_fixture('upload_session_not_using_upload_session_urls'), - ]) -def test_get_chunked_uploader_for_stream(test_upload_session): - file_size = 197520 - part_bytes = b'abcdefgh' - stream = io.BytesIO(part_bytes) - chunked_uploader = test_upload_session.get_chunked_uploader_for_stream(stream, file_size) - assert isinstance(chunked_uploader, ChunkedUploader) - - -@pytest.mark.parametrize( - 'test_upload_session', - [ - lazy_fixture('upload_session_using_upload_session_urls'), - lazy_fixture('upload_session_not_using_upload_session_urls'), - ]) -def test_get_chunked_uploader(mock_content_response, mock_file_path, test_upload_session): - mock_file_stream = io.BytesIO(mock_content_response.content) - file_size = 197520 - with patch('os.stat') as stat: - stat.return_value.st_size = file_size - with patch('boxsdk.object.upload_session.open', return_value=mock_file_stream): - chunked_uploader = test_upload_session.get_chunked_uploader(mock_file_path) - assert isinstance(chunked_uploader, ChunkedUploader) - - -def test_get_url_do_not_use_session_urls_if_base_url_was_changed(upload_session_using_upload_session_urls): - old_base_upload_url = API.UPLOAD_URL - new_base_upload_url = 'https://new-upload.box.com/api/2.0' - API.UPLOAD_URL = new_base_upload_url - try: - url = upload_session_using_upload_session_urls.get_url('commit', url_key='commit') - assert url == f'{new_base_upload_url}/files/upload_sessions/{UPLOAD_SESSION_ID}/commit' - assert url != SESSION_ENDPOINTS['commit'] - finally: - API.UPLOAD_URL = old_base_upload_url - - -def test_get_url_uses_session_urls_if_base_url_was_not_changed(upload_session_using_upload_session_urls): - url = upload_session_using_upload_session_urls.get_url('commit', url_key='commit') - assert url != f'{API.UPLOAD_URL}/files/upload_sessions/{UPLOAD_SESSION_ID}/commit' - assert url == SESSION_ENDPOINTS['commit'] diff --git a/test/unit/object/test_user.py b/test/unit/object/test_user.py deleted file mode 100644 index 9043c5779..000000000 --- a/test/unit/object/test_user.py +++ /dev/null @@ -1,254 +0,0 @@ -import io -from unittest.mock import Mock, mock_open, patch -import json -import pytest - -from boxsdk.config import API -from boxsdk.object.email_alias import EmailAlias -from boxsdk.object.folder import Folder -from boxsdk.object.storage_policy_assignment import StoragePolicyAssignment -from boxsdk.network.default_network import DefaultNetworkResponse - - -def test_user_url(mock_user): - # pylint:disable=redefined-outer-name, protected-access - assert mock_user.get_url() == f'{API.BASE_API_URL}/users/{mock_user.object_id}' - - -def test_get_storage_policy_assignments(test_storage_policy_assignment, mock_user, mock_box_session): - expected_url = mock_box_session.get_url('storage_policy_assignments') - mock_assignment = { - 'type': test_storage_policy_assignment.object_type, - 'id': test_storage_policy_assignment.object_id, - 'assigned_to': { - 'type': mock_user.object_type, - 'id': mock_user.object_id, - }, - } - mock_box_session.get.return_value.json.return_value = { - 'next_marker': None, - 'limit': 1, - 'entries': [mock_assignment], - } - expected_params = { - 'resolved_for_type': mock_user.object_type, - 'resolved_for_id': mock_user.object_id, - } - assignment = mock_user.get_storage_policy_assignment() - mock_box_session.get.assert_called_once_with(expected_url, params=expected_params) - assert isinstance(assignment, StoragePolicyAssignment) - assert assignment.id == mock_assignment['id'] - assert assignment.type == mock_assignment['type'] - - -@pytest.fixture(scope='module') -def memberships_response(): - # pylint disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.json.return_value = { - 'entries': [ - {'type': 'group_membership', 'id': 101, 'user': {'type': 'user', 'id': 100}, - 'group': {'type': 'group', 'id': 300}}, - {'type': 'group_membership', 'id': 202, 'user': {'type': 'user', 'id': 200}, - 'group': {'type': 'group', 'id': 400}} - ], - 'limit': 2, - 'total_count': 2, - 'offset': 0, - } - return mock_network_response - - -@pytest.fixture() -def test_email_alias(mock_box_session): - return EmailAlias( - session=mock_box_session, - object_id='test_alias_id', - ) - - -def test_update(mock_user, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - user_id = mock_user.object_id - expected_url = mock_box_session.get_url('users', user_id) - user = { - 'type': 'user', - 'name': 'Test User', - 'id': 1234, - } - data = { - 'name': 'New User', - } - mock_box_session.put.return_value.json.return_value = user - new_user = mock_user.update_info(data=data) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None) - assert new_user.id == user['id'] - assert new_user.type == user['type'] - assert new_user.name == user['name'] - - -def test_delete(mock_user, mock_box_session): - user_id = mock_user.object_id - expected_url = mock_box_session.get_url('users', user_id) - expected_params = { - 'notify': True, - 'force': False, - } - mock_user.delete() - mock_box_session.delete.assert_called_once_with( - expected_url, - expect_json_response=False, - params=expected_params, - headers=None, - ) - - -def test_get_email_aliases(mock_user, mock_box_session): - # pylint:disable=redefined-outer-name - alias1_json = { - 'type': 'email_alias', - 'id': '12345', - 'email': 'foo@example.com', - } - alias2_json = { - 'type': 'email_alias', - 'id': '67890', - 'email': 'bar@example.com', - } - mock_box_session.get.return_value.json.return_value = { - 'total_count': 2, - 'entries': [alias1_json, alias2_json], - } - aliases = mock_user.get_email_aliases() - for alias, alias_json in zip(aliases, [alias1_json, alias2_json]): - assert alias.object_id == alias_json['id'] - # pylint:disable=protected-access - assert alias._session == mock_box_session - assert alias.email == alias_json['email'] - - -def test_add_email_alias_returns_the_correct_email_alias_object(mock_user, mock_box_session): - # pylint:disable=redefined-outer-name - test_email_alias = 'test@example.com' - expected_url = f'{API.BASE_API_URL}/users/{mock_user.object_id}/email_aliases' - expected_body = json.dumps({ - 'email': test_email_alias, - }) - mock_box_session.post.return_value.json.return_value = { - 'type': 'email_alias', - 'id': '1234', - 'email': test_email_alias, - } - new_email_alias = mock_user.add_email_alias(test_email_alias) - mock_box_session.post.assert_called_once_with(expected_url, data=expected_body) - assert isinstance(new_email_alias, EmailAlias) - assert new_email_alias._session == mock_box_session # pylint: disable=protected-access - assert new_email_alias.object_id == '1234' - - -def test_remove_email_alias(mock_user, mock_box_session, test_email_alias): - expected_url = f'{API.BASE_API_URL}/users/{mock_user.object_id}/email_aliases/{test_email_alias.object_id}' - mock_box_session.delete.return_value.ok = True - - result = mock_user.remove_email_alias(test_email_alias) - - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False) - assert result is True - - -@pytest.mark.parametrize('notify,fields,expected_params', [ - (None, None, {}), - (True, None, {'notify': True}), - (False, None, {'notify': False}), - (None, ['type', 'id', 'name'], {'fields': 'type,id,name'}), - (False, ['type', 'id'], {'notify': False, 'fields': 'type,id'}), -]) -def test_transfer_content(mock_user, mock_box_session, notify, fields, expected_params): - # pylint:disable=redefined-outer-name - expected_url = f"{API.BASE_API_URL}/users/{mock_user.object_id}/folders/0" - expected_body = json.dumps({ - 'owned_by': { - 'id': mock_user.object_id - }, - }) - move_items_response = { - 'type': 'folder', - 'id': '12345', - 'name': 'That Other User\'s Content', - } - mock_box_session.put.return_value.json.return_value = move_items_response - moved_item = mock_user.transfer_content(mock_user, notify=notify, fields=fields) - mock_box_session.put.assert_called_once_with(expected_url, data=expected_body, params=expected_params) - assert isinstance(moved_item, Folder) - assert moved_item.id == move_items_response['id'] - assert moved_item.name == move_items_response['name'] - assert moved_item._session == mock_box_session # pylint:disable=protected-access - - -def test_get_group_memberships( - mock_user, - mock_box_session, - memberships_response, -): - # pylint:disable=redefined-outer-name - expected_url = f'{API.BASE_API_URL}/users/{mock_user.object_id}/memberships' - mock_box_session.get.return_value = memberships_response - memberships = mock_user.get_group_memberships() - for membership, expected_id in zip(memberships, [101, 202]): - assert membership.object_id == expected_id - # pylint:disable=protected-access - assert membership._session == mock_box_session - mock_box_session.get.assert_called_once_with(expected_url, params={'offset': None}) - - -def test_get_user_avatar(mock_user, mock_box_session, mock_content_response): - expected_url = mock_user.get_url('avatar') - mock_box_session.get.return_value = mock_content_response - avatar_content = mock_user.get_avatar() - assert avatar_content == mock_content_response.content - mock_box_session.get.assert_called_once_with( - expected_url, - expect_json_response=False, - ) - - -@pytest.mark.parametrize('image_extension', ['jpg', 'jpeg', 'png']) -def test_upload_avatar(mock_user, mock_box_session, mock_image_path, image_extension): - expected_url = mock_user.get_url('avatar') - upload_avatar_response = {'pic_urls': {'large': 'url1', 'preview': 'url2', 'small': 'url3'}} - mock_box_session.post.return_value.json.return_value = upload_avatar_response - - with patch('boxsdk.object.user.open', mock_open()) as mock_image: - avatar_urls = mock_user.upload_avatar(mock_image_path) - - assert avatar_urls == upload_avatar_response['pic_urls'] - mock_box_session.post.assert_called_once_with( - expected_url, - files={'pic': (f'avatar.{image_extension}', mock_image.return_value, f'image/{image_extension}')} - ) - - -@pytest.mark.parametrize('image_extension', ['jpg', 'jpeg', 'png']) -def test_upload_avatar_stream(mock_user, mock_box_session, image_extension): - expected_url = mock_user.get_url('avatar') - upload_avatar_response = {'pic_urls': {'large': 'url1', 'preview': 'url2', 'small': 'url3'}} - mock_box_session.post.return_value.json.return_value = upload_avatar_response - image_stream = io.BytesIO(b"some image stream") - - avatar_urls = mock_user.upload_avatar_stream(image_stream=image_stream, image_extension=image_extension) - - assert avatar_urls == upload_avatar_response['pic_urls'] - mock_box_session.post.assert_called_once_with( - expected_url, - files={'pic': (f'avatar.{image_extension}', image_stream, f'image/{image_extension}')} - ) - - -def test_delete_avatar_stream(mock_user, mock_box_session): - expected_url = mock_user.get_url('avatar') - mock_box_session.delete.return_value.ok = True - - was_deleted_successfully = mock_user.delete_avatar() - - assert was_deleted_successfully - mock_box_session.delete.assert_called_once_with(expected_url) diff --git a/test/unit/object/test_web_link.py b/test/unit/object/test_web_link.py deleted file mode 100644 index db8c1643a..000000000 --- a/test/unit/object/test_web_link.py +++ /dev/null @@ -1,140 +0,0 @@ -import json - -from boxsdk.config import API -from boxsdk.object.web_link import WebLink -from boxsdk.util.datetime_formatter import normalize_date_to_rfc3339_format -from boxsdk.util.default_arg_value import SDK_VALUE_NOT_SET - - -def test_get(mock_box_session, test_web_link): - # pylint:disable=redefined-outer-name, protected-access - web_link_id = test_web_link.object_id - expected_url = f'{API.BASE_API_URL}/web_links/{web_link_id}' - mock_web_link = { - 'type': 'web_link', - 'url': 'https://test/com', - 'id': 1234, - 'created_at': '2015-05-07T15:00:01-07:00', - } - mock_box_session.get.return_value.json.return_value = mock_web_link - web_link = test_web_link.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert web_link.id == mock_web_link['id'] - assert web_link.type == mock_web_link['type'] - assert web_link.url == mock_web_link['url'] - assert isinstance(web_link, WebLink) - - -def test_update(mock_box_session, test_web_link): - # pylint:disable=redefined-outer-name, protected-access - web_link_id = test_web_link.object_id - expected_url = f'{API.BASE_API_URL}/web_links/{web_link_id}' - mock_web_link = { - 'type': 'web_link', - 'url': 'https://newtest.com', - 'id': 1234, - 'created_at': '2015-05-07T15:00:01-07:00', - } - data = { - 'url': 'https://newtest.com', - } - mock_box_session.put.return_value.json.return_value = mock_web_link - web_link = test_web_link.update_info(data=data) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None) - assert web_link.id == mock_web_link['id'] - assert web_link.type == mock_web_link['type'] - assert web_link.url == mock_web_link['url'] - assert isinstance(web_link, WebLink) - - -def test_delete(mock_box_session, test_web_link): - web_link_id = test_web_link.object_id - expected_url = f'{API.BASE_API_URL}/web_links/{web_link_id}' - test_web_link.delete() - mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=None, params={}) - - -def test_get_shared_link( - test_web_link, - mock_box_session, - shared_link_access, - shared_link_unshared_at, - shared_link_password, - shared_link_vanity_name, - test_url, -): - # pylint:disable=redefined-outer-name, protected-access - expected_url = test_web_link.get_url() - mock_box_session.put.return_value.json.return_value = { - 'type': test_web_link.object_type, - 'id': test_web_link.object_id, - 'shared_link': { - 'url': test_url, - }, - } - expected_data = {'shared_link': {}} - if shared_link_access is not None: - expected_data['shared_link']['access'] = shared_link_access - if shared_link_unshared_at is not SDK_VALUE_NOT_SET: - expected_data['shared_link']['unshared_at'] = normalize_date_to_rfc3339_format(shared_link_unshared_at) - if shared_link_password is not None: - expected_data['shared_link']['password'] = shared_link_password - if shared_link_vanity_name is not None: - expected_data['shared_link']['vanity_name'] = shared_link_vanity_name - - url = test_web_link.get_shared_link( - access=shared_link_access, - unshared_at=shared_link_unshared_at, - password=shared_link_password, - vanity_name=shared_link_vanity_name, - ) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(expected_data), - headers=None, - params=None, - ) - assert url == test_url - - -def test_clear_unshared_at_for_shared_link( - test_web_link, - mock_box_session, - test_url, -): - expected_url = test_web_link.get_url() - mock_box_session.put.return_value.json.return_value = { - 'type': test_web_link.object_type, - 'id': test_web_link.object_id, - 'shared_link': { - 'url': test_url, - 'unshared_at': None, - }, - } - expected_data = {'shared_link': {'unshared_at': None, }, } - shared_link = test_web_link.get_shared_link(unshared_at=None) - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps(expected_data), - headers=None, - params=None, - ) - assert shared_link is test_url - - -def test_remove_shared_link(test_web_link, mock_box_session): - # pylint:disable=redefined-outer-name, protected-access - expected_url = test_web_link.get_url() - mock_box_session.put.return_value.json.return_value = { - 'type': test_web_link.object_type, - 'id': test_web_link.object_id, - 'shared_link': None, - } - removed = test_web_link.remove_shared_link() - mock_box_session.put.assert_called_once_with( - expected_url, - data=json.dumps({'shared_link': None}), - headers=None, - params=None, - ) - assert removed is True diff --git a/test/unit/object/test_webhook.py b/test/unit/object/test_webhook.py deleted file mode 100644 index e598ee79b..000000000 --- a/test/unit/object/test_webhook.py +++ /dev/null @@ -1,95 +0,0 @@ -from unittest.mock import Mock -import json -import pytest - -from boxsdk.config import API -from boxsdk.network.default_network import DefaultNetworkResponse -from boxsdk.object.webhook import Webhook - - -@pytest.fixture(scope='module') -def delete_webhook_response(): - # pylint:disable=redefined-outer-name - mock_network_response = Mock(DefaultNetworkResponse) - mock_network_response.ok = True - return mock_network_response - - -def test_delete_webhook_return_the_correct_response( - test_webhook, - mock_box_session, - delete_webhook_response, -): - # pylint:disable=redefined-outer-name - mock_box_session.delete.return_value = delete_webhook_response - response = test_webhook.delete() - expected_url = f'{API.BASE_API_URL}/webhooks/{test_webhook.object_id}' - mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None) - assert response is True - - -def test_get(test_webhook, mock_box_session): - expected_url = f'{API.BASE_API_URL}/webhooks/{test_webhook.object_id}' - mock_box_session.get.return_value.json.return_value = { - 'type': test_webhook.object_type, - 'id': test_webhook.object_id, - 'created_at': '2016-05-04T18:51:45-07:00', - 'address': 'https://example.com', - 'triggers': ['FILE.UPLOADED'], - } - webhook = test_webhook.get() - mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None) - assert isinstance(webhook, Webhook) - assert webhook.object_type == test_webhook.object_type - assert webhook.object_id == test_webhook.object_id - assert webhook.created_at == '2016-05-04T18:51:45-07:00' - assert webhook.address == 'https://example.com' - assert webhook.triggers == ['FILE.UPLOADED'] - - -def test_update(test_webhook, mock_box_session): - expected_url = f'{API.BASE_API_URL}/webhooks/{test_webhook.object_id}' - mock_box_session.put.return_value.json.return_value = { - 'type': test_webhook.object_type, - 'id': test_webhook.object_id, - 'address': 'https://testnotification.com', - 'triggers': ['FILE.DOWNLOADED'] - } - data = { - 'address': 'https://testnotification.com', - 'triggers': ['FILE.DOWNLOADED'], - } - webhook = test_webhook.update_info(data=data) - mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None) - assert isinstance(webhook, Webhook) - assert webhook.type == test_webhook.object_type - assert webhook.id == test_webhook.object_id - assert webhook.address == 'https://testnotification.com' - assert webhook.triggers == ['FILE.DOWNLOADED'] - - -@pytest.mark.parametrize( - 'signature_version,signature_algorithm,primary_key,secondary_key,expected_result', - [ - ('1', 'HmacSHA256', 'SamplePrimaryKey', 'SampleSecondaryKey', True), - ('1', 'HmacSHA256', 'SamplePrimaryKey', None, True), - ('1', 'HmacSHA256', 'WrongPrimaryKey', 'SampleSecondaryKey', True), - ('1', 'HmacSHA256', 'WrongPrimaryKey', 'WrongSecondaryKey', False), - ('1', 'HmacSHA256', None, None, False), - ('2', 'HmacSHA256', 'SamplePrimaryKey', 'SampleSecondaryKey', False), - ('1', 'WrongAlgorithm', 'SamplePrimaryKey', 'SampleSecondaryKey', False), - ] -) -def test_validate_message(signature_version, signature_algorithm, primary_key, secondary_key, expected_result): - # pylint: disable=C0301 - body = b'{"type":"webhook_event","webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' - headers = { - 'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f', - 'box-delivery-timestamp': '2020-01-01T00:00:00-07:00', - 'box-signature-algorithm': signature_algorithm, - 'box-signature-primary': '6TfeAW3A1PASkgboxxA5yqHNKOwFyMWuEXny/FPD5hI=', - 'box-signature-secondary': 'v+1CD1Jdo3muIcbpv5lxxgPglOqMfsNHPV899xWYydo=', - 'box-signature-version': signature_version, - } - is_validated = Webhook.validate_message(body, headers, primary_key, secondary_key) - assert is_validated is expected_result diff --git a/test/unit/pagination/__init__.py b/test/unit/pagination/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/unit/pagination/box_object_collection_test_base.py b/test/unit/pagination/box_object_collection_test_base.py deleted file mode 100644 index 8645f8bc4..000000000 --- a/test/unit/pagination/box_object_collection_test_base.py +++ /dev/null @@ -1,112 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Any, Union, List, TYPE_CHECKING - -import pytest - -from boxsdk.util.translator import Translator - -if TYPE_CHECKING: - from boxsdk.object.base_object import BaseObject - from boxsdk.pagination.box_object_collection import BoxObjectCollection - from boxsdk.session.session import Session - - -class BoxObjectCollectionTestBase(ABC): - NUM_ENTRIES = 25 - - @staticmethod - @pytest.fixture() - def translator(): - return Translator() - - @pytest.fixture() - def entries(self): - all_entries = [] - for i in range(self.NUM_ENTRIES): - all_entries.append({ - "type": "file", - "id": str(1000 + i), - "sequence_id": str(i), - "etag": str(10 + i), - "name": f"file_{i}.txt", - }) - return all_entries - - @abstractmethod - @pytest.fixture() - def mock_items_response(self, entries): - raise NotImplementedError - - @abstractmethod - @pytest.fixture() - def mock_session(self, translator, mock_items_response): - raise NotImplementedError - - @abstractmethod - def _object_collection_instance( - self, - session: 'Session', - limit: int, - return_full_pages: bool = False, - starting_pointer: Any = None - ) -> 'BoxObjectCollection': - raise NotImplementedError - - @staticmethod - def _assert_items_dict_and_objects_same( - expected_items_dict: Union[list, dict], - returned_item_objects: List['BaseObject'] - ) -> None: - """ - A fixture very specific to this test class. Asserts that the list of items in dictionary form are the - same (at least in name, and in quantity) as a list of BaseObjects. - - :param expected_items_dict: List of expected items, represented as a dictionary. - :param returned_item_objects: List of item instances (BaseObject) returned by SUT. - """ - expected_num = len(expected_items_dict) - actual_num = len(returned_item_objects) - assert actual_num == expected_num, f'Expected {expected_num} items, got {actual_num}' - returned_item_names = [item.name for item in returned_item_objects] - for expected_item_dict in expected_items_dict: - assert expected_item_dict['name'] in returned_item_names, f'Missing item: {expected_item_dict["name"]}' - - @pytest.mark.parametrize('return_full_pages', (True, False)) - @pytest.mark.parametrize('limit', (1, 3, 5, NUM_ENTRIES, 1000)) - def test_object_collection_pages_through_all_entries(self, mock_session, entries, limit, return_full_pages): - """ - Tests the basic iteration functionality of the box object collection. - """ - object_collection = self._object_collection_instance(mock_session, limit, return_full_pages) - iterated_items = [] - for item_or_page in object_collection: - if return_full_pages: - iterated_items.extend(item_or_page) - else: - iterated_items.append(item_or_page) - self._assert_items_dict_and_objects_same(entries, iterated_items) - - def test_new_object_collection_starts_off_from_last_pointer(self, mock_session, entries): - """ - Start paging with one object collection instance, and then finish paging with a new object collection - instance, starting off from the next_pointer() of the previous instance. - - The iterated items should be identical as if it were iterated through with just one object collection - instance. - """ - iterated_items = [] - object_collection = self._object_collection_instance(mock_session, limit=5, return_full_pages=True) - iterated_items.extend(object_collection.next()) - next_pointer = object_collection.next_pointer() - - # Create a new object collection, starting off where the previous collection left off. - new_object_collection = self._object_collection_instance( - mock_session, - limit=5, - return_full_pages=True, - starting_pointer=next_pointer - ) - for page in new_object_collection: - iterated_items.extend(page) - - self._assert_items_dict_and_objects_same(entries, iterated_items) diff --git a/test/unit/pagination/test_limit_offset_based_object_collection.py b/test/unit/pagination/test_limit_offset_based_object_collection.py deleted file mode 100644 index 0d159279e..000000000 --- a/test/unit/pagination/test_limit_offset_based_object_collection.py +++ /dev/null @@ -1,132 +0,0 @@ -import json -from unittest.mock import Mock, PropertyMock - -import pytest - -from boxsdk.network.default_network import DefaultNetworkResponse -from boxsdk.pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection -from boxsdk.session.box_response import BoxResponse -from boxsdk.session.session import Session -from .box_object_collection_test_base import BoxObjectCollectionTestBase - - -class TestLimitOffsetBasedObjectCollection(BoxObjectCollectionTestBase): - DEFAULT_LIMIT = 100 - - @pytest.fixture() - def mock_items_response(self, entries): - """Baseclass override.""" - # pylint:disable=redefined-outer-name - def get_response(limit, offset): - mock_box_response = Mock(BoxResponse) - mock_network_response = Mock(DefaultNetworkResponse) - mock_box_response.network_response = mock_network_response - mock_box_response.json.return_value = mock_json = { - 'entries': entries[offset:limit + offset], - 'total_count': len(entries), - 'limit': limit, - } - mock_box_response.content = json.dumps(mock_json).encode() - mock_box_response.status_code = 200 - mock_box_response.ok = True - return mock_box_response - return get_response - - @pytest.fixture() - def mock_session(self, translator, mock_items_response): - """Baseclass override.""" - mock_box_session = Mock(Session) - type(mock_box_session).translator = PropertyMock( - return_value=translator - ) - - def mock_items_side_effect(_, params): - limit = min(params.get('limit', self.DEFAULT_LIMIT), self.DEFAULT_LIMIT) - offset = params.get('offset', 0) - return mock_items_response(limit, offset) - - mock_box_session.get.side_effect = mock_items_side_effect - return mock_box_session - - @pytest.fixture() - def mock_session_with_bogus_limit(self, mock_session, mock_items_response): - """Baseclass override.""" - # pylint:disable=no-self-use - - def mock_items_side_effect(_, params): - limit = 0 - offset = params.get('offset', 0) - return mock_items_response(limit, offset) - - mock_session.get.side_effect = mock_items_side_effect - return mock_session - - def _object_collection_instance(self, session, limit=None, return_full_pages=False, starting_pointer=None): - """Baseclass override.""" - if starting_pointer is None: - starting_pointer = 0 - return LimitOffsetBasedObjectCollection( - session, - '/some/endpoint', - limit=limit, - return_full_pages=return_full_pages, - offset=starting_pointer, - ) - - @pytest.mark.parametrize('return_full_pages', (True, False)) - def test_object_collection_sets_next_pointer_correctly(self, mock_session, return_full_pages): - page_size = 10 - object_collection = LimitOffsetBasedObjectCollection( - mock_session, - '/some/endpoint', - limit=page_size, - return_full_pages=return_full_pages, - ) - - assert object_collection.next_pointer() == 0 - object_collection.next() - assert object_collection.next_pointer() == page_size - - # Iterate to the last page, which doesn't return a full page. - list(object_collection) - assert object_collection.next_pointer() == self.NUM_ENTRIES - - def test_object_collection_raises_stop_iteration_when_starting_offset_is_too_far(self, mock_session, entries): - """ - If the specified initial offset for the object collection is higher than the total number of items, - then the first call to next() on the object collection should raise a StopIteration. - """ - starting_offset = len(entries) + 10 - object_collection = self._object_collection_instance( - mock_session, - limit=5, - return_full_pages=False, - starting_pointer=starting_offset - ) - with pytest.raises(StopIteration): - object_collection.next() - - def test_object_collection_sets_limit_to_returned_value_if_originally_none(self, mock_session): - # No limit specified - object_collection = self._object_collection_instance(mock_session) - object_collection.next() - assert object_collection._limit == self.DEFAULT_LIMIT # pylint:disable=protected-access - - def test_object_collection_sets_limit_to_returned_value_if_originally_too_high(self, mock_session): - object_collection = self._object_collection_instance(mock_session, limit=(1 + self.DEFAULT_LIMIT)) - object_collection.next() - assert object_collection._limit == self.DEFAULT_LIMIT # pylint:disable=protected-access - - def test_box_returning_bogus_limit_raises_runtime_error(self, mock_session_with_bogus_limit, entries): - """ - Confirm that the SDK raises a RuntimeError is box.com happens to return a bogus `limit` in the response - """ - starting_offset = len(entries) + 10 - object_collection = self._object_collection_instance( - mock_session_with_bogus_limit, - limit=5, - return_full_pages=False, - starting_pointer=starting_offset - ) - with pytest.raises(RuntimeError): - object_collection.next() diff --git a/test/unit/pagination/test_marker_based_object_collection.py b/test/unit/pagination/test_marker_based_object_collection.py deleted file mode 100644 index 1d074fb3d..000000000 --- a/test/unit/pagination/test_marker_based_object_collection.py +++ /dev/null @@ -1,109 +0,0 @@ -import json -from unittest.mock import Mock, PropertyMock, ANY - -import pytest - -from boxsdk.network.default_network import DefaultNetworkResponse -from boxsdk.pagination.marker_based_object_collection import MarkerBasedObjectCollection -from boxsdk.session.box_response import BoxResponse -from boxsdk.session.session import Session -from .box_object_collection_test_base import BoxObjectCollectionTestBase - - -class TestMarkerBasedObjectCollection(BoxObjectCollectionTestBase): - """ - In order to conveniently mimic marker based paging for the purposes of this test, the markers ('next_marker' in - the response) returned by the mock_session object is always going to be of the convention: "marker_i", where - i is the starting 0-index based offset of the element. - """ - - NO_NEXT_MARKER = object() - - @staticmethod - @pytest.fixture(params=['', None, NO_NEXT_MARKER]) - def next_marker_value_for_last_page(request): - return request.param - - @pytest.fixture() - def mock_items_response(self, entries, next_marker_value_for_last_page): - """Baseclass override.""" - # pylint:disable=redefined-outer-name,arguments-differ - def get_response(limit, marker): - mock_box_response = Mock(BoxResponse) - mock_network_response = Mock(DefaultNetworkResponse) - mock_box_response.network_response = mock_network_response - - mock_json = {} - # The marker string should be of format: "marker_i", where i is the offset. Parse that out. - # If the marker is None, then begin paging from the start of the entries. - offset = 0 - if marker is not None: - offset = int(marker.split('_')[1]) - mock_json['entries'] = entries[offset:limit + offset] - - # A next_marker is only returned if there are more pages left. - if (offset + limit) < len(entries): - mock_json['next_marker'] = f'marker_{offset + limit}' - elif next_marker_value_for_last_page is not self.NO_NEXT_MARKER: - mock_json['next_marker'] = next_marker_value_for_last_page - - mock_box_response.json.return_value = mock_json - mock_box_response.content = json.dumps(mock_json).encode() - mock_box_response.status_code = 200 - mock_box_response.ok = True - return mock_box_response - return get_response - - @pytest.fixture() - def mock_session(self, translator, mock_items_response): - """Baseclass override.""" - mock_box_session = Mock(Session) - type(mock_box_session).translator = PropertyMock(return_value=translator) - - def mock_items_side_effect(_, params): - limit = params['limit'] - marker = params.get('marker', None) - return mock_items_response(limit, marker) - - mock_box_session.get.side_effect = mock_items_side_effect - return mock_box_session - - def _object_collection_instance( # pylint:disable=arguments-differ - self, - session, - limit, - return_full_pages=False, - starting_pointer=None, - supports_limit_offset_paging=False - ): - """Baseclass override.""" - return MarkerBasedObjectCollection( - session, - '/some/endpoint', - limit=limit, - return_full_pages=return_full_pages, - marker=starting_pointer, - supports_limit_offset_paging=supports_limit_offset_paging, - ) - - @pytest.mark.parametrize('return_full_pages', (True, False)) - def test_object_collection_sets_next_pointer_correctly(self, mock_session, return_full_pages): - object_collection = self._object_collection_instance(mock_session, limit=5, return_full_pages=return_full_pages) - assert object_collection.next_pointer() is None - object_collection.next() - assert object_collection.next_pointer() == 'marker_5' - - @pytest.mark.parametrize('supports_limit_offset_paging', (True, False)) - def test_object_collection_specifies_marker_param(self, mock_session, supports_limit_offset_paging): - object_collection = self._object_collection_instance( - mock_session, - limit=100, - supports_limit_offset_paging=supports_limit_offset_paging - ) - object_collection.next() - - # Assert - expected_params = {'limit': 100} - if supports_limit_offset_paging: - expected_params['useMarker'] = True - mock_session.get.assert_called_with(ANY, params=expected_params) diff --git a/test/unit/pagination/test_page.py b/test/unit/pagination/test_page.py deleted file mode 100644 index fd99b4a1e..000000000 --- a/test/unit/pagination/test_page.py +++ /dev/null @@ -1,102 +0,0 @@ -from unittest.mock import Mock, PropertyMock -import pytest - -from boxsdk.object.file import File -from boxsdk.object.folder import Folder -from boxsdk.pagination.page import Page -from boxsdk.session.session import Session -from boxsdk.util.translator import Translator - - -@pytest.fixture() -def translator(): - return Translator() - - -@pytest.fixture() -def mock_session(translator): - mock_box_session = Mock(Session) - type(mock_box_session).translator = PropertyMock( - return_value=translator - ) - return mock_box_session - - -@pytest.fixture() -def page_builder(mock_session): - def factory_function(response): - return Page( - session=mock_session, - response_object=response - ) - return factory_function - - -@pytest.fixture() -def item_checker(mock_session): - def item_checker_function(item_type, response_object, item): - assert item.id == response_object['id'] - assert isinstance(item, item_type) - assert item.response_object == response_object - assert item.session is mock_session - - return item_checker_function - - -def test_response_property(page_builder): - response = {1: 10, 2: 20, 3: 30} - page = page_builder(response) - assert response == page.response_object - - -def test_getitem(page_builder, item_checker): - entry_1 = { - "type": "folder", - "id": "192429928", - "sequence_id": "1", - "etag": "1", - "name": "Stephen Curry Three Pointers" - } - entry_2 = { - "type": "file", - "id": "818853862", - "sequence_id": "0", - "etag": "0", - "name": "Warriors.jpg" - } - response_entries = [entry_1, entry_2] - response = {"entries": response_entries} - page = page_builder(response) - - item_checker(Folder, entry_1, page[0]) - item_checker(File, entry_2, page[1]) - assert page[1] == page[-1] - - -def test_getitem_past_length_raises(page_builder): - length = 7 - response_entries = [object() for _ in range(length)] - response = {"entries": response_entries} - page = page_builder(response=response) - - with pytest.raises(IndexError): - page[length] # pylint:disable=pointless-statement - - -@pytest.mark.parametrize('length', (0, 1, 10)) -def test_len(page_builder, length): - response_entries = [object() for _ in range(length)] - response = { - "entries": response_entries - } - page = page_builder(response=response) - assert len(page) == length - - -def test_translation_of_page_entries(page_builder, mock_session, item_checker): - tested_item_types = ['folder', 'file', 'user', 'collaboration', 'group', 'foobar'] - response_entries = [{'type': item_type_string, 'id': str(i)} for i, item_type_string in enumerate(tested_item_types)] - response = {"entries": response_entries} - page = page_builder(response=response) - for i, item in enumerate(page): - item_checker(mock_session.translator.get(response_entries[i]['type']), response_entries[i], item) diff --git a/test/unit/session/__init__.py b/test/unit/session/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/unit/session/test_session.py b/test/unit/session/test_session.py deleted file mode 100644 index ddeaf3a1a..000000000 --- a/test/unit/session/test_session.py +++ /dev/null @@ -1,498 +0,0 @@ -from functools import partial -from io import IOBase, BytesIO -from numbers import Number -import os -from unittest.mock import MagicMock, Mock, PropertyMock, call, patch, ANY -from requests.exceptions import RequestException, SSLError, ConnectionError as RequestsConnectionError -from requests_toolbelt import MultipartEncoder - -import pytest - -from boxsdk import CCGAuth -from boxsdk.auth.oauth2 import OAuth2 -from boxsdk.config import API, Proxy -from boxsdk.exception import BoxAPIException, BoxException -from boxsdk.network.default_network import DefaultNetwork, DefaultNetworkResponse -from boxsdk.session.box_response import BoxResponse -from boxsdk.session.session import Session, Translator, AuthorizedSession - - -@pytest.fixture(scope='function', params=[False, True]) -def translator(default_translator, request): # pylint:disable=unused-argument - if request.param: - return Translator(extend_default_translator=True, new_child=True) - return None - - -@pytest.fixture -def initial_access_token(): - return 'fake_access_token' - - -@pytest.fixture -def mock_oauth(initial_access_token): - mock_oauth = MagicMock(OAuth2) - mock_oauth.access_token = initial_access_token - return mock_oauth - - -@pytest.fixture -def mock_network_layer(): - return Mock(DefaultNetwork) - - -@pytest.fixture -def unauthorized_session(mock_network_layer, translator): - # pylint:disable=redefined-outer-name - return Session(network_layer=mock_network_layer, translator=translator) - - -@pytest.fixture -def box_session(mock_oauth, mock_network_layer, translator): - # pylint:disable=redefined-outer-name - return AuthorizedSession(oauth=mock_oauth, network_layer=mock_network_layer, translator=translator) - - -@pytest.fixture -def ccg_auth(client_id, client_secret, mock_user_id, mock_enterprise_id, box_session) -> CCGAuth: - # pylint:disable=protected-access - auth = CCGAuth(client_id=client_id, client_secret=client_secret, user=mock_user_id, enterprise_id=mock_enterprise_id) - auth._session = box_session - return auth - - -@pytest.mark.parametrize('test_method', [ - Session.get, - Session.post, - Session.put, - Session.delete, - Session.options, -]) -def test_box_session_handles_unauthorized_response( - test_method, - box_session, - mock_oauth, - mock_network_layer, - unauthorized_response, - generic_successful_response, - test_url, -): - # pylint:disable=redefined-outer-name - - def get_access_token_from_auth_object(): - return mock_oauth.access_token - - mock_network_layer.request.side_effect = mock_responses = [unauthorized_response, generic_successful_response] - for mock_response in mock_responses: - type(mock_response).access_token_used = PropertyMock(side_effect=get_access_token_from_auth_object) - - def refresh(access_token_used): - assert access_token_used == mock_oauth.access_token - mock_oauth.access_token = 'fake_new_access_token' - return (mock_oauth.access_token, None) - - mock_oauth.refresh.side_effect = refresh - - box_response = test_method(box_session, url=test_url) - assert box_response.status_code == 200 - - -@pytest.mark.parametrize('test_method', [ - Session.get, - Session.post, - Session.put, - Session.delete, - Session.options, -]) -@pytest.mark.parametrize('initial_access_token', [None]) -def test_box_session_gets_access_token_before_request( - test_method, - box_session, - mock_oauth, - mock_network_layer, - generic_successful_response, - test_url, -): - # pylint:disable=redefined-outer-name - - def get_access_token_from_auth_object(): - return mock_oauth.access_token - - mock_network_layer.request.side_effect = mock_responses = [generic_successful_response] - for mock_response in mock_responses: - type(mock_response).access_token_used = PropertyMock(side_effect=get_access_token_from_auth_object) - - def refresh(access_token_used): - assert access_token_used == mock_oauth.access_token - mock_oauth.access_token = 'fake_new_access_token' - return (mock_oauth.access_token, None) - - mock_oauth.refresh.side_effect = refresh - - box_response = test_method(box_session, url=test_url, auto_session_renewal=True) - assert box_response.status_code == 200 - - -@pytest.mark.parametrize('test_method', [ - Session.get, - Session.post, - Session.put, - Session.delete, - Session.options, - partial(Session.request, method='head'), -]) -def test_box_session_retries_response_after_retry_after( - test_method, - box_session, - mock_network_layer, - retry_after_response, - generic_successful_response, - test_url, -): - # pylint:disable=redefined-outer-name - try: - if retry_after_response.status_code == 202 and test_method.__name__ == 'delete': - pytest.xfail("Delete operation should not be retried on 202 status code") - except AttributeError: - pass - - mock_network_layer.request.side_effect = [retry_after_response, generic_successful_response] - mock_network_layer.retry_after.side_effect = lambda delay, request, *args, **kwargs: request(*args, **kwargs) - - with patch('random.uniform', return_value=0.68): - box_response = test_method(box_session, url=test_url) - assert box_response.status_code == 200 - assert len(mock_network_layer.retry_after.call_args_list) == 1 - assert isinstance(mock_network_layer.retry_after.call_args[0][0], Number) - assert round(mock_network_layer.retry_after.call_args[0][0], 4) == 1 - - -@pytest.mark.parametrize('test_method', [ - Session.get, - Session.post, - Session.put, - Session.delete, - Session.options, - partial(Session.request, method='head'), -]) -def test_box_session_retries_request_after_server_error( - test_method, - box_session, - mock_network_layer, - server_error_response, - generic_successful_response, - test_url, -): - # pylint:disable=redefined-outer-name - mock_network_layer.request.side_effect = [server_error_response, server_error_response, generic_successful_response] - mock_network_layer.retry_after.side_effect = lambda delay, request, *args, **kwargs: request(*args, **kwargs) - - with patch('random.uniform', return_value=0.68): - box_response = test_method(box_session, url=test_url) - assert box_response.status_code == 200 - assert box_response.json() == generic_successful_response.json() - assert box_response.ok == generic_successful_response.ok - assert box_response.content == generic_successful_response.content - assert len(mock_network_layer.retry_after.call_args_list) == 2 - assert isinstance(mock_network_layer.retry_after.call_args_list[0][0][0], Number) - assert isinstance(mock_network_layer.retry_after.call_args_list[1][0][0], Number) - assert round(mock_network_layer.retry_after.call_args_list[0][0][0], 4) == 1.18 - assert round(mock_network_layer.retry_after.call_args_list[1][0][0], 4) == 2.36 - - -def test_box_session_seeks_file_after_retry(box_session, mock_network_layer, server_error_response, generic_successful_response, test_url): - # pylint:disable=redefined-outer-name - mock_network_layer.request.side_effect = [server_error_response, generic_successful_response] - mock_network_layer.retry_after.side_effect = lambda delay, request, *args, **kwargs: request(*args, **kwargs) - mock_file_1, mock_file_2 = MagicMock(IOBase), MagicMock(IOBase) - mock_file_1.tell.return_value = 0 - mock_file_2.tell.return_value = 3 - files = {'file': ('unused', mock_file_1), 'f2': ('unused', mock_file_2)} - - box_response = box_session.post(url=test_url, files=files) - assert box_response.status_code == 200 - assert box_response.json() == generic_successful_response.json() - assert box_response.ok == generic_successful_response.ok - mock_file_1.tell.assert_called_with() - mock_file_2.tell.assert_called_with() - mock_file_1.seek.assert_called_with(0) - assert mock_file_1.seek.call_count == 2 - mock_file_1.seek.assert_has_calls([call(0), call(0)]) - mock_file_2.seek.assert_called_with(3) - assert mock_file_2.seek.call_count == 2 - mock_file_2.seek.assert_has_calls([call(3), call(3)]) - - -def test_box_session_raises_for_non_json_response(box_session, mock_network_layer, non_json_response, generic_successful_response, test_url): - # pylint:disable=redefined-outer-name - mock_network_layer.request.side_effect = [non_json_response, generic_successful_response] - mock_network_layer.retry_after.side_effect = lambda delay, request, *args, **kwargs: request(*args, **kwargs) - - box_session.get(url=test_url) - - -def test_box_session_raises_for_non_json_response_after_retry(box_session, mock_network_layer, non_json_response, test_url): - # pylint:disable=redefined-outer-name - mock_network_layer.request.side_effect = [non_json_response] * (API.MAX_RETRY_ATTEMPTS + 1) - mock_network_layer.retry_after.side_effect = lambda delay, request, *args, **kwargs: request(*args, **kwargs) - - with pytest.raises(BoxAPIException): - box_session.get(url=test_url) - - -def test_box_session_raises_for_failed_response(box_session, mock_network_layer, bad_network_response, test_url): - # pylint:disable=redefined-outer-name - mock_network_layer.request.side_effect = [bad_network_response] - - with pytest.raises(BoxAPIException): - box_session.get(url=test_url) - - -@pytest.mark.parametrize('exc_message', [ - 'Connection aborted', - "Connection broken: ConnectionResetError(54, 'Connection reset by peer')" -]) -def test_box_session_retries_connection_aborted_exception( - box_session, mock_network_layer, generic_successful_request_response, test_url, exc_message -): - mock_network_layer.request.side_effect = [RequestsConnectionError(exc_message), generic_successful_request_response] - mock_network_layer.retry_after.side_effect = lambda delay, request, *args, **kwargs: request(*args, **kwargs) - - box_response = box_session.get(url=test_url) - assert box_response.status_code == 200 - - -def test_box_session_retries_connection_aborted_exception_on_ccg_auth_call(successful_token_response, ccg_auth, mock_user_id, access_token): - # pylint:disable=protected-access - ccg_auth._session._network_layer.request.side_effect = [RequestsConnectionError('Connection aborted'), successful_token_response] - ccg_auth._session._network_layer.retry_after.side_effect = lambda delay, request, *args, **kwargs: request(*args, **kwargs) - - new_access_token = ccg_auth.authenticate_user(mock_user_id) - assert new_access_token == access_token - - -def test_box_session_retries_requests_library_exceptions_only_once(box_session, mock_network_layer, test_url, generic_successful_request_response): - mock_network_layer.request.side_effect = [ - RequestException('Connection aborted'), - RequestException('Connection aborted'), - generic_successful_request_response - ] - mock_network_layer.retry_after.side_effect = lambda delay, request, *args, **kwargs: request(*args, **kwargs) - - with pytest.raises(RequestException): - box_session.get(url=test_url) - - -def test_box_session_raises_requests_library_exception_when_set_no_retries( - box_session, mock_network_layer, test_url, generic_successful_request_response -): - API.MAX_RETRY_ATTEMPTS = 0 - try: - mock_network_layer.request.side_effect = [RequestException('Connection aborted'), generic_successful_request_response] - - with pytest.raises(RequestException): - box_session.get(url=test_url) - finally: - API.MAX_RETRY_ATTEMPTS = 5 - - -def test_box_session_retries_and_reauthenticates_on_violation_of_protocol_exception( - box_session, mock_oauth, mock_network_layer, generic_successful_request_response, test_url -): - def refresh(access_token_used): - assert access_token_used == mock_oauth.access_token - mock_oauth.access_token = 'fake_new_access_token' - return (mock_oauth.access_token, None) - - mock_network_layer.request.side_effect = [ - SSLError("SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2396)')))"), - generic_successful_request_response - ] - mock_network_layer.retry_after.side_effect = lambda delay, request, *args, **kwargs: request(*args, **kwargs) - mock_oauth.refresh.side_effect = refresh - - box_session.get(url=test_url) - - assert mock_network_layer.request.call_count == 2 - assert mock_network_layer.request.mock_calls[0][2]['access_token'] == 'fake_access_token' - assert mock_network_layer.request.mock_calls[1][2]['access_token'] == 'fake_new_access_token' - - -def test_box_session_does_not_retry_other_requests_library_exceptions_than_specified(box_session, mock_network_layer, test_url): - mock_network_layer.request.side_effect = [RequestException('Unknown error')] - - with pytest.raises(RequestException): - box_session.get(url=test_url) - - -def test_box_session_raises_for_failed_response_with_error_and_error_description(box_session, mock_network_layer, bad_network_response_400, test_url): - mock_network_layer.request.side_effect = [bad_network_response_400] - try: - box_session.get(url=test_url) - pytest.fail('Should throw exception because of bad network response') - except BoxAPIException as exception: - assert exception.code == 'Example Error' - assert exception.message == 'Example Error Description' - - -def test_box_session_raises_for_failed_non_json_response(box_session, mock_network_layer, failed_non_json_response, test_url): - # pylint:disable=redefined-outer-name - mock_network_layer.request.side_effect = [failed_non_json_response] - - with pytest.raises(BoxAPIException): - box_session.get(url=test_url, expect_json_response=False) - - -def test_box_response_properties_pass_through_to_network_response_properties(): - mock_network_response = Mock(DefaultNetworkResponse) - box_result = BoxResponse(mock_network_response) - assert box_result.json() == mock_network_response.json() - assert box_result.content == mock_network_response.content - assert box_result.ok == mock_network_response.ok - assert box_result.status_code == mock_network_response.status_code - assert box_result.network_response == mock_network_response - - -def test_translator(box_session, translator, default_translator, original_default_translator): - assert isinstance(box_session.translator, Translator) - assert box_session.translator == default_translator - if translator: - assert box_session.translator is translator - - # Test that adding new registrations works. - - class Foo: - pass - - item_type = 'ƒøø' - box_session.translator.register(item_type, Foo) - assert box_session.translator.get(item_type) is Foo - - # Test that adding new registrations does not affect global state. - assert default_translator == original_default_translator - assert (set(box_session.translator) - set(default_translator)) == {item_type} - - -def test_session_uses_global_config(box_session, mock_network_layer, generic_successful_response, monkeypatch): - mock_network_layer.request.side_effect = generic_successful_response - example_dot_com = 'https://example.com/' - monkeypatch.setattr(API, 'BASE_API_URL', example_dot_com) - assert example_dot_com in box_session.get_url('foo', 'bar') - - -def test_session_uses_local_config(box_session, mock_network_layer, generic_successful_response, monkeypatch): - mock_network_layer.request.side_effect = generic_successful_response - example_dot_com = 'https://example.com/' - box_session.api_config.BASE_API_URL = example_dot_com - monkeypatch.setattr(API, 'BASE_API_URL', 'https://api.box.com') - assert example_dot_com in box_session.get_url('foo', 'bar') - - -@pytest.mark.parametrize( - 'attempt_number,retry_after_header,expected_result', - [ - (0, '', 1.18), - (1, '', 2.36), - (2, '', 4.72), - (3, '', 9.44), - (4, '', 18.88), - ] -) -def test_get_retry_after_time(box_session, attempt_number, retry_after_header, expected_result): - with patch('random.uniform', return_value=0.68): - retry_time = box_session.get_retry_after_time(attempt_number, retry_after_header) # pylint: disable=protected-access - retry_time = round(retry_time, 4) - assert retry_time == expected_result - - -@pytest.mark.parametrize( - 'test_proxy_url,test_proxy_auth,expected_proxy_dict', - [ - ('http://example-proxy.com', {'user': 'test_user', 'password': 'test_password', }, - {'http': 'http://test_user:test_password@example-proxy.com', 'https': 'http://test_user:test_password@example-proxy.com'}), - ('http://example-proxy.com', None, {'http': 'http://example-proxy.com', 'https': 'http://example-proxy.com'}), - ] -) -def test_proxy_attaches_to_request_correctly( - box_session, - monkeypatch, - mock_network_layer, - generic_successful_response, - test_proxy_url, test_proxy_auth, - expected_proxy_dict): - monkeypatch.setattr(Proxy, 'URL', test_proxy_url) - monkeypatch.setattr(Proxy, 'AUTH', test_proxy_auth) - mock_network_layer.request.side_effect = [generic_successful_response] - box_session.request('GET', test_proxy_url) - mock_network_layer.request.assert_called_once_with( - 'GET', - test_proxy_url, - access_token='fake_access_token', - headers=ANY, - proxies=expected_proxy_dict, - log_response_content=True, - ) - - -def test_proxy_malformed_dict_does_not_attach(box_session, monkeypatch, mock_network_layer, generic_successful_response): - test_proxy_url = 'http://example.com' - test_proxy_auth = { - 'foo': 'bar', - } - monkeypatch.setattr(Proxy, 'URL', test_proxy_url) - monkeypatch.setattr(Proxy, 'AUTH', test_proxy_auth) - mock_network_layer.request.side_effect = [generic_successful_response] - with pytest.raises(BoxException) as exc_info: - box_session.request('GET', test_proxy_url) - assert isinstance(exc_info.value, BoxException) - assert exc_info.value.args[0] == "The proxy auth dict you provided does not match pattern " \ - "{'user': 'example_user', 'password': 'example_password'}" - - -def test_proxy_network_config_property(box_session): - assert isinstance(box_session.proxy_config, Proxy) - - -def test_multipart_request_with_disabled_streaming_file_content( - box_session, mock_network_layer, generic_successful_response): - test_url = 'https://example.com' - file_bytes = os.urandom(1024) - mock_network_layer.request.side_effect = [generic_successful_response] - box_session.post( - url=test_url, - files={'file': ('unused', BytesIO(file_bytes))}, - data={'attributes': '{"name": "test_file"}'}, - stream_file_content=False - ) - mock_network_layer.request.assert_called_once_with( - 'POST', - test_url, - access_token='fake_access_token', - headers=ANY, - log_response_content=True, - files={'file': ('unused', ANY)}, - data={'attributes': '{"name": "test_file"}'}, - ) - - -def test_multipart_request_with_enabled_streaming_file_content( - box_session, mock_network_layer, generic_successful_response): - test_url = 'https://example.com' - file_bytes = os.urandom(1024) - mock_network_layer.request.side_effect = [generic_successful_response] - box_session.post( - url=test_url, - files={'file': ('unused', BytesIO(file_bytes))}, - data={'attributes': '{"name": "test_file"}'}, - stream_file_content=True - ) - call_args = mock_network_layer.request.call_args[0] - call_kwargs = mock_network_layer.request.call_args[1] - assert call_args[0] == 'POST' - assert call_args[1] == test_url - assert call_kwargs['access_token'] == 'fake_access_token' - assert call_kwargs['log_response_content'] is True - assert isinstance(call_kwargs['data'], MultipartEncoder) - assert call_kwargs['data'].fields['attributes'] == '{"name": "test_file"}' - assert call_kwargs['data'].fields['file'][0] == 'unused' - assert isinstance(call_kwargs['data'].fields['file'][1], BytesIO) diff --git a/test/unit/test_exception.py b/test/unit/test_exception.py deleted file mode 100644 index 7b100daeb..000000000 --- a/test/unit/test_exception.py +++ /dev/null @@ -1,66 +0,0 @@ -from unittest.mock import Mock -import pytest - -from boxsdk.exception import BoxAPIException, BoxOAuthException -from boxsdk.network.default_network import DefaultNetworkResponse - - -def test_box_api_exception(): - status = 400 - code = 'code' - message = 'message' - request_id = '12345' - headers = {'header': 'value'} - url = 'https://example.com' - method = 'GET' - context_info = {'context': 'value'} - box_exception = BoxAPIException( - status, - code=code, - message=message, - request_id=request_id, - headers=headers, - url=url, - method=method, - context_info=context_info, - ) - assert box_exception.status == status - assert box_exception.code == code - assert box_exception.message == message - assert box_exception.request_id == request_id - assert box_exception.headers == headers # pylint:disable=protected-access - assert box_exception.url == url - assert box_exception.method == method - assert box_exception.context_info == context_info - assert str(box_exception) == f'''Message: {message} -Status: {status} -Code: {code} -Request ID: {request_id} -Headers: {headers} -URL: {url} -Method: {method} -Context Info: {context_info}''' - - -@pytest.mark.parametrize('has_network_response', [True, False]) -def test_box_oauth_exception(has_network_response): - status = 400 - message = 'message' - url = 'https://example.com' - method = 'GET' - headers = {'header': 'value'} - network_response = Mock(DefaultNetworkResponse, headers=headers) if has_network_response else None - box_exception = BoxOAuthException( - status, - message=message, - url=url, - method=method, - network_response=network_response, - ) - assert str(box_exception) == f''' -Message: {message} -Status: {status} -URL: {url} -Method: {method} -Headers: {headers if has_network_response else 'N/A'}''' - assert box_exception.network_response is network_response diff --git a/test/unit/util/__init__.py b/test/unit/util/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/unit/util/test_api_call_decorator.py b/test/unit/util/test_api_call_decorator.py deleted file mode 100644 index 21b569834..000000000 --- a/test/unit/util/test_api_call_decorator.py +++ /dev/null @@ -1,113 +0,0 @@ -from unittest.mock import NonCallableMock -import pytest - -from boxsdk.object.cloneable import Cloneable -from boxsdk.util.api_call_decorator import api_call - - -@pytest.fixture -def api_call_result(): - return {'bar': 'ƒøø'} - - -@pytest.fixture(name='api_call_method') -def api_call_method_fixture(api_call_result): - - @api_call - def api_call_method(self, *args, **kwargs): - return self, args, kwargs, api_call_result - - return api_call_method - - -@pytest.fixture -def cloneable_subclass_with_api_call_method(api_call_method): - api_call_method_fixture = api_call_method - - # pylint:disable=abstract-method - class CloneableSubclass(Cloneable): - api_call_method = api_call_method_fixture - - return CloneableSubclass - - -@pytest.fixture -def mock_cloneable(cloneable_subclass_with_api_call_method): - - # pylint:disable=abstract-method - class MockCloneable(cloneable_subclass_with_api_call_method, NonCallableMock): - pass - - return MockCloneable(spec_set=cloneable_subclass_with_api_call_method, name='Cloneable') - - -def test_api_call_is_decorator(): - - @api_call - def func(): - pass - - assert callable(func) - assert hasattr(func, '__get__') - - -def test_api_call_decorated_function_must_be_a_method(): - - @api_call - def func(): - pass - - with pytest.raises(TypeError): - func() - - -def test_api_call_decorated_method_must_be_a_cloneable_method(): - - class NonCloneable: - @api_call - def func(self): - pass - - obj = NonCloneable() - with pytest.raises(TypeError): - obj.func() - - -def test_api_call_decorated_method_must_be_bound_to_an_instance_of_the_owner(mock_cloneable, api_call_method): - # pylint:disable=abstract-method - class CloneableSubclass2(Cloneable): - pass - - with pytest.raises(TypeError): - api_call_method.__get__(mock_cloneable, CloneableSubclass2) - - -def test_api_call_decorated_method_returns_itself_when_bound_to_none(api_call_method, cloneable_subclass_with_api_call_method): - assert api_call_method.__get__(None, Cloneable) is api_call_method - assert not hasattr(api_call_method.__get__(None, Cloneable), '__self__') - assert cloneable_subclass_with_api_call_method.api_call_method is api_call_method - assert not hasattr(cloneable_subclass_with_api_call_method.api_call_method, '__self__') - - -def test_api_call_decorated_method_binds_to_instance(mock_cloneable, api_call_method): - assert api_call_method.__get__(mock_cloneable, Cloneable) is not api_call_method - assert api_call_method.__get__(mock_cloneable, Cloneable).__self__ is mock_cloneable - assert mock_cloneable.api_call_method is not api_call_method - assert mock_cloneable.api_call_method.__self__ is mock_cloneable - - -def test_api_call_decorated_method_delegates_to_wrapped_method(mock_cloneable, api_call_result): - args = (1, 2, 'ƒøø', 'bar') - kwargs = {'bar': 'ƒøø'} - assert mock_cloneable.api_call_method(*args, **kwargs) == (mock_cloneable, args, kwargs, api_call_result) - - -def test_api_call_decorated_method_can_be_called_as_an_unbound_method_with_an_instance_as_the_first_argument( - mock_cloneable, - api_call_result, - cloneable_subclass_with_api_call_method, -): - args = (1, 2, 'ƒøø', 'bar') - kwargs = {'bar': 'ƒøø'} - api_call_method = cloneable_subclass_with_api_call_method.api_call_method - assert api_call_method(mock_cloneable, *args, **kwargs) == (mock_cloneable, args, kwargs, api_call_result) diff --git a/test/unit/util/test_datetime_formatter.py b/test/unit/util/test_datetime_formatter.py deleted file mode 100644 index f0b2c4789..000000000 --- a/test/unit/util/test_datetime_formatter.py +++ /dev/null @@ -1,81 +0,0 @@ -from unittest.mock import Mock - -import datetime -import pytest - -from pytest_lazyfixture import lazy_fixture - -from boxsdk.util import datetime_formatter - - -@pytest.mark.parametrize( - "valid_datetime_format", - ( - "2035-03-04T10:14:24+14:00", - "2035-03-04T10:14:24-04:00", - lazy_fixture("mock_datetime_rfc3339_str"), - ), -) -def test_leave_datetime_string_unchanged_when_rfc3339_formatted_str_provided( - valid_datetime_format, -): - formatted_str = datetime_formatter.normalize_date_to_rfc3339_format( - valid_datetime_format - ) - assert formatted_str == valid_datetime_format - - -@pytest.mark.parametrize( - "other_datetime_format", - ( - "2035-03-04T10:14:24.000+14:00", - "2035-03-04 10:14:24.000+14:00", - "2035/03/04 10:14:24.000+14:00", - "2035/03/04T10:14:24+14:00", - "2035/3/4T10:14:24+14:00", - lazy_fixture('mock_timezone_aware_datetime_obj'), - ), -) -def test_normalize_date_to_rfc3339_format_timezone_aware_datetime( - other_datetime_format, - mock_datetime_rfc3339_str, -): - formatted_str = datetime_formatter.normalize_date_to_rfc3339_format( - other_datetime_format - ) - assert formatted_str == mock_datetime_rfc3339_str - - -@pytest.mark.parametrize( - "timezone_naive_datetime", - ( - "2035-03-04T10:14:24.000", - "2035-03-04T10:14:24", - lazy_fixture('mock_timezone_naive_datetime_obj') - ), -) -def test_add_timezone_info_when_timezone_naive_datetime_provided( - timezone_naive_datetime, - mock_timezone_naive_datetime_obj, -): - formatted_str = datetime_formatter.normalize_date_to_rfc3339_format( - timezone_naive_datetime - ) - - local_timezone = datetime.datetime.now().tzinfo - expected_datetime = mock_timezone_naive_datetime_obj.astimezone( - tz=local_timezone - ).isoformat(timespec="seconds") - assert formatted_str == expected_datetime - - -def test_return_none_when_none_provided(): - assert datetime_formatter.normalize_date_to_rfc3339_format(None) is None - - -@pytest.mark.parametrize("inavlid_datetime_object", (Mock(),)) -def test_throw_type_error_when_invalid_datetime_object_provided( - inavlid_datetime_object, -): - with pytest.raises(TypeError): - datetime_formatter.normalize_date_to_rfc3339_format(inavlid_datetime_object) diff --git a/test/unit/util/test_enum.py b/test/unit/util/test_enum.py deleted file mode 100644 index a5e5e2362..000000000 --- a/test/unit/util/test_enum.py +++ /dev/null @@ -1,170 +0,0 @@ -from collections import OrderedDict -from enum import Enum -import pytest - -from boxsdk.util.enum import ExtendableEnumMeta - - -# pylint:disable=invalid-name -# So that we can have class definitions as pytest function fixtures. - - -@pytest.fixture(scope='function') -def EnumBase(): - - class EnumBase(Enum, metaclass=ExtendableEnumMeta): - pass - - return EnumBase - - -@pytest.fixture(scope='function') -def Enum1(EnumBase): - - class Enum1(EnumBase): - A = 1 - - return Enum1 - - -@pytest.fixture(scope='function') -def Enum2(EnumBase): - - class Enum2(EnumBase): - pass - - return Enum2 - - -@pytest.fixture(scope='function') -def Enum2_1(Enum2): - - class Enum2_1(Enum2): - B = 2 - - return Enum2_1 - - -@pytest.fixture(scope='function') -def Enum2_2(Enum2): - - class Enum2_2(Enum2): - C = 3 - - return Enum2_2 - - -@pytest.fixture(scope='function') -def EnumBaseWithSubclassesDefined(EnumBase, Enum1, Enum2_1, Enum2_2): # pylint:disable=unused-argument - return EnumBase - - -enum_member_names = ['A', 'B', 'C'] -enum_member_values = [1, 2, 3] - - -@pytest.fixture(scope='session', params=enum_member_names) -def enum_member_name(request): - return request.param - - -@pytest.fixture(scope='session', params=enum_member_values) -def enum_member_value(request): - return request.param - - -@pytest.fixture(scope='function') -def enum_members(Enum1, Enum2_1, Enum2_2): - members = OrderedDict() - for enum_member_name, enum_class in zip(enum_member_names, [Enum1, Enum2_1, Enum2_2]): - members[enum_member_name] = enum_class[enum_member_name] - return members - - -@pytest.fixture(scope='function') -def enum_instance(enum_member_name, enum_members): - return enum_members[enum_member_name] - - -def test_can_construct_enum_hierarchy(EnumBaseWithSubclassesDefined): # pylint:disable=unused-argument - pass - - -def test_can_not_construct_non_leaf_enum_with_members(Enum1): - with pytest.raises(TypeError): - class Enum1_1(Enum1): # pylint:disable=unused-variable - pass - - -def test_lookup(EnumBaseWithSubclassesDefined, enum_member_value): - EnumBase = EnumBaseWithSubclassesDefined - enum_instance = EnumBase.lookup(enum_member_value) - assert isinstance(enum_instance, EnumBase) - assert EnumBase.lookup(enum_instance) == enum_instance - assert enum_instance.__class__.lookup(enum_instance) == enum_instance - assert enum_instance.__class__(enum_instance) == enum_instance - assert enum_instance.__class__(enum_member_value) == enum_instance - - -def test_lookup_raises_value_error_for_non_members(EnumBaseWithSubclassesDefined): - with pytest.raises(Exception): - EnumBaseWithSubclassesDefined.lookup('foobar') - - -def test_members(EnumBaseWithSubclassesDefined, enum_members): - assert dict(EnumBaseWithSubclassesDefined.__members__) == enum_members - - -def test_contains_enum_instances(EnumBaseWithSubclassesDefined, enum_instance): - assert enum_instance in EnumBaseWithSubclassesDefined - - -def test_contains_returns_false_for_non_instances(EnumBaseWithSubclassesDefined, enum_member_name): - assert enum_member_name not in EnumBaseWithSubclassesDefined - - -def test_getitem(EnumBaseWithSubclassesDefined, enum_member_name, enum_instance): - assert EnumBaseWithSubclassesDefined[enum_member_name] == enum_instance - - -def test_getitem_raises_key_error_for_non_member_names(EnumBaseWithSubclassesDefined, enum_member_value): - with pytest.raises(KeyError): - EnumBaseWithSubclassesDefined[enum_member_value] # pylint:disable=pointless-statement - - -def test_getattr(EnumBaseWithSubclassesDefined, enum_member_name, enum_instance): - assert getattr(EnumBaseWithSubclassesDefined, enum_member_name) == enum_instance - - -def test_getattr_raises_attribute_error_for_non_member_names(EnumBaseWithSubclassesDefined): - with pytest.raises(AttributeError): - EnumBaseWithSubclassesDefined.foobar # pylint:disable=pointless-statement - - -def test_getattr_does_get_arbitrary_attributes_from_itself(EnumBaseWithSubclassesDefined): - EnumBase = EnumBaseWithSubclassesDefined - EnumBase.foobar = 'foobar' - assert EnumBase.foobar == 'foobar' - - -def test_getattr_does_not_get_arbitrary_attributes_from_subclasses(EnumBaseWithSubclassesDefined, Enum1): - Enum1.foobar = 'foobar' - with pytest.raises(AttributeError): - EnumBaseWithSubclassesDefined.foobar # pylint:disable=pointless-statement - - -def test_iter(EnumBaseWithSubclassesDefined, enum_members): - assert list(EnumBaseWithSubclassesDefined) == list(enum_members.values()) - - -def test_len(EnumBaseWithSubclassesDefined, enum_members): - assert len(EnumBaseWithSubclassesDefined) == len(enum_members) - - -def test_reversed(EnumBaseWithSubclassesDefined): - EnumBase = EnumBaseWithSubclassesDefined - assert list(reversed(list(reversed(EnumBase)))) == list(EnumBase) - - -def test_dir(EnumBaseWithSubclassesDefined): - assert set(enum_member_names).issubset(dir(EnumBaseWithSubclassesDefined)) diff --git a/test/unit/util/test_log.py b/test/unit/util/test_log.py deleted file mode 100644 index 91b2d7f1e..000000000 --- a/test/unit/util/test_log.py +++ /dev/null @@ -1,124 +0,0 @@ -import io -import logging -from unittest.mock import mock_open, patch, Mock - -import pytest - -import boxsdk.util.log - - -_MOCK_FILEPATH = '/home/user/boxsdk.log' -_MOCK_LOG_NAME = 'boxsdk' - - -@pytest.fixture(params=[io.StringIO(), _MOCK_FILEPATH]) -def stream_or_file(request): - return request.param - - -@pytest.fixture(params=[False, True]) -def debug(request): - return request.param - - -@pytest.fixture -def expected_log_level(debug): - return logging.DEBUG if debug else logging.INFO - - -@pytest.fixture(params=[_MOCK_LOG_NAME, None]) -def name(request): - return request.param - - -@pytest.fixture -def mock_logger(): - return Mock(logging.Logger) - - -def test_setup_logging(stream_or_file, debug, expected_log_level, name, mock_logger): - mock_file_open = mock_open() - - with patch('logging.getLogger') as get_logger: - with patch('logging.open', mock_file_open, create=True): - get_logger.return_value = mock_logger - boxsdk.util.log.Logging().setup_logging(stream_or_file, debug=debug, name=name) - get_logger.assert_called_once_with(name) - - assert mock_logger.addHandler.call_count == 1 - assert isinstance(mock_logger.addHandler.call_args[0][0], logging.Handler) - mock_logger.setLevel.assert_called_once_with(expected_log_level) - - if isinstance(stream_or_file, str): - assert mock_file_open.call_count == 1 - assert mock_file_open.call_args[0][:2] == (stream_or_file, 'a') # Python 3 passes additional args. - - -def test_setup_logging_is_reentrant(mock_logger): - mock_file_open = mock_open() - - with patch('logging.getLogger') as get_logger: - with patch('logging.open', mock_file_open, create=True): - get_logger.return_value = mock_logger - logging_instance = boxsdk.util.log.Logging() - logging_instance.setup_logging(None) - get_logger.assert_called_once_with(None) - get_logger.return_value = Mock() - logging_instance.setup_logging(None) - - assert mock_logger.addHandler.call_count == 1 - assert isinstance(mock_logger.addHandler.call_args[0][0], logging.Handler) - mock_logger.setLevel.assert_called_once() - - if isinstance(stream_or_file, str): - assert mock_file_open.call_count == 1 - - -@pytest.mark.parametrize( - 'unsanitized_dict, expected_result', - [ - # Test for when no sanitization is required - ( - {'name': 'foo'}, - {'name': 'foo'}, - ), - # Test for basic string sanitization - ( - {'access_token': 'askdjfhadsrwedr'}, - {'access_token': '---wedr'}, - ), - # Test for short string sanitization - ( - {'refresh_token': 'abc'}, - {'refresh_token': '---abc'}, - ), - # Test for recursive sanitization - ( - {'stuff': {'shared_link': 'https://example.com/asdfghjkl'}}, - {'stuff': {'shared_link': '---hjkl'}}, - ), - # Test for None type - ( - {'download_url': None}, - {'download_url': None}, - ), - # Test for proxy http - ( - {'http': 'http://username:password@localhost:8080'}, - {'http': 'http://---:---@localhost:8080'}, - ), - # Test for proxy https - ( - {'https': 'http://username:password@localhost:8080'}, - {'https': 'http://---:---@localhost:8080'}, - ), - ] -) -def test_sanitize_dictionary_correctly_sanitizes_params(mock_logger, unsanitized_dict, expected_result): - mock_file_open = mock_open() - - with patch('logging.getLogger') as get_logger: - with patch('logging.open', mock_file_open, create=True): - get_logger.return_value = mock_logger - actual_result = boxsdk.util.log.Logging().sanitize_dictionary(unsanitized_dict) - assert actual_result == expected_result diff --git a/test/unit/util/test_lru_cache.py b/test/unit/util/test_lru_cache.py deleted file mode 100644 index 08446a136..000000000 --- a/test/unit/util/test_lru_cache.py +++ /dev/null @@ -1,42 +0,0 @@ -import pytest -from boxsdk.util.lru_cache import LRUCache - - -@pytest.fixture -def lru_cache(): - return LRUCache() - - -@pytest.fixture -def keys(): - return ['key1', 'key2', 'key3'] - - -def test_lru_cache_returns_minus_one_for_missing_key(lru_cache, keys): - # pylint:disable=redefined-outer-name - for key in keys: - with pytest.raises(KeyError): - lru_cache.get(key) - - -def test_lru_cache_returns_none_for_existing_key(lru_cache, keys): - # pylint:disable=redefined-outer-name - for key in keys: - lru_cache.set(key) - assert lru_cache.get(key) is None - - -def test_lru_cache_ejects_least_recently_used_key(lru_cache, keys): - # pylint:disable=redefined-outer-name - lru_cache.capacity = len(keys) - for key in keys: - lru_cache.set(key) - lru_cache.set('another key') - with pytest.raises(KeyError): - lru_cache.get(keys[0]) - assert lru_cache.get('another key') is None - for key in keys[1:]: - assert lru_cache.get(key) is None - lru_cache.set('yet another key') - with pytest.raises(KeyError): - lru_cache.get('another key') diff --git a/test/unit/util/test_multipart_stream.py b/test/unit/util/test_multipart_stream.py deleted file mode 100644 index 9200a6ad5..000000000 --- a/test/unit/util/test_multipart_stream.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest - -from boxsdk.util.multipart_stream import MultipartStream - - -@pytest.fixture(params=({}, {'data_1': b'data_1_value', 'data_2': b'data_2_value'})) -def multipart_stream_data(request): - return request.param - - -@pytest.fixture(params=({}, {'file_1': b'file_1_value', 'file_2': b'file_2_value'})) -def multipart_stream_files(request): - return request.param - - -def test_multipart_stream_orders_data_before_files(multipart_stream_data, multipart_stream_files): - # pylint:disable=redefined-outer-name - if not multipart_stream_data and not multipart_stream_files: - pytest.xfail('Encoder does not support empty fields.') - stream = MultipartStream(multipart_stream_data, multipart_stream_files) - encoded_stream = stream.to_string() - data_indices = [encoded_stream.find(value) for value in multipart_stream_data.values()] - file_indices = [encoded_stream.find(value) for value in multipart_stream_files.values()] - assert -1 not in data_indices - assert -1 not in file_indices - assert all(all(data_index < f for f in file_indices) for data_index in data_indices) diff --git a/test/unit/util/test_shared_link.py b/test/unit/util/test_shared_link.py deleted file mode 100644 index 84ec5c3c0..000000000 --- a/test/unit/util/test_shared_link.py +++ /dev/null @@ -1,21 +0,0 @@ -import pytest -from boxsdk.util.shared_link import get_shared_link_header - - -@pytest.fixture(params=('mock_shared_link', 'https://app.box.com/s/q2i1024dvguiads6mzj2avsq9hmz43du')) -def shared_link(request): - return request.param - - -@pytest.fixture(params=(None, 'shared_link_password')) -def password(request): - return request.param - - -def test_get_shared_link_header(shared_link, password): - # pylint:disable=redefined-outer-name - header = get_shared_link_header(shared_link, password) - assert 'BoxApi' in header - assert shared_link in header['BoxApi'] - if password is not None: - assert password in header['BoxApi'] diff --git a/test/unit/util/test_text_enum.py b/test/unit/util/test_text_enum.py deleted file mode 100644 index d2a180a92..000000000 --- a/test/unit/util/test_text_enum.py +++ /dev/null @@ -1,13 +0,0 @@ -from boxsdk.util.text_enum import TextEnum - - -class MockTextEnum(TextEnum): - MEMBER = 'member' - - -def test_text_enum_repr_is_value(): - assert MockTextEnum.MEMBER.__repr__() == MockTextEnum.MEMBER.value # pylint:disable=no-member - - -def test_text_enum_str_is_value(): - assert str(MockTextEnum.MEMBER) == str('member') diff --git a/test/unit/util/test_translator.py b/test/unit/util/test_translator.py deleted file mode 100644 index 5896c6042..000000000 --- a/test/unit/util/test_translator.py +++ /dev/null @@ -1,224 +0,0 @@ -from itertools import product - -import pytest - -from boxsdk.object.base_object import BaseObject -from boxsdk.object.file import File -from boxsdk.object.folder import Folder -from boxsdk.object.group import Group -from boxsdk.object.user import User -from boxsdk.object.web_link import WebLink -from boxsdk.util.translator import Translator - - -_response_to_class_mapping = {} - - -@pytest.fixture -def bookmark_response(make_mock_box_request, mock_object_id): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={'type': 'bookmark', 'id': mock_object_id}, - ) - return mock_box_response - - -@pytest.fixture -def box_note_response(make_mock_box_request, mock_object_id): - # pylint:disable=redefined-outer-name - mock_box_response, _ = make_mock_box_request( - response={'type': 'boxnote', 'id': mock_object_id}, - ) - return mock_box_response - - -@pytest.fixture(autouse=True) -def translator_response( - bookmark_response, - box_note_response, - mock_file_response, - mock_folder_response, - mock_group_response, - mock_user_response, - mock_web_link_response, -): - # pylint:disable=redefined-outer-name - _response_to_class_mapping['bookmark'] = (bookmark_response, BaseObject) - _response_to_class_mapping['box_note'] = (box_note_response, BaseObject) - _response_to_class_mapping['file'] = (mock_file_response, File) - _response_to_class_mapping['folder'] = (mock_folder_response, Folder) - _response_to_class_mapping['group'] = (mock_group_response, Group) - _response_to_class_mapping['user'] = (mock_user_response, User) - _response_to_class_mapping['web_link'] = (mock_web_link_response, WebLink) - - -@pytest.fixture(params=('scope', 'id', 'both')) -def metadata_template_response(request): - response = { - 'type': 'metadata_template', - } - if request.param in ('scope', 'both'): - response.update({ - 'scope': 'enterprise', - 'templateKey': 'vContract', - }) - if request.param in ('id', 'both'): - response['id'] = '2f2e84e9-afdb-4e9d-b293-d6d1d932fc85' - - return response - - -@pytest.mark.parametrize('response_type', ['bookmark', 'box_note', 'file', 'folder', 'group', 'user']) -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 test_default_translator(): - assert isinstance(Translator._default_translator, Translator) # pylint:disable=protected-access - - -@pytest.mark.parametrize(('extend_default_translator', 'new_child'), list(product([None, True], [False, True]))) -def test_with_extend_default_translator(default_translator, extend_default_translator, new_child): - item_type = 'foo' - - class Foo: - 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()) - - -@pytest.mark.parametrize('new_child', [False, True]) -def test_without_extend_default_translator(new_child): - item_type = 'foo' - - class Foo: - pass - - mapping = {item_type: Foo} - - translator = Translator(mapping, extend_default_translator=False, new_child=new_child) - assert translator == mapping - - -@pytest.mark.parametrize(('new_child', 'extend_default_translator'), list(product([None, True], [False, True]))) -def test_with_new_child(new_child, extend_default_translator): - item_type = 'foo' - - class Foo: - 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 not 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} - - -@pytest.mark.parametrize('extend_default_translator', [False, True]) -def test_without_new_child(extend_default_translator): - item_type = 'foo' - - class Foo: - 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_translate(default_translator, mock_box_session): - response_object = { - 'entries': [ - { - 'type': 'folder', - 'id': '11111', - 'name': 'Test Folder', - 'created_by': { - 'type': 'user', - 'id': '33333', - 'name': 'Test User', - }, - }, - { - 'type': 'file', - 'id': '22222', - 'name': 'Test File', - 'modified_by': { - 'type': 'user', - 'id': '33333', - 'name': 'Test User', - }, - }, - ], - } - - results = default_translator.translate(mock_box_session, response_object=response_object) - test_folder = results['entries'][0] - test_file = results['entries'][1] - - assert isinstance(test_folder, Folder) - assert isinstance(test_file, File) - assert test_folder.object_id == '11111' - assert test_folder.name == 'Test Folder' - assert test_file.object_id == '22222' - assert test_file.name == 'Test File' - - user_1 = test_folder.created_by - user_2 = test_file.modified_by - assert isinstance(user_1, User) - assert isinstance(user_2, User) - assert user_1 == user_2 - - assert test_folder._session == mock_box_session # pylint:disable=protected-access - assert test_file._session == mock_box_session # pylint:disable=protected-access - assert user_1._session == mock_box_session # pylint:disable=protected-access - assert user_2._session == mock_box_session # pylint:disable=protected-access - - # It should not modify the original - assert isinstance(response_object['entries'][0], dict) - assert isinstance(response_object['entries'][1], dict) - - -def test_translator_translates_metadata_template(default_translator, mock_box_session, metadata_template_response): - metadata_template = default_translator.translate(mock_box_session, metadata_template_response) - - if metadata_template_response.get('id'): - assert metadata_template.id == metadata_template_response['id'] - assert metadata_template.object_id == metadata_template_response['id'] - - if metadata_template_response.get('scope') and metadata_template_response.get('templateKey'): - assert metadata_template.scope == metadata_template_response['scope'] - assert metadata_template.template_key == metadata_template_response['templateKey'] - - if metadata_template_response.get('id') is None: - assert metadata_template.object_id is None diff --git a/test/uploads.py b/test/uploads.py new file mode 100644 index 000000000..13701ee1d --- /dev/null +++ b/test/uploads.py @@ -0,0 +1,99 @@ +import pytest + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.managers.uploads import UploadFileVersionAttributes + +from box_sdk_gen.managers.uploads import UploadWithPreflightCheckAttributes + +from box_sdk_gen.managers.uploads import UploadWithPreflightCheckAttributesParentField + +from box_sdk_gen.schemas.upload_url import UploadUrl + +from box_sdk_gen.managers.uploads import PreflightFileUploadCheckParent + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import generate_byte_stream + +from box_sdk_gen.client import BoxClient + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testUploadFileAndFileVersion(): + new_file_name: str = get_uuid() + file_content_stream: ByteStream = generate_byte_stream(1024 * 1024) + uploaded_files: Files = client.uploads.upload_file( + UploadFileAttributes( + name=new_file_name, parent=UploadFileAttributesParentField(id='0') + ), + file_content_stream, + ) + uploaded_file: FileFull = uploaded_files.entries[0] + assert uploaded_file.name == new_file_name + new_file_version_name: str = get_uuid() + new_file_content_stream: ByteStream = generate_byte_stream(1024 * 1024) + uploaded_files_version: Files = client.uploads.upload_file_version( + uploaded_file.id, + UploadFileVersionAttributes(name=new_file_version_name), + new_file_content_stream, + ) + new_file_version: FileFull = uploaded_files_version.entries[0] + assert new_file_version.name == new_file_version_name + client.files.delete_file_by_id(new_file_version.id) + + +def testUploadFileWithPreflightCheck(): + new_file_name: str = get_uuid() + file_content_stream: ByteStream = generate_byte_stream(1024 * 1024) + with pytest.raises(Exception): + client.uploads.upload_with_preflight_check( + UploadWithPreflightCheckAttributes( + name=new_file_name, + size=-1, + parent=UploadWithPreflightCheckAttributesParentField(id='0'), + ), + file_content_stream, + ) + upload_files_with_preflight: Files = client.uploads.upload_with_preflight_check( + UploadWithPreflightCheckAttributes( + name=new_file_name, + size=1024 * 1024, + parent=UploadWithPreflightCheckAttributesParentField(id='0'), + ), + file_content_stream, + ) + file: FileFull = upload_files_with_preflight.entries[0] + assert file.name == new_file_name + assert file.size == 1024 * 1024 + with pytest.raises(Exception): + client.uploads.upload_with_preflight_check( + UploadWithPreflightCheckAttributes( + name=new_file_name, + size=1024 * 1024, + parent=UploadWithPreflightCheckAttributesParentField(id='0'), + ), + file_content_stream, + ) + client.files.delete_file_by_id(file.id) + + +def testPreflightCheck(): + new_file_name: str = get_uuid() + preflight_check_result: UploadUrl = client.uploads.preflight_file_upload_check( + name=new_file_name, + size=1024 * 1024, + parent=PreflightFileUploadCheckParent(id='0'), + ) + assert not preflight_check_result.upload_url == '' diff --git a/test/user_collaborations.py b/test/user_collaborations.py new file mode 100644 index 000000000..1daf1e52c --- /dev/null +++ b/test/user_collaborations.py @@ -0,0 +1,143 @@ +from box_sdk_gen.internal.utils import to_string + +from typing import Optional + +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.collaboration import Collaboration + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationItem + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationItemTypeField + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationAccessibleBy + +from box_sdk_gen.managers.user_collaborations import ( + CreateCollaborationAccessibleByTypeField, +) + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationRole + +from box_sdk_gen.managers.user_collaborations import UpdateCollaborationByIdRole + +from box_sdk_gen.schemas.collaborations import Collaborations + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +from test.commons import create_new_folder + +client: BoxClient = get_default_client() + + +def testUserCollaborations(): + user_name: str = get_uuid() + user_login: str = ''.join([get_uuid(), '@gmail.com']) + user: UserFull = client.users.create_user( + user_name, login=user_login, is_platform_access_only=True + ) + folder: FolderFull = create_new_folder() + collaboration: Collaboration = client.user_collaborations.create_collaboration( + CreateCollaborationItem( + type=CreateCollaborationItemTypeField.FOLDER, id=folder.id + ), + CreateCollaborationAccessibleBy( + type=CreateCollaborationAccessibleByTypeField.USER, id=user.id + ), + CreateCollaborationRole.EDITOR, + ) + assert to_string(collaboration.role) == 'editor' + collaboration_id: str = collaboration.id + collaboration_from_api: Collaboration = ( + client.user_collaborations.get_collaboration_by_id(collaboration_id) + ) + assert collaboration_id == collaboration_from_api.id + assert to_string(collaboration_from_api.status) == 'accepted' + assert to_string(collaboration_from_api.type) == 'collaboration' + assert collaboration_from_api.invite_email == None + updated_collaboration: Optional[Collaboration] = ( + client.user_collaborations.update_collaboration_by_id( + collaboration_id, UpdateCollaborationByIdRole.VIEWER + ) + ) + assert to_string(updated_collaboration.role) == 'viewer' + client.user_collaborations.delete_collaboration_by_id(collaboration_id) + with pytest.raises(Exception): + client.user_collaborations.get_collaboration_by_id(collaboration_id) + client.folders.delete_folder_by_id(folder.id) + client.users.delete_user_by_id(user.id) + + +def testConvertingUserCollaborationToOwnership(): + user_name: str = get_uuid() + user_login: str = ''.join([get_uuid(), '@gmail.com']) + user: UserFull = client.users.create_user( + user_name, login=user_login, is_platform_access_only=True + ) + folder: FolderFull = create_new_folder() + collaboration: Collaboration = client.user_collaborations.create_collaboration( + CreateCollaborationItem( + type=CreateCollaborationItemTypeField.FOLDER, id=folder.id + ), + CreateCollaborationAccessibleBy( + type=CreateCollaborationAccessibleByTypeField.USER, id=user.id + ), + CreateCollaborationRole.EDITOR, + ) + assert to_string(collaboration.role) == 'editor' + owner_collaboration: Optional[Collaboration] = ( + client.user_collaborations.update_collaboration_by_id( + collaboration.id, UpdateCollaborationByIdRole.OWNER + ) + ) + assert owner_collaboration == None + folder_collaborations: Collaborations = ( + client.list_collaborations.get_folder_collaborations(folder.id) + ) + folder_collaboration: Collaboration = folder_collaborations.entries[0] + client.user_collaborations.delete_collaboration_by_id(folder_collaboration.id) + user_client: BoxClient = client.with_as_user_header(user.id) + user_client.folders.delete_folder_by_id(folder.id) + user_client.trashed_folders.delete_trashed_folder_by_id(folder.id) + client.users.delete_user_by_id(user.id) + + +def testExternalUserCollaborations(): + user_name: str = get_uuid() + user_login: str = ''.join([get_uuid(), '@boxdemo.com']) + folder: FolderFull = create_new_folder() + collaboration: Collaboration = client.user_collaborations.create_collaboration( + CreateCollaborationItem( + type=CreateCollaborationItemTypeField.FOLDER, id=folder.id + ), + CreateCollaborationAccessibleBy( + type=CreateCollaborationAccessibleByTypeField.USER, login=user_login + ), + CreateCollaborationRole.EDITOR, + ) + assert to_string(collaboration.role) == 'editor' + collaboration_id: str = collaboration.id + collaboration_from_api: Collaboration = ( + client.user_collaborations.get_collaboration_by_id(collaboration_id) + ) + assert collaboration_id == collaboration_from_api.id + assert to_string(collaboration_from_api.status) == 'pending' + assert to_string(collaboration_from_api.type) == 'collaboration' + assert collaboration_from_api.invite_email == user_login + updated_collaboration: Optional[Collaboration] = ( + client.user_collaborations.update_collaboration_by_id( + collaboration_id, UpdateCollaborationByIdRole.VIEWER + ) + ) + assert to_string(updated_collaboration.role) == 'viewer' + client.user_collaborations.delete_collaboration_by_id(collaboration_id) + with pytest.raises(Exception): + client.user_collaborations.get_collaboration_by_id(collaboration_id) + client.folders.delete_folder_by_id(folder.id) diff --git a/test/users.py b/test/users.py new file mode 100644 index 000000000..f200b971f --- /dev/null +++ b/test/users.py @@ -0,0 +1,42 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.users import Users + +from box_sdk_gen.schemas.user_full import UserFull + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import create_null + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def test_get_users(): + users: Users = client.users.get_users() + assert users.total_count >= 0 + + +def test_get_user_me(): + current_user: UserFull = client.users.get_user_me() + assert to_string(current_user.type) == 'user' + + +def test_create_update_get_delete_user(): + user_name: str = get_uuid() + user_login: str = ''.join([get_uuid(), '@gmail.com']) + user: UserFull = client.users.create_user( + user_name, login=user_login, is_platform_access_only=True + ) + assert user.name == user_name + user_by_id: UserFull = client.users.get_user_by_id(user.id) + assert user_by_id.id == user.id + updated_user_name: str = get_uuid() + updated_user: UserFull = client.users.update_user_by_id( + user.id, name=updated_user_name + ) + assert updated_user.name == updated_user_name + client.users.delete_user_by_id(user.id) diff --git a/test/util/__init__.py b/test/util/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/util/streamable_mock_open.py b/test/util/streamable_mock_open.py deleted file mode 100644 index d7d018089..000000000 --- a/test/util/streamable_mock_open.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest.mock import mock_open - - -def streamable_mock_open(mock=None, read_data=b''): - mock = mock_open(mock, read_data) - handle = mock.return_value - handle.position = 0 - - def tell(): - return handle.position - - def read(size=-1): - if size == -1: - handle.position = len(read_data) - return read_data - - data = read_data[handle.position:handle.position + size] - handle.position += size - return data - - # pylint:disable=no-member - handle.tell.side_effect = tell - handle.len = len(read_data) - handle.read.side_effect = read - del handle.getvalue - return mock diff --git a/test/webhooks.py b/test/webhooks.py new file mode 100644 index 000000000..632eda6af --- /dev/null +++ b/test/webhooks.py @@ -0,0 +1,310 @@ +from box_sdk_gen.internal.utils import to_string + +import pytest + +from typing import Dict + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.managers.folders import CreateFolderParent + +from box_sdk_gen.schemas.webhook import Webhook + +from box_sdk_gen.managers.webhooks import CreateWebhookTarget + +from box_sdk_gen.managers.webhooks import CreateWebhookTargetTypeField + +from box_sdk_gen.managers.webhooks import CreateWebhookTriggers + +from box_sdk_gen.schemas.webhooks import Webhooks + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import date_time_to_string + +from box_sdk_gen.internal.utils import epoch_seconds_to_date_time + +from box_sdk_gen.internal.utils import get_epoch_time_in_seconds + +from box_sdk_gen.internal.utils import compute_webhook_signature + +from box_sdk_gen.internal.utils import compare_signatures + +from box_sdk_gen.managers.webhooks import WebhooksManager + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testWebhooksCRUD(): + folder: FolderFull = client.folders.create_folder( + get_uuid(), CreateFolderParent(id='0') + ) + webhook: Webhook = client.webhooks.create_webhook( + CreateWebhookTarget(id=folder.id, type=CreateWebhookTargetTypeField.FOLDER), + 'https://example.com/new-webhook', + [CreateWebhookTriggers.FILE_UPLOADED], + ) + assert webhook.target.id == folder.id + assert to_string(webhook.target.type) == 'folder' + assert len(webhook.triggers) == len(['FILE.UPLOADED']) + assert webhook.address == 'https://example.com/new-webhook' + webhooks: Webhooks = client.webhooks.get_webhooks() + assert len(webhooks.entries) > 0 + webhook_from_api: Webhook = client.webhooks.get_webhook_by_id(webhook.id) + assert webhook.id == webhook_from_api.id + assert webhook.target.id == webhook_from_api.target.id + assert webhook.address == webhook_from_api.address + updated_webhook: Webhook = client.webhooks.update_webhook_by_id( + webhook.id, address='https://example.com/updated-webhook' + ) + assert updated_webhook.id == webhook.id + assert updated_webhook.address == 'https://example.com/updated-webhook' + client.webhooks.delete_webhook_by_id(webhook.id) + with pytest.raises(Exception): + client.webhooks.delete_webhook_by_id(webhook.id) + client.folders.delete_folder_by_id(folder.id) + + +def testWebhookValidation(): + primary_key: str = 'SamplePrimaryKey' + secondary_key: str = 'SampleSecondaryKey' + incorrect_key: str = 'IncorrectKey' + body: str = ( + '{"type":"webhook_event","webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' + ) + body_with_japanese: str = ( + '{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"スクリーンショット 2020-08-05.txt"}}' + ) + body_with_emoji: str = ( + '{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"😀 2020-08-05.txt"}}' + ) + body_with_carriage_return: str = ( + '{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"test \r"}}' + ) + body_with_forward_slash: str = ( + '{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"\/"}}' + ) + body_with_back_slash: str = ( + '{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"\\"}}' + ) + headers: Dict[str, str] = { + 'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f', + 'box-delivery-timestamp': '2020-01-01T00:00:00-07:00', + 'box-signature-algorithm': 'HmacSHA256', + 'box-signature-primary': '6TfeAW3A1PASkgboxxA5yqHNKOwFyMWuEXny/FPD5hI=', + 'box-signature-secondary': 'v+1CD1Jdo3muIcbpv5lxxgPglOqMfsNHPV899xWYydo=', + 'box-signature-version': '1', + } + headers_with_japanese: Dict[str, str] = { + **headers, + 'box-signature-primary': 'LV2uCu+5NJtIHrCXDYgZ0v/PP5THGRuegw3RtdnEyuE=', + } + headers_with_emoji: Dict[str, str] = { + **headers, + 'box-signature-primary': 'xF/SDZosX4le+v4A0Qn59sZhuD1RqY5KRUKzVMSbh0E=', + } + headers_with_carriage_return: Dict[str, str] = { + **headers, + 'box-signature-primary': 'SVkbKgy3dEEf2PbbzpNu2lDZS7zZ/aboU7HOZgBGrJk=', + } + headers_with_forward_slash: Dict[str, str] = { + **headers, + 'box-signature-primary': 't41PWT5ZB6OcysnD6SDy9Ud+p9hdXxIdXqcdweyZv/Q=', + } + headers_with_back_slash: Dict[str, str] = { + **headers, + 'box-signature-primary': 'ERpMZwUQsGDTfj82ehdX6VvDZfvOhK5ULNfVmwVAGe0=', + } + current_datetime: str = date_time_to_string( + epoch_seconds_to_date_time(get_epoch_time_in_seconds()) + ) + future_datetime: str = date_time_to_string( + epoch_seconds_to_date_time(get_epoch_time_in_seconds() + 1200) + ) + past_datetime: str = date_time_to_string( + epoch_seconds_to_date_time(get_epoch_time_in_seconds() - 1200) + ) + headers_with_correct_datetime: Dict[str, str] = { + **headers, + 'box-delivery-timestamp': current_datetime, + 'box-signature-primary': compute_webhook_signature( + body, + {**headers, 'box-delivery-timestamp': current_datetime}, + primary_key, + escape_body=True, + ), + 'box-signature-secondary': compute_webhook_signature( + body, + {**headers, 'box-delivery-timestamp': current_datetime}, + secondary_key, + escape_body=True, + ), + } + headers_with_japanese_with_correct_datetime: Dict[str, str] = { + **headers_with_japanese, + 'box-delivery-timestamp': current_datetime, + 'box-signature-primary': compute_webhook_signature( + body_with_japanese, + {**headers_with_japanese, 'box-delivery-timestamp': current_datetime}, + primary_key, + escape_body=True, + ), + 'box-signature-secondary': compute_webhook_signature( + body_with_japanese, + {**headers_with_japanese, 'box-delivery-timestamp': current_datetime}, + secondary_key, + escape_body=True, + ), + } + headers_with_future_datetime: Dict[str, str] = { + **headers, + 'box-delivery-timestamp': future_datetime, + 'box-signature-primary': compute_webhook_signature( + body, + {**headers, 'box-delivery-timestamp': future_datetime}, + primary_key, + escape_body=True, + ), + 'box-signature-secondary': compute_webhook_signature( + body, + {**headers, 'box-delivery-timestamp': future_datetime}, + secondary_key, + escape_body=True, + ), + } + headers_with_past_datetime: Dict[str, str] = { + **headers, + 'box-delivery-timestamp': past_datetime, + 'box-signature-primary': compute_webhook_signature( + body, + {**headers, 'box-delivery-timestamp': past_datetime}, + primary_key, + escape_body=True, + ), + 'box-signature-secondary': compute_webhook_signature( + body, + {**headers, 'box-delivery-timestamp': past_datetime}, + secondary_key, + escape_body=True, + ), + } + headers_with_wrong_signature_version: Dict[str, str] = { + **headers, + 'box-signature-version': '2', + } + headers_with_wrong_signature_algorithm: Dict[str, str] = { + **headers, + 'box-signature-algorithm': 'HmacSHA1', + } + assert compare_signatures( + expected_signature=compute_webhook_signature( + body, headers, primary_key, escape_body=True + ), + received_signature=headers.get('box-signature-primary'), + ) + assert compare_signatures( + expected_signature=compute_webhook_signature( + body, headers, secondary_key, escape_body=True + ), + received_signature=headers.get('box-signature-secondary'), + ) + assert not compare_signatures( + expected_signature=compute_webhook_signature( + body, headers, incorrect_key, escape_body=True + ), + received_signature=headers.get('box-signature-primary'), + ) + assert compare_signatures( + expected_signature=compute_webhook_signature( + body_with_japanese, headers_with_japanese, primary_key, escape_body=True + ), + received_signature=headers_with_japanese.get('box-signature-primary'), + ) + assert compare_signatures( + expected_signature=compute_webhook_signature( + body_with_emoji, headers_with_emoji, primary_key, escape_body=True + ), + received_signature=headers_with_emoji.get('box-signature-primary'), + ) + assert compare_signatures( + expected_signature=compute_webhook_signature( + body_with_carriage_return, + headers_with_carriage_return, + primary_key, + escape_body=True, + ), + received_signature=headers_with_carriage_return.get('box-signature-primary'), + ) + assert compare_signatures( + expected_signature=compute_webhook_signature( + body_with_forward_slash, + headers_with_forward_slash, + primary_key, + escape_body=True, + ), + received_signature=headers_with_forward_slash.get('box-signature-primary'), + ) + assert compare_signatures( + expected_signature=compute_webhook_signature( + body_with_back_slash, headers_with_back_slash, primary_key, escape_body=True + ), + received_signature=headers_with_back_slash.get('box-signature-primary'), + ) + assert WebhooksManager.validate_message( + body, headers_with_correct_datetime, primary_key, secondary_key=secondary_key + ) + assert WebhooksManager.validate_message( + body, headers_with_correct_datetime, primary_key, secondary_key=incorrect_key + ) + assert WebhooksManager.validate_message( + body, headers_with_correct_datetime, incorrect_key, secondary_key=secondary_key + ) + assert not WebhooksManager.validate_message( + body, headers_with_correct_datetime, incorrect_key, secondary_key=incorrect_key + ) + assert not WebhooksManager.validate_message( + body, headers_with_future_datetime, primary_key, secondary_key=secondary_key + ) + assert not WebhooksManager.validate_message( + body, headers_with_past_datetime, primary_key, secondary_key=secondary_key + ) + assert not WebhooksManager.validate_message( + body, + headers_with_wrong_signature_version, + primary_key, + secondary_key=secondary_key, + ) + assert not WebhooksManager.validate_message( + body, + headers_with_wrong_signature_algorithm, + primary_key, + secondary_key=secondary_key, + ) + assert WebhooksManager.validate_message( + body_with_japanese, + headers_with_japanese_with_correct_datetime, + primary_key, + secondary_key=secondary_key, + ) + assert WebhooksManager.validate_message( + body_with_japanese, + headers_with_japanese_with_correct_datetime, + primary_key, + secondary_key=incorrect_key, + ) + assert WebhooksManager.validate_message( + body_with_japanese, + headers_with_japanese_with_correct_datetime, + incorrect_key, + secondary_key=secondary_key, + ) + assert not WebhooksManager.validate_message( + body_with_japanese, + headers_with_japanese, + primary_key, + secondary_key=secondary_key, + ) diff --git a/test/weblinks.py b/test/weblinks.py new file mode 100644 index 000000000..eeeedf71e --- /dev/null +++ b/test/weblinks.py @@ -0,0 +1,50 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.managers.web_links import CreateWebLinkParent + +from box_sdk_gen.managers.web_links import UpdateWebLinkByIdSharedLink + +from box_sdk_gen.managers.web_links import UpdateWebLinkByIdSharedLinkAccessField + +from box_sdk_gen.internal.utils import get_uuid + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def test_createGetDeleteWeblink(): + url: str = 'https://www.box.com' + parent: FolderFull = client.folders.get_folder_by_id('0') + name: str = get_uuid() + description: str = 'Weblink description' + password: str = 'super-secret-password' + weblink: WebLink = client.web_links.create_web_link( + url, CreateWebLinkParent(id=parent.id), name=name, description=description + ) + assert weblink.url == url + assert weblink.parent.id == parent.id + assert weblink.name == name + assert weblink.description == description + weblink_by_id: WebLink = client.web_links.get_web_link_by_id(weblink.id) + assert weblink_by_id.id == weblink.id + assert weblink_by_id.url == url + updated_name: str = get_uuid() + updated_weblink: WebLink = client.web_links.update_web_link_by_id( + weblink.id, + name=updated_name, + shared_link=UpdateWebLinkByIdSharedLink( + access=UpdateWebLinkByIdSharedLinkAccessField.OPEN, password=password + ), + ) + assert updated_weblink.name == updated_name + assert to_string(updated_weblink.shared_link.access) == 'open' + client.web_links.delete_web_link_by_id(weblink.id) + deleted_weblink: WebLink = client.web_links.get_web_link_by_id(weblink.id) + assert to_string(deleted_weblink.item_status) == 'trashed' diff --git a/test/workflows.py b/test/workflows.py new file mode 100644 index 000000000..3a530002e --- /dev/null +++ b/test/workflows.py @@ -0,0 +1,83 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.workflows import Workflows + +from box_sdk_gen.schemas.workflow import Workflow + +from box_sdk_gen.managers.workflows import StartWorkflowType + +from box_sdk_gen.managers.workflows import StartWorkflowFlow + +from box_sdk_gen.managers.workflows import StartWorkflowFiles + +from box_sdk_gen.managers.workflows import StartWorkflowFilesTypeField + +from box_sdk_gen.managers.workflows import StartWorkflowFolder + +from box_sdk_gen.managers.workflows import StartWorkflowFolderTypeField + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import generate_byte_stream + +from test.commons import get_default_client + +from test.commons import upload_new_file + +from test.commons import get_default_client_with_user_subject + +client: BoxClient = get_default_client() + + +def testWorkflows(): + admin_client: BoxClient = get_default_client_with_user_subject( + get_env_var('USER_ID') + ) + workflow_folder_id: str = get_env_var('WORKFLOW_FOLDER_ID') + uploaded_files: Files = admin_client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), + parent=UploadFileAttributesParentField(id=workflow_folder_id), + ), + generate_byte_stream(1024 * 1024), + ) + file: FileFull = uploaded_files.entries[0] + workflow_file_id: str = file.id + workflows: Workflows = admin_client.workflows.get_workflows(workflow_folder_id) + assert len(workflows.entries) == 1 + workflow_to_run: Workflow = workflows.entries[0] + assert to_string(workflow_to_run.type) == 'workflow' + assert workflow_to_run.is_enabled == True + assert to_string(workflow_to_run.flows[0].type) == 'flow' + assert to_string(workflow_to_run.flows[0].trigger.type) == 'trigger' + assert ( + to_string(workflow_to_run.flows[0].trigger.trigger_type) + == 'WORKFLOW_MANUAL_START' + ) + assert to_string(workflow_to_run.flows[0].outcomes[0].action_type) == 'delete_file' + assert to_string(workflow_to_run.flows[0].outcomes[0].type) == 'outcome' + admin_client.workflows.start_workflow( + workflow_to_run.id, + StartWorkflowFlow(type='flow', id=workflow_to_run.flows[0].id), + [ + StartWorkflowFiles( + type=StartWorkflowFilesTypeField.FILE, id=workflow_file_id + ) + ], + StartWorkflowFolder( + type=StartWorkflowFolderTypeField.FOLDER, id=workflow_folder_id + ), + type=StartWorkflowType.WORKFLOW_PARAMETERS, + ) diff --git a/test/zip_downloads.py b/test/zip_downloads.py new file mode 100644 index 000000000..7b668ec26 --- /dev/null +++ b/test/zip_downloads.py @@ -0,0 +1,91 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.managers.zip_downloads import DownloadZipItems + +from box_sdk_gen.managers.zip_downloads import DownloadZipItemsTypeField + +from box_sdk_gen.schemas.zip_download import ZipDownload + +from box_sdk_gen.managers.zip_downloads import CreateZipDownloadItems + +from box_sdk_gen.schemas.zip_download_status import ZipDownloadStatus + +from test.commons import get_default_client + +from test.commons import upload_new_file + +from test.commons import create_new_folder + +from box_sdk_gen.internal.utils import buffer_equals + +from box_sdk_gen.internal.utils import read_byte_stream + +from box_sdk_gen.internal.utils import generate_byte_buffer + +from box_sdk_gen.internal.utils import date_time_to_string + +client: BoxClient = get_default_client() + + +def testZipDownload(): + file_1: FileFull = upload_new_file() + file_2: FileFull = upload_new_file() + folder_1: FolderFull = create_new_folder() + zip_stream: ByteStream = client.zip_downloads.download_zip( + [ + DownloadZipItems(id=file_1.id, type=DownloadZipItemsTypeField.FILE), + DownloadZipItems(id=file_2.id, type=DownloadZipItemsTypeField.FILE), + DownloadZipItems(id=folder_1.id, type=DownloadZipItemsTypeField.FOLDER), + ], + download_file_name='zip', + ) + assert ( + buffer_equals(read_byte_stream(zip_stream), generate_byte_buffer(10)) == False + ) + client.files.delete_file_by_id(file_1.id) + client.files.delete_file_by_id(file_2.id) + client.folders.delete_folder_by_id(folder_1.id) + + +def testManualZipDownloadAndCheckStatus(): + file_1: FileFull = upload_new_file() + file_2: FileFull = upload_new_file() + folder_1: FolderFull = create_new_folder() + zip_download: ZipDownload = client.zip_downloads.create_zip_download( + [ + CreateZipDownloadItems(id=file_1.id, type=DownloadZipItemsTypeField.FILE), + CreateZipDownloadItems(id=file_2.id, type=DownloadZipItemsTypeField.FILE), + CreateZipDownloadItems( + id=folder_1.id, type=DownloadZipItemsTypeField.FOLDER + ), + ], + download_file_name='zip', + ) + assert not zip_download.download_url == '' + assert not zip_download.status_url == '' + assert not date_time_to_string(zip_download.expires_at) == '' + zip_stream: ByteStream = client.zip_downloads.get_zip_download_content( + zip_download.download_url + ) + assert ( + buffer_equals(read_byte_stream(zip_stream), generate_byte_buffer(10)) == False + ) + zip_download_status: ZipDownloadStatus = ( + client.zip_downloads.get_zip_download_status(zip_download.status_url) + ) + assert zip_download_status.total_file_count == 2 + assert zip_download_status.downloaded_file_count == 2 + assert zip_download_status.skipped_file_count == 0 + assert zip_download_status.skipped_folder_count == 0 + assert not to_string(zip_download_status.state) == 'failed' + client.files.delete_file_by_id(file_1.id) + client.files.delete_file_by_id(file_2.id) + client.folders.delete_folder_by_id(folder_1.id) diff --git a/tox.ini b/tox.ini index b2399d2bb..93cda9389 100644 --- a/tox.ini +++ b/tox.ini @@ -15,35 +15,25 @@ envlist = py311, py312, py313, - coverage, - integration-tests - -[gh-actions] -python = - 3.6: py36, pycodestyle, pylint - 3.7: py37 - 3.8: py38 - 3.9: py39 - 3.10: py310 - 3.11: py311 - 3.12: py312 - 3.13: py313 + coverage [testenv] commands = - pytest {posargs} --disable-pytest-warnings + pytest {posargs} --disable-pytest-warnings --reruns 2 deps = -rrequirements-test.txt +allowlist_externals = pytest +passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID [testenv:pycodestyle] commands = - pycodestyle --ignore=E501,W292 boxsdk setup.py + pycodestyle --ignore=E501,W292 box_sdk_gen setup.py pycodestyle --ignore=E501,W292 test deps = pycodestyle [testenv:pylint] commands = - pylint --rcfile=.pylintrc boxsdk setup.py + pylint --rcfile=.pylintrc box_sdk_gen setup.py # pylint:disable W0621(redefined-outer-name) - Using py.test fixtures always breaks this rule. pylint --rcfile=.pylintrc test -d W0621 --ignore=mock_box deps = @@ -51,12 +41,13 @@ deps = -rrequirements-test.txt [testenv:coverage] -basepython = python3.13 +basepython = python3.11 commands = - py.test --cov boxsdk --cov-report term-missing test/unit test/integration + py.test --reruns 2 --cov box_sdk_gen --cov-report term-missing test/ deps = coverage -rrequirements-test.txt +passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID [testenv:py311-build] description = Build the source and binary wheel packages for distribution. @@ -71,7 +62,7 @@ sitepackages = False recreate = True deps = wheel - virtualenv<20.22.0 + setuptools allowlist_externals = rm [testenv:py311-upload] @@ -83,10 +74,3 @@ sitepackages = False recreate = True deps = twine - -[testenv:integration-tests] -passenv = JWT_CONFIG_BASE_64 ADMIN_USER_ID - -commands = - pytest {toxinidir}/test/integration_new {posargs} --disable-pytest-warnings -deps = -rrequirements-test.txt From 8c3152cb3edfe77dc81a329e188b8de07c371da4 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:28:58 +0200 Subject: [PATCH 002/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec (#908) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index c01c9ff2d..4130666af 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "c34d8bf", "specHash": "c27c421", "version": "0.1.0" } +{ "engineHash": "12f2054", "specHash": "5bf3652", "version": "0.1.0" } From 0d84157056d91f4c8cfaf07b385b5afea23a64b1 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:45:06 +0200 Subject: [PATCH 003/139] chore: Adjust `github` workflows for legacy repos (box/box-codegen#772) (#910) --- .codegen.json | 2 +- .github/ISSUE_TEMPLATE/config.yml | 6 +++--- .github/workflows/autoupdate-pr.yml | 2 +- .github/workflows/build.yml | 5 ++--- .github/workflows/notify-changelog.yml | 2 +- .github/workflows/semantic-pr.yml | 2 ++ .github/workflows/spell-check-lint.yml | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.codegen.json b/.codegen.json index 4130666af..0d0685676 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "12f2054", "specHash": "5bf3652", "version": "0.1.0" } +{ "engineHash": "205c0e9", "specHash": "5bf3652", "version": "0.1.0" } diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 5c9f2e19d..246e04877 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,14 +1,14 @@ blank_issues_enabled: false contact_links: - name: SDK documentation - url: https://github.com/box/box-python-sdk-gen/tree/main/docs + url: https://github.com/box/box-python-sdk/tree/sdk-gen/docs about: Before creating an issue, I have checked that the SDK documentation doesn't solve my issue. - name: API documentation url: https://developer.box.com/docs about: Before creating an issue, I have checked that the API documentation doesn't solve my issue. - name: Box Developer Forums - url: https://community.box.com/t5/Platform-and-Development-Forum/bd-p/DeveloperForum + url: https://community.box.com/box-platform-5 about: Before creating an issue, I have searched the Box Developer Forums and my issue isn't already reported there. - name: Issues in this repo - url: https://github.com/box/box-python-sdk-gen/search?type=Issues + url: https://github.com/box/box-python-sdk/search?type=Issues about: Before creating an issue, I have searched Issues in this repo and my issue isn't already reported. diff --git a/.github/workflows/autoupdate-pr.yml b/.github/workflows/autoupdate-pr.yml index 185ab949d..080c51c32 100644 --- a/.github/workflows/autoupdate-pr.yml +++ b/.github/workflows/autoupdate-pr.yml @@ -2,7 +2,7 @@ name: Autoupdate PR on: push: branches: - - main + - sdk-gen jobs: update_pull_requests: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a419259de..b30e4f5a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,11 +2,10 @@ name: Build on: pull_request: branches: - - main + - sdk-gen push: branches: - - main - + - sdk-gen jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/notify-changelog.yml b/.github/workflows/notify-changelog.yml index c7c05c926..be5f76102 100644 --- a/.github/workflows/notify-changelog.yml +++ b/.github/workflows/notify-changelog.yml @@ -24,4 +24,4 @@ jobs: token: ${{ secrets.DISPATCH_ACCESS_TOKEN }} repository: box/box-developer-changelog event-type: new-release-note - client-payload: '{"ref": "${{ github.ref }}", "repository": "${{github.repository}}", "labels": "sdks,python", "repo_display_name": "Box Python SDK Generated"}' + client-payload: '{"ref": "${{ github.ref }}", "repository": "${{github.repository}}", "labels": "sdks,python", "repo_display_name": "Box Python SDK"}' diff --git a/.github/workflows/semantic-pr.yml b/.github/workflows/semantic-pr.yml index cbff636c5..de1b2c83c 100644 --- a/.github/workflows/semantic-pr.yml +++ b/.github/workflows/semantic-pr.yml @@ -6,6 +6,8 @@ on: - opened - edited - synchronize + branches: + - sdk-gen jobs: main: diff --git a/.github/workflows/spell-check-lint.yml b/.github/workflows/spell-check-lint.yml index 659bbd3fa..6bcfc58e4 100644 --- a/.github/workflows/spell-check-lint.yml +++ b/.github/workflows/spell-check-lint.yml @@ -3,7 +3,7 @@ on: pull_request_target: types: [opened, synchronize, edited] branches: - - main + - sdk-gen jobs: spellcheck-request: runs-on: ubuntu-latest From 6ef6d63c37e6eccc3489a9076e0a0b0940a6e0d6 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 13 Aug 2025 15:44:43 +0200 Subject: [PATCH 004/139] feat!: remove unused models from schemas (box/box-openapi#547) (#932) * chore: Update .codegen.json with commit hash of codegen and openapi spec * chore: Update .codegen.json with commit hash of codegen and openapi spec * docs: External User Deletion API (box/box-openapi#542) * chore: Update .codegen.json with commit hash of codegen and openapi spec * chore: Update .codegen.json with commit hash of codegen and openapi spec * docs: revert External User Deletion API updates (box/box-openapi#544) * test: Improve docgen test (box/box-codegen#781) * chore: Update .codegen.json with commit hash of codegen and openapi spec * docs: Link Archive Developer guide from the endpoint specification (box/box-openapi#545) * chore: Update .codegen.json with commit hash of codegen and openapi spec * fix: remove unused UserReference schema definition (box/box-openapi#547) --- .codegen.json | 2 +- box_sdk_gen/managers/archives.py | 9 + box_sdk_gen/schemas/__init__.py | 10 - box_sdk_gen/schemas/metadata_query_index.py | 66 ----- .../retention_policy_assignment_base.py | 30 -- box_sdk_gen/schemas/skill_invocation.py | 257 ------------------ box_sdk_gen/schemas/v2025_r0/__init__.py | 2 - .../schemas/v2025_r0/hub_action_v2025_r0.py | 8 - box_sdk_gen/schemas/webhook_invocation.py | 100 ------- box_sdk_gen/schemas/workflow_full.py | 66 ----- docs/archives.md | 6 + docs/docgen.md | 2 +- test/docgen.py | 4 +- 13 files changed, 19 insertions(+), 543 deletions(-) delete mode 100644 box_sdk_gen/schemas/metadata_query_index.py delete mode 100644 box_sdk_gen/schemas/retention_policy_assignment_base.py delete mode 100644 box_sdk_gen/schemas/skill_invocation.py delete mode 100644 box_sdk_gen/schemas/v2025_r0/hub_action_v2025_r0.py delete mode 100644 box_sdk_gen/schemas/webhook_invocation.py delete mode 100644 box_sdk_gen/schemas/workflow_full.py diff --git a/.codegen.json b/.codegen.json index 0d0685676..20b0a328d 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "205c0e9", "specHash": "5bf3652", "version": "0.1.0" } +{ "engineHash": "be39be6", "specHash": "24fbeb9", "version": "0.1.0" } diff --git a/box_sdk_gen/managers/archives.py b/box_sdk_gen/managers/archives.py index bf0a3f58f..6cfcf5258 100644 --- a/box_sdk_gen/managers/archives.py +++ b/box_sdk_gen/managers/archives.py @@ -63,6 +63,9 @@ def get_archives_v2025_r0( ) -> ArchivesV2025R0: """ Retrieves archives for an enterprise. + + To learn more about the archive APIs, see the [Archive API Guide](g://archives). + :param limit: The maximum number of items to return per page., defaults to None :type limit: Optional[int], optional :param marker: Defines the position marker at which to begin returning results. This is @@ -103,6 +106,9 @@ def create_archive_v2025_r0( ) -> ArchiveV2025R0: """ Creates an archive. + + To learn more about the archive APIs, see the [Archive API Guide](g://archives). + :param name: The name of the archive. :type name: str :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 @@ -139,6 +145,9 @@ def delete_archive_by_id_v2025_r0( ) -> None: """ Permanently deletes an archive. + + To learn more about the archive APIs, see the [Archive API Guide](g://archives). + :param archive_id: The ID of the archive. Example: "982312" :type archive_id: str diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index 8c051af87..e8f4333f6 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -206,8 +206,6 @@ from box_sdk_gen.schemas.metadata_query import * -from box_sdk_gen.schemas.metadata_query_index import * - from box_sdk_gen.schemas.metadata_template import * from box_sdk_gen.schemas.metadata_templates import * @@ -232,8 +230,6 @@ from box_sdk_gen.schemas.file_version_retentions import * -from box_sdk_gen.schemas.retention_policy_assignment_base import * - from box_sdk_gen.schemas.role_variable import * from box_sdk_gen.schemas.outcome import * @@ -484,8 +480,6 @@ from box_sdk_gen.schemas.event import * -from box_sdk_gen.schemas.skill_invocation import * - from box_sdk_gen.schemas.events import * from box_sdk_gen.schemas.collaboration import * @@ -504,16 +498,12 @@ from box_sdk_gen.schemas.webhook import * -from box_sdk_gen.schemas.webhook_invocation import * - from box_sdk_gen.schemas.workflow_mini import * from box_sdk_gen.schemas.workflow import * from box_sdk_gen.schemas.workflows import * -from box_sdk_gen.schemas.workflow_full import * - from box_sdk_gen.schemas.zip_download import * from box_sdk_gen.schemas.zip_download_request import * diff --git a/box_sdk_gen/schemas/metadata_query_index.py b/box_sdk_gen/schemas/metadata_query_index.py deleted file mode 100644 index 8eb1687ae..000000000 --- a/box_sdk_gen/schemas/metadata_query_index.py +++ /dev/null @@ -1,66 +0,0 @@ -from enum import Enum - -from typing import Optional - -from box_sdk_gen.internal.base_object import BaseObject - -from typing import List - -from box_sdk_gen.box.errors import BoxSDKError - - -class MetadataQueryIndexStatusField(str, Enum): - BUILDING = 'building' - ACTIVE = 'active' - DISABLED = 'disabled' - - -class MetadataQueryIndexFieldsSortDirectionField(str, Enum): - ASC = 'asc' - DESC = 'desc' - - -class MetadataQueryIndexFieldsField(BaseObject): - def __init__( - self, - *, - key: Optional[str] = None, - sort_direction: Optional[MetadataQueryIndexFieldsSortDirectionField] = None, - **kwargs - ): - """ - :param key: The metadata template field key., defaults to None - :type key: Optional[str], optional - :param sort_direction: The sort direction of the field., defaults to None - :type sort_direction: Optional[MetadataQueryIndexFieldsSortDirectionField], optional - """ - super().__init__(**kwargs) - self.key = key - self.sort_direction = sort_direction - - -class MetadataQueryIndex(BaseObject): - def __init__( - self, - type: str, - status: MetadataQueryIndexStatusField, - *, - id: Optional[str] = None, - fields: Optional[List[MetadataQueryIndexFieldsField]] = None, - **kwargs - ): - """ - :param type: Value is always `metadata_query_index`. - :type type: str - :param status: The status of the metadata query index. - :type status: MetadataQueryIndexStatusField - :param id: The ID of the metadata query index., defaults to None - :type id: Optional[str], optional - :param fields: A list of template fields which make up the index., defaults to None - :type fields: Optional[List[MetadataQueryIndexFieldsField]], optional - """ - super().__init__(**kwargs) - self.type = type - self.status = status - self.id = id - self.fields = fields diff --git a/box_sdk_gen/schemas/retention_policy_assignment_base.py b/box_sdk_gen/schemas/retention_policy_assignment_base.py deleted file mode 100644 index 35a04809a..000000000 --- a/box_sdk_gen/schemas/retention_policy_assignment_base.py +++ /dev/null @@ -1,30 +0,0 @@ -from enum import Enum - -from box_sdk_gen.internal.base_object import BaseObject - -from box_sdk_gen.box.errors import BoxSDKError - - -class RetentionPolicyAssignmentBaseTypeField(str, Enum): - RETENTION_POLICY_ASSIGNMENT = 'retention_policy_assignment' - - -class RetentionPolicyAssignmentBase(BaseObject): - _discriminator = 'type', {'retention_policy_assignment'} - - def __init__( - self, - id: str, - *, - type: RetentionPolicyAssignmentBaseTypeField = RetentionPolicyAssignmentBaseTypeField.RETENTION_POLICY_ASSIGNMENT, - **kwargs - ): - """ - :param id: The unique identifier that represents a file version. - :type id: str - :param type: The value will always be `retention_policy_assignment`., defaults to RetentionPolicyAssignmentBaseTypeField.RETENTION_POLICY_ASSIGNMENT - :type type: RetentionPolicyAssignmentBaseTypeField, optional - """ - super().__init__(**kwargs) - self.id = id - self.type = type diff --git a/box_sdk_gen/schemas/skill_invocation.py b/box_sdk_gen/schemas/skill_invocation.py deleted file mode 100644 index 29c9f0e32..000000000 --- a/box_sdk_gen/schemas/skill_invocation.py +++ /dev/null @@ -1,257 +0,0 @@ -from enum import Enum - -from typing import Optional - -from box_sdk_gen.internal.base_object import BaseObject - -from typing import Union - -from box_sdk_gen.schemas.file import File - -from box_sdk_gen.schemas.folder import Folder - -from box_sdk_gen.schemas.event import Event - -from box_sdk_gen.box.errors import BoxSDKError - -from box_sdk_gen.internal.utils import DateTime - - -class SkillInvocationTypeField(str, Enum): - SKILL_INVOCATION = 'skill_invocation' - - -class SkillInvocationSkillTypeField(str, Enum): - SKILL = 'skill' - - -class SkillInvocationSkillField(BaseObject): - _discriminator = 'type', {'skill'} - - def __init__( - self, - *, - id: Optional[str] = None, - type: Optional[SkillInvocationSkillTypeField] = None, - name: Optional[str] = None, - api_key: Optional[str] = None, - **kwargs - ): - """ - :param id: The unique identifier for this skill., defaults to None - :type id: Optional[str], optional - :param type: The value will always be `skill`., defaults to None - :type type: Optional[SkillInvocationSkillTypeField], optional - :param name: The name of the skill., defaults to None - :type name: Optional[str], optional - :param api_key: The client ID of the application., defaults to None - :type api_key: Optional[str], optional - """ - super().__init__(**kwargs) - self.id = id - self.type = type - self.name = name - self.api_key = api_key - - -class SkillInvocationTokenReadTokenTypeField(str, Enum): - BEARER = 'bearer' - - -class SkillInvocationTokenReadField(BaseObject): - def __init__( - self, - *, - access_token: Optional[str] = None, - expires_in: Optional[int] = None, - token_type: Optional[SkillInvocationTokenReadTokenTypeField] = None, - restricted_to: Optional[str] = None, - **kwargs - ): - """ - :param access_token: The requested access token., defaults to None - :type access_token: Optional[str], optional - :param expires_in: The time in seconds by which this token will expire., defaults to None - :type expires_in: Optional[int], optional - :param token_type: The type of access token returned., defaults to None - :type token_type: Optional[SkillInvocationTokenReadTokenTypeField], optional - :param restricted_to: The permissions that this access token permits, - providing a list of resources (files, folders, etc) - and the scopes permitted for each of those resources., defaults to None - :type restricted_to: Optional[str], optional - """ - super().__init__(**kwargs) - self.access_token = access_token - self.expires_in = expires_in - self.token_type = token_type - self.restricted_to = restricted_to - - -class SkillInvocationTokenWriteTokenTypeField(str, Enum): - BEARER = 'bearer' - - -class SkillInvocationTokenWriteField(BaseObject): - def __init__( - self, - *, - access_token: Optional[str] = None, - expires_in: Optional[int] = None, - token_type: Optional[SkillInvocationTokenWriteTokenTypeField] = None, - restricted_to: Optional[str] = None, - **kwargs - ): - """ - :param access_token: The requested access token., defaults to None - :type access_token: Optional[str], optional - :param expires_in: The time in seconds by which this token will expire., defaults to None - :type expires_in: Optional[int], optional - :param token_type: The type of access token returned., defaults to None - :type token_type: Optional[SkillInvocationTokenWriteTokenTypeField], optional - :param restricted_to: The permissions that this access token permits, - providing a list of resources (files, folders, etc) - and the scopes permitted for each of those resources., defaults to None - :type restricted_to: Optional[str], optional - """ - super().__init__(**kwargs) - self.access_token = access_token - self.expires_in = expires_in - self.token_type = token_type - self.restricted_to = restricted_to - - -class SkillInvocationTokenField(BaseObject): - def __init__( - self, - *, - read: Optional[SkillInvocationTokenReadField] = None, - write: Optional[SkillInvocationTokenWriteField] = None, - **kwargs - ): - """ - :param read: The basics of an access token., defaults to None - :type read: Optional[SkillInvocationTokenReadField], optional - :param write: The basics of an access token., defaults to None - :type write: Optional[SkillInvocationTokenWriteField], optional - """ - super().__init__(**kwargs) - self.read = read - self.write = write - - -class SkillInvocationStatusStateField(str, Enum): - INVOKED = 'invoked' - PROCESSING = 'processing' - SUCCESS = 'success' - TRANSIENT_FAILURE = 'transient_failure' - PERMANENT_FAILURE = 'permanent_failure' - - -class SkillInvocationStatusField(BaseObject): - def __init__( - self, - *, - state: Optional[SkillInvocationStatusStateField] = None, - message: Optional[str] = None, - error_code: Optional[str] = None, - additional_info: Optional[str] = None, - **kwargs - ): - """ - :param state: The state of this event. - - * `invoked` - Triggered the skill with event details to start - applying skill on the file. - * `processing` - Currently processing. - * `success` - Completed processing with a success. - * `transient_failure` - Encountered an issue which can be - retried. - * `permanent_failure` - Encountered a permanent issue and - retry would not help., defaults to None - :type state: Optional[SkillInvocationStatusStateField], optional - :param message: Status information., defaults to None - :type message: Optional[str], optional - :param error_code: Error code information, if error occurred., defaults to None - :type error_code: Optional[str], optional - :param additional_info: Additional status information., defaults to None - :type additional_info: Optional[str], optional - """ - super().__init__(**kwargs) - self.state = state - self.message = message - self.error_code = error_code - self.additional_info = additional_info - - -class SkillInvocationEnterpriseTypeField(str, Enum): - ENTERPRISE = 'enterprise' - - -class SkillInvocationEnterpriseField(BaseObject): - _discriminator = 'type', {'enterprise'} - - def __init__( - self, - *, - id: Optional[str] = None, - type: Optional[SkillInvocationEnterpriseTypeField] = None, - name: Optional[str] = None, - **kwargs - ): - """ - :param id: The unique identifier for this enterprise., defaults to None - :type id: Optional[str], optional - :param type: The value will always be `enterprise`., defaults to None - :type type: Optional[SkillInvocationEnterpriseTypeField], optional - :param name: The name of the enterprise., defaults to None - :type name: Optional[str], optional - """ - super().__init__(**kwargs) - self.id = id - self.type = type - self.name = name - - -class SkillInvocation(BaseObject): - _discriminator = 'type', {'skill_invocation'} - - def __init__( - self, - *, - type: Optional[SkillInvocationTypeField] = None, - id: Optional[str] = None, - skill: Optional[SkillInvocationSkillField] = None, - token: Optional[SkillInvocationTokenField] = None, - status: Optional[SkillInvocationStatusField] = None, - created_at: Optional[DateTime] = None, - trigger: Optional[str] = None, - enterprise: Optional[SkillInvocationEnterpriseField] = None, - source: Optional[Union[File, Folder]] = None, - event: Optional[Event] = None, - **kwargs - ): - """ - :param type: The value will always be `skill_invocation`., defaults to None - :type type: Optional[SkillInvocationTypeField], optional - :param id: Unique identifier for the invocation request., defaults to None - :type id: Optional[str], optional - :param token: The read-only and read-write access tokens for this item., defaults to None - :type token: Optional[SkillInvocationTokenField], optional - :param status: The details status of this event., defaults to None - :type status: Optional[SkillInvocationStatusField], optional - :param created_at: The time this invocation was created., defaults to None - :type created_at: Optional[DateTime], optional - :param trigger: Action that triggered the invocation., defaults to None - :type trigger: Optional[str], optional - """ - super().__init__(**kwargs) - self.type = type - self.id = id - self.skill = skill - self.token = token - self.status = status - self.created_at = created_at - self.trigger = trigger - self.enterprise = enterprise - self.source = source - self.event = event diff --git a/box_sdk_gen/schemas/v2025_r0/__init__.py b/box_sdk_gen/schemas/v2025_r0/__init__.py index cd0c28ba6..5c88bfc00 100644 --- a/box_sdk_gen/schemas/v2025_r0/__init__.py +++ b/box_sdk_gen/schemas/v2025_r0/__init__.py @@ -44,8 +44,6 @@ from box_sdk_gen.schemas.v2025_r0.hub_base_v2025_r0 import * -from box_sdk_gen.schemas.v2025_r0.hub_action_v2025_r0 import * - from box_sdk_gen.schemas.v2025_r0.hub_collaboration_create_request_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.hub_collaboration_update_request_v2025_r0 import * diff --git a/box_sdk_gen/schemas/v2025_r0/hub_action_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_action_v2025_r0.py deleted file mode 100644 index 7e81ca860..000000000 --- a/box_sdk_gen/schemas/v2025_r0/hub_action_v2025_r0.py +++ /dev/null @@ -1,8 +0,0 @@ -from enum import Enum - -from box_sdk_gen.box.errors import BoxSDKError - - -class HubActionV2025R0(str, Enum): - ADD = 'add' - REMOVE = 'remove' diff --git a/box_sdk_gen/schemas/webhook_invocation.py b/box_sdk_gen/schemas/webhook_invocation.py deleted file mode 100644 index 86ba116e8..000000000 --- a/box_sdk_gen/schemas/webhook_invocation.py +++ /dev/null @@ -1,100 +0,0 @@ -from enum import Enum - -from typing import Optional - -from typing import Union - -from box_sdk_gen.internal.base_object import BaseObject - -from box_sdk_gen.schemas.webhook import Webhook - -from box_sdk_gen.schemas.user_mini import UserMini - -from box_sdk_gen.schemas.file import File - -from box_sdk_gen.schemas.folder import Folder - -from box_sdk_gen.box.errors import BoxSDKError - -from box_sdk_gen.internal.utils import DateTime - - -class WebhookInvocationTypeField(str, Enum): - WEBHOOK_EVENT = 'webhook_event' - - -class WebhookInvocationTriggerField(str, Enum): - FILE_UPLOADED = 'FILE.UPLOADED' - FILE_PREVIEWED = 'FILE.PREVIEWED' - FILE_DOWNLOADED = 'FILE.DOWNLOADED' - FILE_TRASHED = 'FILE.TRASHED' - FILE_DELETED = 'FILE.DELETED' - FILE_RESTORED = 'FILE.RESTORED' - FILE_COPIED = 'FILE.COPIED' - FILE_MOVED = 'FILE.MOVED' - FILE_LOCKED = 'FILE.LOCKED' - FILE_UNLOCKED = 'FILE.UNLOCKED' - FILE_RENAMED = 'FILE.RENAMED' - COMMENT_CREATED = 'COMMENT.CREATED' - COMMENT_UPDATED = 'COMMENT.UPDATED' - COMMENT_DELETED = 'COMMENT.DELETED' - TASK_ASSIGNMENT_CREATED = 'TASK_ASSIGNMENT.CREATED' - TASK_ASSIGNMENT_UPDATED = 'TASK_ASSIGNMENT.UPDATED' - METADATA_INSTANCE_CREATED = 'METADATA_INSTANCE.CREATED' - METADATA_INSTANCE_UPDATED = 'METADATA_INSTANCE.UPDATED' - METADATA_INSTANCE_DELETED = 'METADATA_INSTANCE.DELETED' - FOLDER_CREATED = 'FOLDER.CREATED' - FOLDER_RENAMED = 'FOLDER.RENAMED' - FOLDER_DOWNLOADED = 'FOLDER.DOWNLOADED' - FOLDER_RESTORED = 'FOLDER.RESTORED' - FOLDER_DELETED = 'FOLDER.DELETED' - FOLDER_COPIED = 'FOLDER.COPIED' - FOLDER_MOVED = 'FOLDER.MOVED' - FOLDER_TRASHED = 'FOLDER.TRASHED' - WEBHOOK_DELETED = 'WEBHOOK.DELETED' - COLLABORATION_CREATED = 'COLLABORATION.CREATED' - COLLABORATION_ACCEPTED = 'COLLABORATION.ACCEPTED' - COLLABORATION_REJECTED = 'COLLABORATION.REJECTED' - COLLABORATION_REMOVED = 'COLLABORATION.REMOVED' - COLLABORATION_UPDATED = 'COLLABORATION.UPDATED' - SHARED_LINK_DELETED = 'SHARED_LINK.DELETED' - SHARED_LINK_CREATED = 'SHARED_LINK.CREATED' - SHARED_LINK_UPDATED = 'SHARED_LINK.UPDATED' - SIGN_REQUEST_COMPLETED = 'SIGN_REQUEST.COMPLETED' - SIGN_REQUEST_DECLINED = 'SIGN_REQUEST.DECLINED' - SIGN_REQUEST_EXPIRED = 'SIGN_REQUEST.EXPIRED' - SIGN_REQUEST_SIGNER_EMAIL_BOUNCED = 'SIGN_REQUEST.SIGNER_EMAIL_BOUNCED' - - -class WebhookInvocation(BaseObject): - _discriminator = 'type', {'webhook_event'} - - def __init__( - self, - *, - id: Optional[str] = None, - type: Optional[WebhookInvocationTypeField] = None, - webhook: Optional[Webhook] = None, - created_by: Optional[UserMini] = None, - created_at: Optional[DateTime] = None, - trigger: Optional[WebhookInvocationTriggerField] = None, - source: Optional[Union[File, Folder]] = None, - **kwargs - ): - """ - :param id: The unique identifier for this webhook invocation., defaults to None - :type id: Optional[str], optional - :param type: The value will always be `webhook_event`., defaults to None - :type type: Optional[WebhookInvocationTypeField], optional - :param created_at: A timestamp identifying the time that - the webhook event was triggered., defaults to None - :type created_at: Optional[DateTime], optional - """ - super().__init__(**kwargs) - self.id = id - self.type = type - self.webhook = webhook - self.created_by = created_by - self.created_at = created_at - self.trigger = trigger - self.source = source diff --git a/box_sdk_gen/schemas/workflow_full.py b/box_sdk_gen/schemas/workflow_full.py deleted file mode 100644 index 38a783c23..000000000 --- a/box_sdk_gen/schemas/workflow_full.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Optional - -from typing import List - -from box_sdk_gen.schemas.workflow_mini import WorkflowMiniTypeField - -from box_sdk_gen.schemas.workflow_mini import WorkflowMini - -from box_sdk_gen.schemas.workflow import WorkflowFlowsField - -from box_sdk_gen.schemas.workflow import Workflow - -from box_sdk_gen.schemas.user_base import UserBase - -from box_sdk_gen.box.errors import BoxSDKError - -from box_sdk_gen.internal.utils import DateTime - - -class WorkflowFull(Workflow): - def __init__( - self, - *, - created_at: Optional[DateTime] = None, - modified_at: Optional[DateTime] = None, - created_by: Optional[UserBase] = None, - modified_by: Optional[UserBase] = None, - flows: Optional[List[WorkflowFlowsField]] = None, - id: Optional[str] = None, - type: Optional[WorkflowMiniTypeField] = None, - name: Optional[str] = None, - description: Optional[str] = None, - is_enabled: Optional[bool] = None, - **kwargs - ): - """ - :param created_at: The date and time when the workflow was created on Box., defaults to None - :type created_at: Optional[DateTime], optional - :param modified_at: The date and time when the workflow was last updated on Box., defaults to None - :type modified_at: Optional[DateTime], optional - :param flows: A list of flows assigned to a workflow., defaults to None - :type flows: Optional[List[WorkflowFlowsField]], optional - :param id: The unique identifier for the workflow., defaults to None - :type id: Optional[str], optional - :param type: The value will always be `workflow`., defaults to None - :type type: Optional[WorkflowMiniTypeField], optional - :param name: The name of the workflow., defaults to None - :type name: Optional[str], optional - :param description: The description for a workflow., defaults to None - :type description: Optional[str], optional - :param is_enabled: Specifies if this workflow is enabled., defaults to None - :type is_enabled: Optional[bool], optional - """ - super().__init__( - flows=flows, - id=id, - type=type, - name=name, - description=description, - is_enabled=is_enabled, - **kwargs - ) - self.created_at = created_at - self.modified_at = modified_at - self.created_by = created_by - self.modified_by = modified_by diff --git a/docs/archives.md b/docs/archives.md index cc730a340..cdfd47f18 100644 --- a/docs/archives.md +++ b/docs/archives.md @@ -8,6 +8,8 @@ Retrieves archives for an enterprise. +To learn more about the archive APIs, see the [Archive API Guide](g://archives). + This operation is performed by calling function `get_archives_v2025_r0`. See the endpoint docs at @@ -40,6 +42,8 @@ Returns a list of archives in the enterprise. Creates an archive. +To learn more about the archive APIs, see the [Archive API Guide](g://archives). + This operation is performed by calling function `create_archive_v2025_r0`. See the endpoint docs at @@ -70,6 +74,8 @@ Returns a new archive object. Permanently deletes an archive. +To learn more about the archive APIs, see the [Archive API Guide](g://archives). + This operation is performed by calling function `delete_archive_by_id_v2025_r0`. See the endpoint docs at diff --git a/docs/docgen.md b/docs/docgen.md index 6e2a00bac..6ba00e607 100644 --- a/docs/docgen.md +++ b/docs/docgen.md @@ -47,7 +47,7 @@ See the endpoint docs at ```python -client.docgen.get_docgen_jobs_v2025_r0(limit=500) +client.docgen.get_docgen_jobs_v2025_r0(limit=10000) ``` ### Arguments diff --git a/test/docgen.py b/test/docgen.py index a41bc0342..d1f7d1765 100644 --- a/test/docgen.py +++ b/test/docgen.py @@ -73,7 +73,7 @@ def testDocgenBatchAndJobs(): assert docgen_batch_jobs.entries[0].template_file.id == uploaded_file.id assert docgen_batch_jobs.entries[0].batch.id == docgen_batch.id docgen_jobs: DocGenJobsFullV2025R0 = client.docgen.get_docgen_jobs_v2025_r0( - limit=500 + limit=10000 ) assert len(docgen_jobs.entries) >= 1 assert not docgen_jobs.entries[0].batch.id == '' @@ -90,7 +90,7 @@ def testDocgenBatchAndJobs(): ) assert not docgen_jobs.entries[0].template_file_version.id == '' assert to_string(docgen_jobs.entries[0].type) == 'docgen_job' - index_of_item: int = 0 + index_of_item: int = len(docgen_jobs.entries) - 1 docgen_job_item_from_list: DocGenJobFullV2025R0 = docgen_jobs.entries[index_of_item] docgen_job: DocGenJobV2025R0 = client.docgen.get_docgen_job_by_id_v2025_r0( docgen_job_item_from_list.id From 0ff2c48b2d6f6490cb02de2f16415aefc646e6cf Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:34:58 +0200 Subject: [PATCH 005/139] docs: Adjust `README` and migration guide for `v10` (box/box-codegen#782) (box/box-codegen#785) (#935) --- .codegen.json | 2 +- README.md | 31 +++++---- migration-guide.md | 161 +++++++++++++++++++++++---------------------- 3 files changed, 100 insertions(+), 94 deletions(-) diff --git a/.codegen.json b/.codegen.json index 20b0a328d..f4ba7d87a 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "be39be6", "specHash": "24fbeb9", "version": "0.1.0" } +{ "engineHash": "1d94996", "specHash": "24fbeb9", "version": "0.1.0" } diff --git a/README.md b/README.md index 16aab67a2..1fd84d3da 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,17 @@ “box-dev-logo”

-# Box Python SDK Gen +# Box Python SDK [![Project Status](http://opensource.box.com/badges/active.svg)](http://opensource.box.com/badges) -![build](https://github.com/box/box-python-sdk-gen/actions/workflows/build.yml/badge.svg) -[![PyPI version](https://badge.fury.io/py/box-sdk-gen.svg)](https://badge.fury.io/py/box-sdk-gen) -[![image](https://img.shields.io/pypi/dm/box-sdk-gen.svg)](https://pypi.python.org/pypi/box-sdk-gen) +![build](https://github.com/box/box-python-sdk/actions/workflows/build.yml/badge.svg?branch=sdk-gen) +[![PyPI version](https://badge.fury.io/py/boxsdk.svg)](https://badge.fury.io/py/boxsdk) +[![image](https://img.shields.io/pypi/dm/boxsdk.svg)](https://pypi.python.org/pypi/boxsdk) ![Platform](https://img.shields.io/badge/python-3.8+-blue) -[![Coverage](https://coveralls.io/repos/github/box/box-python-sdk-gen/badge.svg?branch=main)](https://coveralls.io/github/box/box-python-sdk-gen?branch=main) +[![Coverage](https://coveralls.io/repos/github/box/box-python-sdk/badge.svg?branch=sdk-gen)](https://coveralls.io/github/box/box-python-sdk?branch=sdk-gen) -We are excited to introduce the stable release of the latest generation of Box Python SDK, designed to elevate the developer experience and streamline your integration with the Box Content Cloud. +We are excited to introduce the stable release of the latest generation of Box Python SDK, +designed to elevate the developer experience and streamline your integration with the Box Content Cloud. With this SDK, you’ll have access to: @@ -28,7 +29,7 @@ Embrace the new generation of Box SDKs and unlock the full potential of the Box -- [Box Python SDK Gen](#box-python-sdk-gen) +- [Box Python SDK](#box-python-sdk) - [Table of contents](#table-of-contents) - [Installing](#installing) - [Getting Started](#getting-started) @@ -46,17 +47,18 @@ Embrace the new generation of Box SDKs and unlock the full potential of the Box # Installing +The next generation of the SDK starts with version `10.0.0`. + ```console -pip install box-sdk-gen +pip install boxsdk>=10 ``` -This is autogenerated Box SDK version. -Supported Python versions are Python 3.8 and above. +This is autogenerated Box SDK version. Supported Python versions are Python 3.8 and above. To install also extra dependencies required for JWT authentication, use command: ```console -pip install "box-sdk-gen[jwt]" +pip install "boxsdk[jwt]>=10" ``` # Getting Started @@ -89,7 +91,8 @@ Browse the [docs](docs/README.md) or see [API Reference](https://developer.box.c # Upgrades -Upgrading from our legacy SDKs to the new generation SDKs is a straightforward process. See our [migration guide](migration-guide.md) and [changelog](CHANGELOG.md) for more information. +The SDK is updated regularly to include new features, enhancements, and bug fixes. +If you are upgrading from manual v3 SDK version to this new generated v10, checkout the [migration guide](migration-guide.md) and [changelog](CHANGELOG.md) for more information. # Integration Tests @@ -129,9 +132,9 @@ To run integration tests locally: # Questions, Bugs, and Feature Requests? Need to contact us directly? [Browse the issues -tickets](https://github.com/box/box-python-sdk-gen/issues)! Or, if that +tickets](https://github.com/box/box-python-sdk/issues)! Or, if that doesn't work, [file a new -one](https://github.com/box/box-python-sdk-gen/issues/new) and we will get +one](https://github.com/box/box-python-sdk/issues/new) and we will get back to you. If you have general questions about the Box API, you can post to the [Box Developer Forum](https://forum.box.com/). diff --git a/migration-guide.md b/migration-guide.md index ec6dc7594..0ca08dcb0 100644 --- a/migration-guide.md +++ b/migration-guide.md @@ -1,4 +1,4 @@ -# Migration guide from `boxsdk` to `box-sdk-gen` +# Migration guide from v3 to v10 version of `boxsdk` @@ -36,35 +36,38 @@ ## Introduction -The new `box-sdk-gen` SDK library, which helps Python developers to conveniently integrate with Box API. -In the contrary to the previous library (`boxsdk`), it is not manually maintained, but auto-generated +The v10 release of Box Python SDK library helps Python developers to conveniently integrate with Box API. +In the contrary to the previous version (v3 or lower), it is not manually maintained, but auto-generated based on Open API Specification. This means you can leverage the most up-to-date Box API features in your -applications without delay. More information and benefits of using the new can be found in the -[README](https://github.com/box/box-python-sdk-gen/blob/main/README.md) file. +applications without delay. We introduced this major version bump to reflect the significant codebase changes +and to align with other Box SDKs, which will also adopt generated code starting from their v10 releases. +More information and benefits of using the new can be found in the +[README](https://github.com/box/box-python-sdk/blob/main/README.md) file. ## Installation -To install a new Box Python SDK GENERATED use command: +To install v10 version of Box Python SDK use command: ```console -pip install box-sdk-gen +pip install box-sdk-gen>=10 ``` -The new Box Python SDK GENERATED library could be used in the same project along with the legacy one. -If you want to use a feature available only in the new SDK, you don't need to necessarily migrate all your code -to use Box Python SDK GENERATED at once. You can use a new feature from the new library, -while keeping the rest of your code unchanged. Note that it may be required to alias some imported names -from the new SDK to avoid conflicts with the old one. However, we recommend to fully migrate to the new SDK eventually. +Soon we are going to introduce v4 version of Box Python SDK that will combine package `boxsdk` from +v3 and `box_sdk_gen` from v10 of the SDK, so that code from both versions could be used in the same project. +If you would like to use a feature available only in the new SDK, you won't need to necessarily migrate all your code +to use generated SDK at once. You will be able to use a new feature from the `box_sdk_gen` package, +while keeping the rest of your code unchanged. Note that it may be required to use aliases for some of the imported names +to avoid conflicts between two packages. However, we recommend to fully migrate to the v10 of the SDK eventually. ## Key differences ### Manager approach -The main difference between the old SDK and the new one is the way how API methods are aggregated into objects. +The main difference between the manual v3 and v10 version of Box Python SDK is the way how API methods are aggregated into objects. -**Old (`boxsdk`)** +**Old (`v3`)** -Firstly, in the old SDK to be able to perform any action on an API object, e.g. `User`, you first had to create its class. +Firstly, in the v3 release to be able to perform any action on an API object, e.g. `User`, you first had to create its class. To do it is required to call: ```python @@ -83,9 +86,9 @@ Then, you could perform any action on created class, which will affect the user, updated_user = user.update_info(data={'name': 'New User Name'}) ``` -**New (`box-sdk-gen`)** +**New (`v10`)** -In the new SDK the API methods are grouped into dedicated manager classes, e.g. `User` object +In the v10 release, the API methods are grouped into dedicated manager classes, e.g. `User` object has dedicated `UserManager` class. Each manager class instance is available in `BoxClient` object. The fields storing references to the managers are named in the plural form of the resource that the manager handles - `client.users` for `UsersManager`. If you want to perform any operation @@ -113,9 +116,9 @@ updated_user = client.users.update_user_by_id(user_id=user.id, name='New User Na ### Explicitly defined schemas -**Old (`boxsdk`)** +**Old (`v3`)** -In the old SDK there were no data types explicitly defined - +In v3 release, there were no data types explicitly defined - the responses were dynamically mapped into classes in the runtime. For example, if you get information about a file: ```python @@ -125,9 +128,9 @@ file = client.file(file_id='12345678').get() you couldn't be sure which fields to expect in the response object until the runtime, because `File` class doesn't have any predefined fields. -**New (`box-sdk-gen`)** +**New (`v10`)** -In the new SDK the data classe are defined in `schemas` module, so you know, which fields to expect before +In v10 release, the data classes are defined in `schemas` module, so you know, which fields to expect before actually making a call. For example `FileBase` class is defined this way: ```python @@ -141,12 +144,12 @@ class FileBase(BaseObject): ### Immutable design -The new SDK is designed to be mostly immutable. This means that methods, -which used to modify the existing object in old SDK now return a new instance of the class with the modified state. +The new v10 version of Box Python SDK is designed to be mostly immutable. This means that methods, +which used to modify the existing object in v3 release of SDK now return a new instance of the class with the modified state. This design pattern is used to avoid side effects and make the code more predictable and easier to reason about. Methods, which returns a new modified instance of an object, will always have a prefix `with_` in their names, e.g. -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from box_sdk_gen import BoxClient @@ -156,12 +159,12 @@ as_user_client: BoxClient = client.with_as_user_header('USER_ID') ## Authentication -The Box Python SDK GENERATED library offers the same authentication methods as the legacy one. +The v10 release Box Python SDK library offers the same authentication methods as v3 release. Let's see the differences of their usage: ### Developer Token -**Old (`boxsdk`)** +**Old (`v3`)** ```python from boxsdk import Client, OAuth2 @@ -174,10 +177,10 @@ auth = OAuth2( client = Client(auth) ``` -The new SDK provides a convenient `BoxDeveloperTokenAuth`, which allows authenticating +The v10 version, provides a convenient `BoxDeveloperTokenAuth`, which allows authenticating using developer token without necessity to provide a Client ID and Client Secret -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth @@ -190,7 +193,7 @@ client = BoxClient(auth=auth) #### Using JWT configuration file -**Old (`boxsdk`)** +**Old (`v3`)** The static method, which reads the JWT configuration file has been changed: @@ -201,7 +204,7 @@ auth = JWTAuth.from_settings_file('/path/to/config.json') client = Client(auth) ``` -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from box_sdk_gen import BoxClient, BoxJWTAuth, JWTConfig @@ -213,9 +216,9 @@ client = BoxClient(auth=auth) #### Providing JWT configuration manually -Some params in `JWTConfig` constructor have slightly different names than one in old `JWTAuth` class. +Some params in `JWTConfig` constructor have slightly different names than one in the old `JWTAuth` class. -**Old (`boxsdk`)** +**Old (`v3`)** ```python from boxsdk import JWTAuth @@ -232,7 +235,7 @@ auth = JWTAuth( ) ``` -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from box_sdk_gen import BoxJWTAuth, JWTConfig, JwtAlgorithm @@ -252,12 +255,12 @@ auth = BoxJWTAuth(config=jwt_config) #### Authenticate user -In old SDK method for user authentication was named `authenticate_user(self, user: Union[str, 'User'] = None) -> str` +In v3 release, method for user authentication was named `authenticate_user(self, user: Union[str, 'User'] = None) -> str` and was accepting either user object or user id. If none provided, user ID stored in `JWTAuth` class instance was used. The `authenticate_user` method was modifying existing `BoxJWTAuth` class, which was exchanging the existing token with the one with the user access. -**Old (`boxsdk`)** +**Old (`v3`)** ```python auth.authenticate_user(user) @@ -269,9 +272,9 @@ or auth.authenticate_user('USER_ID') ``` -**New (`box-sdk-gen`)** +**New (`v10`)** -In new SDK, to authenticate as user you need to call +In new v10 release, to authenticate as user you need to call `with_user_subject(self, user_id: str, *, token_storage: TokenStorage = None) -> BoxJWTAuth` method with id of the user to authenticate. The method returns a new instance of `BoxJWTAuth` class, which will perform authentication call in scope of the user on the first API call. The `token_storage` parameter is optional and allows to provide a custom @@ -288,10 +291,10 @@ user_client: BoxClient = BoxClient(auth=user_auth) #### Obtaining Service Account token -To authenticate as enterprise, the only difference between the old and the new SDK, +To authenticate as enterprise, the only difference between the versions of SDK, is using the `CCGConfig` as a middle step. -**Old (`boxsdk`)** +**Old (`v3`)** ```python from boxsdk import CCGAuth, Client @@ -305,7 +308,7 @@ auth = CCGAuth( client = Client(auth) ``` -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from box_sdk_gen import BoxClient, BoxCCGAuth, CCGConfig @@ -321,10 +324,10 @@ client = BoxClient(auth=auth) #### Obtaining User token -In old SDK `CCGAuth` was accepting both user object and User ID. In the box-sdk-gen the `BoxCCGAuth` constructor accepts +In v3 `CCGAuth` was accepting both user object and User ID. In v10 the `BoxCCGAuth` constructor accepts only User ID instead. -**Old (`boxsdk`)** +**Old (`v3`)** ```python from boxsdk import CCGAuth @@ -336,7 +339,7 @@ auth = CCGAuth( ) ``` -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from box_sdk_gen import BoxCCGAuth, CCGConfig @@ -351,10 +354,10 @@ auth = BoxCCGAuth(config=ccg_config) ### Switching between Service Account and User -In old SDK there were two methods which allowed to switch between using service and user account. Calling these methods +In v3 release, there were two methods which allowed to switch between using service and user account. Calling these methods were modifying existing state of `CCGAuth` class, which was fetching a new token on the next API call. -**Old (`boxsdk`)** +**Old (`v3`)** ```python auth.authenticate_enterprise('ENTERPRISE_ID') @@ -364,13 +367,13 @@ auth.authenticate_enterprise('ENTERPRISE_ID') auth.authenticate_user('USER_ID') ``` -In the new SDK, to keep the immutability design, the methods switching authenticated subject were replaced with methods +In the new release, to keep the immutability design, the methods switching authenticated subject were replaced with methods returning a new instance of `BoxCCGAuth` class. The new instance will fetch a new token on the next API call. The new auth instance can be used to create a new client instance. You can also specify `token_storage` parameter to provide a custom token storage for the new instance. The old instance of `BoxCCGAuth` class will remain unchanged and will still use the old token. -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from box_sdk_gen import BoxCCGAuth, BoxClient @@ -391,7 +394,7 @@ user and enterprise object too. #### Get Authorization URL -To get authorization url in the new SDK, you need to first create the `BoxOAuth` class (previously `OAuth2`) using +To get authorization url in the v10 version of SDK, you need to first create the `BoxOAuth` class (previously `OAuth2`) using `OAuthConfig` class. Then to get authorization url, call `get_authorize_url(self, *, options: GetAuthorizeUrlOptions = None) -> str` instead of `get_authorization_url(self, redirect_url: Optional[str]) -> Tuple[str, str]`. Note that this method @@ -399,7 +402,7 @@ now accepts the instance of `GetAuthorizeUrlOptions` class, which allows specify The new function returns only the authentication url string, while the old one returns tuple of authentication url and csrf_token. -**Old (`boxsdk`)** +**Old (`v3`)** ```python from boxsdk import OAuth2 @@ -412,7 +415,7 @@ auth = OAuth2( auth_url, csrf_token = auth.get_authorization_url('http://YOUR_REDIRECT_URL') ``` -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from box_sdk_gen import BoxOAuth, OAuthConfig, GetAuthorizeUrlOptions @@ -434,7 +437,7 @@ The signature of method for authenticating with obtained auth code got changed f The method now returns an AccessToken object with `access_token` and `refresh_token` fields, while the old one was returning a tuple of access token and refresh token. -**Old (`boxsdk`)** +**Old (`v3`)** ```python from boxsdk import Client @@ -442,7 +445,7 @@ access_token, refresh_token = auth.authenticate('YOUR_AUTH_CODE') client = Client(auth) ``` -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from box_sdk_gen import BoxClient, AccessToken @@ -453,14 +456,14 @@ client = BoxClient(auth) ### Store token and retrieve token callbacks -In old SDK you could provide a `store_tokens` callback method to an authentication class, which was called each time +In v3 release you could provide a `store_tokens` callback method to an authentication class, which was called each time an access token was refreshed. It could be used to save your access token to a custom token storage and allow to reuse this token later. -What is more, old SDK allowed also to provide `retrieve_tokens` callback, which is called each time the SDK needs to use +What is more, v3 release allowed also to provide `retrieve_tokens` callback, which is called each time the SDK needs to use token to perform an API call. To provide that, it was required to use `CooperativelyManagedOAuth2` and provide `retrieve_tokens` callback method to its constructor. -**Old (`boxsdk`)** +**Old (`v3`)** ```python from typing import Tuple @@ -486,11 +489,11 @@ access_token, refresh_token = auth.authenticate('YOUR_AUTH_CODE') client = Client(auth) ``` -In the new SDK you can define your own class delegated for storing and retrieving a token. It has to inherit from +In the new release you can define your own class delegated for storing and retrieving a token. It has to inherit from `TokenStorage` and implement all of its abstract methods. Next step would be to pass an instance of this class to the AuthConfig constructor. -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from typing import Optional @@ -535,10 +538,10 @@ auth = BoxOAuth( ### Downscope token -The process of downscoping token in the new SDK is similar to the old one. The main difference is that the new method +The process of downscoping token in the new release is similar to the old one. The main difference is that the new method accepts the full resource path instead of file object. -**Old (`boxsdk`)** +**Old (`v3`)** ```python from boxsdk import Client, OAuth2 @@ -553,7 +556,7 @@ downscoped_auth = OAuth2( downscoped_client = Client(downscoped_auth) ``` -**New (`box-sdk-gen`)** +**New (`v10`)** ```python from box_sdk_gen import BoxDeveloperTokenAuth, AccessToken, BoxClient @@ -569,16 +572,16 @@ client = BoxClient(auth=downscoped_auth) ### Revoke token -To revoke current client's tokens in the new SDK, you need to call `revoke_token` method of the auth class instead of +To revoke current client's tokens in the v10 release, you need to call `revoke_token` method of the auth class instead of `revoke` method. -**Old (`boxsdk`)** +**Old (`v3`)** ```python oauth.revoke() ``` -**New (`box-sdk-gen`)** +**New (`v10`)** ```python client.auth.revoke_token() @@ -592,9 +595,9 @@ The As-User header is used by enterprise admins to make API calls on behalf of t This requires the API request to pass an `As-User: USER-ID` header. The following examples assume that the client has been instantiated with an access token with appropriate privileges to make As-User calls. -In old SDK you could call client `as_user(self, user: User)` method to create a new client to impersonate the provided user. +In v3 you could call client `as_user(self, user: User)` method to create a new client to impersonate the provided user. -**Old (`boxsdk`)** +**Old (`v3`)** ```python from boxsdk import Client @@ -603,9 +606,9 @@ user_to_impersonate = client.user(user_id='USER_ID') user_client: Client = client.as_user(user_to_impersonate) ``` -**New (`box-sdk-gen`)** +**New (`v10`)** -In the new SDK the method was renamed to `with_as_user_header(self, user_id: str) -> BoxClient` +In v10 the method was renamed to `with_as_user_header(self, user_id: str) -> BoxClient` and returns a new instance of `BoxClient` class with the As-User header appended to all API calls made by the client. The method accepts only user id as a parameter. @@ -627,9 +630,9 @@ new_client: BoxClient = client.with_extra_headers(extra_headers={'customHeader': ### Custom Base URLs -**Old (`boxsdk`)** +**Old (`v3`)** -In old SDK you could specify the custom base URLs, which will be used for API calls made by setting +In manual v3 release, you could specify the custom base URLs, which will be used for API calls made by setting the new values of static variables of the `API` class. ```python @@ -640,9 +643,9 @@ API.OAUTH2_API_URL = 'https://my-company.com/oauth2' API.UPLOAD_URL = 'https://my-company-upload-url.com' ``` -**New (`box-sdk-gen`)** +**New (`v10`)** -In the new SDK this functionality has been implemented as part of the `BoxClient` class. +In the new release this functionality has been implemented as part of the `BoxClient` class. By calling the `client.with_custom_base_urls()` method, you can specify the custom base URLs that will be used for API calls made by client. Following the immutability pattern, this call creates a new client, leaving the original client unmodified. @@ -662,9 +665,9 @@ new_client: BoxClient = client.with_custom_base_urls(base_urls=BaseUrls( Webhook validation is used to validate a webhook message by verifying the signature and the delivery timestamp. -**Old (`boxsdk`)** +**Old (`v3`)** -In the old SDK, you could pass the `body` as `bytes`, and it would return a `boolean` value indicating whether the message was valid. +In the v3 version of Box Python SDK, you could pass the `body` as `bytes`, and it would return a `boolean` value indicating whether the message was valid. ```python body = b'{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' @@ -680,9 +683,9 @@ is_validated = Webhook.validate_message(body, headers, primary_key, secondary_ke print(f'The webhook message is validated to: {is_validated}') ``` -**New (`box-sdk-gen`)** +**New (`v10`)** -In the new SDK, the `WebhooksManager.validate_message()` method requires the `body` to be of type `string` and +In the new v10 version, the `WebhooksManager.validate_message()` method requires the `body` to be of type `string` and the rest of the code remains the same ```python @@ -707,9 +710,9 @@ WebhooksManager.validate_message( For large files or in cases where the network connection is less reliable, you may want to upload the file in parts. This allows a single part to fail without aborting the entire upload, and failed parts are being retried automatically. -**Old (`boxsdk`)** +**Old (`v3`)** -In the old SDK, you could use the `get_chunked_uploader()` method to create a chunked uploader object. +In v3, you could use the `get_chunked_uploader()` method to create a chunked uploader object. Then, you would call the `start()` method to begin the upload process. The `get_chunked_uploader()` method requires the `file_path` and `file_name` parameters. @@ -719,9 +722,9 @@ uploaded_file = chunked_uploader.start() print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}') ``` -**New (`box-sdk-gen`)** +**New (`v10`)** -In the new SDK, the equivalent method is `chunked_uploads.upload_big_file()`. It accepts a file-like object +In v10, the equivalent method is `chunked_uploads.upload_big_file()`. It accepts a file-like object as the `file` parameter, and the `file_name` and `file_size` parameters are now passed as arguments. The `parent_folder_id` parameter is also required to specify the folder where the file will be uploaded. From 4442a848576c9499bced5294cd8b7b6da7d9bf12 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 14 Aug 2025 13:08:17 +0200 Subject: [PATCH 006/139] feat: Support event with long polling (box/box-codegen#757) (#936) --- .codegen.json | 2 +- box_sdk_gen/box/__init__.py | 2 + box_sdk_gen/box/event_stream.py | 236 ++++++++++++++++++++++++++++++ box_sdk_gen/managers/events.py | 246 +++++++++++++++++++++++++++++++- docs/event-stream.md | 26 ++++ docs/events.md | 22 +++ test/events.py | 7 + 7 files changed, 538 insertions(+), 3 deletions(-) create mode 100644 box_sdk_gen/box/event_stream.py create mode 100644 docs/event-stream.md diff --git a/.codegen.json b/.codegen.json index f4ba7d87a..6a7ff390b 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "1d94996", "specHash": "24fbeb9", "version": "0.1.0" } +{ "engineHash": "1c54d3c", "specHash": "24fbeb9", "version": "0.1.0" } diff --git a/box_sdk_gen/box/__init__.py b/box_sdk_gen/box/__init__.py index 78c141131..877f6093d 100644 --- a/box_sdk_gen/box/__init__.py +++ b/box_sdk_gen/box/__init__.py @@ -1,3 +1,5 @@ +from box_sdk_gen.box.event_stream import * + from box_sdk_gen.box.errors import * from box_sdk_gen.box.token_storage import * diff --git a/box_sdk_gen/box/event_stream.py b/box_sdk_gen/box/event_stream.py new file mode 100644 index 000000000..23b2b3b61 --- /dev/null +++ b/box_sdk_gen/box/event_stream.py @@ -0,0 +1,236 @@ +import threading +from enum import Enum +from typing import Optional, Generator + +from ..box.errors import BoxSDKError +from ..schemas.events import Events +from ..schemas.event import Event +from ..schemas.realtime_server import RealtimeServer +from ..networking.fetch_options import FetchOptions, ResponseFormat +from ..networking.fetch_response import FetchResponse + + +class RealtimeServerEvent(str, Enum): + NEW_CHANGE = 'new_change' + RECONNECT = 'reconnect' + + +class EventStreamAction(str, Enum): + FETCH_EVENTS = 'fetch_events' + RECONNECT = 'reconnect' + RETRY = 'retry' + STOP = 'stop' + + +class EventStream: + """ + EventStream is an iterator that fetches events from the Box API. + It uses long polling to receive real-time updates. + This class is designed to be used as a Python iterator. + + Example usage: + events_stream = client.events.get_event_stream() + for event in events_stream: + print(event) + """ + + def __init__(self, *, events_manager, query_params, headers_input): + """ + Initialize the EventStream. + + :param events_manager: The EventsManager instance which provides relevant methods to fetch events. + :param query_params: The query parameters to use for fetching events. + :param headers_input: The headers to include in the request. + """ + self._events_manager = events_manager + self._query_params = query_params + self._headers_input = headers_input + self._stream_position = query_params.stream_position or 'now' + self._long_polling_info: Optional[RealtimeServer] = None + self._long_polling_retries: int = 0 + self._started: bool = False + self._stopped: bool = False + self._stop_event = threading.Event() + self._deduplication_size = 1000 + self._dedupHash = dict() + + def __iter__(self) -> Generator[Event, None, None]: + """Make EventStream iterable. Yields Event objects.""" + return self._event_generator() + + def _event_generator(self) -> Generator[Event, None, None]: + """Generator that yields Event objects from the stream.""" + if not self._started: + self._started = True + + try: + # Start with fetching events to get initial events and stream position + yield from self._fetch_events() + + # Then start long polling loop + while not self._stopped and not self._stop_event.is_set(): + try: + action = self._get_long_poll_info_and_poll() + + if action == EventStreamAction.FETCH_EVENTS: + # Fetch new events when notified + yield from self._fetch_events() + elif action == EventStreamAction.RECONNECT: + # Continue the loop to get new long polling info + continue + elif action == EventStreamAction.RETRY: + # Wait a bit before retrying + if not self._stop_event.wait(5): + continue + else: + break + elif action == EventStreamAction.STOP: + break + else: + # Continue long polling + continue + + except Exception as e: + if not self._stopped and not self._stop_event.is_set(): + # Wait a bit before retrying + if not self._stop_event.wait(5): + continue + break + + except Exception as e: + if not self._stopped and not self._stop_event.is_set(): + pass + return + + def stop(self): + """Stop the event stream.""" + self._stopped = True + self._stop_event.set() + + def _get_long_poll_info(self): + """Fetch long polling info from the server.""" + if self._stopped or self._stop_event.is_set(): + return + + try: + info = self._events_manager.get_events_with_long_polling() + + server = next( + (e for e in (info.entries or []) if e.type == 'realtime_server'), None + ) + if not server: + raise BoxSDKError(message='No realtime server found in the response.') + + self._long_polling_info = server + self._long_polling_retries = 0 + + except Exception as error: + if not self._stopped and not self._stop_event.is_set(): + raise error + + def _get_long_poll_info_and_poll(self) -> str: + """Get long polling info and perform a long poll, returning the action to take.""" + if self._stopped or self._stop_event.is_set(): + return 'stop' + + # Get long polling info if needed + if not self._long_polling_info or self._long_polling_retries > int( + self._long_polling_info.max_retries or '10' + ): + self._get_long_poll_info() + + return self._do_long_poll() + + def _do_long_poll(self) -> str: + """Perform the long polling request and return action to take.""" + if self._stopped or self._stop_event.is_set(): + return EventStreamAction.STOP + + try: + self._long_polling_retries += 1 + + long_poll_url = self._long_polling_info.url + separator = '&' if '?' in long_poll_url else '?' + long_poll_with_stream_position = ( + f"{long_poll_url}{separator}stream_position={self._stream_position}" + ) + + response: FetchResponse = ( + self._events_manager.network_session.network_client.fetch( + FetchOptions( + url=long_poll_with_stream_position, + method='GET', + headers={ + 'Content-Type': 'application/json', + }, + response_format=ResponseFormat.JSON, + auth=self._events_manager.auth, + network_session=self._events_manager.network_session, + ) + ) + ) + + if self._stopped or self._stop_event.is_set(): + return EventStreamAction.STOP + + if response.data: + message = response.data + + if isinstance(message, dict): + message_text = message.get('message', '') + + if message_text == RealtimeServerEvent.NEW_CHANGE: + return EventStreamAction.FETCH_EVENTS + elif message_text == RealtimeServerEvent.RECONNECT: + return EventStreamAction.RECONNECT + + # Continue long polling + return self._do_long_poll() + + except Exception as error: + if not self._stopped and not self._stop_event.is_set(): + return 'retry' + + return 'stop' + + def _fetch_events(self) -> Generator[Event, None, None]: + """Fetch events from the API and yield Event objects.""" + if self._stopped or self._stop_event.is_set(): + return + + try: + # Prepare query parameters for the get_events call + fetch_params = self._query_params.__dict__ + fetch_params['stream_position'] = self._stream_position + + # Add extra headers if provided + if self._headers_input and self._headers_input.extra_headers: + fetch_params['extra_headers'] = self._headers_input.extra_headers + + events: Events = self._events_manager.get_events(**fetch_params) + + # Update stream position for next request + if events.next_stream_position is not None: + self._stream_position = str(events.next_stream_position) + else: + self._stream_position = 'now' + + # Yield Event objects if any + if events.entries: + for event in events.entries: + event_id = event.event_id + if event_id not in self._dedupHash: + self._dedupHash[event_id] = True + if self._stopped or self._stop_event.is_set(): + return + yield event + + if len(self._dedupHash) > self._deduplication_size: + self._dedupHash.clear() + event_ids = list(events.entries.map(lambda e: e.event_id)) + for event_id in event_ids: + self._dedupHash[event_id] = True + + except Exception as error: + if not self._stopped and not self._stop_event.is_set(): + raise error diff --git a/box_sdk_gen/managers/events.py b/box_sdk_gen/managers/events.py index 16460b95c..1ccd76a85 100644 --- a/box_sdk_gen/managers/events.py +++ b/box_sdk_gen/managers/events.py @@ -2,12 +2,12 @@ from typing import Optional +from typing import List + from typing import Dict from box_sdk_gen.serialization.json import deserialize -from typing import List - from box_sdk_gen.internal.utils import to_string from box_sdk_gen.networking.fetch_options import ResponseFormat @@ -40,6 +40,8 @@ from box_sdk_gen.serialization.json import sd_to_json +from box_sdk_gen.box.event_stream import EventStream + class GetEventsStreamType(str, Enum): ALL = 'all' @@ -181,6 +183,225 @@ class GetEventsEventType(str, Enum): WATERMARK_LABEL_DELETE = 'WATERMARK_LABEL_DELETE' +class GetEventStreamQueryParamsStreamTypeField(str, Enum): + ALL = 'all' + CHANGES = 'changes' + SYNC = 'sync' + ADMIN_LOGS = 'admin_logs' + ADMIN_LOGS_STREAMING = 'admin_logs_streaming' + + +class GetEventStreamQueryParamsEventTypeField(str, Enum): + ACCESS_GRANTED = 'ACCESS_GRANTED' + ACCESS_REVOKED = 'ACCESS_REVOKED' + ADD_DEVICE_ASSOCIATION = 'ADD_DEVICE_ASSOCIATION' + ADD_LOGIN_ACTIVITY_DEVICE = 'ADD_LOGIN_ACTIVITY_DEVICE' + ADMIN_LOGIN = 'ADMIN_LOGIN' + APPLICATION_CREATED = 'APPLICATION_CREATED' + APPLICATION_PUBLIC_KEY_ADDED = 'APPLICATION_PUBLIC_KEY_ADDED' + APPLICATION_PUBLIC_KEY_DELETED = 'APPLICATION_PUBLIC_KEY_DELETED' + CHANGE_ADMIN_ROLE = 'CHANGE_ADMIN_ROLE' + CHANGE_FOLDER_PERMISSION = 'CHANGE_FOLDER_PERMISSION' + COLLABORATION_ACCEPT = 'COLLABORATION_ACCEPT' + COLLABORATION_EXPIRATION = 'COLLABORATION_EXPIRATION' + COLLABORATION_INVITE = 'COLLABORATION_INVITE' + COLLABORATION_REMOVE = 'COLLABORATION_REMOVE' + COLLABORATION_ROLE_CHANGE = 'COLLABORATION_ROLE_CHANGE' + COMMENT_CREATE = 'COMMENT_CREATE' + COMMENT_DELETE = 'COMMENT_DELETE' + CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY = ( + 'CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY' + ) + CONTENT_WORKFLOW_AUTOMATION_ADD = 'CONTENT_WORKFLOW_AUTOMATION_ADD' + CONTENT_WORKFLOW_AUTOMATION_DELETE = 'CONTENT_WORKFLOW_AUTOMATION_DELETE' + CONTENT_WORKFLOW_POLICY_ADD = 'CONTENT_WORKFLOW_POLICY_ADD' + CONTENT_WORKFLOW_SHARING_POLICY_VIOLATION = ( + 'CONTENT_WORKFLOW_SHARING_POLICY_VIOLATION' + ) + CONTENT_WORKFLOW_UPLOAD_POLICY_VIOLATION = ( + 'CONTENT_WORKFLOW_UPLOAD_POLICY_VIOLATION' + ) + COPY = 'COPY' + DATA_RETENTION_CREATE_RETENTION = 'DATA_RETENTION_CREATE_RETENTION' + DATA_RETENTION_REMOVE_RETENTION = 'DATA_RETENTION_REMOVE_RETENTION' + DELETE = 'DELETE' + DELETE_USER = 'DELETE_USER' + DEVICE_TRUST_CHECK_FAILED = 'DEVICE_TRUST_CHECK_FAILED' + DOWNLOAD = 'DOWNLOAD' + EDIT = 'EDIT' + EDIT_USER = 'EDIT_USER' + EMAIL_ALIAS_CONFIRM = 'EMAIL_ALIAS_CONFIRM' + EMAIL_ALIAS_REMOVE = 'EMAIL_ALIAS_REMOVE' + ENTERPRISE_APP_AUTHORIZATION_UPDATE = 'ENTERPRISE_APP_AUTHORIZATION_UPDATE' + EXTERNAL_COLLAB_SECURITY_SETTINGS = 'EXTERNAL_COLLAB_SECURITY_SETTINGS' + FAILED_LOGIN = 'FAILED_LOGIN' + FILE_MARKED_MALICIOUS = 'FILE_MARKED_MALICIOUS' + FILE_WATERMARKED_DOWNLOAD = 'FILE_WATERMARKED_DOWNLOAD' + GROUP_ADD_ITEM = 'GROUP_ADD_ITEM' + GROUP_ADD_USER = 'GROUP_ADD_USER' + GROUP_CREATION = 'GROUP_CREATION' + GROUP_DELETION = 'GROUP_DELETION' + 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' + ITEM_SYNC = 'ITEM_SYNC' + ITEM_UNSYNC = 'ITEM_UNSYNC' + LEGAL_HOLD_ASSIGNMENT_CREATE = 'LEGAL_HOLD_ASSIGNMENT_CREATE' + LEGAL_HOLD_ASSIGNMENT_DELETE = 'LEGAL_HOLD_ASSIGNMENT_DELETE' + LEGAL_HOLD_POLICY_CREATE = 'LEGAL_HOLD_POLICY_CREATE' + LEGAL_HOLD_POLICY_DELETE = 'LEGAL_HOLD_POLICY_DELETE' + LEGAL_HOLD_POLICY_UPDATE = 'LEGAL_HOLD_POLICY_UPDATE' + LOCK = 'LOCK' + LOGIN = 'LOGIN' + METADATA_INSTANCE_CREATE = 'METADATA_INSTANCE_CREATE' + METADATA_INSTANCE_DELETE = 'METADATA_INSTANCE_DELETE' + METADATA_INSTANCE_UPDATE = 'METADATA_INSTANCE_UPDATE' + METADATA_TEMPLATE_CREATE = 'METADATA_TEMPLATE_CREATE' + METADATA_TEMPLATE_DELETE = 'METADATA_TEMPLATE_DELETE' + METADATA_TEMPLATE_UPDATE = 'METADATA_TEMPLATE_UPDATE' + MOVE = 'MOVE' + NEW_USER = 'NEW_USER' + OAUTH2_ACCESS_TOKEN_REVOKE = 'OAUTH2_ACCESS_TOKEN_REVOKE' + PREVIEW = 'PREVIEW' + REMOVE_DEVICE_ASSOCIATION = 'REMOVE_DEVICE_ASSOCIATION' + REMOVE_LOGIN_ACTIVITY_DEVICE = 'REMOVE_LOGIN_ACTIVITY_DEVICE' + 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' + SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION = ( + 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION' + ) + SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED = 'SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED' + SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED_MISSING_JUSTIFICATION = ( + 'SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED_MISSING_JUSTIFICATION' + ) + SHIELD_JUSTIFICATION_APPROVAL = 'SHIELD_JUSTIFICATION_APPROVAL' + SHIELD_SHARED_LINK_ACCESS_BLOCKED = 'SHIELD_SHARED_LINK_ACCESS_BLOCKED' + SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE = ( + 'SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE' + ) + SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_UPDATE = ( + 'SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_UPDATE' + ) + SIGN_DOCUMENT_ASSIGNED = 'SIGN_DOCUMENT_ASSIGNED' + SIGN_DOCUMENT_CANCELLED = 'SIGN_DOCUMENT_CANCELLED' + SIGN_DOCUMENT_COMPLETED = 'SIGN_DOCUMENT_COMPLETED' + SIGN_DOCUMENT_CONVERTED = 'SIGN_DOCUMENT_CONVERTED' + SIGN_DOCUMENT_CREATED = 'SIGN_DOCUMENT_CREATED' + SIGN_DOCUMENT_DECLINED = 'SIGN_DOCUMENT_DECLINED' + SIGN_DOCUMENT_EXPIRED = 'SIGN_DOCUMENT_EXPIRED' + SIGN_DOCUMENT_SIGNED = 'SIGN_DOCUMENT_SIGNED' + SIGN_DOCUMENT_VIEWED_BY_SIGNED = 'SIGN_DOCUMENT_VIEWED_BY_SIGNED' + SIGNER_DOWNLOADED = 'SIGNER_DOWNLOADED' + SIGNER_FORWARDED = 'SIGNER_FORWARDED' + STORAGE_EXPIRATION = 'STORAGE_EXPIRATION' + TASK_ASSIGNMENT_CREATE = 'TASK_ASSIGNMENT_CREATE' + TASK_ASSIGNMENT_DELETE = 'TASK_ASSIGNMENT_DELETE' + TASK_ASSIGNMENT_UPDATE = 'TASK_ASSIGNMENT_UPDATE' + TASK_CREATE = 'TASK_CREATE' + TASK_UPDATE = 'TASK_UPDATE' + TERMS_OF_SERVICE_ACCEPT = 'TERMS_OF_SERVICE_ACCEPT' + TERMS_OF_SERVICE_REJECT = 'TERMS_OF_SERVICE_REJECT' + UNDELETE = 'UNDELETE' + UNLOCK = 'UNLOCK' + UNSHARE = 'UNSHARE' + UPDATE_COLLABORATION_EXPIRATION = 'UPDATE_COLLABORATION_EXPIRATION' + UPDATE_SHARE_EXPIRATION = 'UPDATE_SHARE_EXPIRATION' + UPLOAD = 'UPLOAD' + USER_AUTHENTICATE_OAUTH2_ACCESS_TOKEN_CREATE = ( + 'USER_AUTHENTICATE_OAUTH2_ACCESS_TOKEN_CREATE' + ) + WATERMARK_LABEL_CREATE = 'WATERMARK_LABEL_CREATE' + WATERMARK_LABEL_DELETE = 'WATERMARK_LABEL_DELETE' + + +class GetEventStreamQueryParams: + def __init__( + self, + *, + stream_type: Optional[GetEventStreamQueryParamsStreamTypeField] = None, + stream_position: Optional[str] = None, + limit: Optional[int] = None, + event_type: Optional[List[GetEventStreamQueryParamsEventTypeField]] = None, + created_after: Optional[DateTime] = None, + created_before: Optional[DateTime] = None + ): + """ + :param stream_type: Defines the type of events that are returned + + * `all` returns everything for a user and is the default + * `changes` returns events that may cause file tree changes + such as file updates or collaborations. + * `sync` is similar to `changes` but only applies to synced folders + * `admin_logs` returns all events for an entire enterprise and + requires the user making the API call to have admin permissions. This + stream type is for programmatically pulling from a 1 year history of + events across all users within the enterprise and within a + `created_after` and `created_before` time frame. The complete history + of events will be returned in chronological order based on the event + time, but latency will be much higher than `admin_logs_streaming`. + * `admin_logs_streaming` returns all events for an entire enterprise and + requires the user making the API call to have admin permissions. This + stream type is for polling for recent events across all users within + the enterprise. Latency will be much lower than `admin_logs`, but + events will not be returned in chronological order and may + contain duplicates., defaults to None + :type stream_type: Optional[GetEventStreamQueryParamsStreamTypeField], optional + :param stream_position: The location in the event stream to start receiving events from. + + * `now` will return an empty list events and + the latest stream position for initialization. + * `0` or `null` will return all events., defaults to None + :type stream_position: Optional[str], optional + :param limit: Limits the number of events returned. + + Note: Sometimes, the events less than the limit requested can be returned + even when there may be more events remaining. This is primarily done in + the case where a number of events have already been retrieved and these + retrieved events are returned rather than delaying for an unknown amount + of time to see if there are any more results., defaults to None + :type limit: Optional[int], optional + :param event_type: A comma-separated list of events to filter by. This can only be used when + requesting the events with a `stream_type` of `admin_logs` or + `adming_logs_streaming`. For any other `stream_type` this value will be + ignored., defaults to None + :type event_type: Optional[List[GetEventStreamQueryParamsEventTypeField]], optional + :param created_after: The lower bound date and time to return events for. This can only be used + when requesting the events with a `stream_type` of `admin_logs`. For any + other `stream_type` this value will be ignored., defaults to None + :type created_after: Optional[DateTime], optional + :param created_before: The upper bound date and time to return events for. This can only be used + when requesting the events with a `stream_type` of `admin_logs`. For any + other `stream_type` this value will be ignored., defaults to None + :type created_before: Optional[DateTime], optional + """ + self.stream_type = stream_type + self.stream_position = stream_position + self.limit = limit + self.event_type = event_type + self.created_after = created_after + self.created_before = created_before + + +class GetEventStreamHeaders: + def __init__(self, *, extra_headers: Optional[Dict[str, Optional[str]]] = None): + """ + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + self.extra_headers = extra_headers + + class EventsManager: def __init__( self, @@ -397,3 +618,24 @@ def get_events( ) ) return deserialize(response.data, Events) + + def get_event_stream( + self, + *, + query_params: GetEventStreamQueryParams = None, + headers: GetEventStreamHeaders = None + ) -> EventStream: + """ + Get an event stream for the Box API + :param query_params: Query parameters of getEvents method, defaults to None + :type query_params: GetEventStreamQueryParams, optional + :param headers: Headers of getEvents method, defaults to None + :type headers: GetEventStreamHeaders, optional + """ + if query_params is None: + query_params = GetEventStreamQueryParams() + if headers is None: + headers = GetEventStreamHeaders() + return EventStream( + events_manager=self, query_params=query_params, headers_input=headers + ) diff --git a/docs/event-stream.md b/docs/event-stream.md new file mode 100644 index 000000000..fede611fb --- /dev/null +++ b/docs/event-stream.md @@ -0,0 +1,26 @@ +# Event Stream + +The Event Stream class utilizes long-polling to receive real-time events from Box. The SDK provides an easy way to set up and manage the event stream which returns an iterable object and yields events as they are received. + + + + +- [Event Stream](#event-stream) + - [Listening to the Event Stream](#listening-to-the-event-stream) + - [Deduplication](#deduplication) + + + +## Listening to the Event Stream + +When the `EventStream` is started, it will begin long-polling asynchronously. Events received from the API are then yielded to the caller. + +```python +event_stream = client.events.get_event_stream() +for event in event_stream: + print("Received event:", event) +``` + +## Deduplication + +The `EventStream` class automatically deduplicates events based on their `event_id`. This means that if the same event is received multiple times, it will only be emitted once to the listeners. diff --git a/docs/events.md b/docs/events.md index 4dcf3c644..7e4e45cfd 100644 --- a/docs/events.md +++ b/docs/events.md @@ -2,6 +2,7 @@ - [Get events long poll endpoint](#get-events-long-poll-endpoint) - [List user and enterprise events](#list-user-and-enterprise-events) +- [Get event stream](#get-event-stream) ## Get events long poll endpoint @@ -113,3 +114,24 @@ including a list of event objects. The response includes a `chunk_size` parameter indicating how many events were returned in this chunk, as well as the next `stream_position` that can be queried. + +## Get event stream + +Get an event stream for the Box API + +This operation is performed by calling function `get_event_stream`. + +```python +client.events.get_event_stream() +``` + +### Arguments + +- query_params `GetEventStreamQueryParams` + - Query parameters of getEvents method +- headers `GetEventStreamHeaders` + - Headers of getEvents method + +### Returns + +This function returns a value of type `EventStream`. diff --git a/test/events.py b/test/events.py index 2db83dc41..37d525d4d 100644 --- a/test/events.py +++ b/test/events.py @@ -16,6 +16,8 @@ from box_sdk_gen.internal.utils import DateTime +from box_sdk_gen.box.event_stream import EventStream + from test.commons import get_default_client from box_sdk_gen.schemas.event_source import EventSource @@ -103,3 +105,8 @@ def testGetEventsWithDateFilters(): created_before=created_before_date, ) assert len(servers.entries) == 1 + + +def testGetEventStream(): + event_stream: EventStream = client.events.get_event_stream() + assert not event_stream == None From cf2b1d5b12be0ff2453867b7d3502437283bf695 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 14 Aug 2025 15:21:26 +0200 Subject: [PATCH 007/139] feat!: Change names of unions (box/box-codegen#789) (#939) --- .codegen.json | 2 +- box_sdk_gen/managers/ai.py | 41 +++++++------- box_sdk_gen/managers/file_versions.py | 2 +- box_sdk_gen/managers/files.py | 2 +- box_sdk_gen/managers/search.py | 24 ++++----- box_sdk_gen/managers/skills.py | 54 +++++-------------- box_sdk_gen/managers/tasks.py | 8 +-- box_sdk_gen/managers/user_collaborations.py | 2 +- box_sdk_gen/schemas/__init__.py | 42 ++++++++++++++- box_sdk_gen/schemas/access_token.py | 6 +-- box_sdk_gen/schemas/ai_agent.py | 13 +++++ box_sdk_gen/schemas/ai_ask.py | 12 ++--- box_sdk_gen/schemas/ai_ask_agent.py | 9 ++++ box_sdk_gen/schemas/ai_extract.py | 10 ++-- box_sdk_gen/schemas/ai_extract_agent.py | 9 ++++ box_sdk_gen/schemas/ai_extract_structured.py | 10 ++-- .../schemas/ai_extract_structured_agent.py | 9 ++++ box_sdk_gen/schemas/ai_text_gen.py | 10 ++-- box_sdk_gen/schemas/ai_text_gen_agent.py | 9 ++++ .../schemas/app_item_associated_item.py | 11 ++++ box_sdk_gen/schemas/app_item_association.py | 10 ++-- box_sdk_gen/schemas/collaboration.py | 16 +++--- .../schemas/collaboration_access_grantee.py | 9 ++++ box_sdk_gen/schemas/collaboration_item.py | 11 ++++ box_sdk_gen/schemas/event.py | 14 ++--- box_sdk_gen/schemas/event_source_resource.py | 17 ++++++ box_sdk_gen/schemas/file_full.py | 6 +-- box_sdk_gen/schemas/integration_mapping.py | 6 +-- .../schemas/integration_mapping_teams.py | 6 +-- box_sdk_gen/schemas/item.py | 11 ++++ box_sdk_gen/schemas/items.py | 8 +-- box_sdk_gen/schemas/items_offset_paginated.py | 8 +-- .../legal_hold_policy_assigned_item.py | 11 ++++ .../schemas/legal_hold_policy_assignment.py | 18 ++++--- box_sdk_gen/schemas/metadata_filter.py | 21 ++------ box_sdk_gen/schemas/metadata_filter_value.py | 17 ++++++ .../schemas/metadata_query_result_item.py | 9 ++++ box_sdk_gen/schemas/metadata_query_results.py | 8 +-- box_sdk_gen/schemas/metadata_template.py | 2 +- box_sdk_gen/schemas/recent_item.py | 6 +-- box_sdk_gen/schemas/recent_item_resource.py | 11 ++++ box_sdk_gen/schemas/resource.py | 9 ++++ ...e_or_folder_scope.py => resource_scope.py} | 14 ++--- box_sdk_gen/schemas/search_result_item.py | 11 ++++ .../schemas/search_result_with_shared_link.py | 8 +-- .../search_result_with_shared_link_item.py | 11 ++++ box_sdk_gen/schemas/search_results.py | 8 +-- .../schemas/search_results_response.py | 11 ++++ box_sdk_gen/schemas/skill_card.py | 15 ++++++ box_sdk_gen/schemas/skill_cards_metadata.py | 17 ++---- box_sdk_gen/schemas/task.py | 4 +- .../v2025_r0/hub_item_operation_v2025_r0.py | 12 ++--- docs/ai.md | 10 ++-- docs/file_versions.md | 2 +- docs/files.md | 2 +- docs/search.md | 6 +-- docs/skills.md | 2 +- docs/tasks.md | 4 +- docs/user_collaborations.md | 2 +- test/ai.py | 28 +++------- test/search.py | 46 +++++++--------- 61 files changed, 457 insertions(+), 275 deletions(-) create mode 100644 box_sdk_gen/schemas/ai_agent.py create mode 100644 box_sdk_gen/schemas/ai_ask_agent.py create mode 100644 box_sdk_gen/schemas/ai_extract_agent.py create mode 100644 box_sdk_gen/schemas/ai_extract_structured_agent.py create mode 100644 box_sdk_gen/schemas/ai_text_gen_agent.py create mode 100644 box_sdk_gen/schemas/app_item_associated_item.py create mode 100644 box_sdk_gen/schemas/collaboration_access_grantee.py create mode 100644 box_sdk_gen/schemas/collaboration_item.py create mode 100644 box_sdk_gen/schemas/event_source_resource.py create mode 100644 box_sdk_gen/schemas/item.py create mode 100644 box_sdk_gen/schemas/legal_hold_policy_assigned_item.py create mode 100644 box_sdk_gen/schemas/metadata_filter_value.py create mode 100644 box_sdk_gen/schemas/metadata_query_result_item.py create mode 100644 box_sdk_gen/schemas/recent_item_resource.py create mode 100644 box_sdk_gen/schemas/resource.py rename box_sdk_gen/schemas/{file_or_folder_scope.py => resource_scope.py} (77%) create mode 100644 box_sdk_gen/schemas/search_result_item.py create mode 100644 box_sdk_gen/schemas/search_result_with_shared_link_item.py create mode 100644 box_sdk_gen/schemas/search_results_response.py create mode 100644 box_sdk_gen/schemas/skill_card.py diff --git a/.codegen.json b/.codegen.json index 6a7ff390b..778c6acc3 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "1c54d3c", "specHash": "24fbeb9", "version": "0.1.0" } +{ "engineHash": "ef443c1", "specHash": "99e14a6", "version": "0.1.0" } diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index 4ab53e5b9..9e09529a1 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -8,8 +8,6 @@ from typing import Dict -from typing import Union - from box_sdk_gen.serialization.json import serialize from box_sdk_gen.internal.utils import to_string @@ -22,10 +20,26 @@ from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference +from box_sdk_gen.schemas.ai_agent_ask import AiAgentAsk + +from box_sdk_gen.schemas.ai_ask_agent import AiAskAgent + from box_sdk_gen.networking.fetch_options import ResponseFormat +from box_sdk_gen.schemas.ai_agent_text_gen import AiAgentTextGen + +from box_sdk_gen.schemas.ai_text_gen_agent import AiTextGenAgent + +from box_sdk_gen.schemas.ai_agent_extract import AiAgentExtract + +from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured + from box_sdk_gen.schemas.ai_item_base import AiItemBase +from box_sdk_gen.schemas.ai_extract_agent import AiExtractAgent + +from box_sdk_gen.schemas.ai_extract_structured_agent import AiExtractStructuredAgent + from box_sdk_gen.schemas.ai_response_full import AiResponseFull from box_sdk_gen.schemas.client_error import ClientError @@ -36,13 +50,7 @@ from box_sdk_gen.schemas.ai_text_gen import AiTextGen -from box_sdk_gen.schemas.ai_agent_ask import AiAgentAsk - -from box_sdk_gen.schemas.ai_agent_text_gen import AiAgentTextGen - -from box_sdk_gen.schemas.ai_agent_extract import AiAgentExtract - -from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured +from box_sdk_gen.schemas.ai_agent import AiAgent from box_sdk_gen.schemas.ai_extract import AiExtract @@ -222,7 +230,7 @@ def create_ai_ask( *, dialogue_history: Optional[List[AiDialogueHistory]] = None, include_citations: Optional[bool] = None, - ai_agent: Optional[Union[AiAgentAsk, AiAgentReference]] = None, + ai_agent: Optional[AiAskAgent] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> Optional[AiResponseFull]: """ @@ -280,7 +288,7 @@ def create_ai_text_gen( items: List[CreateAiTextGenItems], *, dialogue_history: Optional[List[AiDialogueHistory]] = None, - ai_agent: Optional[Union[AiAgentReference, AiAgentTextGen]] = None, + ai_agent: Optional[AiTextGenAgent] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> AiResponse: """ @@ -330,7 +338,7 @@ def get_ai_agent_default_config( language: Optional[str] = None, model: Optional[str] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None - ) -> Union[AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured]: + ) -> AiAgent: """ Get the AI agent default config. :param mode: The mode to filter the agent config to return. @@ -366,17 +374,14 @@ def get_ai_agent_default_config( network_session=self.network_session, ) ) - return deserialize( - response.data, - Union[AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured], - ) + return deserialize(response.data, AiAgent) def create_ai_extract( self, prompt: str, items: List[AiItemBase], *, - ai_agent: Optional[Union[AiAgentReference, AiAgentExtract]] = None, + ai_agent: Optional[AiExtractAgent] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> AiResponse: """ @@ -420,7 +425,7 @@ def create_ai_extract_structured( *, metadata_template: Optional[CreateAiExtractStructuredMetadataTemplate] = None, fields: Optional[List[CreateAiExtractStructuredFields]] = None, - ai_agent: Optional[Union[AiAgentReference, AiAgentExtractStructured]] = None, + ai_agent: Optional[AiExtractStructuredAgent] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> AiExtractStructuredResponse: """ diff --git a/box_sdk_gen/managers/file_versions.py b/box_sdk_gen/managers/file_versions.py index 5f79b0122..238a25ae4 100644 --- a/box_sdk_gen/managers/file_versions.py +++ b/box_sdk_gen/managers/file_versions.py @@ -349,7 +349,7 @@ def promote_file_version( top of the versions history. The file will have the exact same contents - as the older version, with the the same hash digest, `etag`, and + as the older version, with the same hash digest, `etag`, and name as the original. diff --git a/box_sdk_gen/managers/files.py b/box_sdk_gen/managers/files.py index 6b975d358..b153e2a61 100644 --- a/box_sdk_gen/managers/files.py +++ b/box_sdk_gen/managers/files.py @@ -485,7 +485,7 @@ def delete_file_by_id( the trash. - The the enterprise settings determine whether the item will + The enterprise settings determine whether the item will be permanently deleted from Box or moved to the trash. diff --git a/box_sdk_gen/managers/search.py b/box_sdk_gen/managers/search.py index 1d6ec33ed..f5adde965 100644 --- a/box_sdk_gen/managers/search.py +++ b/box_sdk_gen/managers/search.py @@ -14,21 +14,21 @@ from box_sdk_gen.internal.utils import to_string -from typing import Union - from box_sdk_gen.networking.fetch_options import ResponseFormat +from box_sdk_gen.schemas.search_results import SearchResults + +from box_sdk_gen.schemas.search_results_with_shared_links import ( + SearchResultsWithSharedLinks, +) + from box_sdk_gen.schemas.metadata_query_results import MetadataQueryResults from box_sdk_gen.schemas.client_error import ClientError from box_sdk_gen.schemas.metadata_query import MetadataQuery -from box_sdk_gen.schemas.search_results import SearchResults - -from box_sdk_gen.schemas.search_results_with_shared_links import ( - SearchResultsWithSharedLinks, -) +from box_sdk_gen.schemas.search_results_response import SearchResultsResponse from box_sdk_gen.schemas.metadata_filter import MetadataFilter @@ -269,7 +269,7 @@ def search_for_content( deleted_user_ids: Optional[List[str]] = None, deleted_at_range: Optional[List[str]] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None - ) -> Union[SearchResults, SearchResultsWithSharedLinks]: + ) -> SearchResultsResponse: """ Searches for files, folders, web links, and shared files across the @@ -332,7 +332,7 @@ def search_for_content( Date ranges are defined as comma separated RFC3339 timestamps. - If the the start date is omitted (`,2014-05-17T13:35:01-07:00`) + If the start date is omitted (`,2014-05-17T13:35:01-07:00`) anything created before the end date will be returned. If the end date is omitted (`2014-05-15T13:35:01-07:00,`) the @@ -500,7 +500,7 @@ def search_for_content( Date ranges are defined as comma separated RFC3339 timestamps. - If the the start date is omitted (`2014-05-17T13:35:01-07:00`), + If the start date is omitted (`2014-05-17T13:35:01-07:00`), anything deleted before the end date will be returned. If the end date is omitted (`2014-05-15T13:35:01-07:00`), @@ -555,6 +555,4 @@ def search_for_content( network_session=self.network_session, ) ) - return deserialize( - response.data, Union[SearchResults, SearchResultsWithSharedLinks] - ) + return deserialize(response.data, SearchResultsResponse) diff --git a/box_sdk_gen/managers/skills.py b/box_sdk_gen/managers/skills.py index 26ae00b73..697d72656 100644 --- a/box_sdk_gen/managers/skills.py +++ b/box_sdk_gen/managers/skills.py @@ -2,8 +2,6 @@ from typing import Optional -from typing import Union - from box_sdk_gen.internal.base_object import BaseObject from typing import List @@ -16,12 +14,6 @@ from box_sdk_gen.serialization.json import serialize -from box_sdk_gen.networking.fetch_options import ResponseFormat - -from box_sdk_gen.schemas.skill_cards_metadata import SkillCardsMetadata - -from box_sdk_gen.schemas.client_error import ClientError - from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCard from box_sdk_gen.schemas.timeline_skill_card import TimelineSkillCard @@ -30,6 +22,14 @@ from box_sdk_gen.schemas.status_skill_card import StatusSkillCard +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.skill_cards_metadata import SkillCardsMetadata + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.skill_card import SkillCard + from box_sdk_gen.box.errors import BoxSDKError from box_sdk_gen.networking.auth import Authentication @@ -61,14 +61,7 @@ def __init__( *, op: Optional[UpdateBoxSkillCardsOnFileRequestBodyOpField] = None, path: Optional[str] = None, - value: Optional[ - Union[ - KeywordSkillCard, - TimelineSkillCard, - TranscriptSkillCard, - StatusSkillCard, - ] - ] = None, + value: Optional[SkillCard] = None, **kwargs ): """ @@ -94,24 +87,10 @@ class UpdateAllSkillCardsOnFileStatus(str, Enum): class UpdateAllSkillCardsOnFileMetadata(BaseObject): - def __init__( - self, - *, - cards: Optional[ - List[ - Union[ - KeywordSkillCard, - TimelineSkillCard, - TranscriptSkillCard, - StatusSkillCard, - ] - ] - ] = None, - **kwargs - ): + def __init__(self, *, cards: Optional[List[SkillCard]] = None, **kwargs): """ :param cards: A list of Box Skill cards to apply to this file., defaults to None - :type cards: Optional[List[Union[KeywordSkillCard, TimelineSkillCard, TranscriptSkillCard, StatusSkillCard]]], optional + :type cards: Optional[List[SkillCard]], optional """ super().__init__(**kwargs) self.cards = cards @@ -236,14 +215,7 @@ def get_box_skill_cards_on_file( def create_box_skill_cards_on_file( self, file_id: str, - cards: List[ - Union[ - KeywordSkillCard, - TimelineSkillCard, - TranscriptSkillCard, - StatusSkillCard, - ] - ], + cards: List[SkillCard], *, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> SkillCardsMetadata: @@ -259,7 +231,7 @@ def create_box_skill_cards_on_file( Example: "12345" :type file_id: str :param cards: A list of Box Skill cards to apply to this file. - :type cards: List[Union[KeywordSkillCard, TimelineSkillCard, TranscriptSkillCard, StatusSkillCard]] + :type cards: List[SkillCard] :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ diff --git a/box_sdk_gen/managers/tasks.py b/box_sdk_gen/managers/tasks.py index 47becd4f6..d6b13c19c 100644 --- a/box_sdk_gen/managers/tasks.py +++ b/box_sdk_gen/managers/tasks.py @@ -174,9 +174,9 @@ def create_task( is considered completed. * `all_assignees` (default) requires all assignees to review or - approve the the task in order for it to be considered completed. + approve the task in order for it to be considered completed. * `any_assignee` accepts any one assignee to review or - approve the the task in order for it to be considered completed., defaults to None + approve the task in order for it to be considered completed., defaults to None :type completion_rule: Optional[CreateTaskCompletionRule], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional @@ -269,9 +269,9 @@ def update_task_by_id( is considered completed. * `all_assignees` (default) requires all assignees to review or - approve the the task in order for it to be considered completed. + approve the task in order for it to be considered completed. * `any_assignee` accepts any one assignee to review or - approve the the task in order for it to be considered completed., defaults to None + approve the task in order for it to be considered completed., defaults to None :type completion_rule: Optional[UpdateTaskByIdCompletionRule], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional diff --git a/box_sdk_gen/managers/user_collaborations.py b/box_sdk_gen/managers/user_collaborations.py index 2357844ca..10641f062 100644 --- a/box_sdk_gen/managers/user_collaborations.py +++ b/box_sdk_gen/managers/user_collaborations.py @@ -369,7 +369,7 @@ def create_collaboration( :param is_access_only: If set to `true`, collaborators have access to shared items, but such items won't be visible in the All Files list. Additionally, collaborators won't - see the the path to the root folder for the + see the path to the root folder for the shared item., defaults to None :type is_access_only: Optional[bool], optional :param can_view_path: Determines if the invited users can see the entire parent path to diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index e8f4333f6..20fab2585 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -52,6 +52,8 @@ from box_sdk_gen.schemas.ai_agent_text_gen import * +from box_sdk_gen.schemas.ai_text_gen_agent import * + from box_sdk_gen.schemas.ai_text_gen import * from box_sdk_gen.schemas.ai_agent_basic_text_tool import * @@ -76,16 +78,24 @@ from box_sdk_gen.schemas.ai_agent_extract_structured import * +from box_sdk_gen.schemas.ai_extract_structured_agent import * + from box_sdk_gen.schemas.ai_extract_structured import * from box_sdk_gen.schemas.ai_agent_extract import * +from box_sdk_gen.schemas.ai_extract_agent import * + from box_sdk_gen.schemas.ai_extract import * from box_sdk_gen.schemas.ai_agent_ask import * +from box_sdk_gen.schemas.ai_ask_agent import * + from box_sdk_gen.schemas.ai_ask import * +from box_sdk_gen.schemas.ai_agent import * + from box_sdk_gen.schemas.app_item import * from box_sdk_gen.schemas.classification import * @@ -138,7 +148,9 @@ from box_sdk_gen.schemas.folder_mini import * -from box_sdk_gen.schemas.file_or_folder_scope import * +from box_sdk_gen.schemas.resource import * + +from box_sdk_gen.schemas.resource_scope import * from box_sdk_gen.schemas.access_token import * @@ -200,6 +212,8 @@ from box_sdk_gen.schemas.metadata_field_filter_float_range import * +from box_sdk_gen.schemas.metadata_filter_value import * + from box_sdk_gen.schemas.metadata_filter import * from box_sdk_gen.schemas.metadata_instance_value import * @@ -298,6 +312,8 @@ from box_sdk_gen.schemas.transcript_skill_card import * +from box_sdk_gen.schemas.skill_card import * + from box_sdk_gen.schemas.skill_cards_metadata import * from box_sdk_gen.schemas.upload_part_mini import * @@ -404,6 +420,8 @@ from box_sdk_gen.schemas.user_collaborations import * +from box_sdk_gen.schemas.collaboration_access_grantee import * + from box_sdk_gen.schemas.shield_information_barrier_segment_restriction import * from box_sdk_gen.schemas.shield_information_barrier_segment_restrictions import * @@ -450,12 +468,16 @@ from box_sdk_gen.schemas.web_link import * +from box_sdk_gen.schemas.item import * + from box_sdk_gen.schemas.items_offset_paginated import * from box_sdk_gen.schemas.items import * from box_sdk_gen.schemas.folder import * +from box_sdk_gen.schemas.legal_hold_policy_assigned_item import * + from box_sdk_gen.schemas.legal_hold_policy_assignment import * from box_sdk_gen.schemas.legal_hold_policy_assignments import * @@ -466,28 +488,44 @@ from box_sdk_gen.schemas.folder_full import * -from box_sdk_gen.schemas.search_results import * +from box_sdk_gen.schemas.search_result_with_shared_link_item import * from box_sdk_gen.schemas.search_result_with_shared_link import * from box_sdk_gen.schemas.search_results_with_shared_links import * +from box_sdk_gen.schemas.search_result_item import * + +from box_sdk_gen.schemas.search_results import * + +from box_sdk_gen.schemas.search_results_response import * + +from box_sdk_gen.schemas.recent_item_resource import * + from box_sdk_gen.schemas.recent_item import * from box_sdk_gen.schemas.recent_items import * +from box_sdk_gen.schemas.metadata_query_result_item import * + from box_sdk_gen.schemas.metadata_query_results import * +from box_sdk_gen.schemas.event_source_resource import * + from box_sdk_gen.schemas.event import * from box_sdk_gen.schemas.events import * +from box_sdk_gen.schemas.collaboration_item import * + from box_sdk_gen.schemas.collaboration import * from box_sdk_gen.schemas.collaborations_offset_paginated import * from box_sdk_gen.schemas.collaborations import * +from box_sdk_gen.schemas.app_item_associated_item import * + from box_sdk_gen.schemas.app_item_association import * from box_sdk_gen.schemas.app_item_associations import * diff --git a/box_sdk_gen/schemas/access_token.py b/box_sdk_gen/schemas/access_token.py index 2b4082048..8ef1567b9 100644 --- a/box_sdk_gen/schemas/access_token.py +++ b/box_sdk_gen/schemas/access_token.py @@ -6,7 +6,7 @@ from box_sdk_gen.internal.base_object import BaseObject -from box_sdk_gen.schemas.file_or_folder_scope import FileOrFolderScope +from box_sdk_gen.schemas.resource_scope import ResourceScope from box_sdk_gen.box.errors import BoxSDKError @@ -28,7 +28,7 @@ def __init__( access_token: Optional[str] = None, expires_in: Optional[int] = None, token_type: Optional[AccessTokenTokenTypeField] = None, - restricted_to: Optional[List[FileOrFolderScope]] = None, + restricted_to: Optional[List[ResourceScope]] = None, refresh_token: Optional[str] = None, issued_token_type: Optional[AccessTokenIssuedTokenTypeField] = None, **kwargs @@ -43,7 +43,7 @@ def __init__( :param restricted_to: The permissions that this access token permits, providing a list of resources (files, folders, etc) and the scopes permitted for each of those resources., defaults to None - :type restricted_to: Optional[List[FileOrFolderScope]], optional + :type restricted_to: Optional[List[ResourceScope]], optional :param refresh_token: The refresh token for this access token, which can be used to request a new access token when the current one expires., defaults to None :type refresh_token: Optional[str], optional diff --git a/box_sdk_gen/schemas/ai_agent.py b/box_sdk_gen/schemas/ai_agent.py new file mode 100644 index 000000000..b0248a7b4 --- /dev/null +++ b/box_sdk_gen/schemas/ai_agent.py @@ -0,0 +1,13 @@ +from typing import Union + +from box_sdk_gen.schemas.ai_agent_ask import AiAgentAsk + +from box_sdk_gen.schemas.ai_agent_text_gen import AiAgentTextGen + +from box_sdk_gen.schemas.ai_agent_extract import AiAgentExtract + +from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured + +from box_sdk_gen.box.errors import BoxSDKError + +AiAgent = Union[AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured] diff --git a/box_sdk_gen/schemas/ai_ask.py b/box_sdk_gen/schemas/ai_ask.py index 398b58d6a..d4a347357 100644 --- a/box_sdk_gen/schemas/ai_ask.py +++ b/box_sdk_gen/schemas/ai_ask.py @@ -4,17 +4,17 @@ from typing import Optional -from typing import Union - from box_sdk_gen.internal.base_object import BaseObject +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +from box_sdk_gen.schemas.ai_agent_ask import AiAgentAsk + from box_sdk_gen.schemas.ai_item_ask import AiItemAsk from box_sdk_gen.schemas.ai_dialogue_history import AiDialogueHistory -from box_sdk_gen.schemas.ai_agent_ask import AiAgentAsk - -from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference +from box_sdk_gen.schemas.ai_ask_agent import AiAskAgent from box_sdk_gen.box.errors import BoxSDKError @@ -33,7 +33,7 @@ def __init__( *, dialogue_history: Optional[List[AiDialogueHistory]] = None, include_citations: Optional[bool] = None, - ai_agent: Optional[Union[AiAgentAsk, AiAgentReference]] = None, + ai_agent: Optional[AiAskAgent] = None, **kwargs ): """ diff --git a/box_sdk_gen/schemas/ai_ask_agent.py b/box_sdk_gen/schemas/ai_ask_agent.py new file mode 100644 index 000000000..a7d4b049f --- /dev/null +++ b/box_sdk_gen/schemas/ai_ask_agent.py @@ -0,0 +1,9 @@ +from typing import Union + +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +from box_sdk_gen.schemas.ai_agent_ask import AiAgentAsk + +from box_sdk_gen.box.errors import BoxSDKError + +AiAskAgent = Union[AiAgentReference, AiAgentAsk] diff --git a/box_sdk_gen/schemas/ai_extract.py b/box_sdk_gen/schemas/ai_extract.py index 13096e498..5b0a0d258 100644 --- a/box_sdk_gen/schemas/ai_extract.py +++ b/box_sdk_gen/schemas/ai_extract.py @@ -2,16 +2,16 @@ from typing import Optional -from typing import Union - from box_sdk_gen.internal.base_object import BaseObject -from box_sdk_gen.schemas.ai_item_base import AiItemBase - from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference from box_sdk_gen.schemas.ai_agent_extract import AiAgentExtract +from box_sdk_gen.schemas.ai_item_base import AiItemBase + +from box_sdk_gen.schemas.ai_extract_agent import AiExtractAgent + from box_sdk_gen.box.errors import BoxSDKError @@ -21,7 +21,7 @@ def __init__( prompt: str, items: List[AiItemBase], *, - ai_agent: Optional[Union[AiAgentReference, AiAgentExtract]] = None, + ai_agent: Optional[AiExtractAgent] = None, **kwargs ): """ diff --git a/box_sdk_gen/schemas/ai_extract_agent.py b/box_sdk_gen/schemas/ai_extract_agent.py new file mode 100644 index 000000000..f06508724 --- /dev/null +++ b/box_sdk_gen/schemas/ai_extract_agent.py @@ -0,0 +1,9 @@ +from typing import Union + +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +from box_sdk_gen.schemas.ai_agent_extract import AiAgentExtract + +from box_sdk_gen.box.errors import BoxSDKError + +AiExtractAgent = Union[AiAgentReference, AiAgentExtract] diff --git a/box_sdk_gen/schemas/ai_extract_structured.py b/box_sdk_gen/schemas/ai_extract_structured.py index 46d6e96ac..5a742b529 100644 --- a/box_sdk_gen/schemas/ai_extract_structured.py +++ b/box_sdk_gen/schemas/ai_extract_structured.py @@ -8,14 +8,14 @@ from typing import Dict -from typing import Union - -from box_sdk_gen.schemas.ai_item_base import AiItemBase - from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured +from box_sdk_gen.schemas.ai_item_base import AiItemBase + +from box_sdk_gen.schemas.ai_extract_structured_agent import AiExtractStructuredAgent + from box_sdk_gen.box.errors import BoxSDKError @@ -114,7 +114,7 @@ def __init__( *, metadata_template: Optional[AiExtractStructuredMetadataTemplateField] = None, fields: Optional[List[AiExtractStructuredFieldsField]] = None, - ai_agent: Optional[Union[AiAgentReference, AiAgentExtractStructured]] = None, + ai_agent: Optional[AiExtractStructuredAgent] = None, **kwargs ): """ diff --git a/box_sdk_gen/schemas/ai_extract_structured_agent.py b/box_sdk_gen/schemas/ai_extract_structured_agent.py new file mode 100644 index 000000000..76284f484 --- /dev/null +++ b/box_sdk_gen/schemas/ai_extract_structured_agent.py @@ -0,0 +1,9 @@ +from typing import Union + +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured + +from box_sdk_gen.box.errors import BoxSDKError + +AiExtractStructuredAgent = Union[AiAgentReference, AiAgentExtractStructured] diff --git a/box_sdk_gen/schemas/ai_text_gen.py b/box_sdk_gen/schemas/ai_text_gen.py index 1d62f7dcc..a994b793a 100644 --- a/box_sdk_gen/schemas/ai_text_gen.py +++ b/box_sdk_gen/schemas/ai_text_gen.py @@ -6,14 +6,14 @@ from typing import List -from typing import Union - -from box_sdk_gen.schemas.ai_dialogue_history import AiDialogueHistory - from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference from box_sdk_gen.schemas.ai_agent_text_gen import AiAgentTextGen +from box_sdk_gen.schemas.ai_dialogue_history import AiDialogueHistory + +from box_sdk_gen.schemas.ai_text_gen_agent import AiTextGenAgent + from box_sdk_gen.box.errors import BoxSDKError @@ -53,7 +53,7 @@ def __init__( items: List[AiTextGenItemsField], *, dialogue_history: Optional[List[AiDialogueHistory]] = None, - ai_agent: Optional[Union[AiAgentReference, AiAgentTextGen]] = None, + ai_agent: Optional[AiTextGenAgent] = None, **kwargs ): """ diff --git a/box_sdk_gen/schemas/ai_text_gen_agent.py b/box_sdk_gen/schemas/ai_text_gen_agent.py new file mode 100644 index 000000000..1b4c82dae --- /dev/null +++ b/box_sdk_gen/schemas/ai_text_gen_agent.py @@ -0,0 +1,9 @@ +from typing import Union + +from box_sdk_gen.schemas.ai_agent_reference import AiAgentReference + +from box_sdk_gen.schemas.ai_agent_text_gen import AiAgentTextGen + +from box_sdk_gen.box.errors import BoxSDKError + +AiTextGenAgent = Union[AiAgentReference, AiAgentTextGen] diff --git a/box_sdk_gen/schemas/app_item_associated_item.py b/box_sdk_gen/schemas/app_item_associated_item.py new file mode 100644 index 000000000..2b384c72b --- /dev/null +++ b/box_sdk_gen/schemas/app_item_associated_item.py @@ -0,0 +1,11 @@ +from typing import Union + +from box_sdk_gen.schemas.file_base import FileBase + +from box_sdk_gen.schemas.folder_base import FolderBase + +from box_sdk_gen.schemas.web_link_base import WebLinkBase + +from box_sdk_gen.box.errors import BoxSDKError + +AppItemAssociatedItem = Union[FileBase, FolderBase, WebLinkBase] diff --git a/box_sdk_gen/schemas/app_item_association.py b/box_sdk_gen/schemas/app_item_association.py index 9981fae29..3b615a190 100644 --- a/box_sdk_gen/schemas/app_item_association.py +++ b/box_sdk_gen/schemas/app_item_association.py @@ -1,17 +1,17 @@ from enum import Enum -from typing import Union - from box_sdk_gen.internal.base_object import BaseObject -from box_sdk_gen.schemas.app_item import AppItem - from box_sdk_gen.schemas.file_base import FileBase from box_sdk_gen.schemas.folder_base import FolderBase from box_sdk_gen.schemas.web_link_base import WebLinkBase +from box_sdk_gen.schemas.app_item import AppItem + +from box_sdk_gen.schemas.app_item_associated_item import AppItemAssociatedItem + from box_sdk_gen.box.errors import BoxSDKError @@ -26,7 +26,7 @@ def __init__( self, id: str, app_item: AppItem, - item: Union[FileBase, FolderBase, WebLinkBase], + item: AppItemAssociatedItem, *, type: AppItemAssociationTypeField = AppItemAssociationTypeField.APP_ITEM_ASSOCIATION, **kwargs diff --git a/box_sdk_gen/schemas/collaboration.py b/box_sdk_gen/schemas/collaboration.py index 7d81fae09..ef7ef0bca 100644 --- a/box_sdk_gen/schemas/collaboration.py +++ b/box_sdk_gen/schemas/collaboration.py @@ -4,19 +4,21 @@ from box_sdk_gen.internal.base_object import BaseObject -from typing import Union - from box_sdk_gen.schemas.file import File from box_sdk_gen.schemas.folder import Folder from box_sdk_gen.schemas.web_link import WebLink +from box_sdk_gen.schemas.group_mini import GroupMini + +from box_sdk_gen.schemas.collaboration_item import CollaborationItem + from box_sdk_gen.schemas.app_item import AppItem -from box_sdk_gen.schemas.user_collaborations import UserCollaborations +from box_sdk_gen.schemas.collaboration_access_grantee import CollaborationAccessGrantee -from box_sdk_gen.schemas.group_mini import GroupMini +from box_sdk_gen.schemas.user_collaborations import UserCollaborations from box_sdk_gen.schemas.terms_of_service_base import TermsOfServiceBase @@ -155,9 +157,9 @@ def __init__( id: str, *, type: CollaborationTypeField = CollaborationTypeField.COLLABORATION, - item: Optional[Union[File, Folder, WebLink]] = None, + item: Optional[CollaborationItem] = None, app_item: Optional[AppItem] = None, - accessible_by: Optional[Union[UserCollaborations, GroupMini]] = None, + accessible_by: Optional[CollaborationAccessGrantee] = None, invite_email: Optional[str] = None, role: Optional[CollaborationRoleField] = None, expires_at: Optional[DateTime] = None, @@ -188,7 +190,7 @@ def __init__( :param is_access_only: If set to `true`, collaborators have access to shared items, but such items won't be visible in the All Files list. Additionally, collaborators won't - see the the path to the root folder for the + see the path to the root folder for the shared item., defaults to None :type is_access_only: Optional[bool], optional :param status: The status of the collaboration invitation. If the status diff --git a/box_sdk_gen/schemas/collaboration_access_grantee.py b/box_sdk_gen/schemas/collaboration_access_grantee.py new file mode 100644 index 000000000..420bd32fb --- /dev/null +++ b/box_sdk_gen/schemas/collaboration_access_grantee.py @@ -0,0 +1,9 @@ +from typing import Union + +from box_sdk_gen.schemas.user_collaborations import UserCollaborations + +from box_sdk_gen.schemas.group_mini import GroupMini + +from box_sdk_gen.box.errors import BoxSDKError + +CollaborationAccessGrantee = Union[UserCollaborations, GroupMini] diff --git a/box_sdk_gen/schemas/collaboration_item.py b/box_sdk_gen/schemas/collaboration_item.py new file mode 100644 index 000000000..f1089f08d --- /dev/null +++ b/box_sdk_gen/schemas/collaboration_item.py @@ -0,0 +1,11 @@ +from typing import Union + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + +CollaborationItem = Union[File, Folder, WebLink] diff --git a/box_sdk_gen/schemas/event.py b/box_sdk_gen/schemas/event.py index 9a1a87cec..eccdf7582 100644 --- a/box_sdk_gen/schemas/event.py +++ b/box_sdk_gen/schemas/event.py @@ -4,12 +4,6 @@ from typing import Optional -from typing import Union - -from typing import Dict - -from box_sdk_gen.schemas.user_mini import UserMini - from box_sdk_gen.schemas.user import User from box_sdk_gen.schemas.event_source import EventSource @@ -20,6 +14,10 @@ from box_sdk_gen.schemas.app_item_event_source import AppItemEventSource +from box_sdk_gen.schemas.user_mini import UserMini + +from box_sdk_gen.schemas.event_source_resource import EventSourceResource + from box_sdk_gen.box.errors import BoxSDKError from box_sdk_gen.internal.utils import DateTime @@ -203,9 +201,7 @@ def __init__( created_by: Optional[UserMini] = None, event_type: Optional[EventEventTypeField] = None, session_id: Optional[str] = None, - source: Optional[ - Union[User, EventSource, File, Folder, Dict, AppItemEventSource] - ] = None, + source: Optional[EventSourceResource] = None, additional_details: Optional[EventAdditionalDetailsField] = None, **kwargs ): diff --git a/box_sdk_gen/schemas/event_source_resource.py b/box_sdk_gen/schemas/event_source_resource.py new file mode 100644 index 000000000..abfcfe698 --- /dev/null +++ b/box_sdk_gen/schemas/event_source_resource.py @@ -0,0 +1,17 @@ +from typing import Union + +from typing import Dict + +from box_sdk_gen.schemas.user import User + +from box_sdk_gen.schemas.event_source import EventSource + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.schemas.app_item_event_source import AppItemEventSource + +from box_sdk_gen.box.errors import BoxSDKError + +EventSourceResource = Union[User, EventSource, File, Folder, Dict, AppItemEventSource] diff --git a/box_sdk_gen/schemas/file_full.py b/box_sdk_gen/schemas/file_full.py index ce0b75c1e..615fac845 100644 --- a/box_sdk_gen/schemas/file_full.py +++ b/box_sdk_gen/schemas/file_full.py @@ -26,7 +26,7 @@ from box_sdk_gen.schemas.user_mini import UserMini -from box_sdk_gen.schemas.file_or_folder_scope import FileOrFolderScope +from box_sdk_gen.schemas.resource_scope import ResourceScope from box_sdk_gen.schemas.metadata_full import MetadataFull @@ -162,7 +162,7 @@ def __init__( access_token: Optional[str] = None, expires_in: Optional[int] = None, token_type: Optional[FileFullExpiringEmbedLinkTokenTypeField] = None, - restricted_to: Optional[List[FileOrFolderScope]] = None, + restricted_to: Optional[List[ResourceScope]] = None, url: Optional[str] = None, **kwargs ): @@ -176,7 +176,7 @@ def __init__( :param restricted_to: The permissions that this access token permits, providing a list of resources (files, folders, etc) and the scopes permitted for each of those resources., defaults to None - :type restricted_to: Optional[List[FileOrFolderScope]], optional + :type restricted_to: Optional[List[ResourceScope]], optional :param url: The actual expiring embed URL for this file, constructed from the file ID and access tokens specified in this object., defaults to None :type url: Optional[str], optional diff --git a/box_sdk_gen/schemas/integration_mapping.py b/box_sdk_gen/schemas/integration_mapping.py index 6c2df8e31..be4efb56a 100644 --- a/box_sdk_gen/schemas/integration_mapping.py +++ b/box_sdk_gen/schemas/integration_mapping.py @@ -2,8 +2,6 @@ from typing import Optional -from typing import Union - from box_sdk_gen.schemas.integration_mapping_base import IntegrationMappingBaseTypeField from box_sdk_gen.schemas.integration_mapping_base import IntegrationMappingBase @@ -32,7 +30,7 @@ class IntegrationMappingIntegrationTypeField(str, Enum): class IntegrationMapping(IntegrationMappingBase): def __init__( self, - partner_item: Union[IntegrationMappingPartnerItemSlack], + partner_item: IntegrationMappingPartnerItemSlack, box_item: FolderMini, id: str, *, @@ -48,7 +46,7 @@ def __init__( ): """ :param partner_item: Mapped item object for Slack. - :type partner_item: Union[IntegrationMappingPartnerItemSlack] + :type partner_item: IntegrationMappingPartnerItemSlack :param box_item: The Box folder, to which the object from the partner app domain (referenced in `partner_item_id`) is mapped. :type box_item: FolderMini diff --git a/box_sdk_gen/schemas/integration_mapping_teams.py b/box_sdk_gen/schemas/integration_mapping_teams.py index f967e4fd3..d4df1fafc 100644 --- a/box_sdk_gen/schemas/integration_mapping_teams.py +++ b/box_sdk_gen/schemas/integration_mapping_teams.py @@ -2,8 +2,6 @@ from typing import Optional -from typing import Union - from box_sdk_gen.schemas.integration_mapping_base import IntegrationMappingBaseTypeField from box_sdk_gen.schemas.integration_mapping_base import IntegrationMappingBase @@ -26,7 +24,7 @@ class IntegrationMappingTeamsIntegrationTypeField(str, Enum): class IntegrationMappingTeams(IntegrationMappingBase): def __init__( self, - partner_item: Union[IntegrationMappingPartnerItemTeams], + partner_item: IntegrationMappingPartnerItemTeams, box_item: FolderReference, id: str, *, @@ -39,7 +37,7 @@ def __init__( ): """ :param partner_item: Mapped item object for Teams. - :type partner_item: Union[IntegrationMappingPartnerItemTeams] + :type partner_item: IntegrationMappingPartnerItemTeams :param id: A unique identifier of a folder mapping (part of a composite key together with `integration_type`). diff --git a/box_sdk_gen/schemas/item.py b/box_sdk_gen/schemas/item.py new file mode 100644 index 000000000..cb203e143 --- /dev/null +++ b/box_sdk_gen/schemas/item.py @@ -0,0 +1,11 @@ +from typing import Union + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + +Item = Union[FileFull, FolderMini, WebLink] diff --git a/box_sdk_gen/schemas/items.py b/box_sdk_gen/schemas/items.py index 9dc24c0f8..f87c48e5b 100644 --- a/box_sdk_gen/schemas/items.py +++ b/box_sdk_gen/schemas/items.py @@ -6,14 +6,14 @@ from typing import List -from typing import Union - from box_sdk_gen.schemas.file_full import FileFull from box_sdk_gen.schemas.folder_mini import FolderMini from box_sdk_gen.schemas.web_link import WebLink +from box_sdk_gen.schemas.item import Item + from box_sdk_gen.box.errors import BoxSDKError @@ -51,7 +51,7 @@ def __init__( total_count: Optional[int] = None, offset: Optional[int] = None, order: Optional[List[ItemsOrderField]] = None, - entries: Optional[List[Union[FileFull, FolderMini, WebLink]]] = None, + entries: Optional[List[Item]] = None, **kwargs ): """ @@ -82,7 +82,7 @@ def __init__( For marker-based paginated APIs, this field will be omitted., defaults to None :type order: Optional[List[ItemsOrderField]], optional :param entries: The items in this collection., defaults to None - :type entries: Optional[List[Union[FileFull, FolderMini, WebLink]]], optional + :type entries: Optional[List[Item]], optional """ super().__init__(**kwargs) self.limit = limit diff --git a/box_sdk_gen/schemas/items_offset_paginated.py b/box_sdk_gen/schemas/items_offset_paginated.py index a360480a6..5549485b9 100644 --- a/box_sdk_gen/schemas/items_offset_paginated.py +++ b/box_sdk_gen/schemas/items_offset_paginated.py @@ -6,14 +6,14 @@ from typing import List -from typing import Union - from box_sdk_gen.schemas.file_full import FileFull from box_sdk_gen.schemas.folder_mini import FolderMini from box_sdk_gen.schemas.web_link import WebLink +from box_sdk_gen.schemas.item import Item + from box_sdk_gen.box.errors import BoxSDKError @@ -49,7 +49,7 @@ def __init__( limit: Optional[int] = None, offset: Optional[int] = None, order: Optional[List[ItemsOffsetPaginatedOrderField]] = None, - entries: Optional[List[Union[FileFull, FolderMini, WebLink]]] = None, + entries: Optional[List[Item]] = None, **kwargs ): """ @@ -76,7 +76,7 @@ def __init__( For marker-based paginated APIs, this field will be omitted., defaults to None :type order: Optional[List[ItemsOffsetPaginatedOrderField]], optional :param entries: The items in this collection., defaults to None - :type entries: Optional[List[Union[FileFull, FolderMini, WebLink]]], optional + :type entries: Optional[List[Item]], optional """ super().__init__(**kwargs) self.total_count = total_count diff --git a/box_sdk_gen/schemas/legal_hold_policy_assigned_item.py b/box_sdk_gen/schemas/legal_hold_policy_assigned_item.py new file mode 100644 index 000000000..b1c950182 --- /dev/null +++ b/box_sdk_gen/schemas/legal_hold_policy_assigned_item.py @@ -0,0 +1,11 @@ +from typing import Union + +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + +LegalHoldPolicyAssignedItem = Union[File, Folder, WebLink] diff --git a/box_sdk_gen/schemas/legal_hold_policy_assignment.py b/box_sdk_gen/schemas/legal_hold_policy_assignment.py index ec38983ab..3b00d1af6 100644 --- a/box_sdk_gen/schemas/legal_hold_policy_assignment.py +++ b/box_sdk_gen/schemas/legal_hold_policy_assignment.py @@ -1,22 +1,24 @@ from typing import Optional -from typing import Union - from box_sdk_gen.schemas.legal_hold_policy_assignment_base import ( LegalHoldPolicyAssignmentBaseTypeField, ) +from box_sdk_gen.schemas.file import File + +from box_sdk_gen.schemas.folder import Folder + +from box_sdk_gen.schemas.web_link import WebLink + from box_sdk_gen.schemas.legal_hold_policy_assignment_base import ( LegalHoldPolicyAssignmentBase, ) from box_sdk_gen.schemas.legal_hold_policy_mini import LegalHoldPolicyMini -from box_sdk_gen.schemas.file import File - -from box_sdk_gen.schemas.folder import Folder - -from box_sdk_gen.schemas.web_link import WebLink +from box_sdk_gen.schemas.legal_hold_policy_assigned_item import ( + LegalHoldPolicyAssignedItem, +) from box_sdk_gen.schemas.user_mini import UserMini @@ -30,7 +32,7 @@ def __init__( self, *, legal_hold_policy: Optional[LegalHoldPolicyMini] = None, - assigned_to: Optional[Union[File, Folder, WebLink]] = None, + assigned_to: Optional[LegalHoldPolicyAssignedItem] = None, assigned_by: Optional[UserMini] = None, assigned_at: Optional[DateTime] = None, deleted_at: Optional[DateTime] = None, diff --git a/box_sdk_gen/schemas/metadata_filter.py b/box_sdk_gen/schemas/metadata_filter.py index 3df54e073..e25881cf3 100644 --- a/box_sdk_gen/schemas/metadata_filter.py +++ b/box_sdk_gen/schemas/metadata_filter.py @@ -4,10 +4,6 @@ from typing import Dict -from typing import Union - -from typing import List - from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.schemas.metadata_field_filter_float_range import ( @@ -18,6 +14,8 @@ MetadataFieldFilterDateRange, ) +from box_sdk_gen.schemas.metadata_filter_value import MetadataFilterValue + from box_sdk_gen.box.errors import BoxSDKError @@ -42,18 +40,7 @@ def __init__( *, scope: Optional[MetadataFilterScopeField] = None, template_key: Optional[str] = None, - filters: Optional[ - Dict[ - str, - Union[ - str, - float, - List[str], - MetadataFieldFilterFloatRange, - MetadataFieldFilterDateRange, - ], - ] - ] = None, + filters: Optional[Dict[str, MetadataFilterValue]] = None, **kwargs ): """ @@ -82,7 +69,7 @@ def __init__( 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 + :type filters: Optional[Dict[str, MetadataFilterValue]], optional """ super().__init__(**kwargs) self.scope = scope diff --git a/box_sdk_gen/schemas/metadata_filter_value.py b/box_sdk_gen/schemas/metadata_filter_value.py new file mode 100644 index 000000000..91fbd65c2 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_filter_value.py @@ -0,0 +1,17 @@ +from typing import Union + +from typing import List + +from box_sdk_gen.schemas.metadata_field_filter_float_range import ( + MetadataFieldFilterFloatRange, +) + +from box_sdk_gen.schemas.metadata_field_filter_date_range import ( + MetadataFieldFilterDateRange, +) + +from box_sdk_gen.box.errors import BoxSDKError + +MetadataFilterValue = Union[ + str, float, List[str], MetadataFieldFilterFloatRange, MetadataFieldFilterDateRange +] diff --git a/box_sdk_gen/schemas/metadata_query_result_item.py b/box_sdk_gen/schemas/metadata_query_result_item.py new file mode 100644 index 000000000..a2425d816 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_query_result_item.py @@ -0,0 +1,9 @@ +from typing import Union + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.box.errors import BoxSDKError + +MetadataQueryResultItem = Union[FileFull, FolderFull] diff --git a/box_sdk_gen/schemas/metadata_query_results.py b/box_sdk_gen/schemas/metadata_query_results.py index 381389e8b..570e918e6 100644 --- a/box_sdk_gen/schemas/metadata_query_results.py +++ b/box_sdk_gen/schemas/metadata_query_results.py @@ -2,14 +2,14 @@ from typing import List -from typing import Union - from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.schemas.file_full import FileFull from box_sdk_gen.schemas.folder_full import FolderFull +from box_sdk_gen.schemas.metadata_query_result_item import MetadataQueryResultItem + from box_sdk_gen.box.errors import BoxSDKError @@ -17,7 +17,7 @@ class MetadataQueryResults(BaseObject): def __init__( self, *, - entries: Optional[List[Union[FileFull, FolderFull]]] = None, + entries: Optional[List[MetadataQueryResultItem]] = None, limit: Optional[int] = None, next_marker: Optional[str] = None, **kwargs @@ -29,7 +29,7 @@ def __init__( By default, this endpoint returns only the most basic info about the items. To get additional fields for each item, including any of the metadata, use the `fields` attribute in the query., defaults to None - :type entries: Optional[List[Union[FileFull, FolderFull]]], optional + :type entries: Optional[List[MetadataQueryResultItem]], optional :param limit: The limit that was used for this search. This will be the same as the `limit` query parameter unless that value exceeded the maximum value allowed., defaults to None diff --git a/box_sdk_gen/schemas/metadata_template.py b/box_sdk_gen/schemas/metadata_template.py index 2e3ae449c..3596a105f 100644 --- a/box_sdk_gen/schemas/metadata_template.py +++ b/box_sdk_gen/schemas/metadata_template.py @@ -30,7 +30,7 @@ def __init__(self, key: str, *, id: Optional[str] = None, **kwargs): :param key: The text value of the option. This represents both the display name of the option and the internal key used when updating templates. :type key: str - :param id: The internal unique identifier of the the option., defaults to None + :param id: The internal unique identifier of the option., defaults to None :type id: Optional[str], optional """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/recent_item.py b/box_sdk_gen/schemas/recent_item.py index 837de2aa6..38b7ebc20 100644 --- a/box_sdk_gen/schemas/recent_item.py +++ b/box_sdk_gen/schemas/recent_item.py @@ -2,8 +2,6 @@ from typing import Optional -from typing import Union - from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.schemas.file_full import FileFull @@ -12,6 +10,8 @@ from box_sdk_gen.schemas.web_link import WebLink +from box_sdk_gen.schemas.recent_item_resource import RecentItemResource + from box_sdk_gen.box.errors import BoxSDKError from box_sdk_gen.internal.utils import DateTime @@ -30,7 +30,7 @@ def __init__( self, *, type: Optional[str] = None, - item: Optional[Union[FileFull, FolderFull, WebLink]] = None, + item: Optional[RecentItemResource] = None, interaction_type: Optional[RecentItemInteractionTypeField] = None, interacted_at: Optional[DateTime] = None, interaction_shared_link: Optional[str] = None, diff --git a/box_sdk_gen/schemas/recent_item_resource.py b/box_sdk_gen/schemas/recent_item_resource.py new file mode 100644 index 000000000..2c707a676 --- /dev/null +++ b/box_sdk_gen/schemas/recent_item_resource.py @@ -0,0 +1,11 @@ +from typing import Union + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + +RecentItemResource = Union[FileFull, FolderFull, WebLink] diff --git a/box_sdk_gen/schemas/resource.py b/box_sdk_gen/schemas/resource.py new file mode 100644 index 000000000..eef77606d --- /dev/null +++ b/box_sdk_gen/schemas/resource.py @@ -0,0 +1,9 @@ +from typing import Union + +from box_sdk_gen.schemas.folder_mini import FolderMini + +from box_sdk_gen.schemas.file_mini import FileMini + +from box_sdk_gen.box.errors import BoxSDKError + +Resource = Union[FolderMini, FileMini] diff --git a/box_sdk_gen/schemas/file_or_folder_scope.py b/box_sdk_gen/schemas/resource_scope.py similarity index 77% rename from box_sdk_gen/schemas/file_or_folder_scope.py rename to box_sdk_gen/schemas/resource_scope.py index fe5956598..9f0d71874 100644 --- a/box_sdk_gen/schemas/file_or_folder_scope.py +++ b/box_sdk_gen/schemas/resource_scope.py @@ -2,18 +2,18 @@ from typing import Optional -from typing import Union - from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.schemas.folder_mini import FolderMini from box_sdk_gen.schemas.file_mini import FileMini +from box_sdk_gen.schemas.resource import Resource + from box_sdk_gen.box.errors import BoxSDKError -class FileOrFolderScopeScopeField(str, Enum): +class ResourceScopeScopeField(str, Enum): ANNOTATION_EDIT = 'annotation_edit' ANNOTATION_VIEW_ALL = 'annotation_view_all' ANNOTATION_VIEW_SELF = 'annotation_view_self' @@ -30,17 +30,17 @@ class FileOrFolderScopeScopeField(str, Enum): ITEM_READ = 'item_read' -class FileOrFolderScope(BaseObject): +class ResourceScope(BaseObject): def __init__( self, *, - scope: Optional[FileOrFolderScopeScopeField] = None, - object: Optional[Union[FolderMini, FileMini]] = None, + scope: Optional[ResourceScopeScopeField] = None, + object: Optional[Resource] = None, **kwargs ): """ :param scope: The scopes for the resource access., defaults to None - :type scope: Optional[FileOrFolderScopeScopeField], optional + :type scope: Optional[ResourceScopeScopeField], optional """ super().__init__(**kwargs) self.scope = scope diff --git a/box_sdk_gen/schemas/search_result_item.py b/box_sdk_gen/schemas/search_result_item.py new file mode 100644 index 000000000..62f136e3c --- /dev/null +++ b/box_sdk_gen/schemas/search_result_item.py @@ -0,0 +1,11 @@ +from typing import Union + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + +SearchResultItem = Union[FileFull, FolderFull, WebLink] diff --git a/box_sdk_gen/schemas/search_result_with_shared_link.py b/box_sdk_gen/schemas/search_result_with_shared_link.py index e7eaf52e5..f3783d4d0 100644 --- a/box_sdk_gen/schemas/search_result_with_shared_link.py +++ b/box_sdk_gen/schemas/search_result_with_shared_link.py @@ -1,7 +1,5 @@ from typing import Optional -from typing import Union - from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.schemas.file_full import FileFull @@ -10,6 +8,10 @@ from box_sdk_gen.schemas.web_link import WebLink +from box_sdk_gen.schemas.search_result_with_shared_link_item import ( + SearchResultWithSharedLinkItem, +) + from box_sdk_gen.box.errors import BoxSDKError @@ -18,7 +20,7 @@ def __init__( self, *, accessible_via_shared_link: Optional[str] = None, - item: Optional[Union[FileFull, FolderFull, WebLink]] = None, + item: Optional[SearchResultWithSharedLinkItem] = None, type: Optional[str] = None, **kwargs ): diff --git a/box_sdk_gen/schemas/search_result_with_shared_link_item.py b/box_sdk_gen/schemas/search_result_with_shared_link_item.py new file mode 100644 index 000000000..8938f59e6 --- /dev/null +++ b/box_sdk_gen/schemas/search_result_with_shared_link_item.py @@ -0,0 +1,11 @@ +from typing import Union + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.folder_full import FolderFull + +from box_sdk_gen.schemas.web_link import WebLink + +from box_sdk_gen.box.errors import BoxSDKError + +SearchResultWithSharedLinkItem = Union[FileFull, FolderFull, WebLink] diff --git a/box_sdk_gen/schemas/search_results.py b/box_sdk_gen/schemas/search_results.py index aa30ca8bd..b4ea14cd1 100644 --- a/box_sdk_gen/schemas/search_results.py +++ b/box_sdk_gen/schemas/search_results.py @@ -4,8 +4,6 @@ from typing import List -from typing import Union - from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.schemas.file_full import FileFull @@ -14,6 +12,8 @@ from box_sdk_gen.schemas.web_link import WebLink +from box_sdk_gen.schemas.search_result_item import SearchResultItem + from box_sdk_gen.box.errors import BoxSDKError @@ -31,7 +31,7 @@ def __init__( limit: Optional[int] = None, offset: Optional[int] = None, type: SearchResultsTypeField = SearchResultsTypeField.SEARCH_RESULTS_ITEMS, - entries: Optional[List[Union[FileFull, FolderFull, WebLink]]] = None, + entries: Optional[List[SearchResultItem]] = None, **kwargs ): """ @@ -49,7 +49,7 @@ def __init__( :param type: Specifies the response as search result items without shared links., defaults to SearchResultsTypeField.SEARCH_RESULTS_ITEMS :type type: SearchResultsTypeField, optional :param entries: The search results for the query provided., defaults to None - :type entries: Optional[List[Union[FileFull, FolderFull, WebLink]]], optional + :type entries: Optional[List[SearchResultItem]], optional """ super().__init__(**kwargs) self.total_count = total_count diff --git a/box_sdk_gen/schemas/search_results_response.py b/box_sdk_gen/schemas/search_results_response.py new file mode 100644 index 000000000..252c70f50 --- /dev/null +++ b/box_sdk_gen/schemas/search_results_response.py @@ -0,0 +1,11 @@ +from typing import Union + +from box_sdk_gen.schemas.search_results import SearchResults + +from box_sdk_gen.schemas.search_results_with_shared_links import ( + SearchResultsWithSharedLinks, +) + +from box_sdk_gen.box.errors import BoxSDKError + +SearchResultsResponse = Union[SearchResults, SearchResultsWithSharedLinks] diff --git a/box_sdk_gen/schemas/skill_card.py b/box_sdk_gen/schemas/skill_card.py new file mode 100644 index 000000000..8646039f2 --- /dev/null +++ b/box_sdk_gen/schemas/skill_card.py @@ -0,0 +1,15 @@ +from typing import Union + +from box_sdk_gen.schemas.keyword_skill_card import KeywordSkillCard + +from box_sdk_gen.schemas.timeline_skill_card import TimelineSkillCard + +from box_sdk_gen.schemas.transcript_skill_card import TranscriptSkillCard + +from box_sdk_gen.schemas.status_skill_card import StatusSkillCard + +from box_sdk_gen.box.errors import BoxSDKError + +SkillCard = Union[ + KeywordSkillCard, TimelineSkillCard, TranscriptSkillCard, StatusSkillCard +] diff --git a/box_sdk_gen/schemas/skill_cards_metadata.py b/box_sdk_gen/schemas/skill_cards_metadata.py index 0c2135962..5c0b8e819 100644 --- a/box_sdk_gen/schemas/skill_cards_metadata.py +++ b/box_sdk_gen/schemas/skill_cards_metadata.py @@ -2,8 +2,6 @@ from typing import List -from typing import Union - from typing import Dict from box_sdk_gen.internal.base_object import BaseObject @@ -16,6 +14,8 @@ from box_sdk_gen.schemas.status_skill_card import StatusSkillCard +from box_sdk_gen.schemas.skill_card import SkillCard + from box_sdk_gen.box.errors import BoxSDKError @@ -54,16 +54,7 @@ def __init__( type: Optional[str] = None, type_version: Optional[int] = None, version: Optional[int] = None, - cards: Optional[ - List[ - Union[ - KeywordSkillCard, - TimelineSkillCard, - TranscriptSkillCard, - StatusSkillCard, - ] - ] - ] = None, + cards: Optional[List[SkillCard]] = None, **kwargs ): """ @@ -88,7 +79,7 @@ def __init__( a user-defined property is modified., defaults to None :type version: Optional[int], optional :param cards: A list of Box Skill cards that have been applied to this file., defaults to None - :type cards: Optional[List[Union[KeywordSkillCard, TimelineSkillCard, TranscriptSkillCard, StatusSkillCard]]], optional + :type cards: Optional[List[SkillCard]], optional """ super().__init__(**kwargs) self.can_edit = can_edit diff --git a/box_sdk_gen/schemas/task.py b/box_sdk_gen/schemas/task.py index 053942b06..fc6b2b75a 100644 --- a/box_sdk_gen/schemas/task.py +++ b/box_sdk_gen/schemas/task.py @@ -68,9 +68,9 @@ def __init__( is considered completed. * `all_assignees` requires all assignees to review or - approve the the task in order for it to be considered completed. + approve the task in order for it to be considered completed. * `any_assignee` accepts any one assignee to review or - approve the the task in order for it to be considered completed., defaults to None + approve the task in order for it to be considered completed., defaults to None :type completion_rule: Optional[TaskCompletionRuleField], optional """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py index 3584f0d20..5dd003e18 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py @@ -1,7 +1,5 @@ from enum import Enum -from typing import Union - from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 @@ -14,6 +12,10 @@ WeblinkReferenceV2025R0, ) +from box_sdk_gen.schemas.v2025_r0.hub_item_reference_v2025_r0 import ( + HubItemReferenceV2025R0, +) + from box_sdk_gen.box.errors import BoxSDKError @@ -26,16 +28,12 @@ class HubItemOperationV2025R0(BaseObject): def __init__( self, action: HubItemOperationV2025R0ActionField, - item: Union[ - FileReferenceV2025R0, FolderReferenceV2025R0, WeblinkReferenceV2025R0 - ], + item: HubItemReferenceV2025R0, **kwargs ): """ :param action: The action to perform on a Hub item. :type action: HubItemOperationV2025R0ActionField - :param item: Reference to an item that can be added to a Hub. - :type item: Union[FileReferenceV2025R0, FolderReferenceV2025R0, WeblinkReferenceV2025R0] """ super().__init__(**kwargs) self.action = action diff --git a/docs/ai.md b/docs/ai.md index 59784e916..d26dd5090 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -44,7 +44,7 @@ client.ai.create_ai_ask( - The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response. - include_citations `Optional[bool]` - A flag to indicate whether citations should be returned. -- ai_agent `Optional[Union[AiAgentAsk, AiAgentReference]]` +- ai_agent `Optional[AiAskAgent]` - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -99,7 +99,7 @@ client.ai.create_ai_text_gen( - The items to be processed by the LLM, often files. The array can include **exactly one** element. **Note**: Box AI handles documents with text representations up to 1MB in size. If the file size exceeds 1MB, the first 1MB of text representation will be processed. - dialogue_history `Optional[List[AiDialogueHistory]]` - The history of prompts and answers previously passed to the LLM. This parameter provides the additional context to the LLM when generating the response. -- ai_agent `Optional[Union[AiAgentReference, AiAgentTextGen]]` +- ai_agent `Optional[AiTextGenAgent]` - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -137,7 +137,7 @@ client.ai.get_ai_agent_default_config(GetAiAgentDefaultConfigMode.ASK, language= ### Returns -This function returns a value of type `Union[AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured]`. +This function returns a value of type `AiAgent`. A successful response including the default agent configuration. This response can be one of the following four objects: @@ -175,7 +175,7 @@ client.ai.create_ai_extract( - The prompt provided to a Large Language Model (LLM) in the request. The prompt can be up to 10000 characters long and it can be an XML or a JSON schema. - items `List[AiItemBase]` - The items that LLM will process. Currently, you can use files only. -- ai_agent `Optional[Union[AiAgentReference, AiAgentExtract]]` +- ai_agent `Optional[AiExtractAgent]` - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -256,7 +256,7 @@ client.ai.create_ai_extract_structured( - The metadata template containing the fields to extract. For your request to work, you must provide either `metadata_template` or `fields`, but not both. - fields `Optional[List[CreateAiExtractStructuredFields]]` - The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both. -- ai_agent `Optional[Union[AiAgentReference, AiAgentExtractStructured]]` +- ai_agent `Optional[AiExtractStructuredAgent]` - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/file_versions.md b/docs/file_versions.md index dbd919f82..b217d2af5 100644 --- a/docs/file_versions.md +++ b/docs/file_versions.md @@ -162,7 +162,7 @@ promote one of the older versions to the top of the version history. This creates a new copy of the old version and puts it at the top of the versions history. The file will have the exact same contents -as the older version, with the the same hash digest, `etag`, and +as the older version, with the same hash digest, `etag`, and name as the original. Other properties such as comments do not get updated to their diff --git a/docs/files.md b/docs/files.md index 9ce0de627..9522994ab 100644 --- a/docs/files.md +++ b/docs/files.md @@ -109,7 +109,7 @@ any specific fields. Deletes a file, either permanently or by moving it to the trash. -The the enterprise settings determine whether the item will +The enterprise settings determine whether the item will be permanently deleted from Box or moved to the trash. This operation is performed by calling function `delete_file_by_id`. diff --git a/docs/search.md b/docs/search.md index 580dcab9e..803050620 100644 --- a/docs/search.md +++ b/docs/search.md @@ -95,7 +95,7 @@ client.search.search_for_content( - file_extensions `Optional[List[str]]` - Limits the search results to any files that match any of the provided file extensions. This list is a comma-separated list of file extensions without the dots. - created_at_range `Optional[List[str]]` - - Limits the search results to any items created within a given date range. Date ranges are defined as comma separated RFC3339 timestamps. If the the start date is omitted (`,2014-05-17T13:35:01-07:00`) anything created before the end date will be returned. If the end date is omitted (`2014-05-15T13:35:01-07:00,`) the current date will be used as the end date instead. + - Limits the search results to any items created within a given date range. Date ranges are defined as comma separated RFC3339 timestamps. If the start date is omitted (`,2014-05-17T13:35:01-07:00`) anything created before the end date will be returned. If the end date is omitted (`2014-05-15T13:35:01-07:00,`) the current date will be used as the end date instead. - updated_at_range `Optional[List[str]]` - Limits the search results to any items updated within a given date range. Date ranges are defined as comma separated RFC3339 timestamps. If the start date is omitted (`,2014-05-17T13:35:01-07:00`) anything updated before the end date will be returned. If the end date is omitted (`2014-05-15T13:35:01-07:00,`) the current date will be used as the end date instead. - size_range `Optional[List[int]]` @@ -129,13 +129,13 @@ client.search.search_for_content( - deleted_user_ids `Optional[List[str]]` - Limits the search results to items that were deleted by the given list of users, defined as a list of comma separated user IDs. The `trash_content` parameter needs to be set to `trashed_only`. If searching in trash is not performed, an empty result set is returned. The items need to be owned or shared with the currently authenticated user for them to show up in the search results. If the user does not have access to any files owned by any of the users, an empty result set is returned. Data available from 2023-02-01 onwards. - deleted_at_range `Optional[List[str]]` - - Limits the search results to any items deleted within a given date range. Date ranges are defined as comma separated RFC3339 timestamps. If the the start date is omitted (`2014-05-17T13:35:01-07:00`), anything deleted before the end date will be returned. If the end date is omitted (`2014-05-15T13:35:01-07:00`), the current date will be used as the end date instead. The `trash_content` parameter needs to be set to `trashed_only`. If searching in trash is not performed, then an empty result is returned. Data available from 2023-02-01 onwards. + - Limits the search results to any items deleted within a given date range. Date ranges are defined as comma separated RFC3339 timestamps. If the start date is omitted (`2014-05-17T13:35:01-07:00`), anything deleted before the end date will be returned. If the end date is omitted (`2014-05-15T13:35:01-07:00`), the current date will be used as the end date instead. The `trash_content` parameter needs to be set to `trashed_only`. If searching in trash is not performed, then an empty result is returned. Data available from 2023-02-01 onwards. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. ### Returns -This function returns a value of type `Union[SearchResults, SearchResultsWithSharedLinks]`. +This function returns a value of type `SearchResultsResponse`. Returns a collection of search results. If there are no matching search results, the `entries` array will be empty. diff --git a/docs/skills.md b/docs/skills.md index 1c15dbe9b..76de2520c 100644 --- a/docs/skills.md +++ b/docs/skills.md @@ -56,7 +56,7 @@ client.skills.create_box_skill_cards_on_file(file.id, cards_to_create) - file_id `str` - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" -- cards `List[Union[KeywordSkillCard, TimelineSkillCard, TranscriptSkillCard, StatusSkillCard]]` +- cards `List[SkillCard]` - A list of Box Skill cards to apply to this file. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/tasks.md b/docs/tasks.md index 755487964..ae6dc3b58 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -71,7 +71,7 @@ client.tasks.create_task( - due_at `Optional[DateTime]` - Defines when the task is due. Defaults to `null` if not provided. - completion_rule `Optional[CreateTaskCompletionRule]` - - Defines which assignees need to complete this task before the task is considered completed. _ `all_assignees` (default) requires all assignees to review or approve the the task in order for it to be considered completed. _ `any_assignee` accepts any one assignee to review or approve the the task in order for it to be considered completed. + - Defines which assignees need to complete this task before the task is considered completed. _ `all_assignees` (default) requires all assignees to review or approve the task in order for it to be considered completed. _ `any_assignee` accepts any one assignee to review or approve the task in order for it to be considered completed. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -136,7 +136,7 @@ client.tasks.update_task_by_id(task.id, message="updated message") - due_at `Optional[DateTime]` - When the task is due at. - completion_rule `Optional[UpdateTaskByIdCompletionRule]` - - Defines which assignees need to complete this task before the task is considered completed. _ `all_assignees` (default) requires all assignees to review or approve the the task in order for it to be considered completed. _ `any_assignee` accepts any one assignee to review or approve the the task in order for it to be considered completed. + - Defines which assignees need to complete this task before the task is considered completed. _ `all_assignees` (default) requires all assignees to review or approve the task in order for it to be considered completed. _ `any_assignee` accepts any one assignee to review or approve the task in order for it to be considered completed. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/user_collaborations.md b/docs/user_collaborations.md index 480860131..1cbdc0b7a 100644 --- a/docs/user_collaborations.md +++ b/docs/user_collaborations.md @@ -150,7 +150,7 @@ client.user_collaborations.create_collaboration( - role `CreateCollaborationRole` - The level of access granted. - is_access_only `Optional[bool]` - - If set to `true`, collaborators have access to shared items, but such items won't be visible in the All Files list. Additionally, collaborators won't see the the path to the root folder for the shared item. + - If set to `true`, collaborators have access to shared items, but such items won't be visible in the All Files list. Additionally, collaborators won't see the path to the root folder for the shared item. - can_view_path `Optional[bool]` - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only owner or co-owners can invite collaborators with a `can_view_path` of `true`. `can_view_path` can only be used for folder collaborations. - expires_at `Optional[DateTime]` diff --git a/test/ai.py b/test/ai.py index 9668804b5..98c6d26e3 100644 --- a/test/ai.py +++ b/test/ai.py @@ -1,11 +1,11 @@ -from typing import Union - from typing import Optional from box_sdk_gen.internal.utils import to_string from box_sdk_gen.client import BoxClient +from box_sdk_gen.schemas.ai_agent import AiAgent + from box_sdk_gen.managers.ai import GetAiAgentDefaultConfigMode from box_sdk_gen.schemas.file_full import FileFull @@ -90,9 +90,7 @@ def testAskAISingleItem(): - ai_agent_config: Union[ - AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured - ] = client.ai.get_ai_agent_default_config( + ai_agent_config: AiAgent = client.ai.get_ai_agent_default_config( GetAiAgentDefaultConfigMode.ASK, language='en-US' ) ai_ask_agent_config: AiAgentAsk = ai_agent_config @@ -141,9 +139,7 @@ def testAskAIMultipleItems(): def testAITextGenWithDialogueHistory(): file_to_ask: FileFull = upload_new_file() - ai_agent_config: Union[ - AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured - ] = client.ai.get_ai_agent_default_config( + ai_agent_config: AiAgent = client.ai.get_ai_agent_default_config( GetAiAgentDefaultConfigMode.TEXT_GEN, language='en-US' ) ai_text_gen_agent_config: AiAgentTextGen = ai_agent_config @@ -176,9 +172,7 @@ def testAITextGenWithDialogueHistory(): def testGettingAIAskAgentConfig(): - ai_agent_config: Union[ - AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured - ] = client.ai.get_ai_agent_default_config( + ai_agent_config: AiAgent = client.ai.get_ai_agent_default_config( GetAiAgentDefaultConfigMode.ASK, language='en-US' ) assert ai_agent_config.type == 'ai_agent_ask' @@ -206,9 +200,7 @@ def testGettingAIAskAgentConfig(): def testGettingAITextGenAgentConfig(): - ai_agent_config: Union[ - AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured - ] = client.ai.get_ai_agent_default_config( + ai_agent_config: AiAgent = client.ai.get_ai_agent_default_config( GetAiAgentDefaultConfigMode.TEXT_GEN, language='en-US' ) assert ai_agent_config.type == 'ai_agent_text_gen' @@ -223,9 +215,7 @@ def testGettingAITextGenAgentConfig(): def testAIExtract(): - ai_agent_config: Union[ - AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured - ] = client.ai.get_ai_agent_default_config( + ai_agent_config: AiAgent = client.ai.get_ai_agent_default_config( GetAiAgentDefaultConfigMode.EXTRACT, language='en-US' ) ai_extract_agent_config: AiAgentExtract = ai_agent_config @@ -265,9 +255,7 @@ def testAIExtract(): def testAIExtractStructuredWithFields(): - ai_agent_config: Union[ - AiAgentAsk, AiAgentTextGen, AiAgentExtract, AiAgentExtractStructured - ] = client.ai.get_ai_agent_default_config( + ai_agent_config: AiAgent = client.ai.get_ai_agent_default_config( GetAiAgentDefaultConfigMode.EXTRACT_STRUCTURED, language='en-US' ) ai_extract_structured_agent_config: AiAgentExtractStructured = ai_agent_config diff --git a/test/search.py b/test/search.py index 6846f43f0..25476b48c 100644 --- a/test/search.py +++ b/test/search.py @@ -1,7 +1,5 @@ from typing import Dict -from typing import Union - from box_sdk_gen.internal.utils import to_string from box_sdk_gen.client import BoxClient @@ -34,6 +32,8 @@ from box_sdk_gen.managers.metadata_templates import DeleteMetadataTemplateScope +from box_sdk_gen.schemas.search_results_response import SearchResultsResponse + from box_sdk_gen.schemas.metadata_filter import MetadataFilter from box_sdk_gen.schemas.metadata_filter import MetadataFilterScopeField @@ -224,17 +224,15 @@ def testMetadataFilters(): 'enumField': 'enumValue2', 'multiSelectField': ['multiSelectValue1', 'multiSelectValue2'], } - query: Union[SearchResults, SearchResultsWithSharedLinks] = ( - client.search.search_for_content( - ancestor_folder_ids=['0'], - mdfilters=[ - MetadataFilter( - filters=search_filters, - scope=MetadataFilterScopeField.ENTERPRISE, - template_key=template_key, - ) - ], - ) + query: SearchResultsResponse = client.search.search_for_content( + ancestor_folder_ids=['0'], + mdfilters=[ + MetadataFilter( + filters=search_filters, + scope=MetadataFilterScopeField.ENTERPRISE, + template_key=template_key, + ) + ], ) query_results: SearchResults = query assert len(query_results.entries) >= 0 @@ -246,23 +244,19 @@ def testMetadataFilters(): def testGetSearch(): keyword: str = 'test' - search: Union[SearchResults, SearchResultsWithSharedLinks] = ( - client.search.search_for_content( - query=keyword, - ancestor_folder_ids=['0'], - trash_content=SearchForContentTrashContent.NON_TRASHED_ONLY, - ) + search: SearchResultsResponse = client.search.search_for_content( + query=keyword, + ancestor_folder_ids=['0'], + trash_content=SearchForContentTrashContent.NON_TRASHED_ONLY, ) assert to_string(search.type) == 'search_results_items' search_results: SearchResults = search assert len(search_results.entries) >= 0 - search_with_shared_link: Union[SearchResults, SearchResultsWithSharedLinks] = ( - client.search.search_for_content( - query=keyword, - ancestor_folder_ids=['0'], - trash_content=SearchForContentTrashContent.NON_TRASHED_ONLY, - include_recent_shared_links=True, - ) + search_with_shared_link: SearchResultsResponse = client.search.search_for_content( + query=keyword, + ancestor_folder_ids=['0'], + trash_content=SearchForContentTrashContent.NON_TRASHED_ONLY, + include_recent_shared_links=True, ) assert to_string(search_with_shared_link.type) == 'search_results_with_shared_links' search_results_with_shared_link: SearchResultsWithSharedLinks = ( From a80ad856b3193e54272e04f01ddb025b2d9f781f Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 20 Aug 2025 09:28:36 +0200 Subject: [PATCH 008/139] feat: Add External User Deletion API (box/box-openapi#550) (#941) --- .codegen.json | 2 +- box_sdk_gen/client.py | 5 + box_sdk_gen/managers/__init__.py | 2 + box_sdk_gen/managers/external_users.py | 106 ++++++++++++++++++ box_sdk_gen/schemas/v2025_r0/__init__.py | 8 ++ .../external_user_deletion_result_v2025_r0.py | 23 ++++ ...sers_submit_delete_job_request_v2025_r0.py | 17 +++ ...ers_submit_delete_job_response_v2025_r0.py | 19 ++++ .../shield_list_content_ip_v2025_r0.py | 2 +- .../v2025_r0/user_reference_v2025_r0.py | 30 +++++ .../v2025_r0/weblink_reference_v2025_r0.py | 2 +- docs/README.md | 1 + docs/external_users.md | 28 +++++ 13 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 box_sdk_gen/managers/external_users.py create mode 100644 box_sdk_gen/schemas/v2025_r0/external_user_deletion_result_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/external_users_submit_delete_job_request_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/external_users_submit_delete_job_response_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/user_reference_v2025_r0.py create mode 100644 docs/external_users.md diff --git a/.codegen.json b/.codegen.json index 778c6acc3..8082af814 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "ef443c1", "specHash": "99e14a6", "version": "0.1.0" } +{ "engineHash": "54ce521", "specHash": "623b811", "version": "0.1.0" } diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py index b49eebb45..9d5c34948 100644 --- a/box_sdk_gen/client.py +++ b/box_sdk_gen/client.py @@ -182,6 +182,8 @@ from box_sdk_gen.managers.archives import ArchivesManager +from box_sdk_gen.managers.external_users import ExternalUsersManager + from box_sdk_gen.networking.auth import Authentication from box_sdk_gen.networking.network import NetworkSession @@ -440,6 +442,9 @@ def __init__(self, auth: Authentication, *, network_session: NetworkSession = No self.archives = ArchivesManager( auth=self.auth, network_session=self.network_session ) + self.external_users = ExternalUsersManager( + auth=self.auth, network_session=self.network_session + ) def make_request(self, fetch_options: FetchOptions) -> FetchResponse: """ diff --git a/box_sdk_gen/managers/__init__.py b/box_sdk_gen/managers/__init__.py index d5a840f81..f3a23b7fb 100644 --- a/box_sdk_gen/managers/__init__.py +++ b/box_sdk_gen/managers/__init__.py @@ -155,3 +155,5 @@ from box_sdk_gen.managers.shield_lists import * from box_sdk_gen.managers.archives import * + +from box_sdk_gen.managers.external_users import * diff --git a/box_sdk_gen/managers/external_users.py b/box_sdk_gen/managers/external_users.py new file mode 100644 index 000000000..fdbdb8c49 --- /dev/null +++ b/box_sdk_gen/managers/external_users.py @@ -0,0 +1,106 @@ +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.schemas.v2025_r0.user_reference_v2025_r0 import UserReferenceV2025R0 + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2025_r0.external_users_submit_delete_job_response_v2025_r0 import ( + ExternalUsersSubmitDeleteJobResponseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import ClientErrorV2025R0 + +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import ( + BoxVersionHeaderV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.external_users_submit_delete_job_request_v2025_r0 import ( + ExternalUsersSubmitDeleteJobRequestV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class ExternalUsersManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_external_user_submit_delete_job_v2025_r0( + self, + external_users: List[UserReferenceV2025R0], + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ExternalUsersSubmitDeleteJobResponseV2025R0: + """ + Delete external users from current user enterprise. This will remove each + + external user from all invited collaborations within the current enterprise. + + :param external_users: List of external users to delete. + :type external_users: List[UserReferenceV2025R0] + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'external_users': external_users} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/external_users/submit_delete_job', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ExternalUsersSubmitDeleteJobResponseV2025R0) diff --git a/box_sdk_gen/schemas/v2025_r0/__init__.py b/box_sdk_gen/schemas/v2025_r0/__init__.py index 5c88bfc00..b5405a205 100644 --- a/box_sdk_gen/schemas/v2025_r0/__init__.py +++ b/box_sdk_gen/schemas/v2025_r0/__init__.py @@ -18,6 +18,10 @@ from box_sdk_gen.schemas.v2025_r0.enterprise_reference_v2025_r0 import * +from box_sdk_gen.schemas.v2025_r0.external_user_deletion_result_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.external_users_submit_delete_job_response_v2025_r0 import * + from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.doc_gen_template_create_request_v2025_r0 import * @@ -104,6 +108,10 @@ from box_sdk_gen.schemas.v2025_r0.doc_gen_jobs_full_v2025_r0 import * +from box_sdk_gen.schemas.v2025_r0.user_reference_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.external_users_submit_delete_job_request_v2025_r0 import * + from box_sdk_gen.schemas.v2025_r0.weblink_reference_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.hub_item_reference_v2025_r0 import * diff --git a/box_sdk_gen/schemas/v2025_r0/external_user_deletion_result_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/external_user_deletion_result_v2025_r0.py new file mode 100644 index 000000000..c452fced0 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/external_user_deletion_result_v2025_r0.py @@ -0,0 +1,23 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ExternalUserDeletionResultV2025R0(BaseObject): + def __init__( + self, user_id: str, status: int, *, detail: Optional[str] = None, **kwargs + ): + """ + :param user_id: The ID of the external user. + :type user_id: str + :param status: HTTP status code for a specific user's deletion request. + :type status: int + :param detail: Deletion request status details., defaults to None + :type detail: Optional[str], optional + """ + super().__init__(**kwargs) + self.user_id = user_id + self.status = status + self.detail = detail diff --git a/box_sdk_gen/schemas/v2025_r0/external_users_submit_delete_job_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/external_users_submit_delete_job_request_v2025_r0.py new file mode 100644 index 000000000..29960b159 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/external_users_submit_delete_job_request_v2025_r0.py @@ -0,0 +1,17 @@ +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.user_reference_v2025_r0 import UserReferenceV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class ExternalUsersSubmitDeleteJobRequestV2025R0(BaseObject): + def __init__(self, external_users: List[UserReferenceV2025R0], **kwargs): + """ + :param external_users: List of external users to delete. + :type external_users: List[UserReferenceV2025R0] + """ + super().__init__(**kwargs) + self.external_users = external_users diff --git a/box_sdk_gen/schemas/v2025_r0/external_users_submit_delete_job_response_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/external_users_submit_delete_job_response_v2025_r0.py new file mode 100644 index 000000000..9c75db4e9 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/external_users_submit_delete_job_response_v2025_r0.py @@ -0,0 +1,19 @@ +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.external_user_deletion_result_v2025_r0 import ( + ExternalUserDeletionResultV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class ExternalUsersSubmitDeleteJobResponseV2025R0(BaseObject): + def __init__(self, entries: List[ExternalUserDeletionResultV2025R0], **kwargs): + """ + :param entries: Array of results of each external user deletion request. + :type entries: List[ExternalUserDeletionResultV2025R0] + """ + super().__init__(**kwargs) + self.entries = entries diff --git a/box_sdk_gen/schemas/v2025_r0/shield_list_content_ip_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_list_content_ip_v2025_r0.py index fdaef0ecf..74fd61531 100644 --- a/box_sdk_gen/schemas/v2025_r0/shield_list_content_ip_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/shield_list_content_ip_v2025_r0.py @@ -22,7 +22,7 @@ def __init__( **kwargs ): """ - :param ip_addresses: List of ips and cidrs. + :param ip_addresses: List of ip addresses and CIDRs. :type ip_addresses: List[str] :param type: The type of content in the shield list., defaults to ShieldListContentIpV2025R0TypeField.IP :type type: ShieldListContentIpV2025R0TypeField, optional diff --git a/box_sdk_gen/schemas/v2025_r0/user_reference_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/user_reference_v2025_r0.py new file mode 100644 index 000000000..afe32b0f5 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/user_reference_v2025_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserReferenceV2025R0TypeField(str, Enum): + USER = 'user' + + +class UserReferenceV2025R0(BaseObject): + _discriminator = 'type', {'user'} + + def __init__( + self, + id: str, + *, + type: UserReferenceV2025R0TypeField = UserReferenceV2025R0TypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for the user. + :type id: str + :param type: The value is always `user`., defaults to UserReferenceV2025R0TypeField.USER + :type type: UserReferenceV2025R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py index 1e767a598..d172f86e2 100644 --- a/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py @@ -20,7 +20,7 @@ def __init__( **kwargs ): """ - :param id: ID of the weblink. + :param id: ID of the web link. :type id: str :param type: The value will always be `weblink`., defaults to WeblinkReferenceV2025R0TypeField.WEBLINK :type type: WeblinkReferenceV2025R0TypeField, optional diff --git a/docs/README.md b/docs/README.md index 7b54f27b4..3ed7efdee 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,6 +23,7 @@ the SDK are available by topic: - [Downloads](downloads.md) - [Email aliases](email_aliases.md) - [Events](events.md) +- [External users](external_users.md) - [File classifications](file_classifications.md) - [File metadata](file_metadata.md) - [File requests](file_requests.md) diff --git a/docs/external_users.md b/docs/external_users.md new file mode 100644 index 000000000..9fd61470e --- /dev/null +++ b/docs/external_users.md @@ -0,0 +1,28 @@ +# ExternalUsersManager + +- [Submit job to delete external users](#submit-job-to-delete-external-users) + +## Submit job to delete external users + +Delete external users from current user enterprise. This will remove each +external user from all invited collaborations within the current enterprise. + +This operation is performed by calling function `create_external_user_submit_delete_job_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/post-external-users-submit-delete-job/). + +_Currently we don't have an example for calling `create_external_user_submit_delete_job_v2025_r0` in integration tests_ + +### Arguments + +- external_users `List[UserReferenceV2025R0]` + - List of external users to delete. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ExternalUsersSubmitDeleteJobResponseV2025R0`. From 381aec2aa835f043bc083d11a8b00a8f8dd75bf9 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 20 Aug 2025 11:38:36 +0200 Subject: [PATCH 009/139] fix: Rename external user deletion method (box/box-codegen#796) (#953) --- .codegen.json | 2 +- box_sdk_gen/managers/external_users.py | 2 +- docs/external_users.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.codegen.json b/.codegen.json index 8082af814..600dbf1fe 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "54ce521", "specHash": "623b811", "version": "0.1.0" } +{ "engineHash": "02b749b", "specHash": "623b811", "version": "0.1.0" } diff --git a/box_sdk_gen/managers/external_users.py b/box_sdk_gen/managers/external_users.py index fdbdb8c49..cf69a6a3c 100644 --- a/box_sdk_gen/managers/external_users.py +++ b/box_sdk_gen/managers/external_users.py @@ -61,7 +61,7 @@ def __init__( self.auth = auth self.network_session = network_session - def create_external_user_submit_delete_job_v2025_r0( + def submit_job_to_delete_external_users_v2025_r0( self, external_users: List[UserReferenceV2025R0], *, diff --git a/docs/external_users.md b/docs/external_users.md index 9fd61470e..77d54abb5 100644 --- a/docs/external_users.md +++ b/docs/external_users.md @@ -7,12 +7,12 @@ Delete external users from current user enterprise. This will remove each external user from all invited collaborations within the current enterprise. -This operation is performed by calling function `create_external_user_submit_delete_job_v2025_r0`. +This operation is performed by calling function `submit_job_to_delete_external_users_v2025_r0`. See the endpoint docs at [API Reference](https://developer.box.com/reference/v2025.0/post-external-users-submit-delete-job/). -_Currently we don't have an example for calling `create_external_user_submit_delete_job_v2025_r0` in integration tests_ +_Currently we don't have an example for calling `submit_job_to_delete_external_users_v2025_r0` in integration tests_ ### Arguments From c9c3bf90a2c3404e890a9678437f278ab1fff026 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:34:25 +0200 Subject: [PATCH 010/139] chore: Adjust package metadata in `setup.py` for `v10` release(box/box-codegen#802) (#959) --- .codegen.json | 2 +- setup.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.codegen.json b/.codegen.json index 600dbf1fe..dac9cdebc 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "02b749b", "specHash": "623b811", "version": "0.1.0" } +{ "engineHash": "f036f69", "specHash": "623b811", "version": "0.1.0" } diff --git a/setup.py b/setup.py index 7fd0792bd..6dc91bfcc 100644 --- a/setup.py +++ b/setup.py @@ -20,10 +20,10 @@ def main(): 'jwt': jwt_requires, } setup( - name='box-sdk-gen', + name='boxsdk', version=__version__, - description='Official Box Python Generated SDK', - url='https://github.com/box/box-python-sdk-gen.git', + description='Official Box Python SDK', + url='https://github.com/box/box-python-sdk.git', licence='Apache-2.0, http://www.apache.org/licenses/LICENSE-2.0', author='Box', long_description_content_type='text/markdown', From 8e063d1aede38fb07d85a0b6b9b101960af2b07c Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 22 Aug 2025 11:22:35 +0200 Subject: [PATCH 011/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec (#965) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index dac9cdebc..3d750e40e 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "f036f69", "specHash": "623b811", "version": "0.1.0" } +{ "engineHash": "46ac366", "specHash": "623b811", "version": "0.1.0" } From 25f3f3e46b0ce02aece803c43a1a15dfe57b988a Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 22 Aug 2025 16:40:24 +0200 Subject: [PATCH 012/139] docs: Update description for `ExternalUserDeletionResult` (box/box-openapi#551) (#969) --- .codegen.json | 2 +- .../schemas/v2025_r0/external_user_deletion_result_v2025_r0.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 3d750e40e..e116f021f 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "46ac366", "specHash": "623b811", "version": "0.1.0" } +{ "engineHash": "3fbcb0c", "specHash": "ec8720b", "version": "0.1.0" } diff --git a/box_sdk_gen/schemas/v2025_r0/external_user_deletion_result_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/external_user_deletion_result_v2025_r0.py index c452fced0..9f52e5b93 100644 --- a/box_sdk_gen/schemas/v2025_r0/external_user_deletion_result_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/external_user_deletion_result_v2025_r0.py @@ -14,7 +14,7 @@ def __init__( :type user_id: str :param status: HTTP status code for a specific user's deletion request. :type status: int - :param detail: Deletion request status details., defaults to None + :param detail: Deletion request status details. This property is only present when the deletion request is not successful., defaults to None :type detail: Optional[str], optional """ super().__init__(**kwargs) From 126690322f713cfd7644c082f75274543e4573a3 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:22:06 +0200 Subject: [PATCH 013/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#970) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index e116f021f..4ba6aa255 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "3fbcb0c", "specHash": "ec8720b", "version": "0.1.0" } +{ "engineHash": "3ba449c", "specHash": "ec8720b", "version": "0.1.0" } From 6c23887982cd71728f29eb0ec758cd80f0029c42 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:42:47 +0200 Subject: [PATCH 014/139] test: remove hub tests temporarily (box/box-codegen#812) (#975) * test: ExternalUsers API integration tests (box/box-codegen#799) * chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] * chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] * chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] * test: remove hub tests temporarily (box/box-codegen#812) --- .codegen.json | 2 +- .github/workflows/build.yml | 2 + README.md | 2 +- docs/external_users.md | 8 +++- test/external_users.py | 53 +++++++++++++++++++++ test/hub_collaborations.py | 88 ----------------------------------- test/hub_items.py | 93 ------------------------------------- test/hubs.py | 74 ----------------------------- tox.ini | 12 ++--- 9 files changed, 70 insertions(+), 264 deletions(-) create mode 100644 test/external_users.py delete mode 100644 test/hub_collaborations.py delete mode 100644 test/hub_items.py delete mode 100644 test/hubs.py diff --git a/.codegen.json b/.codegen.json index 4ba6aa255..1d50897d2 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "3ba449c", "specHash": "ec8720b", "version": "0.1.0" } +{ "engineHash": "dd00f3f", "specHash": "ec8720b", "version": "0.1.0" } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b30e4f5a8..82c6ee5f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,6 +39,7 @@ jobs: ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }} BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }} BOX_EXTERNAL_USER_EMAIL: ${{ secrets.BOX_EXTERNAL_USER_EMAIL }} + BOX_EXTERNAL_USER_ID: ${{ secrets.BOX_EXTERNAL_USER_ID }} APP_ITEM_ASSOCIATION_FILE_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FILE_ID }} APP_ITEM_ASSOCIATION_FOLDER_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FOLDER_ID }} WORKFLOW_FOLDER_ID: ${{ secrets.WORKFLOW_FOLDER_ID }} @@ -78,6 +79,7 @@ jobs: ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }} BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }} BOX_EXTERNAL_USER_EMAIL: ${{ secrets.BOX_EXTERNAL_USER_EMAIL }} + BOX_EXTERNAL_USER_ID: ${{ secrets.BOX_EXTERNAL_USER_ID }} WORKFLOW_FOLDER_ID: ${{ secrets.WORKFLOW_FOLDER_ID }} APP_ITEM_ASSOCIATION_FILE_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FILE_ID }} APP_ITEM_ASSOCIATION_FOLDER_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FOLDER_ID }} diff --git a/README.md b/README.md index 1fd84d3da..9a1cd35fc 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Now select `Authorization` and submit application to be reviewed by account admi download your app configuration settings as JSON. 2. Encode configuration file to Base64, e.g. using command: `base64 -i path_to_json_file` 3. Set environment variable: `JWT_CONFIG_BASE_64` with base64 encoded jwt configuration file -4. Set environment variable: `BOX_FILE_REQUEST_ID` with ID of file request already created in the user account, `BOX_EXTERNAL_USER_EMAIL` with email of free external user which not belongs to any enterprise. +4. Set environment variable: `BOX_FILE_REQUEST_ID` with ID of file request already created in the user account, `BOX_EXTERNAL_USER_EMAIL` with email of free external user which not belongs to any enterprise and `BOX_EXTERNAL_USER_ID` with its ID. 5. Set environment variable: `WORKFLOW_FOLDER_ID` with the ID of the Relay workflow that deletes the file that triggered the workflow. The workflow should have a manual start to be able to start it from the API. 6. Set environment variable: `APP_ITEM_ASSOCIATION_FILE_ID` to the ID of the file with associated app item and `APP_ITEM_ASSOCIATION_FOLDER_ID` to the ID of the folder with associated app item. 7. Set environment variable: `APP_ITEM_SHARED_LINK` to the shared link associated with app item. diff --git a/docs/external_users.md b/docs/external_users.md index 77d54abb5..cb245baa4 100644 --- a/docs/external_users.md +++ b/docs/external_users.md @@ -12,7 +12,13 @@ This operation is performed by calling function `submit_job_to_delete_external_u See the endpoint docs at [API Reference](https://developer.box.com/reference/v2025.0/post-external-users-submit-delete-job/). -_Currently we don't have an example for calling `submit_job_to_delete_external_users_v2025_r0` in integration tests_ + + +```python +client.external_users.submit_job_to_delete_external_users_v2025_r0( + [UserReferenceV2025R0(id=get_env_var("BOX_EXTERNAL_USER_ID"))] +) +``` ### Arguments diff --git a/test/external_users.py b/test/external_users.py new file mode 100644 index 000000000..f5563781a --- /dev/null +++ b/test/external_users.py @@ -0,0 +1,53 @@ +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.collaboration import Collaboration + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationItem + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationItemTypeField + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationAccessibleBy + +from box_sdk_gen.managers.user_collaborations import ( + CreateCollaborationAccessibleByTypeField, +) + +from box_sdk_gen.managers.user_collaborations import CreateCollaborationRole + +from box_sdk_gen.schemas.v2025_r0.external_users_submit_delete_job_response_v2025_r0 import ( + ExternalUsersSubmitDeleteJobResponseV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.user_reference_v2025_r0 import UserReferenceV2025R0 + +from test.commons import get_default_client_with_user_subject + +from test.commons import upload_new_file + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + + +def testSubmitJobToDeleteExternalUsers(): + file: FileFull = upload_new_file() + file_collaboration: Collaboration = client.user_collaborations.create_collaboration( + CreateCollaborationItem(type=CreateCollaborationItemTypeField.FILE, id=file.id), + CreateCollaborationAccessibleBy( + type=CreateCollaborationAccessibleByTypeField.USER, + id=get_env_var('BOX_EXTERNAL_USER_ID'), + ), + CreateCollaborationRole.EDITOR, + ) + external_users_job_delete_response: ExternalUsersSubmitDeleteJobResponseV2025R0 = ( + client.external_users.submit_job_to_delete_external_users_v2025_r0( + [UserReferenceV2025R0(id=get_env_var('BOX_EXTERNAL_USER_ID'))] + ) + ) + assert len(external_users_job_delete_response.entries) == 1 + assert external_users_job_delete_response.entries[0].status == 202 + client.files.delete_file_by_id(file.id) diff --git a/test/hub_collaborations.py b/test/hub_collaborations.py deleted file mode 100644 index 5a1a108f9..000000000 --- a/test/hub_collaborations.py +++ /dev/null @@ -1,88 +0,0 @@ -from box_sdk_gen.internal.utils import to_string - -from box_sdk_gen.client import BoxClient - -from box_sdk_gen.schemas.v2025_r0.hubs_v2025_r0 import HubsV2025R0 - -from box_sdk_gen.managers.hubs import GetHubsV2025R0Direction - -from box_sdk_gen.schemas.v2025_r0.hub_v2025_r0 import HubV2025R0 - -from box_sdk_gen.schemas.user_full import UserFull - -from box_sdk_gen.schemas.v2025_r0.hub_collaboration_v2025_r0 import ( - HubCollaborationV2025R0, -) - -from box_sdk_gen.managers.hub_collaborations import CreateHubCollaborationV2025R0Hub - -from box_sdk_gen.managers.hub_collaborations import ( - CreateHubCollaborationV2025R0AccessibleBy, -) - -from box_sdk_gen.schemas.v2025_r0.hub_collaborations_v2025_r0 import ( - HubCollaborationsV2025R0, -) - -from test.commons import get_default_client_with_user_subject - -from box_sdk_gen.internal.utils import get_env_var - -from box_sdk_gen.internal.utils import get_uuid - -client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) - - -def testCRUDHubCollaboration(): - hubs: HubsV2025R0 = client.hubs.get_hubs_v2025_r0( - scope='all', sort='name', direction=GetHubsV2025R0Direction.ASC - ) - hub: HubV2025R0 = hubs.entries[0] - user_name: str = get_uuid() - user_login: str = ''.join([get_uuid(), '@gmail.com']) - user: UserFull = client.users.create_user( - user_name, login=user_login, is_platform_access_only=True - ) - created_collaboration: HubCollaborationV2025R0 = ( - client.hub_collaborations.create_hub_collaboration_v2025_r0( - CreateHubCollaborationV2025R0Hub(id=hub.id), - CreateHubCollaborationV2025R0AccessibleBy(type='user', id=user.id), - 'viewer', - ) - ) - assert not created_collaboration.id == '' - assert to_string(created_collaboration.type) == 'hub_collaboration' - assert created_collaboration.hub.id == hub.id - assert to_string(created_collaboration.accessible_by.type) == 'user' - assert created_collaboration.accessible_by.id == user.id - assert created_collaboration.role == 'viewer' - updated_collaboration: HubCollaborationV2025R0 = ( - client.hub_collaborations.update_hub_collaboration_by_id_v2025_r0( - created_collaboration.id, role='editor' - ) - ) - assert not updated_collaboration.id == '' - assert to_string(updated_collaboration.type) == 'hub_collaboration' - assert updated_collaboration.hub.id == hub.id - assert to_string(updated_collaboration.accessible_by.type) == 'user' - assert updated_collaboration.accessible_by.id == user.id - assert updated_collaboration.role == 'editor' - collaborations: HubCollaborationsV2025R0 = ( - client.hub_collaborations.get_hub_collaborations_v2025_r0(hub.id) - ) - assert len(collaborations.entries) >= 1 - retrieved_collaboration: HubCollaborationV2025R0 = ( - client.hub_collaborations.get_hub_collaboration_by_id_v2025_r0( - created_collaboration.id - ) - ) - assert retrieved_collaboration.id == created_collaboration.id - assert to_string(retrieved_collaboration.type) == 'hub_collaboration' - assert retrieved_collaboration.hub.id == hub.id - assert to_string(retrieved_collaboration.accessible_by.type) == 'user' - assert retrieved_collaboration.accessible_by.id == user.id - assert retrieved_collaboration.role == 'editor' - client.hub_collaborations.delete_hub_collaboration_by_id_v2025_r0( - created_collaboration.id - ) - client.users.delete_user_by_id(user.id) diff --git a/test/hub_items.py b/test/hub_items.py deleted file mode 100644 index 8e843929e..000000000 --- a/test/hub_items.py +++ /dev/null @@ -1,93 +0,0 @@ -from box_sdk_gen.internal.utils import to_string - -from box_sdk_gen.client import BoxClient - -from box_sdk_gen.schemas.folder_full import FolderFull - -from box_sdk_gen.managers.folders import CreateFolderParent - -from box_sdk_gen.schemas.v2025_r0.hub_v2025_r0 import HubV2025R0 - -from box_sdk_gen.schemas.v2025_r0.hub_items_v2025_r0 import HubItemsV2025R0 - -from box_sdk_gen.schemas.v2025_r0.hub_items_manage_response_v2025_r0 import ( - HubItemsManageResponseV2025R0, -) - -from box_sdk_gen.schemas.v2025_r0.hub_item_operation_v2025_r0 import ( - HubItemOperationV2025R0, -) - -from box_sdk_gen.schemas.v2025_r0.hub_item_operation_v2025_r0 import ( - HubItemOperationV2025R0ActionField, -) - -from box_sdk_gen.schemas.v2025_r0.hub_item_operation_result_v2025_r0 import ( - HubItemOperationResultV2025R0, -) - -from test.commons import get_default_client_with_user_subject - -from box_sdk_gen.internal.utils import get_env_var - -from box_sdk_gen.internal.utils import get_uuid - -from box_sdk_gen.schemas.v2025_r0.folder_reference_v2025_r0 import ( - FolderReferenceV2025R0, -) - -client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) - - -def testCreateDeleteGetHubItems(): - hub_title: str = get_uuid() - folder: FolderFull = client.folders.create_folder( - get_uuid(), CreateFolderParent(id='0') - ) - created_hub: HubV2025R0 = client.hubs.create_hub_v2025_r0(hub_title) - hub_items_before_add: HubItemsV2025R0 = client.hub_items.get_hub_items_v2025_r0( - created_hub.id - ) - assert len(hub_items_before_add.entries) == 0 - added_hub_items: HubItemsManageResponseV2025R0 = ( - client.hub_items.manage_hub_items_v2025_r0( - created_hub.id, - operations=[ - HubItemOperationV2025R0( - action=HubItemOperationV2025R0ActionField.ADD, - item=FolderReferenceV2025R0(id=folder.id), - ) - ], - ) - ) - added_hub_item: HubItemOperationResultV2025R0 = added_hub_items.operations[0] - assert to_string(added_hub_item.action) == 'add' - assert added_hub_item.status == 200 - assert added_hub_item.item.id == folder.id - assert added_hub_item.item.type == 'folder' - hub_items_after_add: HubItemsV2025R0 = client.hub_items.get_hub_items_v2025_r0( - created_hub.id - ) - assert len(hub_items_after_add.entries) == 1 - removed_hub_items: HubItemsManageResponseV2025R0 = ( - client.hub_items.manage_hub_items_v2025_r0( - created_hub.id, - operations=[ - HubItemOperationV2025R0( - action=HubItemOperationV2025R0ActionField.REMOVE, - item=FolderReferenceV2025R0(id=folder.id), - ) - ], - ) - ) - removed_hub_item: HubItemOperationResultV2025R0 = removed_hub_items.operations[0] - assert to_string(removed_hub_item.action) == 'remove' - assert removed_hub_item.status == 200 - assert removed_hub_item.item.id == folder.id - assert removed_hub_item.item.type == 'folder' - hub_items_after_remove: HubItemsV2025R0 = client.hub_items.get_hub_items_v2025_r0( - created_hub.id - ) - assert len(hub_items_after_remove.entries) == 0 - client.hubs.delete_hub_by_id_v2025_r0(created_hub.id) - client.folders.delete_folder_by_id(folder.id) diff --git a/test/hubs.py b/test/hubs.py deleted file mode 100644 index bc61cf7bc..000000000 --- a/test/hubs.py +++ /dev/null @@ -1,74 +0,0 @@ -from box_sdk_gen.internal.utils import to_string - -import pytest - -from box_sdk_gen.client import BoxClient - -from box_sdk_gen.schemas.v2025_r0.hub_v2025_r0 import HubV2025R0 - -from box_sdk_gen.schemas.v2025_r0.hubs_v2025_r0 import HubsV2025R0 - -from box_sdk_gen.managers.hubs import GetHubsV2025R0Direction - -from box_sdk_gen.managers.hubs import GetEnterpriseHubsV2025R0Direction - -from test.commons import get_default_client_with_user_subject - -from box_sdk_gen.internal.utils import get_env_var - -from box_sdk_gen.internal.utils import get_uuid - -client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) - - -def testCreateUpdateGetAndDeleteHubs(): - hub_title: str = get_uuid() - hub_description: str = 'new Hub description' - created_hub: HubV2025R0 = client.hubs.create_hub_v2025_r0( - hub_title, description=hub_description - ) - assert created_hub.title == hub_title - assert created_hub.description == hub_description - assert to_string(created_hub.type) == 'hubs' - hub_id: str = created_hub.id - users_hubs: HubsV2025R0 = client.hubs.get_hubs_v2025_r0( - scope='all', sort='name', direction=GetHubsV2025R0Direction.ASC - ) - assert len(users_hubs.entries) > 0 - enterprise_hubs: HubsV2025R0 = client.hubs.get_enterprise_hubs_v2025_r0( - sort='name', direction=GetEnterpriseHubsV2025R0Direction.ASC - ) - assert len(enterprise_hubs.entries) > 0 - hub_by_id: HubV2025R0 = client.hubs.get_hub_by_id_v2025_r0(hub_id) - assert hub_by_id.id == hub_id - assert hub_by_id.title == hub_title - assert hub_by_id.description == hub_description - assert to_string(hub_by_id.type) == 'hubs' - new_hub_title: str = get_uuid() - new_hub_description: str = 'updated Hub description' - updated_hub: HubV2025R0 = client.hubs.update_hub_by_id_v2025_r0( - hub_id, title=new_hub_title, description=new_hub_description - ) - assert updated_hub.title == new_hub_title - assert updated_hub.description == new_hub_description - client.hubs.delete_hub_by_id_v2025_r0(hub_id) - with pytest.raises(Exception): - client.hubs.delete_hub_by_id_v2025_r0(hub_id) - - -def copyHub(): - hub_title: str = get_uuid() - hub_description: str = 'new Hub description' - created_hub: HubV2025R0 = client.hubs.create_hub_v2025_r0( - hub_title, description=hub_description - ) - copied_hub_title: str = get_uuid() - copied_hub_description: str = 'copied Hub description' - copied_hub: HubV2025R0 = client.hubs.copy_hub_v2025_r0( - created_hub.id, title=copied_hub_title, description=copied_hub_description - ) - assert not copied_hub.id == created_hub.id - assert copied_hub.title == copied_hub_title - assert copied_hub.description == copied_hub_description - client.hubs.delete_hub_by_id_v2025_r0(created_hub.id) - client.hubs.delete_hub_by_id_v2025_r0(copied_hub.id) diff --git a/tox.ini b/tox.ini index 93cda9389..18a3da4b3 100644 --- a/tox.ini +++ b/tox.ini @@ -22,13 +22,13 @@ commands = pytest {posargs} --disable-pytest-warnings --reruns 2 deps = -rrequirements-test.txt allowlist_externals = pytest -passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID +passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,BOX_EXTERNAL_USER_ID,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID [testenv:pycodestyle] -commands = +commands = pycodestyle --ignore=E501,W292 box_sdk_gen setup.py pycodestyle --ignore=E501,W292 test -deps = +deps = pycodestyle [testenv:pylint] @@ -36,7 +36,7 @@ commands = pylint --rcfile=.pylintrc box_sdk_gen setup.py # pylint:disable W0621(redefined-outer-name) - Using py.test fixtures always breaks this rule. pylint --rcfile=.pylintrc test -d W0621 --ignore=mock_box -deps = +deps = pylint -rrequirements-test.txt @@ -44,10 +44,10 @@ deps = basepython = python3.11 commands = py.test --reruns 2 --cov box_sdk_gen --cov-report term-missing test/ -deps = +deps = coverage -rrequirements-test.txt -passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID +passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,BOX_EXTERNAL_USER_ID,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID [testenv:py311-build] description = Build the source and binary wheel packages for distribution. From 1e83452e7259527b484f28ad17b9b59e37350df7 Mon Sep 17 00:00:00 2001 From: Minh Nguyen Cong Date: Thu, 28 Aug 2025 16:13:37 +0200 Subject: [PATCH 015/139] chore: Fix auto update PR workflow (box/box-codegen#810) (#977) --- .codegen.json | 2 +- .github/workflows/autoupdate-pr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 1d50897d2..c1132aff1 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "dd00f3f", "specHash": "ec8720b", "version": "0.1.0" } +{ "engineHash": "91b0f90", "specHash": "ec8720b", "version": "0.1.0" } diff --git a/.github/workflows/autoupdate-pr.yml b/.github/workflows/autoupdate-pr.yml index 080c51c32..bbf7ad303 100644 --- a/.github/workflows/autoupdate-pr.yml +++ b/.github/workflows/autoupdate-pr.yml @@ -27,7 +27,7 @@ jobs: PR_LIST=$(curl -s -H "Authorization: Bearer ${{ secrets.DISPATCH_ACCESS_TOKEN }}" "https://api.github.com/repos/$GITHUB_REPOSITORY/pulls?state=open" | jq -r '.[] | .head.ref') for pr_branch in $PR_LIST; do git checkout "$pr_branch" - if git merge origin/main; then + if git merge origin/sdk-gen; then git push else # Conflict occurred, resolve by keeping our changes From 90ccba0dde2274cf47b5f3d16ceed45e466b1008 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:11:34 +0200 Subject: [PATCH 016/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#980) * chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] * fix: Fix autoupdate PR workflow (box/box-codegen#810) * chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] * chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] * chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] --------- Co-authored-by: box-sdk-build --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index c1132aff1..81fc6d31b 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "91b0f90", "specHash": "ec8720b", "version": "0.1.0" } +{ "engineHash": "c0b8367", "specHash": "ec8720b", "version": "0.1.0" } From a37adc6095bac002b71759112b8df5b26c5b6c92 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:54:01 +0200 Subject: [PATCH 017/139] docs: Add Migration guide (box/box-codegen#816) (#981) --- .codegen.json | 2 +- README.md | 2 +- ...python-sdk-gen-v1-to-box-python-sdk-v10.md | 32 +++ .../from-v3-to-v10.md | 253 ++++++++++-------- 4 files changed, 172 insertions(+), 117 deletions(-) create mode 100644 migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md rename migration-guide.md => migration-guides/from-v3-to-v10.md (78%) diff --git a/.codegen.json b/.codegen.json index 81fc6d31b..23770fac0 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "c0b8367", "specHash": "ec8720b", "version": "0.1.0" } +{ "engineHash": "aa448e3", "specHash": "ec8720b", "version": "0.1.0" } diff --git a/README.md b/README.md index 9a1cd35fc..fd823b33d 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Browse the [docs](docs/README.md) or see [API Reference](https://developer.box.c # Upgrades The SDK is updated regularly to include new features, enhancements, and bug fixes. -If you are upgrading from manual v3 SDK version to this new generated v10, checkout the [migration guide](migration-guide.md) and [changelog](CHANGELOG.md) for more information. +If you are upgrading from manual v3 SDK version to this new generated v10, checkout the [migration guide](migration-guides/from-v3-to-v10.md) and [changelog](CHANGELOG.md) for more information. # Integration Tests diff --git a/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md b/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md new file mode 100644 index 000000000..afab2bbf3 --- /dev/null +++ b/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md @@ -0,0 +1,32 @@ +# Migration guide from v1 version of the `box-python-sdk-gen` to the v10 version of the `box-pythom-sdk` + + + + +- [Installation](#installation) + - [How to migrate](#how-to-migrate) + + + +## Installation + +In order to start using v10 version of the Box Python SDK, you need to change the dependency in your project. +The artifact name has changed from `box-sdk-gen` to `boxsdk`. +You also need to set the version to `10.0.0` or higher. You can find the latest version on [PyPI](https://pypi.org/project/boxsdk/). + +### How to migrate + +To start using v10 version of Box Python SDK in you Maven project replace the dependency in your `requirements.txt` +or installation command. + +**Old (`box-python-sdk-gen-v1`)** + +```console +pip install box-sdk-gen +``` + +**New (`box-python-sdk-v10`)** + +```console +pip install boxsdk>=10 +``` diff --git a/migration-guide.md b/migration-guides/from-v3-to-v10.md similarity index 78% rename from migration-guide.md rename to migration-guides/from-v3-to-v10.md index 0ca08dcb0..5d151c3f4 100644 --- a/migration-guide.md +++ b/migration-guides/from-v3-to-v10.md @@ -49,7 +49,7 @@ More information and benefits of using the new can be found in the To install v10 version of Box Python SDK use command: ```console -pip install box-sdk-gen>=10 +pip install boxsdk>=10 ``` Soon we are going to introduce v4 version of Box Python SDK that will combine package `boxsdk` from @@ -71,19 +71,19 @@ Firstly, in the v3 release to be able to perform any action on an API object, e. To do it is required to call: ```python -user = client.user(user_id='123456') +user = client.user(user_id="123456") ``` to create a class representing an already existing User with id '12345', or create a new one with a call: ```python -user = client.create_user(name='Some User') +user = client.create_user(name="Some User") ``` Then, you could perform any action on created class, which will affect the user, e.g. ```python -updated_user = user.update_info(data={'name': 'New User Name'}) +updated_user = user.update_info(data={"name": "New User Name"}) ``` **New (`v10`)** @@ -96,13 +96,13 @@ connected with a `User` you need to call a respective method of `UserManager`. For example, to get info about existing user you need to call: ```python -user = client.users.get_user_by_id(user_id='123456') +user = client.users.get_user_by_id(user_id="123456") ``` or to create a new user: ```python -user = client.users.create_user(name='Some User') +user = client.users.create_user(name="Some User") ``` The `User` object returned by both of these methods is a data class - it does not contain any methods to call. @@ -111,7 +111,7 @@ Usually these methods have a first argument, which accepts id of the object you e.g. to update a user name, call method: ```python -updated_user = client.users.update_user_by_id(user_id=user.id, name='New User Name') +updated_user = client.users.update_user_by_id(user_id=user.id, name="New User Name") ``` ### Explicitly defined schemas @@ -122,7 +122,7 @@ In v3 release, there were no data types explicitly defined - the responses were dynamically mapped into classes in the runtime. For example, if you get information about a file: ```python -file = client.file(file_id='12345678').get() +file = client.file(file_id="12345678").get() ``` you couldn't be sure which fields to expect in the response object until the runtime, @@ -135,7 +135,14 @@ actually making a call. For example `FileBase` class is defined this way: ```python class FileBase(BaseObject): - def __init__(self, id: str, *, etag: Optional[str] = None, type: FileBaseTypeField = FileBaseTypeField.FILE.value, **kwargs): + def __init__( + self, + id: str, + *, + etag: Optional[str] = None, + type: FileBaseTypeField = FileBaseTypeField.FILE.value, + **kwargs + ): super().__init__(**kwargs) self.id = id self.type = type @@ -154,7 +161,7 @@ Methods, which returns a new modified instance of an object, will always have a ```python from box_sdk_gen import BoxClient -as_user_client: BoxClient = client.with_as_user_header('USER_ID') +as_user_client: BoxClient = client.with_as_user_header("USER_ID") ``` ## Authentication @@ -170,9 +177,9 @@ Let's see the differences of their usage: from boxsdk import Client, OAuth2 auth = OAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - access_token='DEVELOPER_TOKEN_GOES_HERE', + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + access_token="DEVELOPER_TOKEN_GOES_HERE", ) client = Client(auth) ``` @@ -185,7 +192,7 @@ using developer token without necessity to provide a Client ID and Client Secret ```python from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth -auth = BoxDeveloperTokenAuth(token='DEVELOPER_TOKEN_GOES_HERE') +auth = BoxDeveloperTokenAuth(token="DEVELOPER_TOKEN_GOES_HERE") client = BoxClient(auth=auth) ``` @@ -200,7 +207,7 @@ The static method, which reads the JWT configuration file has been changed: ```python from boxsdk import JWTAuth, Client -auth = JWTAuth.from_settings_file('/path/to/config.json') +auth = JWTAuth.from_settings_file("/path/to/config.json") client = Client(auth) ``` @@ -209,7 +216,7 @@ client = Client(auth) ```python from box_sdk_gen import BoxClient, BoxJWTAuth, JWTConfig -jwt_config = JWTConfig.from_config_file(config_file_path='/path/to/config.json') +jwt_config = JWTConfig.from_config_file(config_file_path="/path/to/config.json") auth = BoxJWTAuth(config=jwt_config) client = BoxClient(auth=auth) ``` @@ -224,14 +231,14 @@ Some params in `JWTConfig` constructor have slightly different names than one in from boxsdk import JWTAuth auth = JWTAuth( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - enterprise_id='YOUR_ENTERPRISE_ID', - user_id='USER_ID', - jwt_key_id='YOUR_JWT_KEY_ID', - rsa_private_key_file_sys_path='CERT.PEM', - rsa_private_key_passphrase='PASSPHRASE', - jwt_algorithm='RS256', + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + enterprise_id="YOUR_ENTERPRISE_ID", + user_id="USER_ID", + jwt_key_id="YOUR_JWT_KEY_ID", + rsa_private_key_file_sys_path="CERT.PEM", + rsa_private_key_passphrase="PASSPHRASE", + jwt_algorithm="RS256", ) ``` @@ -241,13 +248,13 @@ auth = JWTAuth( from box_sdk_gen import BoxJWTAuth, JWTConfig, JwtAlgorithm jwt_config = JWTConfig( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - enterprise_id='YOUR_ENTERPRISE_ID', - user_id='USER_ID', - jwt_key_id='YOUR_JWT_KEY_ID', - private_key='YOUR_PRIVATE_KEY', - private_key_passphrase='PASSPHRASE', + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + enterprise_id="YOUR_ENTERPRISE_ID", + user_id="USER_ID", + jwt_key_id="YOUR_JWT_KEY_ID", + private_key="YOUR_PRIVATE_KEY", + private_key_passphrase="PASSPHRASE", algorithm=JwtAlgorithm.RS256, ) auth = BoxJWTAuth(config=jwt_config) @@ -269,7 +276,7 @@ auth.authenticate_user(user) or ```python -auth.authenticate_user('USER_ID') +auth.authenticate_user("USER_ID") ``` **New (`v10`)** @@ -283,7 +290,8 @@ instance. ```python from box_sdk_gen import BoxJWTAuth, BoxClient -user_auth: BoxJWTAuth = auth.with_user_subject('USER_ID') + +user_auth: BoxJWTAuth = auth.with_user_subject("USER_ID") user_client: BoxClient = BoxClient(auth=user_auth) ``` @@ -333,9 +341,7 @@ only User ID instead. from boxsdk import CCGAuth auth = CCGAuth( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - user="YOUR_USER_ID" + client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", user="YOUR_USER_ID" ) ``` @@ -345,9 +351,9 @@ auth = CCGAuth( from box_sdk_gen import BoxCCGAuth, CCGConfig ccg_config = CCGConfig( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - user_id="YOUR_USER_ID" + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + user_id="YOUR_USER_ID", ) auth = BoxCCGAuth(config=ccg_config) ``` @@ -360,11 +366,11 @@ were modifying existing state of `CCGAuth` class, which was fetching a new token **Old (`v3`)** ```python -auth.authenticate_enterprise('ENTERPRISE_ID') +auth.authenticate_enterprise("ENTERPRISE_ID") ``` ```python -auth.authenticate_user('USER_ID') +auth.authenticate_user("USER_ID") ``` In the new release, to keep the immutability design, the methods switching authenticated subject were replaced with methods @@ -377,13 +383,17 @@ The old instance of `BoxCCGAuth` class will remain unchanged and will still use ```python from box_sdk_gen import BoxCCGAuth, BoxClient -enterprise_auth: BoxCCGAuth = auth.with_enterprise_subject(enterprise_id='ENTERPRISE_ID') + +enterprise_auth: BoxCCGAuth = auth.with_enterprise_subject( + enterprise_id="ENTERPRISE_ID" +) enterprise_client: BoxClient = BoxClient(auth=enterprise_auth) ``` ```python from box_sdk_gen import BoxCCGAuth, BoxClient -user_auth: BoxCCGAuth = auth.with_user_subject(user_id='USER_ID') + +user_auth: BoxCCGAuth = auth.with_user_subject(user_id="USER_ID") user_client: BoxClient = BoxClient(auth=user_auth) ``` @@ -408,11 +418,11 @@ authentication url and csrf_token. from boxsdk import OAuth2 auth = OAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", ) -auth_url, csrf_token = auth.get_authorization_url('http://YOUR_REDIRECT_URL') +auth_url, csrf_token = auth.get_authorization_url("http://YOUR_REDIRECT_URL") ``` **New (`v10`)** @@ -421,12 +431,14 @@ auth_url, csrf_token = auth.get_authorization_url('http://YOUR_REDIRECT_URL') from box_sdk_gen import BoxOAuth, OAuthConfig, GetAuthorizeUrlOptions auth = BoxOAuth( - OAuthConfig( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - ) + OAuthConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) +) +auth_url = auth.get_authorize_url( + options=GetAuthorizeUrlOptions(redirect_uri="http://YOUR_REDIRECT_URL") ) -auth_url = auth.get_authorize_url(options=GetAuthorizeUrlOptions(redirect_uri='http://YOUR_REDIRECT_URL')) ``` #### Authenticate @@ -441,7 +453,8 @@ while the old one was returning a tuple of access token and refresh token. ```python from boxsdk import Client -access_token, refresh_token = auth.authenticate('YOUR_AUTH_CODE') + +access_token, refresh_token = auth.authenticate("YOUR_AUTH_CODE") client = Client(auth) ``` @@ -450,7 +463,7 @@ client = Client(auth) ```python from box_sdk_gen import BoxClient, AccessToken -access_token: AccessToken = auth.get_tokens_authorization_code_grant('YOUR_AUTH_CODE') +access_token: AccessToken = auth.get_tokens_authorization_code_grant("YOUR_AUTH_CODE") client = BoxClient(auth) ``` @@ -470,22 +483,24 @@ from typing import Tuple from boxsdk.auth import CooperativelyManagedOAuth2 from boxsdk import Client + def retrieve_tokens() -> Tuple[str, str]: # retrieve access_token and refresh_token return access_token, refresh_token + def store_tokens(access_token: str, refresh_token: str): # store access_token and refresh_token pass auth = CooperativelyManagedOAuth2( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - retrieve_tokens=retrieve_tokens, - store_tokens=store_tokens + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + retrieve_tokens=retrieve_tokens, + store_tokens=store_tokens, ) -access_token, refresh_token = auth.authenticate('YOUR_AUTH_CODE') +access_token, refresh_token = auth.authenticate("YOUR_AUTH_CODE") client = Client(auth) ``` @@ -499,26 +514,27 @@ AuthConfig constructor. from typing import Optional from box_sdk_gen import BoxOAuth, OAuthConfig, TokenStorage, AccessToken + class MyCustomTokenStorage(TokenStorage): - def store(self, token: AccessToken) -> None: - # store token - pass + def store(self, token: AccessToken) -> None: + # store token + pass - def get(self) -> Optional[AccessToken]: - # get token - pass + def get(self) -> Optional[AccessToken]: + # get token + pass - def clear(self) -> None: - # clear token - pass + def clear(self) -> None: + # clear token + pass auth = BoxOAuth( - OAuthConfig( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - token_storage=MyCustomTokenStorage() - ) + OAuthConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + token_storage=MyCustomTokenStorage(), + ) ) ``` @@ -528,11 +544,11 @@ or reuse one of the provided implementations: `FileTokenStorage` or `FileWithInM from box_sdk_gen import BoxOAuth, OAuthConfig, FileWithInMemoryCacheTokenStorage auth = BoxOAuth( - OAuthConfig( - client_id='YOUR_CLIENT_ID', - client_secret='YOUR_CLIENT_SECRET', - token_storage=FileWithInMemoryCacheTokenStorage() - ) + OAuthConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + token_storage=FileWithInMemoryCacheTokenStorage(), + ) ) ``` @@ -546,12 +562,10 @@ accepts the full resource path instead of file object. ```python from boxsdk import Client, OAuth2 -target_file = client.file(file_id='FILE_ID_HERE') -token_info = client.downscope_token(['item_preview'], target_file) +target_file = client.file(file_id="FILE_ID_HERE") +token_info = client.downscope_token(["item_preview"], target_file) downscoped_auth = OAuth2( - client_id=None, - client_secret=None, - access_token=token_info.access_token + client_id=None, client_secret=None, access_token=token_info.access_token ) downscoped_client = Client(downscoped_auth) ``` @@ -561,9 +575,9 @@ downscoped_client = Client(downscoped_auth) ```python from box_sdk_gen import BoxDeveloperTokenAuth, AccessToken, BoxClient -resource = 'https://api.box.com/2.0/files/123456789' +resource = "https://api.box.com/2.0/files/123456789" downscoped_token: AccessToken = auth.downscope_token( - scopes=['item_preview'], + scopes=["item_preview"], resource=resource, ) downscoped_auth = BoxDeveloperTokenAuth(token=downscoped_token.access_token) @@ -602,7 +616,7 @@ In v3 you could call client `as_user(self, user: User)` method to create a new c ```python from boxsdk import Client -user_to_impersonate = client.user(user_id='USER_ID') +user_to_impersonate = client.user(user_id="USER_ID") user_client: Client = client.as_user(user_to_impersonate) ``` @@ -615,7 +629,7 @@ The method accepts only user id as a parameter. ```python from box_sdk_gen import BoxClient -user_client: BoxClient = client.with_as_user_header(user_id='USER_ID') +user_client: BoxClient = client.with_as_user_header(user_id="USER_ID") ``` Additionally `BoxClient` offers a `with_extra_headers(self, *, extra_headers: Dict[str, str] = None) -> BoxClient` @@ -625,7 +639,9 @@ Calling the `client.with_extra_headers()` method creates a new client, leaving t ```python from box_sdk_gen import BoxClient -new_client: BoxClient = client.with_extra_headers(extra_headers={'customHeader': 'customValue'}) +new_client: BoxClient = client.with_extra_headers( + extra_headers={"customHeader": "customValue"} +) ``` ### Custom Base URLs @@ -638,9 +654,9 @@ the new values of static variables of the `API` class. ```python from boxsdk.config import API -API.BASE_API_URL = 'https://new-base-url.com' -API.OAUTH2_API_URL = 'https://my-company.com/oauth2' -API.UPLOAD_URL = 'https://my-company-upload-url.com' +API.BASE_API_URL = "https://new-base-url.com" +API.OAUTH2_API_URL = "https://my-company.com/oauth2" +API.UPLOAD_URL = "https://my-company-upload-url.com" ``` **New (`v10`)** @@ -652,11 +668,13 @@ calls made by client. Following the immutability pattern, this call creates a ne ```python from box_sdk_gen import BoxClient, BaseUrls -new_client: BoxClient = client.with_custom_base_urls(base_urls=BaseUrls( - base_url='https://new-base-url.com', - upload_url='https://my-company-upload-url.com', - oauth_2_url='https://my-company.com/oauth2', -)) +new_client: BoxClient = client.with_custom_base_urls( + base_urls=BaseUrls( + base_url="https://new-base-url.com", + upload_url="https://my-company-upload-url.com", + oauth_2_url="https://my-company.com/oauth2", + ) +) ``` ## Convenience methods @@ -672,15 +690,15 @@ In the v3 version of Box Python SDK, you could pass the `body` as `bytes`, and i ```python body = b'{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' headers = { - 'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f', - 'box-delivery-timestamp': '2020-01-01T00:00:00-07:00', - 'box-signature-algorithm': 'HmacSHA256', - 'box-signature-primary': '4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=', - 'box-signature-secondary': 'yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=', - 'box-signature-version': '1', + "box-delivery-id": "f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f", + "box-delivery-timestamp": "2020-01-01T00:00:00-07:00", + "box-signature-algorithm": "HmacSHA256", + "box-signature-primary": "4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=", + "box-signature-secondary": "yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=", + "box-signature-version": "1", } is_validated = Webhook.validate_message(body, headers, primary_key, secondary_key) -print(f'The webhook message is validated to: {is_validated}') +print(f"The webhook message is validated to: {is_validated}") ``` **New (`v10`)** @@ -693,15 +711,15 @@ from box_sdk_gen import WebhooksManager body = '{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' headers = { - 'box-delivery-id': 'f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f', - 'box-delivery-timestamp': '2020-01-01T00:00:00-07:00', - 'box-signature-algorithm': 'HmacSHA256', - 'box-signature-primary': '4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=', - 'box-signature-secondary': 'yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=', - 'box-signature-version': '1', + "box-delivery-id": "f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f", + "box-delivery-timestamp": "2020-01-01T00:00:00-07:00", + "box-signature-algorithm": "HmacSHA256", + "box-signature-primary": "4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=", + "box-signature-secondary": "yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=", + "box-signature-version": "1", } WebhooksManager.validate_message( - body=body, headers=headers, primary_key=primary_key, secondary_key=secondary_key + body=body, headers=headers, primary_key=primary_key, secondary_key=secondary_key ) ``` @@ -717,7 +735,9 @@ Then, you would call the `start()` method to begin the upload process. The `get_chunked_uploader()` method requires the `file_path` and `file_name` parameters. ```python -chunked_uploader = client.folder('0').get_chunked_uploader(file_path='/path/to/file.txt', file_name='new_name.txt') +chunked_uploader = client.folder("0").get_chunked_uploader( + file_path="/path/to/file.txt", file_name="new_name.txt" +) uploaded_file = chunked_uploader.start() print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}') ``` @@ -731,11 +751,14 @@ The `parent_folder_id` parameter is also required to specify the folder where th ```python import os -with open('/path/to/file.txt', 'rb') as file_byte_stream: - file_name = 'new_name.txt' - file_size = os.path.getsize('/path/to/file.txt') - parent_folder_id = '0' # ID of the folder where the file will be uploaded +with open("/path/to/file.txt", "rb") as file_byte_stream: + file_name = "new_name.txt" + file_size = os.path.getsize("/path/to/file.txt") + parent_folder_id = "0" # ID of the folder where the file will be uploaded uploaded_file = client.chunked_uploads.upload_big_file( - file=file_byte_stream, file_name=file_name, file_size=file_size, parent_folder_id=parent_folder_id + file=file_byte_stream, + file_name=file_name, + file_size=file_size, + parent_folder_id=parent_folder_id, ) ``` From 9b593e655c7f80a41d7aefa219c97158f55591cf Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:57:09 +0200 Subject: [PATCH 018/139] docs: Brand Hubs as Box Hubs (box/box-openapi#553) (#986) --- .codegen.json | 2 +- box_sdk_gen/managers/hub_collaborations.py | 18 ++--- box_sdk_gen/managers/hub_items.py | 6 +- box_sdk_gen/managers/hubs.py | 40 +++++------ .../schemas/v2025_r0/hub_base_v2025_r0.py | 6 +- ...b_collaboration_create_request_v2025_r0.py | 4 +- ...b_collaboration_update_request_v2025_r0.py | 2 +- .../v2025_r0/hub_collaboration_v2025_r0.py | 2 +- .../v2025_r0/hub_collaborations_v2025_r0.py | 2 +- .../v2025_r0/hub_copy_request_v2025_r0.py | 4 +- .../v2025_r0/hub_create_request_v2025_r0.py | 4 +- .../v2025_r0/hub_item_operation_v2025_r0.py | 2 +- .../hub_items_manage_request_v2025_r0.py | 2 +- .../hub_items_manage_response_v2025_r0.py | 2 +- .../schemas/v2025_r0/hub_items_v2025_r0.py | 2 +- .../v2025_r0/hub_update_request_v2025_r0.py | 10 +-- box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py | 20 +++--- docs/hub_collaborations.md | 48 ++++++------- docs/hub_items.md | 16 ++--- docs/hubs.md | 72 +++++++++---------- 20 files changed, 132 insertions(+), 132 deletions(-) diff --git a/.codegen.json b/.codegen.json index 23770fac0..a75d41fbe 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "aa448e3", "specHash": "ec8720b", "version": "0.1.0" } +{ "engineHash": "fa469c0", "specHash": "60778fb", "version": "0.1.0" } diff --git a/box_sdk_gen/managers/hub_collaborations.py b/box_sdk_gen/managers/hub_collaborations.py index 7950575f9..158617823 100644 --- a/box_sdk_gen/managers/hub_collaborations.py +++ b/box_sdk_gen/managers/hub_collaborations.py @@ -133,7 +133,7 @@ def get_hub_collaborations_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubCollaborationsV2025R0: """ - Retrieves all collaborations for a hub. + Retrieves all collaborations for a Box Hub. :param hub_id: The unique identifier that represent a hub. The ID for any hub can be determined @@ -191,15 +191,15 @@ def create_hub_collaboration_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubCollaborationV2025R0: """ - Adds a collaboration for a single user or a single group to a hub. + Adds a collaboration for a single user or a single group to a Box Hub. Collaborations can be created using email address, user IDs, or group IDs. - :param hub: Hubs reference. + :param hub: Box Hubs reference. :type hub: CreateHubCollaborationV2025R0Hub :param accessible_by: The user or group who gets access to the item. :type accessible_by: CreateHubCollaborationV2025R0AccessibleBy - :param role: The level of access granted to hub. + :param role: The level of access granted to a Box Hub. Possible values are `editor`, `viewer`, and `co-owner`. :type role: str :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 @@ -237,7 +237,7 @@ def get_hub_collaboration_by_id_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubCollaborationV2025R0: """ - Retrieves details for a hub collaboration by collaboration ID. + Retrieves details for a Box Hub collaboration by collaboration ID. :param hub_collaboration_id: The ID of the hub collaboration. Example: "1234" :type hub_collaboration_id: str @@ -278,14 +278,14 @@ def update_hub_collaboration_by_id_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubCollaborationV2025R0: """ - Updates a hub collaboration. + Updates a Box Hub collaboration. - Can be used to change the hub role. + Can be used to change the Box Hub role. :param hub_collaboration_id: The ID of the hub collaboration. Example: "1234" :type hub_collaboration_id: str - :param role: The level of access granted to hub. + :param role: The level of access granted to a Box Hub. Possible values are `editor`, `viewer`, and `co-owner`., defaults to None :type role: Optional[str], optional :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 @@ -327,7 +327,7 @@ def delete_hub_collaboration_by_id_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> None: """ - Deletes a single hub collaboration. + Deletes a single Box Hub collaboration. :param hub_collaboration_id: The ID of the hub collaboration. Example: "1234" :type hub_collaboration_id: str diff --git a/box_sdk_gen/managers/hub_items.py b/box_sdk_gen/managers/hub_items.py index 4ad4e448f..a05cc2ef5 100644 --- a/box_sdk_gen/managers/hub_items.py +++ b/box_sdk_gen/managers/hub_items.py @@ -75,7 +75,7 @@ def get_hub_items_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubItemsV2025R0: """ - Retrieves all items associated with a Hub. + Retrieves all items associated with a Box Hub. :param hub_id: The unique identifier that represent a hub. The ID for any hub can be determined @@ -132,7 +132,7 @@ def manage_hub_items_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubItemsManageResponseV2025R0: """ - Adds and/or removes Hub items from a Hub. + Adds and/or removes Box Hub items from a Box Hub. :param hub_id: The unique identifier that represent a hub. The ID for any hub can be determined @@ -142,7 +142,7 @@ def manage_hub_items_v2025_r0( the `hub_id` is `123`. Example: "12345" :type hub_id: str - :param operations: List of operations to perform on Hub items., defaults to None + :param operations: List of operations to perform on Box Hub items., defaults to None :type operations: Optional[List[HubItemOperationV2025R0]], optional :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 :type box_version: BoxVersionHeaderV2025R0, optional diff --git a/box_sdk_gen/managers/hubs.py b/box_sdk_gen/managers/hubs.py index 549ad3566..47669e810 100644 --- a/box_sdk_gen/managers/hubs.py +++ b/box_sdk_gen/managers/hubs.py @@ -88,10 +88,10 @@ def get_hubs_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubsV2025R0: """ - Retrieves all hubs for requesting user. - :param query: The query string to search for hubs., defaults to None + Retrieves all Box Hubs for requesting user. + :param query: The query string to search for Box Hubs., defaults to None :type query: Optional[str], optional - :param scope: The scope of the hubs to retrieve. Possible values include `editable`, + :param scope: The scope of the Box Hubs to retrieve. Possible values include `editable`, `view_only`, and `all`. Default is `all`., defaults to None :type scope: Optional[str], optional :param sort: The field to sort results by. @@ -149,10 +149,10 @@ def create_hub_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubV2025R0: """ - Creates a new Hub. - :param title: Title of the Hub. It cannot be empty and should be less than 50 characters. + Creates a new Box Hub. + :param title: Title of the Box Hub. It cannot be empty and should be less than 50 characters. :type title: str - :param description: Description of the Hub., defaults to None + :param description: Description of the Box Hub., defaults to None :type description: Optional[str], optional :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 :type box_version: BoxVersionHeaderV2025R0, optional @@ -191,14 +191,14 @@ def get_enterprise_hubs_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubsV2025R0: """ - Retrieves all hubs for a given enterprise. + Retrieves all Box Hubs for a given enterprise. Admins or Hub Co-admins of an enterprise with GCM scope can make this call. - :param query: The query string to search for hubs., defaults to None + :param query: The query string to search for Box Hubs., defaults to None :type query: Optional[str], optional :param sort: The field to sort results by. Possible values include `name`, `updated_at`, @@ -255,7 +255,7 @@ def get_hub_by_id_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubV2025R0: """ - Retrieves details for a hub by its ID. + Retrieves details for a Box Hub by its ID. :param hub_id: The unique identifier that represent a hub. The ID for any hub can be determined @@ -307,7 +307,7 @@ def update_hub_by_id_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubV2025R0: """ - Updates a Hub. Can be used to change title, description, or Hub settings. + Updates a Box Hub. Can be used to change title, description, or Box Hub settings. :param hub_id: The unique identifier that represent a hub. The ID for any hub can be determined @@ -317,17 +317,17 @@ def update_hub_by_id_v2025_r0( the `hub_id` is `123`. Example: "12345" :type hub_id: str - :param title: Title of the Hub. It cannot be empty and should be less than 50 characters., defaults to None + :param title: Title of the Box Hub. It cannot be empty and should be less than 50 characters., defaults to None :type title: Optional[str], optional - :param description: Description of the Hub., defaults to None + :param description: Description of the Box Hub., defaults to None :type description: Optional[str], optional - :param is_ai_enabled: Indicates if AI features are enabled for the Hub., defaults to None + :param is_ai_enabled: Indicates if AI features are enabled for the Box Hub., defaults to None :type is_ai_enabled: Optional[bool], optional :param is_collaboration_restricted_to_enterprise: Indicates if collaboration is restricted to the enterprise., defaults to None :type is_collaboration_restricted_to_enterprise: Optional[bool], optional - :param can_non_owners_invite: Indicates if non-owners can invite others to the Hub., defaults to None + :param can_non_owners_invite: Indicates if non-owners can invite others to the Box Hub., defaults to None :type can_non_owners_invite: Optional[bool], optional - :param can_shared_link_be_created: Indicates if a shared link can be created for the Hub., defaults to None + :param can_shared_link_be_created: Indicates if a shared link can be created for the Box Hub., defaults to None :type can_shared_link_be_created: Optional[bool], optional :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 :type box_version: BoxVersionHeaderV2025R0, optional @@ -377,7 +377,7 @@ def delete_hub_by_id_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> None: """ - Deletes a single hub. + Deletes a single Box Hub. :param hub_id: The unique identifier that represent a hub. The ID for any hub can be determined @@ -425,9 +425,9 @@ def copy_hub_v2025_r0( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubV2025R0: """ - Creates a copy of a Hub. + Creates a copy of a Box Hub. - The original Hub will not be modified. + The original Box Hub will not be modified. :param hub_id: The unique identifier that represent a hub. @@ -438,9 +438,9 @@ def copy_hub_v2025_r0( the `hub_id` is `123`. Example: "12345" :type hub_id: str - :param title: Title of the Hub. It cannot be empty and should be less than 50 characters., defaults to None + :param title: Title of the Box Hub. It cannot be empty and should be less than 50 characters., defaults to None :type title: Optional[str], optional - :param description: Description of the Hub., defaults to None + :param description: Description of the Box Hub., defaults to None :type description: Optional[str], optional :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 :type box_version: BoxVersionHeaderV2025R0, optional diff --git a/box_sdk_gen/schemas/v2025_r0/hub_base_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_base_v2025_r0.py index 78212df31..099703dd6 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_base_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_base_v2025_r0.py @@ -20,10 +20,10 @@ def __init__( **kwargs ): """ - :param id: The unique identifier that represent a hub. + :param id: The unique identifier that represent a Box Hub. - The ID for any hub can be determined - by visiting a hub in the web application + The ID for any Box Hub can be determined + by visiting a Box Hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. diff --git a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_create_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_create_request_v2025_r0.py index cb926aa1f..1fc7eb978 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_create_request_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_create_request_v2025_r0.py @@ -70,11 +70,11 @@ def __init__( **kwargs ): """ - :param hub: Hubs reference. + :param hub: Box Hubs reference. :type hub: HubCollaborationCreateRequestV2025R0HubField :param accessible_by: The user or group who gets access to the item. :type accessible_by: HubCollaborationCreateRequestV2025R0AccessibleByField - :param role: The level of access granted to hub. + :param role: The level of access granted to a Box Hub. Possible values are `editor`, `viewer`, and `co-owner`. :type role: str """ diff --git a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_update_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_update_request_v2025_r0.py index 28990c279..388c4049e 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_update_request_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_update_request_v2025_r0.py @@ -8,7 +8,7 @@ class HubCollaborationUpdateRequestV2025R0(BaseObject): def __init__(self, *, role: Optional[str] = None, **kwargs): """ - :param role: The level of access granted to hub. + :param role: The level of access granted to a Box Hub. Possible values are `editor`, `viewer`, and `co-owner`., defaults to None :type role: Optional[str], optional """ diff --git a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_v2025_r0.py index aa88f752f..78411ef88 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_v2025_r0.py @@ -156,7 +156,7 @@ def __init__( :type id: str :param type: The value will always be `hub_collaboration`., defaults to HubCollaborationV2025R0TypeField.HUB_COLLABORATION :type type: HubCollaborationV2025R0TypeField, optional - :param role: The level of access granted to hub. + :param role: The level of access granted to a Box Hub. Possible values are `editor`, `viewer`, and `co-owner`., defaults to None :type role: Optional[str], optional :param status: The status of the collaboration invitation. If the status diff --git a/box_sdk_gen/schemas/v2025_r0/hub_collaborations_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_collaborations_v2025_r0.py index b63fd5e66..9a319ff00 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_collaborations_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_collaborations_v2025_r0.py @@ -21,7 +21,7 @@ def __init__( **kwargs ): """ - :param entries: A list of hub collaborations., defaults to None + :param entries: A list of Box Hub collaborations., defaults to None :type entries: Optional[List[HubCollaborationV2025R0]], optional :param limit: The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value diff --git a/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py index 31da19359..a3926a9a2 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py @@ -14,9 +14,9 @@ def __init__( **kwargs ): """ - :param title: Title of the Hub. It cannot be empty and should be less than 50 characters., defaults to None + :param title: Title of the Box Hub. It cannot be empty and should be less than 50 characters., defaults to None :type title: Optional[str], optional - :param description: Description of the Hub., defaults to None + :param description: Description of the Box Hub., defaults to None :type description: Optional[str], optional """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/v2025_r0/hub_create_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_create_request_v2025_r0.py index 16b9cd32e..f825bd932 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_create_request_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_create_request_v2025_r0.py @@ -8,9 +8,9 @@ class HubCreateRequestV2025R0(BaseObject): def __init__(self, title: str, *, description: Optional[str] = None, **kwargs): """ - :param title: Title of the Hub. It cannot be empty and should be less than 50 characters. + :param title: Title of the Box Hub. It cannot be empty and should be less than 50 characters. :type title: str - :param description: Description of the Hub., defaults to None + :param description: Description of the Box Hub., defaults to None :type description: Optional[str], optional """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py index 5dd003e18..9c6f99759 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py @@ -32,7 +32,7 @@ def __init__( **kwargs ): """ - :param action: The action to perform on a Hub item. + :param action: The action to perform on a Box Hub item. :type action: HubItemOperationV2025R0ActionField """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/v2025_r0/hub_items_manage_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_items_manage_request_v2025_r0.py index 563d2502d..17d0f3e8d 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_items_manage_request_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_items_manage_request_v2025_r0.py @@ -16,7 +16,7 @@ def __init__( self, *, operations: Optional[List[HubItemOperationV2025R0]] = None, **kwargs ): """ - :param operations: List of operations to perform on Hub items., defaults to None + :param operations: List of operations to perform on Box Hub items., defaults to None :type operations: Optional[List[HubItemOperationV2025R0]], optional """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/v2025_r0/hub_items_manage_response_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_items_manage_response_v2025_r0.py index 36747ceaf..030b01ceb 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_items_manage_response_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_items_manage_response_v2025_r0.py @@ -12,7 +12,7 @@ class HubItemsManageResponseV2025R0(BaseObject): def __init__(self, operations: List[HubItemOperationResultV2025R0], **kwargs): """ - :param operations: List of operations performed on Hub items. + :param operations: List of operations performed on Box Hub items. :type operations: List[HubItemOperationResultV2025R0] """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/v2025_r0/hub_items_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_items_v2025_r0.py index 652d1a114..6a792b86a 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_items_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_items_v2025_r0.py @@ -19,7 +19,7 @@ def __init__( **kwargs ): """ - :param entries: A list of Hub items., defaults to None + :param entries: A list of Box Hub items., defaults to None :type entries: Optional[List[HubItemV2025R0]], optional :param limit: The limit that was used for these entries. This will be the same as the `limit` query parameter unless that value exceeded the maximum value diff --git a/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py index dd75180d6..67a29953d 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py @@ -18,17 +18,17 @@ def __init__( **kwargs ): """ - :param title: Title of the Hub. It cannot be empty and should be less than 50 characters., defaults to None + :param title: Title of the Box Hub. It cannot be empty and should be less than 50 characters., defaults to None :type title: Optional[str], optional - :param description: Description of the Hub., defaults to None + :param description: Description of the Box Hub., defaults to None :type description: Optional[str], optional - :param is_ai_enabled: Indicates if AI features are enabled for the Hub., defaults to None + :param is_ai_enabled: Indicates if AI features are enabled for the Box Hub., defaults to None :type is_ai_enabled: Optional[bool], optional :param is_collaboration_restricted_to_enterprise: Indicates if collaboration is restricted to the enterprise., defaults to None :type is_collaboration_restricted_to_enterprise: Optional[bool], optional - :param can_non_owners_invite: Indicates if non-owners can invite others to the Hub., defaults to None + :param can_non_owners_invite: Indicates if non-owners can invite others to the Box Hub., defaults to None :type can_non_owners_invite: Optional[bool], optional - :param can_shared_link_be_created: Indicates if a shared link can be created for the Hub., defaults to None + :param can_shared_link_be_created: Indicates if a shared link can be created for the Box Hub., defaults to None :type can_shared_link_be_created: Optional[bool], optional """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py index efe6f8ef1..51ed529aa 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py @@ -31,33 +31,33 @@ def __init__( **kwargs ): """ - :param id: The unique identifier that represent a hub. + :param id: The unique identifier that represent a Box Hub. - The ID for any hub can be determined - by visiting a hub in the web application + The ID for any Box Hub can be determined + by visiting a Box Hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. :type id: str - :param title: The title given to the hub., defaults to None + :param title: The title given to the Box Hub., defaults to None :type title: Optional[str], optional - :param description: The description of the hub. First 200 characters are returned., defaults to None + :param description: The description of the Box Hub. First 200 characters are returned., defaults to None :type description: Optional[str], optional :param created_at: The date and time when the folder was created. This value may be `null` for some folders such as the root folder or the trash folder., defaults to None :type created_at: Optional[DateTime], optional - :param updated_at: The date and time when the hub was last updated., defaults to None + :param updated_at: The date and time when the Box Hub was last updated., defaults to None :type updated_at: Optional[DateTime], optional - :param view_count: The number of views for the hub., defaults to None + :param view_count: The number of views for the Box Hub., defaults to None :type view_count: Optional[int], optional - :param is_ai_enabled: Indicates if AI features are enabled for the hub., defaults to None + :param is_ai_enabled: Indicates if AI features are enabled for the Box Hub., defaults to None :type is_ai_enabled: Optional[bool], optional :param is_collaboration_restricted_to_enterprise: Indicates if collaboration is restricted to the enterprise., defaults to None :type is_collaboration_restricted_to_enterprise: Optional[bool], optional - :param can_non_owners_invite: Indicates if non-owners can invite others to the hub., defaults to None + :param can_non_owners_invite: Indicates if non-owners can invite others to the Box Hub., defaults to None :type can_non_owners_invite: Optional[bool], optional - :param can_shared_link_be_created: Indicates if a shared link can be created for the hub., defaults to None + :param can_shared_link_be_created: Indicates if a shared link can be created for the Box Hub., defaults to None :type can_shared_link_be_created: Optional[bool], optional :param type: The value will always be `hubs`., defaults to HubBaseV2025R0TypeField.HUBS :type type: HubBaseV2025R0TypeField, optional diff --git a/docs/hub_collaborations.md b/docs/hub_collaborations.md index 5859f6a7e..585e0ba91 100644 --- a/docs/hub_collaborations.md +++ b/docs/hub_collaborations.md @@ -1,14 +1,14 @@ # HubCollaborationsManager -- [Get hub collaborations](#get-hub-collaborations) -- [Create hub collaboration](#create-hub-collaboration) -- [Get hub collaboration by collaboration ID](#get-hub-collaboration-by-collaboration-id) -- [Update hub collaboration](#update-hub-collaboration) -- [Remove hub collaboration](#remove-hub-collaboration) +- [Get Box Hub collaborations](#get-box-hub-collaborations) +- [Create Box Hub collaboration](#create-box-hub-collaboration) +- [Get Box Hub collaboration by collaboration ID](#get-box-hub-collaboration-by-collaboration-id) +- [Update Box Hub collaboration](#update-box-hub-collaboration) +- [Remove Box Hub collaboration](#remove-box-hub-collaboration) -## Get hub collaborations +## Get Box Hub collaborations -Retrieves all collaborations for a hub. +Retrieves all collaborations for a Box Hub. This operation is performed by calling function `get_hub_collaborations_v2025_r0`. @@ -38,11 +38,11 @@ client.hub_collaborations.get_hub_collaborations_v2025_r0(hub.id) This function returns a value of type `HubCollaborationsV2025R0`. -Retrieves the collaborations associated with the specified hub. +Retrieves the collaborations associated with the specified Box Hub. -## Create hub collaboration +## Create Box Hub collaboration -Adds a collaboration for a single user or a single group to a hub. +Adds a collaboration for a single user or a single group to a Box Hub. Collaborations can be created using email address, user IDs, or group IDs. @@ -64,11 +64,11 @@ client.hub_collaborations.create_hub_collaboration_v2025_r0( ### Arguments - hub `CreateHubCollaborationV2025R0Hub` - - Hubs reference. + - Box Hubs reference. - accessible_by `CreateHubCollaborationV2025R0AccessibleBy` - The user or group who gets access to the item. - role `str` - - The level of access granted to hub. Possible values are `editor`, `viewer`, and `co-owner`. + - The level of access granted to a Box Hub. Possible values are `editor`, `viewer`, and `co-owner`. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` @@ -78,11 +78,11 @@ client.hub_collaborations.create_hub_collaboration_v2025_r0( This function returns a value of type `HubCollaborationV2025R0`. -Returns a new hub collaboration object. +Returns a new Box Hub collaboration object. -## Get hub collaboration by collaboration ID +## Get Box Hub collaboration by collaboration ID -Retrieves details for a hub collaboration by collaboration ID. +Retrieves details for a Box Hub collaboration by collaboration ID. This operation is performed by calling function `get_hub_collaboration_by_id_v2025_r0`. @@ -108,12 +108,12 @@ client.hub_collaborations.get_hub_collaboration_by_id_v2025_r0(created_collabora This function returns a value of type `HubCollaborationV2025R0`. -Returns a hub collaboration object. +Returns a Box Hub collaboration object. -## Update hub collaboration +## Update Box Hub collaboration -Updates a hub collaboration. -Can be used to change the hub role. +Updates a Box Hub collaboration. +Can be used to change the Box Hub role. This operation is performed by calling function `update_hub_collaboration_by_id_v2025_r0`. @@ -133,7 +133,7 @@ client.hub_collaborations.update_hub_collaboration_by_id_v2025_r0( - hub_collaboration_id `str` - The ID of the hub collaboration. Example: "1234" - role `Optional[str]` - - The level of access granted to hub. Possible values are `editor`, `viewer`, and `co-owner`. + - The level of access granted to a Box Hub. Possible values are `editor`, `viewer`, and `co-owner`. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` @@ -143,11 +143,11 @@ client.hub_collaborations.update_hub_collaboration_by_id_v2025_r0( This function returns a value of type `HubCollaborationV2025R0`. -Returns an updated hub collaboration object. +Returns an updated Box Hub collaboration object. -## Remove hub collaboration +## Remove Box Hub collaboration -Deletes a single hub collaboration. +Deletes a single Box Hub collaboration. This operation is performed by calling function `delete_hub_collaboration_by_id_v2025_r0`. @@ -175,5 +175,5 @@ client.hub_collaborations.delete_hub_collaboration_by_id_v2025_r0( This function returns a value of type `None`. -A blank response is returned if the hub collaboration was +A blank response is returned if the Box Hub collaboration was successfully deleted. diff --git a/docs/hub_items.md b/docs/hub_items.md index cca06abca..966349d66 100644 --- a/docs/hub_items.md +++ b/docs/hub_items.md @@ -1,11 +1,11 @@ # HubItemsManager -- [Get hub items](#get-hub-items) -- [Manage hub items](#manage-hub-items) +- [Get Box Hub items](#get-box-hub-items) +- [Manage Box Hub items](#manage-box-hub-items) -## Get hub items +## Get Box Hub items -Retrieves all items associated with a Hub. +Retrieves all items associated with a Box Hub. This operation is performed by calling function `get_hub_items_v2025_r0`. @@ -35,11 +35,11 @@ client.hub_items.get_hub_items_v2025_r0(created_hub.id) This function returns a value of type `HubItemsV2025R0`. -Retrieves the items associated with the specified Hub. +Retrieves the items associated with the specified Box Hub. -## Manage hub items +## Manage Box Hub items -Adds and/or removes Hub items from a Hub. +Adds and/or removes Box Hub items from a Box Hub. This operation is performed by calling function `manage_hub_items_v2025_r0`. @@ -65,7 +65,7 @@ client.hub_items.manage_hub_items_v2025_r0( - hub_id `str` - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. Example: "12345" - operations `Optional[List[HubItemOperationV2025R0]]` - - List of operations to perform on Hub items. + - List of operations to perform on Box Hub items. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` diff --git a/docs/hubs.md b/docs/hubs.md index c31652d30..10b1459b1 100644 --- a/docs/hubs.md +++ b/docs/hubs.md @@ -1,16 +1,16 @@ # HubsManager -- [List all hubs](#list-all-hubs) -- [Create hub](#create-hub) -- [List all hubs for requesting enterprise](#list-all-hubs-for-requesting-enterprise) -- [Get hub information by ID](#get-hub-information-by-id) -- [Update hub information by ID](#update-hub-information-by-id) -- [Delete hub](#delete-hub) -- [Copy hub](#copy-hub) +- [List all Box Hubs](#list-all-box-hubs) +- [Create Box Hub](#create-box-hub) +- [List all Box Hubs for requesting enterprise](#list-all-box-hubs-for-requesting-enterprise) +- [Get Box Hub information by ID](#get-box-hub-information-by-id) +- [Update Box Hub information by ID](#update-box-hub-information-by-id) +- [Delete Box Hub](#delete-box-hub) +- [Copy Box Hub](#copy-box-hub) -## List all hubs +## List all Box Hubs -Retrieves all hubs for requesting user. +Retrieves all Box Hubs for requesting user. This operation is performed by calling function `get_hubs_v2025_r0`. @@ -28,9 +28,9 @@ client.hubs.get_hubs_v2025_r0( ### Arguments - query `Optional[str]` - - The query string to search for hubs. + - The query string to search for Box Hubs. - scope `Optional[str]` - - The scope of the hubs to retrieve. Possible values include `editable`, `view_only`, and `all`. Default is `all`. + - The scope of the Box Hubs to retrieve. Possible values include `editable`, `view_only`, and `all`. Default is `all`. - sort `Optional[str]` - The field to sort results by. Possible values include `name`, `updated_at`, `last_accessed_at`, `view_count`, and `relevance`. Default is `relevance`. - direction `Optional[GetHubsV2025R0Direction]` @@ -48,11 +48,11 @@ client.hubs.get_hubs_v2025_r0( This function returns a value of type `HubsV2025R0`. -Returns all hubs for the given user or enterprise. +Returns all Box Hubs for the given user or enterprise. -## Create hub +## Create Box Hub -Creates a new Hub. +Creates a new Box Hub. This operation is performed by calling function `create_hub_v2025_r0`. @@ -68,9 +68,9 @@ client.hubs.create_hub_v2025_r0(hub_title, description=hub_description) ### Arguments - title `str` - - Title of the Hub. It cannot be empty and should be less than 50 characters. + - Title of the Box Hub. It cannot be empty and should be less than 50 characters. - description `Optional[str]` - - Description of the Hub. + - Description of the Box Hub. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` @@ -82,9 +82,9 @@ This function returns a value of type `HubV2025R0`. Returns a new Hub object. -## List all hubs for requesting enterprise +## List all Box Hubs for requesting enterprise -Retrieves all hubs for a given enterprise. +Retrieves all Box Hubs for a given enterprise. Admins or Hub Co-admins of an enterprise with GCM scope can make this call. @@ -105,7 +105,7 @@ client.hubs.get_enterprise_hubs_v2025_r0( ### Arguments - query `Optional[str]` - - The query string to search for hubs. + - The query string to search for Box Hubs. - sort `Optional[str]` - The field to sort results by. Possible values include `name`, `updated_at`, `last_accessed_at`, `view_count`, and `relevance`. Default is `relevance`. - direction `Optional[GetEnterpriseHubsV2025R0Direction]` @@ -123,11 +123,11 @@ client.hubs.get_enterprise_hubs_v2025_r0( This function returns a value of type `HubsV2025R0`. -Returns all hubs for the given user or enterprise. +Returns all Box Hubs for the given user or enterprise. -## Get hub information by ID +## Get Box Hub information by ID -Retrieves details for a hub by its ID. +Retrieves details for a Box Hub by its ID. This operation is performed by calling function `get_hub_by_id_v2025_r0`. @@ -155,9 +155,9 @@ This function returns a value of type `HubV2025R0`. Returns a hub object. -## Update hub information by ID +## Update Box Hub information by ID -Updates a Hub. Can be used to change title, description, or Hub settings. +Updates a Box Hub. Can be used to change title, description, or Box Hub settings. This operation is performed by calling function `update_hub_by_id_v2025_r0`. @@ -177,17 +177,17 @@ client.hubs.update_hub_by_id_v2025_r0( - hub_id `str` - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. Example: "12345" - title `Optional[str]` - - Title of the Hub. It cannot be empty and should be less than 50 characters. + - Title of the Box Hub. It cannot be empty and should be less than 50 characters. - description `Optional[str]` - - Description of the Hub. + - Description of the Box Hub. - is_ai_enabled `Optional[bool]` - - Indicates if AI features are enabled for the Hub. + - Indicates if AI features are enabled for the Box Hub. - is_collaboration_restricted_to_enterprise `Optional[bool]` - Indicates if collaboration is restricted to the enterprise. - can_non_owners_invite `Optional[bool]` - - Indicates if non-owners can invite others to the Hub. + - Indicates if non-owners can invite others to the Box Hub. - can_shared_link_be_created `Optional[bool]` - - Indicates if a shared link can be created for the Hub. + - Indicates if a shared link can be created for the Box Hub. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` @@ -199,9 +199,9 @@ This function returns a value of type `HubV2025R0`. Returns a Hub object. -## Delete hub +## Delete Box Hub -Deletes a single hub. +Deletes a single Box Hub. This operation is performed by calling function `delete_hub_by_id_v2025_r0`. @@ -230,11 +230,11 @@ This function returns a value of type `None`. A blank response is returned if the hub was successfully deleted. -## Copy hub +## Copy Box Hub -Creates a copy of a Hub. +Creates a copy of a Box Hub. -The original Hub will not be modified. +The original Box Hub will not be modified. This operation is performed by calling function `copy_hub_v2025_r0`. @@ -254,9 +254,9 @@ client.hubs.copy_hub_v2025_r0( - hub_id `str` - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. Example: "12345" - title `Optional[str]` - - Title of the Hub. It cannot be empty and should be less than 50 characters. + - Title of the Box Hub. It cannot be empty and should be less than 50 characters. - description `Optional[str]` - - Description of the Hub. + - Description of the Box Hub. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` From 575ce0b6d48f90db90349244414e98afe5fcbb9f Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 15 Sep 2025 11:50:49 +0200 Subject: [PATCH 019/139] feat: Add missing webhook events (box/box-openapi#554) (#988) --- .codegen.json | 2 +- box_sdk_gen/managers/webhooks.py | 6 ++++++ box_sdk_gen/schemas/webhook.py | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index a75d41fbe..b5b489e1e 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "fa469c0", "specHash": "60778fb", "version": "0.1.0" } +{ "engineHash": "fa469c0", "specHash": "a05e5d7", "version": "0.1.0" } diff --git a/box_sdk_gen/managers/webhooks.py b/box_sdk_gen/managers/webhooks.py index accecd298..27f3e3a17 100644 --- a/box_sdk_gen/managers/webhooks.py +++ b/box_sdk_gen/managers/webhooks.py @@ -122,6 +122,9 @@ class CreateWebhookTriggers(str, Enum): SIGN_REQUEST_DECLINED = 'SIGN_REQUEST.DECLINED' SIGN_REQUEST_EXPIRED = 'SIGN_REQUEST.EXPIRED' SIGN_REQUEST_SIGNER_EMAIL_BOUNCED = 'SIGN_REQUEST.SIGNER_EMAIL_BOUNCED' + SIGN_REQUEST_SIGN_SIGNER_SIGNED = 'SIGN_REQUEST.SIGN_SIGNER_SIGNED' + SIGN_REQUEST_SIGN_DOCUMENT_CREATED = 'SIGN_REQUEST.SIGN_DOCUMENT_CREATED' + SIGN_REQUEST_SIGN_ERROR_FINALIZING = 'SIGN_REQUEST.SIGN_ERROR_FINALIZING' class UpdateWebhookByIdTargetTypeField(str, Enum): @@ -191,6 +194,9 @@ class UpdateWebhookByIdTriggers(str, Enum): SIGN_REQUEST_DECLINED = 'SIGN_REQUEST.DECLINED' SIGN_REQUEST_EXPIRED = 'SIGN_REQUEST.EXPIRED' SIGN_REQUEST_SIGNER_EMAIL_BOUNCED = 'SIGN_REQUEST.SIGNER_EMAIL_BOUNCED' + SIGN_REQUEST_SIGN_SIGNER_SIGNED = 'SIGN_REQUEST.SIGN_SIGNER_SIGNED' + SIGN_REQUEST_SIGN_DOCUMENT_CREATED = 'SIGN_REQUEST.SIGN_DOCUMENT_CREATED' + SIGN_REQUEST_SIGN_ERROR_FINALIZING = 'SIGN_REQUEST.SIGN_ERROR_FINALIZING' class WebhooksManager: diff --git a/box_sdk_gen/schemas/webhook.py b/box_sdk_gen/schemas/webhook.py index 3cbf167f6..8163c7ee9 100644 --- a/box_sdk_gen/schemas/webhook.py +++ b/box_sdk_gen/schemas/webhook.py @@ -58,6 +58,9 @@ class WebhookTriggersField(str, Enum): SIGN_REQUEST_DECLINED = 'SIGN_REQUEST.DECLINED' SIGN_REQUEST_EXPIRED = 'SIGN_REQUEST.EXPIRED' SIGN_REQUEST_SIGNER_EMAIL_BOUNCED = 'SIGN_REQUEST.SIGNER_EMAIL_BOUNCED' + SIGN_REQUEST_SIGN_SIGNER_SIGNED = 'SIGN_REQUEST.SIGN_SIGNER_SIGNED' + SIGN_REQUEST_SIGN_DOCUMENT_CREATED = 'SIGN_REQUEST.SIGN_DOCUMENT_CREATED' + SIGN_REQUEST_SIGN_ERROR_FINALIZING = 'SIGN_REQUEST.SIGN_ERROR_FINALIZING' class Webhook(WebhookMini): From ab78cbabbdf9ccd187d7e20d41f6b8489ce8b05b Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:51:51 +0200 Subject: [PATCH 020/139] chore: Run only smoke tests on PR and all tests on release (#1017) --- .codegen.json | 2 +- .github/workflows/build.yml | 42 +++++-------------------------- box_sdk_gen/networking/retries.py | 6 +++-- 3 files changed, 11 insertions(+), 39 deletions(-) diff --git a/.codegen.json b/.codegen.json index b5b489e1e..31f95bead 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "fa469c0", "specHash": "a05e5d7", "version": "0.1.0" } +{ "engineHash": "ee965c4", "specHash": "a05e5d7", "version": "0.1.0" } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82c6ee5f3..3312f5637 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,9 +3,6 @@ on: pull_request: branches: - sdk-gen - push: - branches: - - sdk-gen jobs: build: runs-on: ubuntu-latest @@ -29,7 +26,8 @@ jobs: python -m pip install --upgrade pip python -m pip install -e .[dev] python -m pip install tox-gh-actions - - name: Test with tox + - name: All Tests + if: startsWith(github.head_ref, 'codegen-release') env: JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} ADMIN_USER_ID: ${{ secrets.ADMIN_USER_ID }} @@ -49,41 +47,13 @@ jobs: SLACK_PARTNER_ITEM_ID: ${{ secrets.SLACK_PARTNER_ITEM_ID }} run: | tox - - coverage: - name: Coverage report - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Python 3.11 - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e .[dev] - python -m pip install coveralls - - name: Send coverage report to Coveralls - run: | - tox -e coverage - coveralls --service=github + - name: Smoke Tests + if: "!startsWith(github.head_ref, 'codegen-release')" env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} - ADMIN_USER_ID: ${{ secrets.ADMIN_USER_ID }} CLIENT_ID: ${{ secrets.CLIENT_ID }} CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} USER_ID: ${{ secrets.USER_ID }} ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }} - BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }} - BOX_EXTERNAL_USER_EMAIL: ${{ secrets.BOX_EXTERNAL_USER_EMAIL }} - BOX_EXTERNAL_USER_ID: ${{ secrets.BOX_EXTERNAL_USER_ID }} - WORKFLOW_FOLDER_ID: ${{ secrets.WORKFLOW_FOLDER_ID }} - APP_ITEM_ASSOCIATION_FILE_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FILE_ID }} - APP_ITEM_ASSOCIATION_FOLDER_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FOLDER_ID }} - APP_ITEM_SHARED_LINK: ${{ secrets.APP_ITEM_SHARED_LINK }} - SLACK_AUTOMATION_USER_ID: ${{ secrets.SLACK_AUTOMATION_USER_ID }} - SLACK_ORG_ID: ${{ secrets.SLACK_ORG_ID }} - SLACK_PARTNER_ITEM_ID: ${{ secrets.SLACK_PARTNER_ITEM_ID }} + run: | + tox -- test/auth.py test/files.py test/downloads.py test/uploads.py diff --git a/box_sdk_gen/networking/retries.py b/box_sdk_gen/networking/retries.py index 30539dcdd..4d9c6c257 100644 --- a/box_sdk_gen/networking/retries.py +++ b/box_sdk_gen/networking/retries.py @@ -1,5 +1,7 @@ from abc import abstractmethod +from typing import Optional + from box_sdk_gen.networking.fetch_options import FetchOptions from box_sdk_gen.networking.fetch_response import FetchResponse @@ -57,7 +59,7 @@ def should_retry( is_successful: bool = ( fetch_response.status >= 200 and fetch_response.status < 400 ) - retry_after_header: str = fetch_response.headers.get('Retry-After') + retry_after_header: Optional[str] = fetch_response.headers.get('Retry-After') is_accepted_with_retry_after: bool = ( fetch_response.status == 202 and not retry_after_header == None ) @@ -84,7 +86,7 @@ def retry_after( fetch_response: FetchResponse, attempt_number: int, ) -> float: - retry_after_header: str = fetch_response.headers.get('Retry-After') + retry_after_header: Optional[str] = fetch_response.headers.get('Retry-After') if not retry_after_header == None: return float(retry_after_header) randomization: float = random( From d97486de8c6d7b0c87eda2a63daebe1a54eec965 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 16 Sep 2025 15:12:09 +0200 Subject: [PATCH 021/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1029) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 31f95bead..038d4f257 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "ee965c4", "specHash": "a05e5d7", "version": "0.1.0" } +{ "engineHash": "72ae59b", "specHash": "a05e5d7", "version": "0.1.0" } From 0fdb638f633cc256ccea47c0ba7a7ace8f06eceb Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 16 Sep 2025 15:50:21 +0200 Subject: [PATCH 022/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1030) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 038d4f257..6ef5feea7 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "72ae59b", "specHash": "a05e5d7", "version": "0.1.0" } +{ "engineHash": "b294f83", "specHash": "a05e5d7", "version": "0.1.0" } From b3744e4ae174cde4b31e20255c6012711aabcf8f Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:22:06 +0200 Subject: [PATCH 023/139] docs: Adjust docs to `v10` (box/box-codegen#834) (#1033) --- .codegen.json | 2 +- docs/working-with-nulls.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.codegen.json b/.codegen.json index 6ef5feea7..8eb0e13b7 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "b294f83", "specHash": "a05e5d7", "version": "0.1.0" } +{ "engineHash": "87747c8", "specHash": "a05e5d7", "version": "0.1.0" } diff --git a/docs/working-with-nulls.md b/docs/working-with-nulls.md index 8ebc3d103..83ec96582 100644 --- a/docs/working-with-nulls.md +++ b/docs/working-with-nulls.md @@ -1,10 +1,10 @@ -# Handling null values in Box Python SDK Gen +# Handling null values in v10 version of Box Python SDK -While using Box Python SDK it's important to understand how null values behave. This document provides a general overview of null value behaviour in Box Python SDK to help developers manage data consistently and predictably. +While using v10 version of Box Python SDK it's important to understand how null values behave. This document provides a general overview of null value behaviour in Box Python SDK to help developers manage data consistently and predictably. ## Understanding null behaviour -The Box Python SDK follows a consistent pattern when handling null values in update operations. This behaviour applies to most endpoints that modify resources such as users, files, folders and metadata. The updating field behaves differently depending on weather you omit it, set it to null, or provide a value: +The v10 of Box Python SDK follows a consistent pattern when handling null values in update operations. This behaviour applies to most endpoints that modify resources such as users, files, folders and metadata. The updating field behaves differently depending on weather you omit it, set it to null, or provide a value: - Omitting the field: The field won't be included in request and the value will remain unchanged. - Setting it to null: Setting a field to null, will cause sending HTTP request with field value set to null, what will result in removing its current value or disassociates it from the resource. From 573434405347c13bf8e7122688d2f431fcb34430 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 17 Sep 2025 16:16:54 +0200 Subject: [PATCH 024/139] chore: release version 10.0.0 (#1035) --- .codegen.json | 2 +- CHANGELOG.md | 38 +++++++++++++++++++++++++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 8eb0e13b7..4b70ff577 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "87747c8", "specHash": "a05e5d7", "version": "0.1.0" } +{ "engineHash": "87747c8", "specHash": "a05e5d7", "version": "10.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 957db78ce..6bae043a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,44 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.0.0](https://github.com/box/box-python-sdk/compare/v3.14.0...v10.0.0) (2025-09-17) + +Introducing **`Box SDK v10`**, a major release designed to elevate your developer experience and streamline integration with Box Content Cloud. Currently available as a separate [`sdk-gen`](https://github.com/box/box-python-sdk/tree/sdk-gen) branch, `v10` will ultimately become the main branch. + +### Breaking Changes + +- This SDK version is auto-generated and introduces a new interface for all methods. Extensive documentation is available [here](https://github.com/box/box-python-sdk/tree/sdk-gen/docs). + +### What's New in `v10` + +- **Full API Support** — Complete coverage of the Box API ecosystem, giving you access to all the latest features and functionalities to build feature-rich applications. + +- **Rapid API Updates** — Our new auto-generation approach enables Box API additions within days, ensuring you can leverage the most up-to-date features without delay. + +- **Embedded Documentation** — All objects and parameters are documented directly in the SDK source code, keeping essential information in one place. + +- **Enhanced Convenience Methods** — New methods for authentication, chunk uploads, automatic retries, retry strategy, and more. + +### Important Notes + +This version includes the Box Next Generation SDK artifact, previously developed in parallel to core Box SDKs. To facilitate migration and provide access to the newest features, we'll soon release an additional major version combining both artifacts. Migration to `v10` includes breaking changes, please review the [migration guide](https://github.com/box/box-python-sdk/blob/sdk-gen/migration-guides/from-v3-to-v10.md) for details. For more information, see the [SDK versioning strategy document](https://developer.box.com/tooling/sdks/sdk-versioning). Follow developer changelog for future updates. + +--- + +### What’s New Compared to [Python SDK Gen](https://github.com/box/box-python-sdk-gen) `v1.17.0` + +#### New Features and Enhancements + +* Add External User Deletion API (box/box-openapi[#550](https://github.com/box/box-python-sdk/issues/550)) ([#941](https://github.com/box/box-python-sdk/issues/941)) ([a80ad85](https://github.com/box/box-python-sdk/commit/a80ad856b3193e54272e04f01ddb025b2d9f781f)) + +* Add missing webhook events (box/box-openapi[#554](https://github.com/box/box-python-sdk/issues/554)) ([#988](https://github.com/box/box-python-sdk/issues/988)) ([575ce0b](https://github.com/box/box-python-sdk/commit/575ce0b6d48f90db90349244414e98afe5fcbb9f)) + +* Change names of unions (box/box-codegen[#789](https://github.com/box/box-python-sdk/issues/789)) ([#939](https://github.com/box/box-python-sdk/issues/939)) ([cf2b1d5](https://github.com/box/box-python-sdk/commit/cf2b1d5b12be0ff2453867b7d3502437283bf695)) + +* Remove unused models from schemas (box/box-openapi[#547](https://github.com/box/box-python-sdk/issues/547)) ([#932](https://github.com/box/box-python-sdk/issues/932)) ([6ef6d63](https://github.com/box/box-python-sdk/commit/6ef6d63c37e6eccc3489a9076e0a0b0940a6e0d6)), closes [box/box-openapi#542](https://github.com/box/box-openapi/issues/542) [box/box-openapi#544](https://github.com/box/box-openapi/issues/544) [box/box-codegen#781](https://github.com/box/box-codegen/issues/781) [box/box-openapi#545](https://github.com/box/box-openapi/issues/545) + +For more details check [migration guide](https://github.com/box/box-python-sdk/blob/sdk-gen/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md) from `box-python-sdk-gen` `v1` to `box-python-sdk` `v10`. + ## [3.14.0](https://github.com/box/box-python-sdk/compare/v3.13.0...v3.14.0) (2025-04-09) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index b794fd409..2e568bf45 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '0.1.0' +__version__ = '10.0.0' From 9f1aac61c77784803810d368c834975f88ebd9e1 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 22 Sep 2025 15:04:59 +0200 Subject: [PATCH 025/139] docs: Add info on Enhanced Extract Agent (box/box-openapi#555) (#1040) --- .codegen.json | 2 +- box_sdk_gen/box/event_stream.py | 2 +- box_sdk_gen/managers/ai.py | 2 +- box_sdk_gen/schemas/ai_agent_reference.py | 10 ++++++---- docs/ai.md | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.codegen.json b/.codegen.json index 4b70ff577..53cda30cb 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "87747c8", "specHash": "a05e5d7", "version": "10.0.0" } +{ "engineHash": "0271743", "specHash": "1ed059a", "version": "10.0.0" } diff --git a/box_sdk_gen/box/event_stream.py b/box_sdk_gen/box/event_stream.py index 23b2b3b61..efa8719b3 100644 --- a/box_sdk_gen/box/event_stream.py +++ b/box_sdk_gen/box/event_stream.py @@ -116,7 +116,7 @@ def _get_long_poll_info(self): info = self._events_manager.get_events_with_long_polling() server = next( - (e for e in (info.entries or []) if e.type == 'realtime_server'), None + (e for e in info.entries or [] if e.type == 'realtime_server'), None ) if not server: raise BoxSDKError(message='No realtime server found in the response.') diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index 9e09529a1..d75d5cbe6 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -440,7 +440,7 @@ def create_ai_extract_structured( To learn more about creating templates, see [Creating metadata templates in the Admin Console](https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates) - or use the [metadata template API](g://metadata/templates/create). + or use the [metadata template API](g://metadata/templates/create). This endpoint also supports [Enhanced Extract Agent](g://box-ai/ai-tutorials/extract-metadata-structured/#enhanced-extract-agent). :param items: The items to be processed by the LLM. Currently you can use files only. :type items: List[AiItemBase] diff --git a/box_sdk_gen/schemas/ai_agent_reference.py b/box_sdk_gen/schemas/ai_agent_reference.py index bf961999e..f8b2bb38b 100644 --- a/box_sdk_gen/schemas/ai_agent_reference.py +++ b/box_sdk_gen/schemas/ai_agent_reference.py @@ -22,10 +22,12 @@ def __init__( **kwargs ): """ - :param type: The type of AI agent used to handle queries., defaults to AiAgentReferenceTypeField.AI_AGENT_ID - :type type: AiAgentReferenceTypeField, optional - :param id: The ID of an Agent., defaults to None - :type id: Optional[str], optional + :param type: The type of AI agent used to handle queries., defaults to AiAgentReferenceTypeField.AI_AGENT_ID + :type type: AiAgentReferenceTypeField, optional + :param id: The ID of an Agent. This can be a numeric ID for custom agents (for example, `14031`) + or a unique identifier for pre-built agents (for example, `enhanced_extract_agent` + for the [Enhanced Extract Agent](g://box-ai/ai-tutorials/extract-metadata-structured/#enhanced-extract-agent))., defaults to None + :type id: Optional[str], optional """ super().__init__(**kwargs) self.type = type diff --git a/docs/ai.md b/docs/ai.md index d26dd5090..93ff72218 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -191,7 +191,7 @@ Sends an AI request to supported Large Language Models (LLMs) and returns extrac For this request, you either need a metadata template or a list of fields you want to extract. Input is **either** a metadata template or a list of fields to ensure the structure. To learn more about creating templates, see [Creating metadata templates in the Admin Console](https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates) -or use the [metadata template API](g://metadata/templates/create). +or use the [metadata template API](g://metadata/templates/create). This endpoint also supports [Enhanced Extract Agent](g://box-ai/ai-tutorials/extract-metadata-structured/#enhanced-extract-agent). This operation is performed by calling function `create_ai_extract_structured`. From 2bf565530347dec2e5378d65b01ec6f1ce31d76f Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:44:00 +0200 Subject: [PATCH 026/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1044) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 53cda30cb..51e7551bf 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "0271743", "specHash": "1ed059a", "version": "10.0.0" } +{ "engineHash": "eed6b7a", "specHash": "1ed059a", "version": "10.0.0" } From 36916806ea1ed37e82103b38bab517ff27f1c11b Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 26 Sep 2025 21:11:08 +0200 Subject: [PATCH 027/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1060) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 51e7551bf..14def41c6 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "eed6b7a", "specHash": "1ed059a", "version": "10.0.0" } +{ "engineHash": "d99613e", "specHash": "1ed059a", "version": "10.0.0" } From bcfb94220b170f56c01cc22bb00f321271f82853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Socha?= <31014760+lukaszsocha2@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:06:52 +0200 Subject: [PATCH 028/139] docs: Fix typo in migration guide and update developer guide link in Changelog (#1070) --- .codegen.json | 2 +- CHANGELOG.md | 2 +- .../from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.codegen.json b/.codegen.json index 14def41c6..ab2d2c669 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "d99613e", "specHash": "1ed059a", "version": "10.0.0" } +{ "engineHash": "d16c766", "specHash": "1ed059a", "version": "10.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bae043a6..953e013ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ Introducing **`Box SDK v10`**, a major release designed to elevate your develope ### Important Notes -This version includes the Box Next Generation SDK artifact, previously developed in parallel to core Box SDKs. To facilitate migration and provide access to the newest features, we'll soon release an additional major version combining both artifacts. Migration to `v10` includes breaking changes, please review the [migration guide](https://github.com/box/box-python-sdk/blob/sdk-gen/migration-guides/from-v3-to-v10.md) for details. For more information, see the [SDK versioning strategy document](https://developer.box.com/tooling/sdks/sdk-versioning). Follow developer changelog for future updates. +This version includes the Box Next Generation SDK artifact, previously developed in parallel to core Box SDKs. To facilitate migration and provide access to the newest features, we'll soon release an additional major version combining both artifacts. Migration to `v10` includes breaking changes, please review the [migration guide](https://github.com/box/box-python-sdk/blob/sdk-gen/migration-guides/from-v3-to-v10.md) for details. For more information, see the [SDK versioning strategy document](https://developer.box.com/guides/tooling/sdks/sdk-versioning). Follow developer changelog for future updates. --- diff --git a/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md b/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md index afab2bbf3..16da46401 100644 --- a/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md +++ b/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md @@ -1,4 +1,4 @@ -# Migration guide from v1 version of the `box-python-sdk-gen` to the v10 version of the `box-pythom-sdk` +# Migration guide from v1 version of the `box-python-sdk-gen` to the v10 version of the `box-python-sdk` From 72f6aecaa7064bbd2dafcd614357a6447672b5a4 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 2 Oct 2025 12:16:23 +0200 Subject: [PATCH 029/139] chore: Remove empty file (box/box-codegen#850) (#1081) --- .codegen.json | 2 +- box_sdk_gen/schemas/__init__.py | 2 -- box_sdk_gen/schemas/generic_source.py | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 box_sdk_gen/schemas/generic_source.py diff --git a/.codegen.json b/.codegen.json index ab2d2c669..43216ed21 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "d16c766", "specHash": "1ed059a", "version": "10.0.0" } +{ "engineHash": "52d5b2e", "specHash": "1ed059a", "version": "10.0.0" } diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index 20fab2585..ca1144376 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -156,8 +156,6 @@ from box_sdk_gen.schemas.folder_reference import * -from box_sdk_gen.schemas.generic_source import * - from box_sdk_gen.schemas.group_base import * from box_sdk_gen.schemas.group_mini import * diff --git a/box_sdk_gen/schemas/generic_source.py b/box_sdk_gen/schemas/generic_source.py deleted file mode 100644 index e476ab914..000000000 --- a/box_sdk_gen/schemas/generic_source.py +++ /dev/null @@ -1 +0,0 @@ -from box_sdk_gen.box.errors import BoxSDKError From 7faa6c95ac50c8ea97605e564f1188637829d412 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:37:34 +0200 Subject: [PATCH 030/139] ci: Update auto-update job (box/box-codegen#855) (#1083) --- .codegen.json | 2 +- .github/workflows/autoupdate-pr.yml | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.codegen.json b/.codegen.json index 43216ed21..f7af01094 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "52d5b2e", "specHash": "1ed059a", "version": "10.0.0" } +{ "engineHash": "504b5a7", "specHash": "1ed059a", "version": "10.0.0" } diff --git a/.github/workflows/autoupdate-pr.yml b/.github/workflows/autoupdate-pr.yml index bbf7ad303..0540ae82a 100644 --- a/.github/workflows/autoupdate-pr.yml +++ b/.github/workflows/autoupdate-pr.yml @@ -2,7 +2,9 @@ name: Autoupdate PR on: push: branches: + - main - sdk-gen + - combined-sdk jobs: update_pull_requests: @@ -24,10 +26,10 @@ jobs: - name: Auto update pull requests run: | - PR_LIST=$(curl -s -H "Authorization: Bearer ${{ secrets.DISPATCH_ACCESS_TOKEN }}" "https://api.github.com/repos/$GITHUB_REPOSITORY/pulls?state=open" | jq -r '.[] | .head.ref') + PR_LIST=$(curl -s -H "Authorization: Bearer ${{ secrets.DISPATCH_ACCESS_TOKEN }}" "https://api.github.com/repos/$GITHUB_REPOSITORY/pulls?state=open&head=${{ github.ref_name }}" | jq -r '.[] | .head.ref') for pr_branch in $PR_LIST; do git checkout "$pr_branch" - if git merge origin/sdk-gen; then + if git merge origin/${{ github.ref_name }}; then git push else # Conflict occurred, resolve by keeping our changes From f63cffec92ccf98af21d6227328aab00fa159187 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:52:02 +0200 Subject: [PATCH 031/139] fix: Allow flexible key-value data in `AiExtractResponse.answer` and `Event.additionalDetails` (box/box-openapi#556) (#1084) --- .codegen.json | 2 +- box_sdk_gen/schemas/__init__.py | 2 -- box_sdk_gen/schemas/ai_extract_response.py | 8 -------- .../schemas/ai_extract_structured_response.py | 6 +++--- box_sdk_gen/schemas/event.py | 15 ++++++--------- 5 files changed, 10 insertions(+), 23 deletions(-) delete mode 100644 box_sdk_gen/schemas/ai_extract_response.py diff --git a/.codegen.json b/.codegen.json index f7af01094..57aae7499 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "504b5a7", "specHash": "1ed059a", "version": "10.0.0" } +{ "engineHash": "504b5a7", "specHash": "62fdfd1", "version": "10.0.0" } diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index ca1144376..1306426d7 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -10,8 +10,6 @@ from box_sdk_gen.schemas.ai_dialogue_history import * -from box_sdk_gen.schemas.ai_extract_response import * - from box_sdk_gen.schemas.ai_extract_structured_response import * from box_sdk_gen.schemas.ai_item_base import * diff --git a/box_sdk_gen/schemas/ai_extract_response.py b/box_sdk_gen/schemas/ai_extract_response.py deleted file mode 100644 index e18207b35..000000000 --- a/box_sdk_gen/schemas/ai_extract_response.py +++ /dev/null @@ -1,8 +0,0 @@ -from box_sdk_gen.internal.base_object import BaseObject - -from box_sdk_gen.box.errors import BoxSDKError - - -class AiExtractResponse(BaseObject): - def __init__(self, **kwargs): - super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/ai_extract_structured_response.py b/box_sdk_gen/schemas/ai_extract_structured_response.py index 9c9e7f3ed..76ae79525 100644 --- a/box_sdk_gen/schemas/ai_extract_structured_response.py +++ b/box_sdk_gen/schemas/ai_extract_structured_response.py @@ -1,9 +1,9 @@ +from typing import Dict + from typing import Optional from box_sdk_gen.internal.base_object import BaseObject -from box_sdk_gen.schemas.ai_extract_response import AiExtractResponse - from box_sdk_gen.schemas.ai_agent_info import AiAgentInfo from box_sdk_gen.box.errors import BoxSDKError @@ -14,7 +14,7 @@ class AiExtractStructuredResponse(BaseObject): def __init__( self, - answer: AiExtractResponse, + answer: Dict, created_at: DateTime, *, completion_reason: Optional[str] = None, diff --git a/box_sdk_gen/schemas/event.py b/box_sdk_gen/schemas/event.py index eccdf7582..56ac2b68e 100644 --- a/box_sdk_gen/schemas/event.py +++ b/box_sdk_gen/schemas/event.py @@ -1,9 +1,11 @@ from enum import Enum -from box_sdk_gen.internal.base_object import BaseObject - from typing import Optional +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + from box_sdk_gen.schemas.user import User from box_sdk_gen.schemas.event_source import EventSource @@ -185,11 +187,6 @@ class EventEventTypeField(str, Enum): WATERMARK_LABEL_DELETE = 'WATERMARK_LABEL_DELETE' -class EventAdditionalDetailsField(BaseObject): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - class Event(BaseObject): def __init__( self, @@ -202,7 +199,7 @@ def __init__( event_type: Optional[EventEventTypeField] = None, session_id: Optional[str] = None, source: Optional[EventSourceResource] = None, - additional_details: Optional[EventAdditionalDetailsField] = None, + additional_details: Optional[Dict] = None, **kwargs ): """ @@ -223,7 +220,7 @@ def __init__( information to correlate an event to external KeySafe logs. Not all events have an `additional_details` object. This object is only available in the Enterprise Events., defaults to None - :type additional_details: Optional[EventAdditionalDetailsField], optional + :type additional_details: Optional[Dict], optional """ super().__init__(**kwargs) self.type = type From d10195abf52dd767397e0d3ce2c516b691ceb177 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 3 Oct 2025 10:38:16 +0200 Subject: [PATCH 032/139] test: Change ai and events tests to use fields instead of raw json (box/box-codegen#852) (#1088) --- .codegen.json | 2 +- test/ai.py | 40 ++++++++++------------------------------ test/events.py | 1 + 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/.codegen.json b/.codegen.json index 57aae7499..18c0213b6 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "504b5a7", "specHash": "62fdfd1", "version": "10.0.0" } +{ "engineHash": "10c68c7", "specHash": "62fdfd1", "version": "10.0.0" } diff --git a/test/ai.py b/test/ai.py index 98c6d26e3..d982ee16c 100644 --- a/test/ai.py +++ b/test/ai.py @@ -328,21 +328,11 @@ def testAIExtractStructuredWithFields(): ], ai_agent=agent_ignoring_overriding_embeddings_model, ) - assert to_string( - get_value_from_object_raw_data(response, 'answer.hobby') - ) == to_string(['guitar']) - assert ( - to_string(get_value_from_object_raw_data(response, 'answer.firstName')) - == 'John' - ) - assert ( - to_string(get_value_from_object_raw_data(response, 'answer.lastName')) == 'Doe' - ) - assert ( - to_string(get_value_from_object_raw_data(response, 'answer.dateOfBirth')) - == '1990-07-04' - ) - assert to_string(get_value_from_object_raw_data(response, 'answer.age')) == '34' + assert to_string(response.answer.get('hobby')) == to_string(['guitar']) + assert to_string(response.answer.get('firstName')) == 'John' + assert to_string(response.answer.get('lastName')) == 'Doe' + assert to_string(response.answer.get('dateOfBirth')) == '1990-07-04' + assert to_string(response.answer.get('age')) == '34' assert response.completion_reason == 'done' client.files.delete_file_by_id(file.id) @@ -407,21 +397,11 @@ def testAIExtractStructuredWithMetadataTemplate(): template_key=template_key, scope='enterprise' ), ) - assert ( - to_string(get_value_from_object_raw_data(response, 'answer.firstName')) - == 'John' - ) - assert ( - to_string(get_value_from_object_raw_data(response, 'answer.lastName')) == 'Doe' - ) - assert ( - to_string(get_value_from_object_raw_data(response, 'answer.dateOfBirth')) - == '1990-07-04T00:00:00Z' - ) - assert to_string(get_value_from_object_raw_data(response, 'answer.age')) == '34' - assert to_string( - get_value_from_object_raw_data(response, 'answer.hobby') - ) == to_string(['guitar']) + assert to_string(response.answer.get('firstName')) == 'John' + assert to_string(response.answer.get('lastName')) == 'Doe' + assert to_string(response.answer.get('dateOfBirth')) == '1990-07-04T00:00:00Z' + assert to_string(response.answer.get('age')) == '34' + assert to_string(response.answer.get('hobby')) == to_string(['guitar']) assert response.completion_reason == 'done' client.metadata_templates.delete_metadata_template( DeleteMetadataTemplateScope.ENTERPRISE, template.template_key diff --git a/test/events.py b/test/events.py index 37d525d4d..5cafcfd8d 100644 --- a/test/events.py +++ b/test/events.py @@ -53,6 +53,7 @@ def testEventUpload(): assert len(events.entries) > 0 first_event: Event = events.entries[0] assert to_string(first_event.event_type) == 'UPLOAD' + assert to_string(first_event.additional_details.get('hash_type')) == 'sha1' source: EventSource = first_event.source assert ( to_string(source.item_type) == 'file' or to_string(source.item_type) == 'folder' From 384751f4e905b84c2bf321e3d43184909211329c Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:22:10 +0200 Subject: [PATCH 033/139] chore: Fix auto-update job (box/box-codegen#856) (#1091) --- .codegen.json | 2 +- .github/workflows/autoupdate-pr.yml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.codegen.json b/.codegen.json index 18c0213b6..277033299 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "10c68c7", "specHash": "62fdfd1", "version": "10.0.0" } +{ "engineHash": "9334953", "specHash": "62fdfd1", "version": "10.0.0" } diff --git a/.github/workflows/autoupdate-pr.yml b/.github/workflows/autoupdate-pr.yml index 0540ae82a..030e9154c 100644 --- a/.github/workflows/autoupdate-pr.yml +++ b/.github/workflows/autoupdate-pr.yml @@ -4,7 +4,6 @@ on: branches: - main - sdk-gen - - combined-sdk jobs: update_pull_requests: @@ -26,7 +25,7 @@ jobs: - name: Auto update pull requests run: | - PR_LIST=$(curl -s -H "Authorization: Bearer ${{ secrets.DISPATCH_ACCESS_TOKEN }}" "https://api.github.com/repos/$GITHUB_REPOSITORY/pulls?state=open&head=${{ github.ref_name }}" | jq -r '.[] | .head.ref') + PR_LIST=$(curl -s -H "Authorization: Bearer ${{ secrets.DISPATCH_ACCESS_TOKEN }}" "https://api.github.com/repos/$GITHUB_REPOSITORY/pulls?state=open&base=${{ github.ref_name }}" | jq -r '.[] | .head.ref') for pr_branch in $PR_LIST; do git checkout "$pr_branch" if git merge origin/${{ github.ref_name }}; then From 5c21907869d359fdb8fe4c83317a9eca5aeffdc3 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:57:52 +0200 Subject: [PATCH 034/139] fix: Make `role` parameter of update collaboration optional (box/box-openapi#557) (#1093) --- .codegen.json | 2 +- box_sdk_gen/managers/user_collaborations.py | 8 ++++---- docs/user_collaborations.md | 6 +++--- test/user_collaborations.py | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.codegen.json b/.codegen.json index 277033299..4b5052051 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "9334953", "specHash": "62fdfd1", "version": "10.0.0" } +{ "engineHash": "9334953", "specHash": "c7acada", "version": "10.0.0" } diff --git a/box_sdk_gen/managers/user_collaborations.py b/box_sdk_gen/managers/user_collaborations.py index 10641f062..5b6b0e345 100644 --- a/box_sdk_gen/managers/user_collaborations.py +++ b/box_sdk_gen/managers/user_collaborations.py @@ -195,8 +195,8 @@ def get_collaboration_by_id( def update_collaboration_by_id( self, collaboration_id: str, - role: UpdateCollaborationByIdRole, *, + role: Optional[UpdateCollaborationByIdRole] = None, status: Optional[UpdateCollaborationByIdStatus] = None, expires_at: Optional[DateTime] = None, can_view_path: Optional[bool] = None, @@ -208,13 +208,13 @@ def update_collaboration_by_id( Can be used to change the owner of an item, or to - accept collaboration invites. + accept collaboration invites. In case of accepting collaboration invite, role is not required. :param collaboration_id: The ID of the collaboration. Example: "1234" :type collaboration_id: str - :param role: The level of access granted. - :type role: UpdateCollaborationByIdRole + :param role: The level of access granted., defaults to None + :type role: Optional[UpdateCollaborationByIdRole], optional :param status: Set the status of a `pending` collaboration invitation, effectively accepting, or rejecting the invite., defaults to None :type status: Optional[UpdateCollaborationByIdStatus], optional diff --git a/docs/user_collaborations.md b/docs/user_collaborations.md index 1cbdc0b7a..193e6b2e6 100644 --- a/docs/user_collaborations.md +++ b/docs/user_collaborations.md @@ -39,7 +39,7 @@ Returns a collaboration object. Updates a collaboration. Can be used to change the owner of an item, or to -accept collaboration invites. +accept collaboration invites. In case of accepting collaboration invite, role is not required. This operation is performed by calling function `update_collaboration_by_id`. @@ -50,7 +50,7 @@ See the endpoint docs at ```python client.user_collaborations.update_collaboration_by_id( - collaboration_id, UpdateCollaborationByIdRole.VIEWER + collaboration_id, role=UpdateCollaborationByIdRole.VIEWER ) ``` @@ -58,7 +58,7 @@ client.user_collaborations.update_collaboration_by_id( - collaboration_id `str` - The ID of the collaboration. Example: "1234" -- role `UpdateCollaborationByIdRole` +- role `Optional[UpdateCollaborationByIdRole]` - The level of access granted. - status `Optional[UpdateCollaborationByIdStatus]` - Set the status of a `pending` collaboration invitation, effectively accepting, or rejecting the invite. diff --git a/test/user_collaborations.py b/test/user_collaborations.py index 1daf1e52c..2e9a578e0 100644 --- a/test/user_collaborations.py +++ b/test/user_collaborations.py @@ -64,7 +64,7 @@ def testUserCollaborations(): assert collaboration_from_api.invite_email == None updated_collaboration: Optional[Collaboration] = ( client.user_collaborations.update_collaboration_by_id( - collaboration_id, UpdateCollaborationByIdRole.VIEWER + collaboration_id, role=UpdateCollaborationByIdRole.VIEWER ) ) assert to_string(updated_collaboration.role) == 'viewer' @@ -94,7 +94,7 @@ def testConvertingUserCollaborationToOwnership(): assert to_string(collaboration.role) == 'editor' owner_collaboration: Optional[Collaboration] = ( client.user_collaborations.update_collaboration_by_id( - collaboration.id, UpdateCollaborationByIdRole.OWNER + collaboration.id, role=UpdateCollaborationByIdRole.OWNER ) ) assert owner_collaboration == None @@ -133,7 +133,7 @@ def testExternalUserCollaborations(): assert collaboration_from_api.invite_email == user_login updated_collaboration: Optional[Collaboration] = ( client.user_collaborations.update_collaboration_by_id( - collaboration_id, UpdateCollaborationByIdRole.VIEWER + collaboration_id, role=UpdateCollaborationByIdRole.VIEWER ) ) assert to_string(updated_collaboration.role) == 'viewer' From c6f8ab8711b1025af1503bbfeada1f1a2a08e11a Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:51:44 +0200 Subject: [PATCH 035/139] docs: Update README with versioning strategy (box/box-codegen#857) (#1100) --- .codegen.json | 2 +- README.md | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 4b5052051..717f836ab 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "9334953", "specHash": "c7acada", "version": "10.0.0" } +{ "engineHash": "7523fdd", "specHash": "fa34496", "version": "10.0.0" } diff --git a/README.md b/README.md index fd823b33d..08a417e20 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,28 @@ “box-dev-logo”

+# Versioning Strategy + +Starting with v10 of the SDK, we’ve introduced a new generated codebase designed to enhance your experience with the Box API. +It is currently available on the [sdk-gen](https://github.com/box/box-python-sdk/tree/sdk-gen) branch and uses the new `box_sdk_gen` package instead of the old `boxsdk`. + +v10 is targeted at new users of Box Python SDK and users already working with the generated Box Python SDK previously available under the [Box Python SDK Gen repository](https://github.com/box/box-python-sdk-gen). + +For users of v3 of the Box Python SDK, no action is required at this time — we’ll be providing an upcoming v4 release that will include both `boxsdk` and `box_sdk_gen` packages for a smooth migration path. To avoid unintentional upgrades, pin your version to `boxsdk~=3.0`. + +For full guidance on SDK versioning, see the [Box SDK Versioning Guide](https://developer.box.com/guides/tooling/sdks/sdk-versioning/). + +--- + +## Which Version Should I Use? + +| Scenario | Recommended Version | Example `pip install` | +| --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------- | +| Creating a new application | Use [v10](https://github.com/box/box-python-sdk/tree/sdk-gen) | `pip install "boxsdk>=10"` | +| Existing app using [box-sdk-gen](https://pypi.org/project/box-sdk-gen/) artifact | Upgrade to [v10](https://github.com/box/box-python-sdk/tree/sdk-gen) | `pip install "boxsdk>=10"` | +| Existing app using [boxsdk](https://pypi.org/project/boxsdk/) artifact planning to use new features | Wait for v4 to start migration process | TBD | +| Existing app using [boxsdk](https://pypi.org/project/boxsdk/) artifact not planning any changes | Stay on `boxsdk~=3.0` | `pip install "boxsdk~=3.0"` | + # Box Python SDK [![Project Status](http://opensource.box.com/badges/active.svg)](http://opensource.box.com/badges) From 8931d8e2ba5a6acb3681728422a4ea88cc893a1f Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:02:46 +0200 Subject: [PATCH 036/139] chore: release version 10.0.1 (#1103) --- .codegen.json | 2 +- CHANGELOG.md | 8 ++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 717f836ab..46c9a81e6 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "7523fdd", "specHash": "fa34496", "version": "10.0.0" } +{ "engineHash": "7523fdd", "specHash": "fa34496", "version": "10.0.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 953e013ca..d47bb73c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [10.0.1](https://github.com/box/box-python-sdk/compare/v10.0.0...v10.0.1) (2025-10-06) + + +### Bug Fixes + +* Allow flexible key-value data in `AiExtractResponse.answer` and `Event.additionalDetails` (box/box-openapi[#556](https://github.com/box/box-python-sdk/issues/556)) ([#1084](https://github.com/box/box-python-sdk/issues/1084)) ([f63cffe](https://github.com/box/box-python-sdk/commit/f63cffec92ccf98af21d6227328aab00fa159187)) +* Make `role` parameter of update collaboration optional (box/box-openapi[#557](https://github.com/box/box-python-sdk/issues/557)) ([#1093](https://github.com/box/box-python-sdk/issues/1093)) ([5c21907](https://github.com/box/box-python-sdk/commit/5c21907869d359fdb8fe4c83317a9eca5aeffdc3)) + ## [10.0.0](https://github.com/box/box-python-sdk/compare/v3.14.0...v10.0.0) (2025-09-17) Introducing **`Box SDK v10`**, a major release designed to elevate your developer experience and streamline integration with Box Content Cloud. Currently available as a separate [`sdk-gen`](https://github.com/box/box-python-sdk/tree/sdk-gen) branch, `v10` will ultimately become the main branch. diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 2e568bf45..540d81da6 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.0.0' +__version__ = '10.0.1' From c83a93bc2a34a9564c9486e918397cee17e804dd Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:36:52 +0200 Subject: [PATCH 037/139] test: Change `Event.additionalDetails` field assertion in events test (box/box-codegen#858) (#1105) --- .codegen.json | 2 +- test/events.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 46c9a81e6..fd72071e5 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "7523fdd", "specHash": "fa34496", "version": "10.0.1" } +{ "engineHash": "733e376", "specHash": "fa34496", "version": "10.0.1" } diff --git a/test/events.py b/test/events.py index 5cafcfd8d..5937880bc 100644 --- a/test/events.py +++ b/test/events.py @@ -53,7 +53,7 @@ def testEventUpload(): assert len(events.entries) > 0 first_event: Event = events.entries[0] assert to_string(first_event.event_type) == 'UPLOAD' - assert to_string(first_event.additional_details.get('hash_type')) == 'sha1' + assert not to_string(first_event.additional_details.get('service_id')) == '' source: EventSource = first_event.source assert ( to_string(source.item_type) == 'file' or to_string(source.item_type) == 'folder' From b193b7783ad5c4a9db77517bc0a967bab325df0c Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:52:21 +0200 Subject: [PATCH 038/139] docs: Improve `working-with-nulls.md` document (box/box-codegen#860) (#1114) --- .codegen.json | 2 +- docs/working-with-nulls.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.codegen.json b/.codegen.json index fd72071e5..2a8a59262 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "733e376", "specHash": "fa34496", "version": "10.0.1" } +{ "engineHash": "48e5311", "specHash": "fa34496", "version": "10.0.1" } diff --git a/docs/working-with-nulls.md b/docs/working-with-nulls.md index 83ec96582..564b4f9c0 100644 --- a/docs/working-with-nulls.md +++ b/docs/working-with-nulls.md @@ -1,10 +1,10 @@ -# Handling null values in v10 version of Box Python SDK +# Handling null values in Box Python SDK -While using v10 version of Box Python SDK it's important to understand how null values behave. This document provides a general overview of null value behaviour in Box Python SDK to help developers manage data consistently and predictably. +While using Box Python SDK it's important to understand how null values behave. This document provides a general overview of null value behaviour in Box Python SDK to help developers manage data consistently and predictably. ## Understanding null behaviour -The v10 of Box Python SDK follows a consistent pattern when handling null values in update operations. This behaviour applies to most endpoints that modify resources such as users, files, folders and metadata. The updating field behaves differently depending on weather you omit it, set it to null, or provide a value: +The Box Python SDK follows a consistent pattern when handling null values in update operations. This behaviour applies to most endpoints that modify resources such as users, files, folders and metadata. The updating field behaves differently depending on weather you omit it, set it to null, or provide a value: - Omitting the field: The field won't be included in request and the value will remain unchanged. - Setting it to null: Setting a field to null, will cause sending HTTP request with field value set to null, what will result in removing its current value or disassociates it from the resource. From a78646d4102f48fdb40156304a0bcb25bd2f70b2 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:46:01 +0200 Subject: [PATCH 039/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1121) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 2a8a59262..b4ce71024 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "48e5311", "specHash": "fa34496", "version": "10.0.1" } +{ "engineHash": "fa0419f", "specHash": "fa34496", "version": "10.0.1" } From 786f1f352d901fea69a547544ca5dfd9c2615d60 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:24:25 +0200 Subject: [PATCH 040/139] docs: Add migration guides (box/box-codegen#866) (#1129) --- .codegen.json | 2 +- ...python-sdk-gen-v1-to-box-python-sdk-v10.md | 32 - ...box-python-sdk-gen-v1-to-box-python-sdk.md | 114 +++ .../from-boxsdk-to-box_sdk_gen.md | 775 ++++++++++++++++++ migration-guides/from-v3-to-v10.md | 743 +---------------- migration-guides/from-v3-to-v4.md | 92 +++ migration-guides/from-v4-to-v10.md | 44 + 7 files changed, 1036 insertions(+), 766 deletions(-) delete mode 100644 migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md create mode 100644 migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk.md create mode 100644 migration-guides/from-boxsdk-to-box_sdk_gen.md create mode 100644 migration-guides/from-v3-to-v4.md create mode 100644 migration-guides/from-v4-to-v10.md diff --git a/.codegen.json b/.codegen.json index b4ce71024..afec1fed3 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "fa0419f", "specHash": "fa34496", "version": "10.0.1" } +{ "engineHash": "4a6585f", "specHash": "fa34496", "version": "10.0.1" } diff --git a/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md b/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md deleted file mode 100644 index 16da46401..000000000 --- a/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk-v10.md +++ /dev/null @@ -1,32 +0,0 @@ -# Migration guide from v1 version of the `box-python-sdk-gen` to the v10 version of the `box-python-sdk` - - - - -- [Installation](#installation) - - [How to migrate](#how-to-migrate) - - - -## Installation - -In order to start using v10 version of the Box Python SDK, you need to change the dependency in your project. -The artifact name has changed from `box-sdk-gen` to `boxsdk`. -You also need to set the version to `10.0.0` or higher. You can find the latest version on [PyPI](https://pypi.org/project/boxsdk/). - -### How to migrate - -To start using v10 version of Box Python SDK in you Maven project replace the dependency in your `requirements.txt` -or installation command. - -**Old (`box-python-sdk-gen-v1`)** - -```console -pip install box-sdk-gen -``` - -**New (`box-python-sdk-v10`)** - -```console -pip install boxsdk>=10 -``` diff --git a/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk.md b/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk.md new file mode 100644 index 000000000..3c3eb270d --- /dev/null +++ b/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk.md @@ -0,0 +1,114 @@ +# Migration guide from v1 version of the `box-python-sdk-gen` to the `box-python-sdk` + +Note: This guide applies only to migrations targeting Box Python SDK v4.X.Y or v10.X.Y. +It does not apply to other major versions (e.g., v5.X, v11.X). + + + + +- [Introduction](#introduction) +- [Installation](#installation) + - [How to migrate](#how-to-migrate) +- [Usage](#usage) + - [Using the Box Python SDK v10](#using-the-box-python-sdk-v10) + - [Using the Box Python SDK v4](#using-the-box-python-sdk-v4) + + + +## Introduction + +From the `box-python-sdk-gen` you can migrate either to v4 or v10 of the Box Python SDK. +Your choice should depend on whether you want to continue using the manually maintained SDK (Box Python SDK v3) alongside the generated one or not. + +The v4 version of the Box Python SDK consolidates both the legacy SDK package `boxsdk` and the generated one `box_sdk_gen`. + +- If previously you were using both artifacts `boxsdk` v3 and `box-sdk-gen` v1, migrate to v4 version of the Box Python SDK which consolidates `boxsdk` and `box_sdk_gen` packages. +- If you were only using the generated artifact `box-sdk-gen`, migrate to v10 version of the Box Python SDK which contains only the generated `box_sdk_gen` package. + +| Scenario | Your current usage | Recommended target | Packages included in target | Why this choice | Notes | +| -------------------------------------------- | -------------------------------------------------- | ------------------ | --------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | +| Using both manual and generated SDK together | `boxsdk` v3 + `box-sdk-gen` v1 in the same project | v4.X.Y | `boxsdk` (manual) + `box_sdk_gen` (generated) | Keep existing v3 code while adopting new features from the generated SDK | Run both modules side-by-side; use type aliases to avoid name conflicts if necessary | +| Using only the generated SDK | `box-sdk-gen` v1 only | v10.X.Y | `box_sdk_gen` (generated) only | Clean upgrade path with no legacy module; simpler dependency surface | Best when you don’t need the manual `boxsdk` package | + +## Installation + +In order to start using v4 or v10 version of the Box Python SDK, you need to change the dependency in your project. +The artifact name has changed from `box-sdk-gen` to `boxsdk`. +You also need to set the version to `4` if you are migrating to v4 or `10` if you are migrating to v10. +You can find the latest version of each major version on [PyPI](https://pypi.org/project/boxsdk/). + +### How to migrate + +To start using v4 or v10 version of Box Python SDK in your project, replace the dependency in your `requirements.txt` +or installation command. + +**Old (`box-python-sdk-gen-v1`)** + +```console +pip install box-sdk-gen +``` + +**New (`box-python-sdk-v10`)** + +```console +pip install boxsdk>=10 +``` + +**New (`box-python-sdk-v4`)** + +```console +pip install boxsdk~=4.0 +``` + +## Usage + +### Using the Box Python SDK v10 + +After migration from `box-sdk-gen` to the `boxsdk` v10, you can still use the `box_sdk_gen` package in the same way as before. +To access the client for interacting with the Box API, simply import `BoxClient` and any other necessary classes from the `box_sdk_gen` package. + +```python +from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth + +auth: BoxDeveloperTokenAuth = BoxDeveloperTokenAuth( + token="INSERT YOUR DEVELOPER TOKEN HERE" +) +client: BoxClient = BoxClient(auth=auth) +for item in client.folders.get_folder_items("0").entries: + print(item.name) +``` + +### Using the Box Python SDK v4 + +After migration to Box Python SDK v4, you can use both the manual Box Python SDK package `boxsdk` and the generated one `box_sdk_gen`. +You just need to import the required classes from the appropriate package depending on which SDK you intend to use. +If both packages contain classes with the same name, you can use type aliases to resolve any naming conflicts. + +```python +from boxsdk import JWTAuth, Client +from boxsdk.object.folder import Folder as FolderOld +from box_sdk_gen import BoxJWTAuth, JWTConfig, BoxClient, Folder + + +def main(): + auth = JWTAuth.from_settings_file("/path/to/settings.json") + legacy_client = Client(auth) + + jwt_config = JWTConfig.from_config_file(config_file_path="/path/to/settings.json") + auth = BoxJWTAuth(config=jwt_config) + new_client = BoxClient(auth=auth) + + folder: FolderOld = legacy_client.folder(folder_id="0").create_subfolder( + "My Subfolder" + ) + updated_folder: Folder = new_client.folders.update_folder( + folder_id=folder.id, name="My Updated Subfolder" + ) + print( + f"Created folder with ID {folder.id} has been updated to {updated_folder.name}" + ) + + +if __name__ == "__main__": + main() +``` diff --git a/migration-guides/from-boxsdk-to-box_sdk_gen.md b/migration-guides/from-boxsdk-to-box_sdk_gen.md new file mode 100644 index 000000000..ac7b6b108 --- /dev/null +++ b/migration-guides/from-boxsdk-to-box_sdk_gen.md @@ -0,0 +1,775 @@ +# Migration guide: migrate from `boxsdk` to `box_sdk_gen` package + + + + +- [Introduction](#introduction) + - [Who is this for?](#who-is-this-for) +- [Installation](#installation) +- [Key differences](#key-differences) + - [Manager approach](#manager-approach) + - [Explicitly defined schemas](#explicitly-defined-schemas) + - [Immutable design](#immutable-design) +- [Authentication](#authentication) + - [Developer Token](#developer-token) + - [JWT Auth](#jwt-auth) + - [Using JWT configuration file](#using-jwt-configuration-file) + - [Providing JWT configuration manually](#providing-jwt-configuration-manually) + - [Authenticate user](#authenticate-user) + - [Client Credentials Grant](#client-credentials-grant) + - [Obtaining Service Account token](#obtaining-service-account-token) + - [Obtaining User token](#obtaining-user-token) + - [Switching between Service Account and User](#switching-between-service-account-and-user) + - [OAuth 2.0 Auth](#oauth-20-auth) + - [Get Authorization URL](#get-authorization-url) + - [Authenticate](#authenticate) + - [Store token and retrieve token callbacks](#store-token-and-retrieve-token-callbacks) + - [Downscope token](#downscope-token) + - [Revoke token](#revoke-token) +- [Configuration](#configuration) + - [As-User header](#as-user-header) + - [Custom Base URLs](#custom-base-urls) +- [Convenience methods](#convenience-methods) + - [Webhook validation](#webhook-validation) + - [Chunked upload of big files](#chunked-upload-of-big-files) + + + +## Introduction + +Version availability: + +- v3: ships only `boxsdk` package +- v4: ships both `boxsdk` and `box_sdk_gen` packages (side-by-side) +- v10+: ships only `box_sdk_gen` module + +This document focuses on helping you migrate code from the manually maintained `boxsdk` module to the generated `box_sdk_gen` package. +Many APIs were redesigned for consistency and modern Python patterns, so this guide calls out how to adopt the new shapes safely and incrementally. + +Supported migration paths: + +- v3 → v4: adopt `box_sdk_gen` gradually while keeping existing `boxsdk` usage +- v3 → v10+: migrate directly to `box_sdk_gen` package only +- v4 (within the same version): move usage from `boxsdk` to `box_sdk_gen` gradually + +For comprehensive API docs with sample code for all methods, see the repository documentation in the root `docs` directory: [`docs/`](../docs/). + +We recommend using `box_sdk_gen` as the preferred SDK going forward. This SDK is automatically generated from the +Box OpenAPI specification, ensuring consistency, reliability, and full API coverage. +Key Benefits: + +- Comprehensive Coverage: Supports all Box API endpoints with consistent and predictable method signatures. +- Rapid Feature Availability: Automatically includes new features as soon as they are released in the Box API. +- Strong Typing: Provides complete type hints for every method and data structure, improving development efficiency and reducing runtime errors. +- Explicit Data Models: Includes clear, well-defined models for all API resources to improve readability and maintainability. +- Immutable Design: Built for immutability, making code behavior more predictable and easier to reason about. +- Rich Documentation: Offers detailed usage examples for every API method to help developers get started quickly. + +### Who is this for? + +- Developers with existing code using `boxsdk` who want to start using `box_sdk_gen` APIs. +- Developers using v4 of Box Python SDK that want to transition usage from `boxsdk` to `box_sdk_gen` within the same app. + +## Key differences + +### Manager approach + +The main difference between the manual `boxsdk` and generated `box_sdk_gen` package is the way how API methods are aggregated into objects. + +**Old (`boxsdk`)** + +Firstly, in the `boxsdk` package, to be able to perform any action on an API object, e.g. `User`, you first had to create its class. +To do it is required to call: + +```python +user = client.user(user_id="123456") +``` + +to create a class representing an already existing User with id '12345', or create a new one with a call: + +```python +user = client.create_user(name="Some User") +``` + +Then, you could perform any action on created class, which will affect the user, e.g. + +```python +updated_user = user.update_info(data={"name": "New User Name"}) +``` + +**New (`box_sdk_gen`)** + +In the `box_sdk_gen` package, the API methods are grouped into dedicated manager classes, e.g. `User` object +has dedicated `UserManager` class. Each manager class instance is available in `BoxClient` object. +The fields storing references to the managers are named in the plural form of the resource that the +manager handles - `client.users` for `UsersManager`. If you want to perform any operation +connected with a `User` you need to call a respective method of `UserManager`. +For example, to get info about existing user you need to call: + +```python +user = client.users.get_user_by_id(user_id="123456") +``` + +or to create a new user: + +```python +user = client.users.create_user(name="Some User") +``` + +The `User` object returned by both of these methods is a data class - it does not contain any methods to call. +To perform any action on `User` object, you need to still use a `UserManager` method for that. +Usually these methods have a first argument, which accepts id of the object you want to access, +e.g. to update a user's name, call method: + +```python +updated_user = client.users.update_user_by_id(user_id=user.id, name="New User Name") +``` + +### Explicitly defined schemas + +**Old (`boxsdk`)** + +In `boxsdk` package, there were no data types explicitly defined - +the responses were dynamically mapped into classes in the runtime. For example, if you get information about a file: + +```python +file = client.file(file_id="12345678").get() +``` + +you couldn't be sure which fields to expect in the response object until the runtime, +because `File` class doesn't have any predefined fields. + +**New (`box_sdk_gen`)** + +In `box_sdk_gen` package, the data classes are defined in `schemas` module, so you know, which fields to expect before +actually making a call. For example `FileBase` class is defined this way: + +```python +class FileBase(BaseObject): + def __init__( + self, + id: str, + *, + etag: Optional[str] = None, + type: FileBaseTypeField = FileBaseTypeField.FILE.value, + **kwargs + ): + super().__init__(**kwargs) + self.id = id + self.type = type + self.etag = etag +``` + +### Immutable design + +The new `box_sdk_gen` package is designed to be mostly immutable. This means that methods, +which used to modify the existing object in `boxsdk` package now return a new instance of the class with the modified state. +This design pattern is used to avoid side effects and make the code more predictable and easier to reason about. +Methods, which returns a new modified instance of an object, will always have a prefix `with_` in their names, e.g. + +**New (`box_sdk_gen`)** + +```python +from box_sdk_gen import BoxClient + +as_user_client: BoxClient = client.with_as_user_header("USER_ID") +``` + +## Authentication + +The `box_sdk_gen` package offers the same authentication methods as `boxsdk` package. +Let's see the differences of their usage: + +### Developer Token + +**Old (`boxsdk`)** + +```python +from boxsdk import Client, OAuth2 + +auth = OAuth2( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + access_token="DEVELOPER_TOKEN_GOES_HERE", +) +client = Client(auth) +``` + +The `box_sdk_gen` package, provides a convenient `BoxDeveloperTokenAuth`, which allows authenticating +using developer token without necessity to provide a Client ID and Client Secret + +**New (`box_sdk_gen`)** + +```python +from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth + +auth = BoxDeveloperTokenAuth(token="DEVELOPER_TOKEN_GOES_HERE") +client = BoxClient(auth=auth) +``` + +### JWT Auth + +#### Using JWT configuration file + +**Old (`boxsdk`)** + +The static method, which reads the JWT configuration file has been changed: + +```python +from boxsdk import JWTAuth, Client + +auth = JWTAuth.from_settings_file("/path/to/config.json") +client = Client(auth) +``` + +**New (`box_sdk_gen`)** + +```python +from box_sdk_gen import BoxClient, BoxJWTAuth, JWTConfig + +jwt_config = JWTConfig.from_config_file(config_file_path="/path/to/config.json") +auth = BoxJWTAuth(config=jwt_config) +client = BoxClient(auth=auth) +``` + +#### Providing JWT configuration manually + +Some params in `JWTConfig` constructor have slightly different names than one in the old `JWTAuth` class. + +**Old (`boxsdk`)** + +```python +from boxsdk import JWTAuth + +auth = JWTAuth( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + enterprise_id="YOUR_ENTERPRISE_ID", + user_id="USER_ID", + jwt_key_id="YOUR_JWT_KEY_ID", + rsa_private_key_file_sys_path="CERT.PEM", + rsa_private_key_passphrase="PASSPHRASE", + jwt_algorithm="RS256", +) +``` + +**New (`box_sdk_gen`)** + +```python +from box_sdk_gen import BoxJWTAuth, JWTConfig, JwtAlgorithm + +jwt_config = JWTConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + enterprise_id="YOUR_ENTERPRISE_ID", + user_id="USER_ID", + jwt_key_id="YOUR_JWT_KEY_ID", + private_key="YOUR_PRIVATE_KEY", + private_key_passphrase="PASSPHRASE", + algorithm=JwtAlgorithm.RS256, +) +auth = BoxJWTAuth(config=jwt_config) +``` + +#### Authenticate user + +In `boxsdk` package, method for user authentication was named `authenticate_user(self, user: Union[str, 'User'] = None) -> str` +and was accepting either user object or user id. If none provided, user ID stored in `JWTAuth` class instance was used. +The `authenticate_user` method was modifying existing `BoxJWTAuth` class, which was exchanging the existing token with +the one with the user access. + +**Old (`boxsdk`)** + +```python +auth.authenticate_user(user) +``` + +or + +```python +auth.authenticate_user("USER_ID") +``` + +**New (`box_sdk_gen`)** + +In new `box_sdk_gen` package, to authenticate as user you need to call +`with_user_subject(self, user_id: str, *, token_storage: TokenStorage = None) -> BoxJWTAuth` method with id of the user +to authenticate. The method returns a new instance of `BoxJWTAuth` class, which will perform authentication call +in scope of the user on the first API call. The `token_storage` parameter is optional and allows to provide a custom +token storage for the new instance of `BoxJWTAuth` class. The new auth instance can be used to create a new user client +instance. + +```python +from box_sdk_gen import BoxJWTAuth, BoxClient + +user_auth: BoxJWTAuth = auth.with_user_subject("USER_ID") +user_client: BoxClient = BoxClient(auth=user_auth) +``` + +### Client Credentials Grant + +#### Obtaining Service Account token + +To authenticate as enterprise, the only difference between the versions of SDK, +is using the `CCGConfig` as a middle step. + +**Old (`boxsdk`)** + +```python +from boxsdk import CCGAuth, Client + +auth = CCGAuth( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + enterprise_id="YOUR_ENTERPRISE_ID", +) + +client = Client(auth) +``` + +**New (`box_sdk_gen`)** + +```python +from box_sdk_gen import BoxClient, BoxCCGAuth, CCGConfig + +ccg_config = CCGConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + enterprise_id="YOUR_ENTERPRISE_ID", +) +auth = BoxCCGAuth(config=ccg_config) +client = BoxClient(auth=auth) +``` + +#### Obtaining User token + +In `boxsdk` package `CCGAuth` was accepting both user object and User ID. In `box_sdk_gen` package +the `BoxCCGAuth` constructor accepts only User ID instead. + +**Old (`boxsdk`)** + +```python +from boxsdk import CCGAuth + +auth = CCGAuth( + client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", user="YOUR_USER_ID" +) +``` + +**New (`box_sdk_gen`)** + +```python +from box_sdk_gen import BoxCCGAuth, CCGConfig + +ccg_config = CCGConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + user_id="YOUR_USER_ID", +) +auth = BoxCCGAuth(config=ccg_config) +``` + +### Switching between Service Account and User + +In `boxsdk` package, there were two methods which allowed to switch between using service and user account. Calling these methods +were modifying existing state of `CCGAuth` class, which was fetching a new token on the next API call. + +**Old (`boxsdk`)** + +```python +auth.authenticate_enterprise("ENTERPRISE_ID") +``` + +```python +auth.authenticate_user("USER_ID") +``` + +In the `box_sdk_gen` package, to keep the immutability design, the methods switching authenticated subject were replaced with methods +returning a new instance of `BoxCCGAuth` class. The new instance will fetch a new token on the next API call. +The new auth instance can be used to create a new client instance. You can also specify `token_storage` parameter +to provide a custom token storage for the new instance. +The old instance of `BoxCCGAuth` class will remain unchanged and will still use the old token. + +**New (`box_sdk_gen`)** + +```python +from box_sdk_gen import BoxCCGAuth, BoxClient + +enterprise_auth: BoxCCGAuth = auth.with_enterprise_subject( + enterprise_id="ENTERPRISE_ID" +) +enterprise_client: BoxClient = BoxClient(auth=enterprise_auth) +``` + +```python +from box_sdk_gen import BoxCCGAuth, BoxClient + +user_auth: BoxCCGAuth = auth.with_user_subject(user_id="USER_ID") +user_client: BoxClient = BoxClient(auth=user_auth) +``` + +Note that the new methods accept only user id or enterprise id, while the old ones were accepting +user and enterprise object too. + +### OAuth 2.0 Auth + +#### Get Authorization URL + +To get authorization url in the `box_sdk_gen` package, you need to first create the `BoxOAuth` class (previously `OAuth2`) using +`OAuthConfig` class. Then to get authorization url, call +`get_authorize_url(self, *, options: GetAuthorizeUrlOptions = None) -> str` instead of +`get_authorization_url(self, redirect_url: Optional[str]) -> Tuple[str, str]`. Note that this method +now accepts the instance of `GetAuthorizeUrlOptions` class, which allows specifying extra options to API call. +The new function returns only the authentication url string, while the old one returns tuple of +authentication url and csrf_token. + +**Old (`boxsdk`)** + +```python +from boxsdk import OAuth2 + +auth = OAuth2( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", +) + +auth_url, csrf_token = auth.get_authorization_url("http://YOUR_REDIRECT_URL") +``` + +**New (`box_sdk_gen`)** + +```python +from box_sdk_gen import BoxOAuth, OAuthConfig, GetAuthorizeUrlOptions + +auth = BoxOAuth( + OAuthConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + ) +) +auth_url = auth.get_authorize_url( + options=GetAuthorizeUrlOptions(redirect_uri="http://YOUR_REDIRECT_URL") +) +``` + +#### Authenticate + +The signature of method for authenticating with obtained auth code got changed from: +`authenticate(self, auth_code: Optional[str]) -> Tuple[str, str]` to +`get_tokens_authorization_code_grant(self, authorization_code: str, *, network_session: Optional[NetworkSession] = None) -> AccessToken`. +The method now returns an AccessToken object with `access_token` and `refresh_token` fields, +while the old one was returning a tuple of access token and refresh token. + +**Old (`boxsdk`)** + +```python +from boxsdk import Client + +access_token, refresh_token = auth.authenticate("YOUR_AUTH_CODE") +client = Client(auth) +``` + +**New (`box_sdk_gen`)** + +```python +from box_sdk_gen import BoxClient, AccessToken + +access_token: AccessToken = auth.get_tokens_authorization_code_grant("YOUR_AUTH_CODE") +client = BoxClient(auth) +``` + +### Store token and retrieve token callbacks + +In `boxsdk` package you could provide a `store_tokens` callback method to an authentication class, which was called each time +an access token was refreshed. It could be used to save your access token to a custom token storage +and allow to reuse this token later. +What is more, `boxsdk` package allowed also to provide `retrieve_tokens` callback, which is called each time the SDK needs to use +token to perform an API call. To provide that, it was required to use `CooperativelyManagedOAuth2` and provide +`retrieve_tokens` callback method to its constructor. + +**Old (`boxsdk`)** + +```python +from typing import Tuple +from boxsdk.auth import CooperativelyManagedOAuth2 +from boxsdk import Client + + +def retrieve_tokens() -> Tuple[str, str]: + # retrieve access_token and refresh_token + return access_token, refresh_token + + +def store_tokens(access_token: str, refresh_token: str): + # store access_token and refresh_token + pass + + +auth = CooperativelyManagedOAuth2( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + retrieve_tokens=retrieve_tokens, + store_tokens=store_tokens, +) +access_token, refresh_token = auth.authenticate("YOUR_AUTH_CODE") +client = Client(auth) +``` + +In the `box_sdk_gen` package, you can define your own class delegated for storing and retrieving a token. It has to inherit from +`TokenStorage` and implement all of its abstract methods. Next step would be to pass an instance of this class to the +AuthConfig constructor. + +**New (`box_sdk_gen`)** + +```python +from typing import Optional +from box_sdk_gen import BoxOAuth, OAuthConfig, TokenStorage, AccessToken + + +class MyCustomTokenStorage(TokenStorage): + def store(self, token: AccessToken) -> None: + # store token + pass + + def get(self) -> Optional[AccessToken]: + # get token + pass + + def clear(self) -> None: + # clear token + pass + + +auth = BoxOAuth( + OAuthConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + token_storage=MyCustomTokenStorage(), + ) +) +``` + +or reuse one of the provided implementations: `FileTokenStorage` or `FileWithInMemoryCacheTokenStorage`: + +```python +from box_sdk_gen import BoxOAuth, OAuthConfig, FileWithInMemoryCacheTokenStorage + +auth = BoxOAuth( + OAuthConfig( + client_id="YOUR_CLIENT_ID", + client_secret="YOUR_CLIENT_SECRET", + token_storage=FileWithInMemoryCacheTokenStorage(), + ) +) +``` + +### Downscope token + +The process of downscoping token in the new package is similar to the old one. The main difference is that the new method +accepts the full resource path instead of file object. + +**Old (`boxsdk`)** + +```python +from boxsdk import Client, OAuth2 + +target_file = client.file(file_id="FILE_ID_HERE") +token_info = client.downscope_token(["item_preview"], target_file) +downscoped_auth = OAuth2( + client_id=None, client_secret=None, access_token=token_info.access_token +) +downscoped_client = Client(downscoped_auth) +``` + +**New (`box_sdk_gen`)** + +```python +from box_sdk_gen import BoxDeveloperTokenAuth, AccessToken, BoxClient + +resource = "https://api.box.com/2.0/files/123456789" +downscoped_token: AccessToken = auth.downscope_token( + scopes=["item_preview"], + resource=resource, +) +downscoped_auth = BoxDeveloperTokenAuth(token=downscoped_token.access_token) +client = BoxClient(auth=downscoped_auth) +``` + +### Revoke token + +To revoke current client's tokens in the `box_sdk_gen` package, you need to call `revoke_token` method of the auth class instead of +`revoke` method. + +**Old (`boxsdk`)** + +```python +oauth.revoke() +``` + +**New (`box_sdk_gen`)** + +```python +client.auth.revoke_token() +``` + +## Configuration + +### As-User header + +The As-User header is used by enterprise admins to make API calls on behalf of their enterprise's users. +This requires the API request to pass an `As-User: USER-ID` header. The following examples assume that the client has +been instantiated with an access token with appropriate privileges to make As-User calls. + +In `boxsdk` package you could call client `as_user(self, user: User)` method to create a new client to impersonate the provided user. + +**Old (`boxsdk`)** + +```python +from boxsdk import Client + +user_to_impersonate = client.user(user_id="USER_ID") +user_client: Client = client.as_user(user_to_impersonate) +``` + +**New (`box_sdk_gen`)** + +In `box_sdk_gen` package the method was renamed to `with_as_user_header(self, user_id: str) -> BoxClient` +and returns a new instance of `BoxClient` class with the As-User header appended to all API calls made by the client. +The method accepts only user id as a parameter. + +```python +from box_sdk_gen import BoxClient + +user_client: BoxClient = client.with_as_user_header(user_id="USER_ID") +``` + +Additionally `BoxClient` offers a `with_extra_headers(self, *, extra_headers: Dict[str, str] = None) -> BoxClient` +method, which allows you to specify the custom set of headers, which will be included in every API call made by client. +Calling the `client.with_extra_headers()` method creates a new client, leaving the original client unmodified. + +```python +from box_sdk_gen import BoxClient + +new_client: BoxClient = client.with_extra_headers( + extra_headers={"customHeader": "customValue"} +) +``` + +### Custom Base URLs + +**Old (`boxsdk`)** + +In manual `boxsdk` package, you could specify the custom base URLs, which will be used for API calls made by setting +the new values of static variables of the `API` class. + +```python +from boxsdk.config import API + +API.BASE_API_URL = "https://new-base-url.com" +API.OAUTH2_API_URL = "https://my-company.com/oauth2" +API.UPLOAD_URL = "https://my-company-upload-url.com" +``` + +**New (`box_sdk_gen`)** + +In the new package this functionality has been implemented as part of the `BoxClient` class. +By calling the `client.with_custom_base_urls()` method, you can specify the custom base URLs that will be used for API +calls made by client. Following the immutability pattern, this call creates a new client, leaving the original client unmodified. + +```python +from box_sdk_gen import BoxClient, BaseUrls + +new_client: BoxClient = client.with_custom_base_urls( + base_urls=BaseUrls( + base_url="https://new-base-url.com", + upload_url="https://my-company-upload-url.com", + oauth_2_url="https://my-company.com/oauth2", + ) +) +``` + +## Convenience methods + +### Webhook validation + +Webhook validation is used to validate a webhook message by verifying the signature and the delivery timestamp. + +**Old (`boxsdk`)** + +In the `boxsdk` package of Box Python SDK, you could pass the `body` as `bytes`, and it would return a `boolean` value indicating whether the message was valid. + +```python +body = b'{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' +headers = { + "box-delivery-id": "f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f", + "box-delivery-timestamp": "2020-01-01T00:00:00-07:00", + "box-signature-algorithm": "HmacSHA256", + "box-signature-primary": "4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=", + "box-signature-secondary": "yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=", + "box-signature-version": "1", +} +is_validated = Webhook.validate_message(body, headers, primary_key, secondary_key) +print(f"The webhook message is validated to: {is_validated}") +``` + +**New (`box_sdk_gen`)** + +In the new `box_sdk_gen` package, the `WebhooksManager.validate_message()` method requires the `body` to be of type `string` and +the rest of the code remains the same + +```python +from box_sdk_gen import WebhooksManager + +body = '{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' +headers = { + "box-delivery-id": "f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f", + "box-delivery-timestamp": "2020-01-01T00:00:00-07:00", + "box-signature-algorithm": "HmacSHA256", + "box-signature-primary": "4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=", + "box-signature-secondary": "yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=", + "box-signature-version": "1", +} +WebhooksManager.validate_message( + body=body, headers=headers, primary_key=primary_key, secondary_key=secondary_key +) +``` + +### Chunked upload of big files + +For large files or in cases where the network connection is less reliable, you may want to upload the file in parts. +This allows a single part to fail without aborting the entire upload, and failed parts are being retried automatically. + +**Old (`boxsdk`)** + +In `boxsdk`, you could use the `get_chunked_uploader()` method to create a chunked uploader object. +Then, you would call the `start()` method to begin the upload process. +The `get_chunked_uploader()` method requires the `file_path` and `file_name` parameters. + +```python +chunked_uploader = client.folder("0").get_chunked_uploader( + file_path="/path/to/file.txt", file_name="new_name.txt" +) +uploaded_file = chunked_uploader.start() +print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}') +``` + +**New (`box_sdk_gen`)** + +In `box_sdk_gen`, the equivalent method is `chunked_uploads.upload_big_file()`. It accepts a file-like object +as the `file` parameter, and the `file_name` and `file_size` parameters are now passed as arguments. +The `parent_folder_id` parameter is also required to specify the folder where the file will be uploaded. + +```python +import os + +with open("/path/to/file.txt", "rb") as file_byte_stream: + file_name = "new_name.txt" + file_size = os.path.getsize("/path/to/file.txt") + parent_folder_id = "0" # ID of the folder where the file will be uploaded + uploaded_file = client.chunked_uploads.upload_big_file( + file=file_byte_stream, + file_name=file_name, + file_size=file_size, + parent_folder_id=parent_folder_id, + ) +``` diff --git a/migration-guides/from-v3-to-v10.md b/migration-guides/from-v3-to-v10.md index 5d151c3f4..ae9a8b658 100644 --- a/migration-guides/from-v3-to-v10.md +++ b/migration-guides/from-v3-to-v10.md @@ -5,32 +5,7 @@ - [Introduction](#introduction) - [Installation](#installation) -- [Key differences](#key-differences) - - [Manager approach](#manager-approach) - - [Explicitly defined schemas](#explicitly-defined-schemas) - - [Immutable design](#immutable-design) -- [Authentication](#authentication) - - [Developer Token](#developer-token) - - [JWT Auth](#jwt-auth) - - [Using JWT configuration file](#using-jwt-configuration-file) - - [Providing JWT configuration manually](#providing-jwt-configuration-manually) - - [Authenticate user](#authenticate-user) - - [Client Credentials Grant](#client-credentials-grant) - - [Obtaining Service Account token](#obtaining-service-account-token) - - [Obtaining User token](#obtaining-user-token) - - [Switching between Service Account and User](#switching-between-service-account-and-user) - - [OAuth 2.0 Auth](#oauth-20-auth) - - [Get Authorization URL](#get-authorization-url) - - [Authenticate](#authenticate) - - [Store token and retrieve token callbacks](#store-token-and-retrieve-token-callbacks) - - [Downscope token](#downscope-token) - - [Revoke token](#revoke-token) -- [Configuration](#configuration) - - [As-User header](#as-user-header) - - [Custom Base URLs](#custom-base-urls) -- [Convenience methods](#convenience-methods) - - [Webhook validation](#webhook-validation) - - [Chunked upload of big files](#chunked-upload-of-big-files) +- [Highlighting the Key Differences](#highlighting-the-key-differences) @@ -42,7 +17,7 @@ based on Open API Specification. This means you can leverage the most up-to-date applications without delay. We introduced this major version bump to reflect the significant codebase changes and to align with other Box SDKs, which will also adopt generated code starting from their v10 releases. More information and benefits of using the new can be found in the -[README](https://github.com/box/box-python-sdk/blob/main/README.md) file. +[README](../README.md) file. ## Installation @@ -52,713 +27,15 @@ To install v10 version of Box Python SDK use command: pip install boxsdk>=10 ``` -Soon we are going to introduce v4 version of Box Python SDK that will combine package `boxsdk` from -v3 and `box_sdk_gen` from v10 of the SDK, so that code from both versions could be used in the same project. +We have also introduced v4 version of Box Python SDK that consolidates both the manually maintained `boxsdk` package from v3 +and the new, auto-generated `box_sdk_gen` package from v10. If you would like to use a feature available only in the new SDK, you won't need to necessarily migrate all your code to use generated SDK at once. You will be able to use a new feature from the `box_sdk_gen` package, -while keeping the rest of your code unchanged. Note that it may be required to use aliases for some of the imported names -to avoid conflicts between two packages. However, we recommend to fully migrate to the v10 of the SDK eventually. +while keeping the rest of your code unchanged. However, we recommend to fully migrate to the v10 of the SDK eventually. +More information about v4 version can be found in the [migration guide from v3 to v4](./from-v3-to-v4.md). -## Key differences +## Highlighting the Key Differences -### Manager approach - -The main difference between the manual v3 and v10 version of Box Python SDK is the way how API methods are aggregated into objects. - -**Old (`v3`)** - -Firstly, in the v3 release to be able to perform any action on an API object, e.g. `User`, you first had to create its class. -To do it is required to call: - -```python -user = client.user(user_id="123456") -``` - -to create a class representing an already existing User with id '12345', or create a new one with a call: - -```python -user = client.create_user(name="Some User") -``` - -Then, you could perform any action on created class, which will affect the user, e.g. - -```python -updated_user = user.update_info(data={"name": "New User Name"}) -``` - -**New (`v10`)** - -In the v10 release, the API methods are grouped into dedicated manager classes, e.g. `User` object -has dedicated `UserManager` class. Each manager class instance is available in `BoxClient` object. -The fields storing references to the managers are named in the plural form of the resource that the -manager handles - `client.users` for `UsersManager`. If you want to perform any operation -connected with a `User` you need to call a respective method of `UserManager`. -For example, to get info about existing user you need to call: - -```python -user = client.users.get_user_by_id(user_id="123456") -``` - -or to create a new user: - -```python -user = client.users.create_user(name="Some User") -``` - -The `User` object returned by both of these methods is a data class - it does not contain any methods to call. -To perform any action on `User` object, you need to still use a `UserManager` method for that. -Usually these methods have a first argument, which accepts id of the object you want to access, -e.g. to update a user name, call method: - -```python -updated_user = client.users.update_user_by_id(user_id=user.id, name="New User Name") -``` - -### Explicitly defined schemas - -**Old (`v3`)** - -In v3 release, there were no data types explicitly defined - -the responses were dynamically mapped into classes in the runtime. For example, if you get information about a file: - -```python -file = client.file(file_id="12345678").get() -``` - -you couldn't be sure which fields to expect in the response object until the runtime, -because `File` class doesn't have any predefined fields. - -**New (`v10`)** - -In v10 release, the data classes are defined in `schemas` module, so you know, which fields to expect before -actually making a call. For example `FileBase` class is defined this way: - -```python -class FileBase(BaseObject): - def __init__( - self, - id: str, - *, - etag: Optional[str] = None, - type: FileBaseTypeField = FileBaseTypeField.FILE.value, - **kwargs - ): - super().__init__(**kwargs) - self.id = id - self.type = type - self.etag = etag -``` - -### Immutable design - -The new v10 version of Box Python SDK is designed to be mostly immutable. This means that methods, -which used to modify the existing object in v3 release of SDK now return a new instance of the class with the modified state. -This design pattern is used to avoid side effects and make the code more predictable and easier to reason about. -Methods, which returns a new modified instance of an object, will always have a prefix `with_` in their names, e.g. - -**New (`v10`)** - -```python -from box_sdk_gen import BoxClient - -as_user_client: BoxClient = client.with_as_user_header("USER_ID") -``` - -## Authentication - -The v10 release Box Python SDK library offers the same authentication methods as v3 release. -Let's see the differences of their usage: - -### Developer Token - -**Old (`v3`)** - -```python -from boxsdk import Client, OAuth2 - -auth = OAuth2( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - access_token="DEVELOPER_TOKEN_GOES_HERE", -) -client = Client(auth) -``` - -The v10 version, provides a convenient `BoxDeveloperTokenAuth`, which allows authenticating -using developer token without necessity to provide a Client ID and Client Secret - -**New (`v10`)** - -```python -from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth - -auth = BoxDeveloperTokenAuth(token="DEVELOPER_TOKEN_GOES_HERE") -client = BoxClient(auth=auth) -``` - -### JWT Auth - -#### Using JWT configuration file - -**Old (`v3`)** - -The static method, which reads the JWT configuration file has been changed: - -```python -from boxsdk import JWTAuth, Client - -auth = JWTAuth.from_settings_file("/path/to/config.json") -client = Client(auth) -``` - -**New (`v10`)** - -```python -from box_sdk_gen import BoxClient, BoxJWTAuth, JWTConfig - -jwt_config = JWTConfig.from_config_file(config_file_path="/path/to/config.json") -auth = BoxJWTAuth(config=jwt_config) -client = BoxClient(auth=auth) -``` - -#### Providing JWT configuration manually - -Some params in `JWTConfig` constructor have slightly different names than one in the old `JWTAuth` class. - -**Old (`v3`)** - -```python -from boxsdk import JWTAuth - -auth = JWTAuth( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - enterprise_id="YOUR_ENTERPRISE_ID", - user_id="USER_ID", - jwt_key_id="YOUR_JWT_KEY_ID", - rsa_private_key_file_sys_path="CERT.PEM", - rsa_private_key_passphrase="PASSPHRASE", - jwt_algorithm="RS256", -) -``` - -**New (`v10`)** - -```python -from box_sdk_gen import BoxJWTAuth, JWTConfig, JwtAlgorithm - -jwt_config = JWTConfig( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - enterprise_id="YOUR_ENTERPRISE_ID", - user_id="USER_ID", - jwt_key_id="YOUR_JWT_KEY_ID", - private_key="YOUR_PRIVATE_KEY", - private_key_passphrase="PASSPHRASE", - algorithm=JwtAlgorithm.RS256, -) -auth = BoxJWTAuth(config=jwt_config) -``` - -#### Authenticate user - -In v3 release, method for user authentication was named `authenticate_user(self, user: Union[str, 'User'] = None) -> str` -and was accepting either user object or user id. If none provided, user ID stored in `JWTAuth` class instance was used. -The `authenticate_user` method was modifying existing `BoxJWTAuth` class, which was exchanging the existing token with -the one with the user access. - -**Old (`v3`)** - -```python -auth.authenticate_user(user) -``` - -or - -```python -auth.authenticate_user("USER_ID") -``` - -**New (`v10`)** - -In new v10 release, to authenticate as user you need to call -`with_user_subject(self, user_id: str, *, token_storage: TokenStorage = None) -> BoxJWTAuth` method with id of the user -to authenticate. The method returns a new instance of `BoxJWTAuth` class, which will perform authentication call -in scope of the user on the first API call. The `token_storage` parameter is optional and allows to provide a custom -token storage for the new instance of `BoxJWTAuth` class. The new auth instance can be used to create a new user client -instance. - -```python -from box_sdk_gen import BoxJWTAuth, BoxClient - -user_auth: BoxJWTAuth = auth.with_user_subject("USER_ID") -user_client: BoxClient = BoxClient(auth=user_auth) -``` - -### Client Credentials Grant - -#### Obtaining Service Account token - -To authenticate as enterprise, the only difference between the versions of SDK, -is using the `CCGConfig` as a middle step. - -**Old (`v3`)** - -```python -from boxsdk import CCGAuth, Client - -auth = CCGAuth( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - enterprise_id="YOUR_ENTERPRISE_ID", -) - -client = Client(auth) -``` - -**New (`v10`)** - -```python -from box_sdk_gen import BoxClient, BoxCCGAuth, CCGConfig - -ccg_config = CCGConfig( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - enterprise_id="YOUR_ENTERPRISE_ID", -) -auth = BoxCCGAuth(config=ccg_config) -client = BoxClient(auth=auth) -``` - -#### Obtaining User token - -In v3 `CCGAuth` was accepting both user object and User ID. In v10 the `BoxCCGAuth` constructor accepts -only User ID instead. - -**Old (`v3`)** - -```python -from boxsdk import CCGAuth - -auth = CCGAuth( - client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", user="YOUR_USER_ID" -) -``` - -**New (`v10`)** - -```python -from box_sdk_gen import BoxCCGAuth, CCGConfig - -ccg_config = CCGConfig( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - user_id="YOUR_USER_ID", -) -auth = BoxCCGAuth(config=ccg_config) -``` - -### Switching between Service Account and User - -In v3 release, there were two methods which allowed to switch between using service and user account. Calling these methods -were modifying existing state of `CCGAuth` class, which was fetching a new token on the next API call. - -**Old (`v3`)** - -```python -auth.authenticate_enterprise("ENTERPRISE_ID") -``` - -```python -auth.authenticate_user("USER_ID") -``` - -In the new release, to keep the immutability design, the methods switching authenticated subject were replaced with methods -returning a new instance of `BoxCCGAuth` class. The new instance will fetch a new token on the next API call. -The new auth instance can be used to create a new client instance. You can also specify `token_storage` parameter -to provide a custom token storage for the new instance. -The old instance of `BoxCCGAuth` class will remain unchanged and will still use the old token. - -**New (`v10`)** - -```python -from box_sdk_gen import BoxCCGAuth, BoxClient - -enterprise_auth: BoxCCGAuth = auth.with_enterprise_subject( - enterprise_id="ENTERPRISE_ID" -) -enterprise_client: BoxClient = BoxClient(auth=enterprise_auth) -``` - -```python -from box_sdk_gen import BoxCCGAuth, BoxClient - -user_auth: BoxCCGAuth = auth.with_user_subject(user_id="USER_ID") -user_client: BoxClient = BoxClient(auth=user_auth) -``` - -Note that the new methods accept only user id or enterprise id, while the old ones were accepting -user and enterprise object too. - -### OAuth 2.0 Auth - -#### Get Authorization URL - -To get authorization url in the v10 version of SDK, you need to first create the `BoxOAuth` class (previously `OAuth2`) using -`OAuthConfig` class. Then to get authorization url, call -`get_authorize_url(self, *, options: GetAuthorizeUrlOptions = None) -> str` instead of -`get_authorization_url(self, redirect_url: Optional[str]) -> Tuple[str, str]`. Note that this method -now accepts the instance of `GetAuthorizeUrlOptions` class, which allows specifying extra options to API call. -The new function returns only the authentication url string, while the old one returns tuple of -authentication url and csrf_token. - -**Old (`v3`)** - -```python -from boxsdk import OAuth2 - -auth = OAuth2( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", -) - -auth_url, csrf_token = auth.get_authorization_url("http://YOUR_REDIRECT_URL") -``` - -**New (`v10`)** - -```python -from box_sdk_gen import BoxOAuth, OAuthConfig, GetAuthorizeUrlOptions - -auth = BoxOAuth( - OAuthConfig( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - ) -) -auth_url = auth.get_authorize_url( - options=GetAuthorizeUrlOptions(redirect_uri="http://YOUR_REDIRECT_URL") -) -``` - -#### Authenticate - -The signature of method for authenticating with obtained auth code got changed from: -`authenticate(self, auth_code: Optional[str]) -> Tuple[str, str]` to -`get_tokens_authorization_code_grant(self, authorization_code: str, *, network_session: Optional[NetworkSession] = None) -> AccessToken`. -The method now returns an AccessToken object with `access_token` and `refresh_token` fields, -while the old one was returning a tuple of access token and refresh token. - -**Old (`v3`)** - -```python -from boxsdk import Client - -access_token, refresh_token = auth.authenticate("YOUR_AUTH_CODE") -client = Client(auth) -``` - -**New (`v10`)** - -```python -from box_sdk_gen import BoxClient, AccessToken - -access_token: AccessToken = auth.get_tokens_authorization_code_grant("YOUR_AUTH_CODE") -client = BoxClient(auth) -``` - -### Store token and retrieve token callbacks - -In v3 release you could provide a `store_tokens` callback method to an authentication class, which was called each time -an access token was refreshed. It could be used to save your access token to a custom token storage -and allow to reuse this token later. -What is more, v3 release allowed also to provide `retrieve_tokens` callback, which is called each time the SDK needs to use -token to perform an API call. To provide that, it was required to use `CooperativelyManagedOAuth2` and provide -`retrieve_tokens` callback method to its constructor. - -**Old (`v3`)** - -```python -from typing import Tuple -from boxsdk.auth import CooperativelyManagedOAuth2 -from boxsdk import Client - - -def retrieve_tokens() -> Tuple[str, str]: - # retrieve access_token and refresh_token - return access_token, refresh_token - - -def store_tokens(access_token: str, refresh_token: str): - # store access_token and refresh_token - pass - - -auth = CooperativelyManagedOAuth2( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - retrieve_tokens=retrieve_tokens, - store_tokens=store_tokens, -) -access_token, refresh_token = auth.authenticate("YOUR_AUTH_CODE") -client = Client(auth) -``` - -In the new release you can define your own class delegated for storing and retrieving a token. It has to inherit from -`TokenStorage` and implement all of its abstract methods. Next step would be to pass an instance of this class to the -AuthConfig constructor. - -**New (`v10`)** - -```python -from typing import Optional -from box_sdk_gen import BoxOAuth, OAuthConfig, TokenStorage, AccessToken - - -class MyCustomTokenStorage(TokenStorage): - def store(self, token: AccessToken) -> None: - # store token - pass - - def get(self) -> Optional[AccessToken]: - # get token - pass - - def clear(self) -> None: - # clear token - pass - - -auth = BoxOAuth( - OAuthConfig( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - token_storage=MyCustomTokenStorage(), - ) -) -``` - -or reuse one of the provided implementations: `FileTokenStorage` or `FileWithInMemoryCacheTokenStorage`: - -```python -from box_sdk_gen import BoxOAuth, OAuthConfig, FileWithInMemoryCacheTokenStorage - -auth = BoxOAuth( - OAuthConfig( - client_id="YOUR_CLIENT_ID", - client_secret="YOUR_CLIENT_SECRET", - token_storage=FileWithInMemoryCacheTokenStorage(), - ) -) -``` - -### Downscope token - -The process of downscoping token in the new release is similar to the old one. The main difference is that the new method -accepts the full resource path instead of file object. - -**Old (`v3`)** - -```python -from boxsdk import Client, OAuth2 - -target_file = client.file(file_id="FILE_ID_HERE") -token_info = client.downscope_token(["item_preview"], target_file) -downscoped_auth = OAuth2( - client_id=None, client_secret=None, access_token=token_info.access_token -) -downscoped_client = Client(downscoped_auth) -``` - -**New (`v10`)** - -```python -from box_sdk_gen import BoxDeveloperTokenAuth, AccessToken, BoxClient - -resource = "https://api.box.com/2.0/files/123456789" -downscoped_token: AccessToken = auth.downscope_token( - scopes=["item_preview"], - resource=resource, -) -downscoped_auth = BoxDeveloperTokenAuth(token=downscoped_token.access_token) -client = BoxClient(auth=downscoped_auth) -``` - -### Revoke token - -To revoke current client's tokens in the v10 release, you need to call `revoke_token` method of the auth class instead of -`revoke` method. - -**Old (`v3`)** - -```python -oauth.revoke() -``` - -**New (`v10`)** - -```python -client.auth.revoke_token() -``` - -## Configuration - -### As-User header - -The As-User header is used by enterprise admins to make API calls on behalf of their enterprise's users. -This requires the API request to pass an `As-User: USER-ID` header. The following examples assume that the client has -been instantiated with an access token with appropriate privileges to make As-User calls. - -In v3 you could call client `as_user(self, user: User)` method to create a new client to impersonate the provided user. - -**Old (`v3`)** - -```python -from boxsdk import Client - -user_to_impersonate = client.user(user_id="USER_ID") -user_client: Client = client.as_user(user_to_impersonate) -``` - -**New (`v10`)** - -In v10 the method was renamed to `with_as_user_header(self, user_id: str) -> BoxClient` -and returns a new instance of `BoxClient` class with the As-User header appended to all API calls made by the client. -The method accepts only user id as a parameter. - -```python -from box_sdk_gen import BoxClient - -user_client: BoxClient = client.with_as_user_header(user_id="USER_ID") -``` - -Additionally `BoxClient` offers a `with_extra_headers(self, *, extra_headers: Dict[str, str] = None) -> BoxClient` -method, which allows you to specify the custom set of headers, which will be included in every API call made by client. -Calling the `client.with_extra_headers()` method creates a new client, leaving the original client unmodified. - -```python -from box_sdk_gen import BoxClient - -new_client: BoxClient = client.with_extra_headers( - extra_headers={"customHeader": "customValue"} -) -``` - -### Custom Base URLs - -**Old (`v3`)** - -In manual v3 release, you could specify the custom base URLs, which will be used for API calls made by setting -the new values of static variables of the `API` class. - -```python -from boxsdk.config import API - -API.BASE_API_URL = "https://new-base-url.com" -API.OAUTH2_API_URL = "https://my-company.com/oauth2" -API.UPLOAD_URL = "https://my-company-upload-url.com" -``` - -**New (`v10`)** - -In the new release this functionality has been implemented as part of the `BoxClient` class. -By calling the `client.with_custom_base_urls()` method, you can specify the custom base URLs that will be used for API -calls made by client. Following the immutability pattern, this call creates a new client, leaving the original client unmodified. - -```python -from box_sdk_gen import BoxClient, BaseUrls - -new_client: BoxClient = client.with_custom_base_urls( - base_urls=BaseUrls( - base_url="https://new-base-url.com", - upload_url="https://my-company-upload-url.com", - oauth_2_url="https://my-company.com/oauth2", - ) -) -``` - -## Convenience methods - -### Webhook validation - -Webhook validation is used to validate a webhook message by verifying the signature and the delivery timestamp. - -**Old (`v3`)** - -In the v3 version of Box Python SDK, you could pass the `body` as `bytes`, and it would return a `boolean` value indicating whether the message was valid. - -```python -body = b'{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' -headers = { - "box-delivery-id": "f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f", - "box-delivery-timestamp": "2020-01-01T00:00:00-07:00", - "box-signature-algorithm": "HmacSHA256", - "box-signature-primary": "4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=", - "box-signature-secondary": "yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=", - "box-signature-version": "1", -} -is_validated = Webhook.validate_message(body, headers, primary_key, secondary_key) -print(f"The webhook message is validated to: {is_validated}") -``` - -**New (`v10`)** - -In the new v10 version, the `WebhooksManager.validate_message()` method requires the `body` to be of type `string` and -the rest of the code remains the same - -```python -from box_sdk_gen import WebhooksManager - -body = '{"webhook":{"id":"1234567890"},"trigger":"FILE.UPLOADED","source":{"id":"1234567890","type":"file","name":"Test.txt"}}' -headers = { - "box-delivery-id": "f96bb54b-ee16-4fc5-aa65-8c2d9e5b546f", - "box-delivery-timestamp": "2020-01-01T00:00:00-07:00", - "box-signature-algorithm": "HmacSHA256", - "box-signature-primary": "4KvFa5/unRL8aaqOlnbInTwkOmieZkn1ZVzsAJuRipE=", - "box-signature-secondary": "yxxwBNk7tFyQSy95/VNKAf1o+j8WMPJuo/KcFc7OS0Q=", - "box-signature-version": "1", -} -WebhooksManager.validate_message( - body=body, headers=headers, primary_key=primary_key, secondary_key=secondary_key -) -``` - -### Chunked upload of big files - -For large files or in cases where the network connection is less reliable, you may want to upload the file in parts. -This allows a single part to fail without aborting the entire upload, and failed parts are being retried automatically. - -**Old (`v3`)** - -In v3, you could use the `get_chunked_uploader()` method to create a chunked uploader object. -Then, you would call the `start()` method to begin the upload process. -The `get_chunked_uploader()` method requires the `file_path` and `file_name` parameters. - -```python -chunked_uploader = client.folder("0").get_chunked_uploader( - file_path="/path/to/file.txt", file_name="new_name.txt" -) -uploaded_file = chunked_uploader.start() -print(f'File "{uploaded_file.name}" uploaded to Box with file ID {uploaded_file.id}') -``` - -**New (`v10`)** - -In v10, the equivalent method is `chunked_uploads.upload_big_file()`. It accepts a file-like object -as the `file` parameter, and the `file_name` and `file_size` parameters are now passed as arguments. -The `parent_folder_id` parameter is also required to specify the folder where the file will be uploaded. - -```python -import os - -with open("/path/to/file.txt", "rb") as file_byte_stream: - file_name = "new_name.txt" - file_size = os.path.getsize("/path/to/file.txt") - parent_folder_id = "0" # ID of the folder where the file will be uploaded - uploaded_file = client.chunked_uploads.upload_big_file( - file=file_byte_stream, - file_name=file_name, - file_size=file_size, - parent_folder_id=parent_folder_id, - ) -``` +There are important differences between the `boxsdk` (v3) and the generated `box_sdk_gen` (v10) packages. +We have prepared a separate document that presents the main differences and provides guidance to help you migrate. +For side-by-side code examples, see: [Migration guide: migrate from BoxSDK to BoxSdkGen](./from-boxsdk-to-box_sdk_gen.md). diff --git a/migration-guides/from-v3-to-v4.md b/migration-guides/from-v3-to-v4.md new file mode 100644 index 000000000..f75e79d76 --- /dev/null +++ b/migration-guides/from-v3-to-v4.md @@ -0,0 +1,92 @@ +# Migration guide from v3 to v4 of the Box Python SDK + + + + +- [Introduction](#introduction) +- [Installation](#installation) + - [How to migrate](#how-to-migrate) +- [Supported Environments](#supported-environments) +- [Highlighting the Key Differences](#highlighting-the-key-differences) + - [Using boxsdk and box_sdk_gen together](#using-boxsdk-and-box_sdk_gen-together) + + + +## Introduction + +The v4 release of the Box Python SDK is a transitional version designed to help developers migrate from +the manually maintained v3 SDK to the modern, auto-generated v10+ SDK. + +This release combines two packages into a single artifact: + +- `boxsdk` - the manually maintained package from v3. +- `box_sdk_gen` - the new, auto-generated module built from the official OpenAPI specification (and the sole component of the v10 SDK). + +This hybrid approach allows you to gradually adopt the new `box_sdk_gen` features +while continuing to use your existing v3 integration, eliminating the need for an immediate full rewrite. + +## Installation + +To start using v4 of the Box Python SDK, update your project's `boxsdk` dependency to version 4. +You can find the latest available version on [PyPI](https://pypi.org/project/boxsdk/). + +### How to migrate + +To upgrade from v3 to v4, bump the version of the `boxsdk` dependency in your `requirements.txt` file or your installation command. + +```console +pip install boxsdk~=4.0 +``` + +## Supported Environments + +Because v4 of the Box Python SDK consolidates the manually maintained v3 `boxsdk` and the new auto-generated `box_sdk_gen` packages, +it now follows the newer package minimum platform requirements. + +Supported Python versions: 3.8+ +Note: Python 3.6 and 3.7 are no longer supported. + +If your application currently targets an older Python version, update your deployment environment to meet these minimum requirements. + +## Highlighting the Key Differences + +The `box_sdk_gen` package usage in v4 remains the same as in v3 and is not covered in this document. +If you are migrating code from `BoxSDK` to `boxsdk`, which we recommend, +the key differences between the packages are documented in: + +- [Migration guide: boxsdk → box_sdk_gen](./from-boxsdk-to-box_sdk_gen.md) + +### Using boxsdk and box_sdk_gen together + +After migration to Box Python SDK v4, you can use both the manual Box Python SDK package `boxsdk` and the generated one `box_sdk_gen`. +You just need to import the required classes from the appropriate package depending on which SDK you intend to use. +If both packages contain classes with the same name, you can use type aliases to resolve any naming conflicts. + +```python +from boxsdk import JWTAuth, Client +from boxsdk.object.folder import Folder as FolderOld +from box_sdk_gen import BoxJWTAuth, JWTConfig, BoxClient, Folder + + +def main(): + auth = JWTAuth.from_settings_file("/path/to/settings.json") + legacy_client = Client(auth) + + jwt_config = JWTConfig.from_config_file(config_file_path="/path/to/settings.json") + auth = BoxJWTAuth(config=jwt_config) + new_client = BoxClient(auth=auth) + + folder: FolderOld = legacy_client.folder(folder_id="0").create_subfolder( + "My Subfolder" + ) + updated_folder: Folder = new_client.folders.update_folder( + folder_id=folder.id, name="My Updated Subfolder" + ) + print( + f"Created folder with ID {folder.id} has been updated to {updated_folder.name}" + ) + + +if __name__ == "__main__": + main() +``` diff --git a/migration-guides/from-v4-to-v10.md b/migration-guides/from-v4-to-v10.md new file mode 100644 index 000000000..bf8dbe089 --- /dev/null +++ b/migration-guides/from-v4-to-v10.md @@ -0,0 +1,44 @@ +# Migration guide from v4 to v10 version of `boxsdk` + + + + +- [Introduction](#introduction) +- [Installation](#installation) +- [Supported Environments](#supported-environments) +- [Migration Scope and Module Compatibility](#migration-scope-and-module-compatibility) + + + +## Introduction + +Version 10 of the Box Python SDK is a modern, fully auto-generated SDK built entirely from the `box_sdk_gen` package. +In version 4, the SDK included two packages side by side: the manually maintained `boxsdk` and the generated `box_sdk_gen`. +Starting with version 10, the `boxsdk` package has been removed, and only the `box_sdk_gen` package remains. + +If you are migrating code from `boxsdk` to `box_sdk_gen` package, detailed instructions are available in the dedicated +[Migration guide: migrate from `boxsdk` to `box_sdk_gen` package](./from-boxsdk-to-box_sdk_gen.md). + +## Installation + +To install v10 version of Box Python SDK use command: + +```console +pip install boxsdk>=10 +``` + +Starting with v10, the legacy `boxsdk` package is no longer included. +Installing v10 provides only the `box_sdk_gen` package. + +## Supported Environments + +Both v4 and v10 of the Box Python SDK share the same Python version requirement: Python 3.8 or higher. +No changes to your environment are needed when upgrading from v4 to v10. + +## Migration Scope and Module Compatibility + +If your project only uses the `box_sdk_gen` package from v4, no code changes are required to migrate to v10. +The generated `box_sdk_gen` package is the same in both v4 and v10. + +If your project still includes code that uses the legacy `boxsdk` module, follow the dedicated guide to update it: +[Migration guide: migrate from `boxsdk` to `box_sdk_gen` package](./from-boxsdk-to-box_sdk_gen.md). From 2fd19f7f16d511af853e979df6981f09a353b03f Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:30:49 +0200 Subject: [PATCH 041/139] docs: Fixes in migration guides (box/box-codegen#867) (#1132) --- .codegen.json | 2 +- migration-guides/from-boxsdk-to-box_sdk_gen.md | 1 - migration-guides/from-v3-to-v10.md | 8 ++++++++ migration-guides/from-v3-to-v4.md | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.codegen.json b/.codegen.json index afec1fed3..9f21ce570 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "4a6585f", "specHash": "fa34496", "version": "10.0.1" } +{ "engineHash": "192deac", "specHash": "fa34496", "version": "10.0.1" } diff --git a/migration-guides/from-boxsdk-to-box_sdk_gen.md b/migration-guides/from-boxsdk-to-box_sdk_gen.md index ac7b6b108..54b085e6c 100644 --- a/migration-guides/from-boxsdk-to-box_sdk_gen.md +++ b/migration-guides/from-boxsdk-to-box_sdk_gen.md @@ -5,7 +5,6 @@ - [Introduction](#introduction) - [Who is this for?](#who-is-this-for) -- [Installation](#installation) - [Key differences](#key-differences) - [Manager approach](#manager-approach) - [Explicitly defined schemas](#explicitly-defined-schemas) diff --git a/migration-guides/from-v3-to-v10.md b/migration-guides/from-v3-to-v10.md index ae9a8b658..3bfa29874 100644 --- a/migration-guides/from-v3-to-v10.md +++ b/migration-guides/from-v3-to-v10.md @@ -5,6 +5,7 @@ - [Introduction](#introduction) - [Installation](#installation) +- [Supported Environments](#supported-environments) - [Highlighting the Key Differences](#highlighting-the-key-differences) @@ -34,6 +35,13 @@ to use generated SDK at once. You will be able to use a new feature from the `bo while keeping the rest of your code unchanged. However, we recommend to fully migrate to the v10 of the SDK eventually. More information about v4 version can be found in the [migration guide from v3 to v4](./from-v3-to-v4.md). +## Supported Environments + +Version v10 of the Box Python SDK no longer supports Python 3.6 or 3.7. +The SDK now requires Python 3.8 or higher. + +If your application currently targets an older Python version, update your deployment environment to meet these minimum requirements. + ## Highlighting the Key Differences There are important differences between the `boxsdk` (v3) and the generated `box_sdk_gen` (v10) packages. diff --git a/migration-guides/from-v3-to-v4.md b/migration-guides/from-v3-to-v4.md index f75e79d76..452b15166 100644 --- a/migration-guides/from-v3-to-v4.md +++ b/migration-guides/from-v3-to-v4.md @@ -50,8 +50,8 @@ If your application currently targets an older Python version, update your deplo ## Highlighting the Key Differences -The `box_sdk_gen` package usage in v4 remains the same as in v3 and is not covered in this document. -If you are migrating code from `BoxSDK` to `boxsdk`, which we recommend, +The `boxsdk` package usage in v4 remains the same as in v3 and is not covered in this document. +If you are migrating code from `boxsdk` to `box_sdk_gen` package, which we recommend, the key differences between the packages are documented in: - [Migration guide: boxsdk → box_sdk_gen](./from-boxsdk-to-box_sdk_gen.md) From 1106d325973df9704f5102538ac0130bda6e9c38 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:44:03 +0200 Subject: [PATCH 042/139] feat: Add GET enterprise configuration endpoint (box/box-openapi#559) (#1134) --- .codegen.json | 2 +- box_sdk_gen/client.py | 7 + box_sdk_gen/managers/__init__.py | 2 + .../managers/enterprise_configurations.py | 100 +++++ box_sdk_gen/schemas/v2025_r0/__init__.py | 42 ++ .../collaboration_permissions_v2025_r0.py | 44 ++ .../collaboration_restriction_v2025_r0.py | 8 + ...om_session_duration_group_item_v2025_r0.py | 20 + ...figuration_content_and_sharing_v2025_r0.py | 389 ++++++++++++++++++ ...ise_configuration_item_boolean_v2025_r0.py | 21 + ...ise_configuration_item_integer_v2025_r0.py | 21 + ...rise_configuration_item_string_v2025_r0.py | 21 + .../enterprise_configuration_item_v2025_r0.py | 15 + ...erprise_configuration_security_v2025_r0.py | 285 +++++++++++++ ...nterprise_configuration_shield_v2025_r0.py | 21 + ...se_configuration_user_settings_v2025_r0.py | 148 +++++++ .../enterprise_configuration_v2025_r0.py | 60 +++ .../enterprise_feature_setting_v2025_r0.py | 60 +++ ...terprise_feature_settings_item_v2025_r0.py | 27 ++ ...ernal_collab_security_settings_v2025_r0.py | 52 +++ .../v2025_r0/keysafe_settings_v2025_r0.py | 44 ++ .../schemas/v2025_r0/list_user_v2025_r0.py | 28 ++ .../shared_link_permissions_v2025_r0.py | 32 ++ .../v2025_r0/shield_rule_item_v2025_r0.py | 66 +++ .../user_or_group_reference_v2025_r0.py | 33 ++ .../v2025_r0/user_tracking_code_v2025_r0.py | 20 + docs/README.md | 1 + docs/enterprise_configurations.md | 31 ++ 28 files changed, 1599 insertions(+), 1 deletion(-) create mode 100644 box_sdk_gen/managers/enterprise_configurations.py create mode 100644 box_sdk_gen/schemas/v2025_r0/collaboration_permissions_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/collaboration_restriction_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/custom_session_duration_group_item_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_configuration_content_and_sharing_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_boolean_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_integer_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_string_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_configuration_security_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_configuration_shield_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_configuration_user_settings_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_configuration_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_feature_setting_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/enterprise_feature_settings_item_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/external_collab_security_settings_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/keysafe_settings_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/list_user_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shared_link_permissions_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/shield_rule_item_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/user_or_group_reference_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/user_tracking_code_v2025_r0.py create mode 100644 docs/enterprise_configurations.md diff --git a/.codegen.json b/.codegen.json index 9f21ce570..c22868b02 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "192deac", "specHash": "fa34496", "version": "10.0.1" } +{ "engineHash": "192deac", "specHash": "cf21406", "version": "10.0.1" } diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py index 9d5c34948..914844877 100644 --- a/box_sdk_gen/client.py +++ b/box_sdk_gen/client.py @@ -172,6 +172,10 @@ from box_sdk_gen.managers.docgen import DocgenManager +from box_sdk_gen.managers.enterprise_configurations import ( + EnterpriseConfigurationsManager, +) + from box_sdk_gen.managers.hubs import HubsManager from box_sdk_gen.managers.hub_collaborations import HubCollaborationsManager @@ -429,6 +433,9 @@ def __init__(self, auth: Authentication, *, network_session: NetworkSession = No self.docgen = DocgenManager( auth=self.auth, network_session=self.network_session ) + self.enterprise_configurations = EnterpriseConfigurationsManager( + auth=self.auth, network_session=self.network_session + ) self.hubs = HubsManager(auth=self.auth, network_session=self.network_session) self.hub_collaborations = HubCollaborationsManager( auth=self.auth, network_session=self.network_session diff --git a/box_sdk_gen/managers/__init__.py b/box_sdk_gen/managers/__init__.py index f3a23b7fb..59e3e15f2 100644 --- a/box_sdk_gen/managers/__init__.py +++ b/box_sdk_gen/managers/__init__.py @@ -146,6 +146,8 @@ from box_sdk_gen.managers.docgen import * +from box_sdk_gen.managers.enterprise_configurations import * + from box_sdk_gen.managers.hubs import * from box_sdk_gen.managers.hub_collaborations import * diff --git a/box_sdk_gen/managers/enterprise_configurations.py b/box_sdk_gen/managers/enterprise_configurations.py new file mode 100644 index 000000000..56c09e02a --- /dev/null +++ b/box_sdk_gen/managers/enterprise_configurations.py @@ -0,0 +1,100 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_v2025_r0 import ( + EnterpriseConfigurationV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import ClientErrorV2025R0 + +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import ( + BoxVersionHeaderV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class EnterpriseConfigurationsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_enterprise_configuration_by_id_v2025_r0( + self, + enterprise_id: str, + categories: str, + *, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> EnterpriseConfigurationV2025R0: + """ + Retrieves the configuration for an enterprise. + :param enterprise_id: The ID of the enterprise. + Example: "3442311" + :type enterprise_id: str + :param categories: The comma-delimited list of the enterprise configuration categories. + Allowed values: `security`, `content_and_sharing`, `user_settings`, `shield`. + :type categories: str + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'categories': to_string(categories)} + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/enterprise_configurations/', + to_string(enterprise_id), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, EnterpriseConfigurationV2025R0) diff --git a/box_sdk_gen/schemas/v2025_r0/__init__.py b/box_sdk_gen/schemas/v2025_r0/__init__.py index b5405a205..ed524c996 100644 --- a/box_sdk_gen/schemas/v2025_r0/__init__.py +++ b/box_sdk_gen/schemas/v2025_r0/__init__.py @@ -4,6 +4,12 @@ from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import * +from box_sdk_gen.schemas.v2025_r0.collaboration_permissions_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.collaboration_restriction_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.custom_session_duration_group_item_v2025_r0 import * + from box_sdk_gen.schemas.v2025_r0.doc_gen_batch_base_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.doc_gen_document_generation_data_v2025_r0 import * @@ -16,8 +22,18 @@ from box_sdk_gen.schemas.v2025_r0.doc_gen_tags_processing_message_v2025_r0 import * +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_string_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_integer_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_boolean_v2025_r0 import * + from box_sdk_gen.schemas.v2025_r0.enterprise_reference_v2025_r0 import * +from box_sdk_gen.schemas.v2025_r0.external_collab_security_settings_v2025_r0 import * + from box_sdk_gen.schemas.v2025_r0.external_user_deletion_result_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.external_users_submit_delete_job_response_v2025_r0 import * @@ -62,6 +78,14 @@ from box_sdk_gen.schemas.v2025_r0.hub_update_request_v2025_r0 import * +from box_sdk_gen.schemas.v2025_r0.keysafe_settings_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_security_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.list_user_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.shared_link_permissions_v2025_r0 import * + from box_sdk_gen.schemas.v2025_r0.shield_list_mini_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.shield_lists_v2025_r0 import * @@ -86,6 +110,10 @@ from box_sdk_gen.schemas.v2025_r0.shield_list_v2025_r0 import * +from box_sdk_gen.schemas.v2025_r0.shield_rule_item_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_shield_v2025_r0 import * + from box_sdk_gen.schemas.v2025_r0.terms_of_service_base_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.user_base_v2025_r0 import * @@ -108,10 +136,24 @@ from box_sdk_gen.schemas.v2025_r0.doc_gen_jobs_full_v2025_r0 import * +from box_sdk_gen.schemas.v2025_r0.user_or_group_reference_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_feature_setting_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_feature_settings_item_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_content_and_sharing_v2025_r0 import * + from box_sdk_gen.schemas.v2025_r0.user_reference_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.external_users_submit_delete_job_request_v2025_r0 import * +from box_sdk_gen.schemas.v2025_r0.user_tracking_code_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_user_settings_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_v2025_r0 import * + from box_sdk_gen.schemas.v2025_r0.weblink_reference_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.hub_item_reference_v2025_r0 import * diff --git a/box_sdk_gen/schemas/v2025_r0/collaboration_permissions_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/collaboration_permissions_v2025_r0.py new file mode 100644 index 000000000..722aed583 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/collaboration_permissions_v2025_r0.py @@ -0,0 +1,44 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class CollaborationPermissionsV2025R0(BaseObject): + def __init__( + self, + *, + is_co_owner_role_enabled: Optional[bool] = None, + is_editor_role_enabled: Optional[bool] = None, + is_previewer_role_enabled: Optional[bool] = None, + is_previewer_uploader_role_enabled: Optional[bool] = None, + is_uploader_role_enabled: Optional[bool] = None, + is_viewer_role_enabled: Optional[bool] = None, + is_viewer_uploader_role_enabled: Optional[bool] = None, + **kwargs + ): + """ + :param is_co_owner_role_enabled: The co-owner role is enabled for collaboration., defaults to None + :type is_co_owner_role_enabled: Optional[bool], optional + :param is_editor_role_enabled: The editor role is enabled for collaboration., defaults to None + :type is_editor_role_enabled: Optional[bool], optional + :param is_previewer_role_enabled: The previewer role is enabled for collaboration., defaults to None + :type is_previewer_role_enabled: Optional[bool], optional + :param is_previewer_uploader_role_enabled: The previewer uploader role is enabled for collaboration., defaults to None + :type is_previewer_uploader_role_enabled: Optional[bool], optional + :param is_uploader_role_enabled: The uploader role is enabled for collaboration., defaults to None + :type is_uploader_role_enabled: Optional[bool], optional + :param is_viewer_role_enabled: The viewer role is enabled for collaboration., defaults to None + :type is_viewer_role_enabled: Optional[bool], optional + :param is_viewer_uploader_role_enabled: The viewer uploader role is enabled for collaboration., defaults to None + :type is_viewer_uploader_role_enabled: Optional[bool], optional + """ + super().__init__(**kwargs) + self.is_co_owner_role_enabled = is_co_owner_role_enabled + self.is_editor_role_enabled = is_editor_role_enabled + self.is_previewer_role_enabled = is_previewer_role_enabled + self.is_previewer_uploader_role_enabled = is_previewer_uploader_role_enabled + self.is_uploader_role_enabled = is_uploader_role_enabled + self.is_viewer_role_enabled = is_viewer_role_enabled + self.is_viewer_uploader_role_enabled = is_viewer_uploader_role_enabled diff --git a/box_sdk_gen/schemas/v2025_r0/collaboration_restriction_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/collaboration_restriction_v2025_r0.py new file mode 100644 index 000000000..ee9f7ac7c --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/collaboration_restriction_v2025_r0.py @@ -0,0 +1,8 @@ +from enum import Enum + +from box_sdk_gen.box.errors import BoxSDKError + + +class CollaborationRestrictionV2025R0(str, Enum): + INTERNAL = 'internal' + EXTERNAL = 'external' diff --git a/box_sdk_gen/schemas/v2025_r0/custom_session_duration_group_item_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/custom_session_duration_group_item_v2025_r0.py new file mode 100644 index 000000000..9e730640b --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/custom_session_duration_group_item_v2025_r0.py @@ -0,0 +1,20 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class CustomSessionDurationGroupItemV2025R0(BaseObject): + def __init__( + self, *, id: Optional[str] = None, name: Optional[str] = None, **kwargs + ): + """ + :param id: Group ID (numerical)., defaults to None + :type id: Optional[str], optional + :param name: Group Name., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.name = name diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_content_and_sharing_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_content_and_sharing_v2025_r0.py new file mode 100644 index 000000000..0fe0cfaa4 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_content_and_sharing_v2025_r0.py @@ -0,0 +1,389 @@ +from typing import Optional + +from typing import List + +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.enterprise_feature_settings_item_v2025_r0 import ( + EnterpriseFeatureSettingsItemV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_string_v2025_r0 import ( + EnterpriseConfigurationItemStringV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_v2025_r0 import ( + EnterpriseConfigurationItemV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.shared_link_permissions_v2025_r0 import ( + SharedLinkPermissionsV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_boolean_v2025_r0 import ( + EnterpriseConfigurationItemBooleanV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.collaboration_permissions_v2025_r0 import ( + CollaborationPermissionsV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.collaboration_restriction_v2025_r0 import ( + CollaborationRestrictionV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.list_user_v2025_r0 import ListUserV2025R0 + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_integer_v2025_r0 import ( + EnterpriseConfigurationItemIntegerV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseConfigurationContentAndSharingV2025R0SharedLinkDefaultPermissionsSelectedField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[SharedLinkPermissionsV2025R0] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationContentAndSharingV2025R0CollaborationPermissionsField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[CollaborationPermissionsV2025R0] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationContentAndSharingV2025R0CollaborationRestrictionsField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[List[CollaborationRestrictionV2025R0]] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationStatusFieldValueField( + str, Enum +): + ENABLE_EXTERNAL_COLLABORATION = 'enable_external_collaboration' + LIMIT_COLLABORATION_TO_ALLOWLISTED_DOMAINS = ( + 'limit_collaboration_to_allowlisted_domains' + ) + + +class EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationStatusField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[ + EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationStatusFieldValueField + ] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param value: The external collaboration status., defaults to None + :type value: Optional[EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationStatusFieldValueField], optional + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationAllowlistUsersField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[List[ListUserV2025R0]] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationContentAndSharingV2025R0PermanentDeletionAllowlistUsersField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[List[ListUserV2025R0]] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationContentAndSharingV2025R0(BaseObject): + def __init__( + self, + *, + enterprise_feature_settings: Optional[ + List[EnterpriseFeatureSettingsItemV2025R0] + ] = None, + sharing_item_type: Optional[EnterpriseConfigurationItemStringV2025R0] = None, + shared_link_company_definition: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + shared_link_access: Optional[EnterpriseConfigurationItemStringV2025R0] = None, + shared_link_default_access: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + shared_link_default_permissions_selected: Optional[ + EnterpriseConfigurationContentAndSharingV2025R0SharedLinkDefaultPermissionsSelectedField + ] = None, + is_open_custom_urls_disabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_custom_domain_hidden_in_shared_link: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + collaboration_permissions: Optional[ + EnterpriseConfigurationContentAndSharingV2025R0CollaborationPermissionsField + ] = None, + default_collaboration_role: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + is_invite_privilege_restricted: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + collaboration_restrictions: Optional[ + EnterpriseConfigurationContentAndSharingV2025R0CollaborationRestrictionsField + ] = None, + is_collaborator_invite_links_disabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_invite_group_collaborator_disabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_ownership_transfer_restricted: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + external_collaboration_status: Optional[ + EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationStatusField + ] = None, + external_collaboration_allowlist_users: Optional[ + EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationAllowlistUsersField + ] = None, + is_watermarking_enterprise_feature_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_root_content_creation_restricted: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_tag_creation_restricted: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + tag_creation_restriction: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + is_email_uploads_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_custom_settings_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_forms_login_required: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_forms_branding_default_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_cc_free_trial_active: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_file_request_editors_allowed: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_file_request_branding_default_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_file_request_login_required: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_shared_links_expiration_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + shared_links_expiration_days: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + is_public_shared_links_expiration_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + public_shared_links_expiration_days: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + shared_expiration_target: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + is_shared_links_expiration_notification_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + shared_links_expiration_notification_days: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + is_shared_links_expiration_notification_prevented: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_auto_delete_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + auto_delete_days: Optional[EnterpriseConfigurationItemIntegerV2025R0] = None, + is_auto_delete_expiration_modification_prevented: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + auto_delete_target: Optional[EnterpriseConfigurationItemStringV2025R0] = None, + is_collaboration_expiration_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + collaboration_expiration_days: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + is_collaboration_expiration_modification_prevented: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_collaboration_expiration_notification_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + collaboration_expiration_target: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + trash_auto_clear_time: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + permanent_deletion_access: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + permanent_deletion_allowlist_users: Optional[ + EnterpriseConfigurationContentAndSharingV2025R0PermanentDeletionAllowlistUsersField + ] = None, + **kwargs + ): + super().__init__(**kwargs) + self.enterprise_feature_settings = enterprise_feature_settings + self.sharing_item_type = sharing_item_type + self.shared_link_company_definition = shared_link_company_definition + self.shared_link_access = shared_link_access + self.shared_link_default_access = shared_link_default_access + self.shared_link_default_permissions_selected = ( + shared_link_default_permissions_selected + ) + self.is_open_custom_urls_disabled = is_open_custom_urls_disabled + self.is_custom_domain_hidden_in_shared_link = ( + is_custom_domain_hidden_in_shared_link + ) + self.collaboration_permissions = collaboration_permissions + self.default_collaboration_role = default_collaboration_role + self.is_invite_privilege_restricted = is_invite_privilege_restricted + self.collaboration_restrictions = collaboration_restrictions + self.is_collaborator_invite_links_disabled = ( + is_collaborator_invite_links_disabled + ) + self.is_invite_group_collaborator_disabled = ( + is_invite_group_collaborator_disabled + ) + self.is_ownership_transfer_restricted = is_ownership_transfer_restricted + self.external_collaboration_status = external_collaboration_status + self.external_collaboration_allowlist_users = ( + external_collaboration_allowlist_users + ) + self.is_watermarking_enterprise_feature_enabled = ( + is_watermarking_enterprise_feature_enabled + ) + self.is_root_content_creation_restricted = is_root_content_creation_restricted + self.is_tag_creation_restricted = is_tag_creation_restricted + self.tag_creation_restriction = tag_creation_restriction + self.is_email_uploads_enabled = is_email_uploads_enabled + self.is_custom_settings_enabled = is_custom_settings_enabled + self.is_forms_login_required = is_forms_login_required + self.is_forms_branding_default_enabled = is_forms_branding_default_enabled + self.is_cc_free_trial_active = is_cc_free_trial_active + self.is_file_request_editors_allowed = is_file_request_editors_allowed + self.is_file_request_branding_default_enabled = ( + is_file_request_branding_default_enabled + ) + self.is_file_request_login_required = is_file_request_login_required + self.is_shared_links_expiration_enabled = is_shared_links_expiration_enabled + self.shared_links_expiration_days = shared_links_expiration_days + self.is_public_shared_links_expiration_enabled = ( + is_public_shared_links_expiration_enabled + ) + self.public_shared_links_expiration_days = public_shared_links_expiration_days + self.shared_expiration_target = shared_expiration_target + self.is_shared_links_expiration_notification_enabled = ( + is_shared_links_expiration_notification_enabled + ) + self.shared_links_expiration_notification_days = ( + shared_links_expiration_notification_days + ) + self.is_shared_links_expiration_notification_prevented = ( + is_shared_links_expiration_notification_prevented + ) + self.is_auto_delete_enabled = is_auto_delete_enabled + self.auto_delete_days = auto_delete_days + self.is_auto_delete_expiration_modification_prevented = ( + is_auto_delete_expiration_modification_prevented + ) + self.auto_delete_target = auto_delete_target + self.is_collaboration_expiration_enabled = is_collaboration_expiration_enabled + self.collaboration_expiration_days = collaboration_expiration_days + self.is_collaboration_expiration_modification_prevented = ( + is_collaboration_expiration_modification_prevented + ) + self.is_collaboration_expiration_notification_enabled = ( + is_collaboration_expiration_notification_enabled + ) + self.collaboration_expiration_target = collaboration_expiration_target + self.trash_auto_clear_time = trash_auto_clear_time + self.permanent_deletion_access = permanent_deletion_access + self.permanent_deletion_allowlist_users = permanent_deletion_allowlist_users diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_boolean_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_boolean_v2025_r0.py new file mode 100644 index 000000000..8ee1c95e6 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_boolean_v2025_r0.py @@ -0,0 +1,21 @@ +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_v2025_r0 import ( + EnterpriseConfigurationItemV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseConfigurationItemBooleanV2025R0(EnterpriseConfigurationItemV2025R0): + def __init__( + self, *, value: Optional[bool] = None, is_used: Optional[bool] = None, **kwargs + ): + """ + :param value: The value of the enterprise configuration as a boolean., defaults to None + :type value: Optional[bool], optional + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_integer_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_integer_v2025_r0.py new file mode 100644 index 000000000..cc6300cec --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_integer_v2025_r0.py @@ -0,0 +1,21 @@ +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_v2025_r0 import ( + EnterpriseConfigurationItemV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseConfigurationItemIntegerV2025R0(EnterpriseConfigurationItemV2025R0): + def __init__( + self, *, value: Optional[int] = None, is_used: Optional[bool] = None, **kwargs + ): + """ + :param value: The value of the enterprise configuration as an integer., defaults to None + :type value: Optional[int], optional + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_string_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_string_v2025_r0.py new file mode 100644 index 000000000..3c50f3ba9 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_string_v2025_r0.py @@ -0,0 +1,21 @@ +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_v2025_r0 import ( + EnterpriseConfigurationItemV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseConfigurationItemStringV2025R0(EnterpriseConfigurationItemV2025R0): + def __init__( + self, *, value: Optional[str] = None, is_used: Optional[bool] = None, **kwargs + ): + """ + :param value: The value of the enterprise configuration as a string., defaults to None + :type value: Optional[str], optional + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_v2025_r0.py new file mode 100644 index 000000000..0846de57d --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_item_v2025_r0.py @@ -0,0 +1,15 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseConfigurationItemV2025R0(BaseObject): + def __init__(self, *, is_used: Optional[bool] = None, **kwargs): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(**kwargs) + self.is_used = is_used diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_security_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_security_v2025_r0.py new file mode 100644 index 000000000..41d50498e --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_security_v2025_r0.py @@ -0,0 +1,285 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_boolean_v2025_r0 import ( + EnterpriseConfigurationItemBooleanV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_v2025_r0 import ( + EnterpriseConfigurationItemV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_string_v2025_r0 import ( + EnterpriseConfigurationItemStringV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_integer_v2025_r0 import ( + EnterpriseConfigurationItemIntegerV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.external_collab_security_settings_v2025_r0 import ( + ExternalCollabSecuritySettingsV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.keysafe_settings_v2025_r0 import ( + KeysafeSettingsV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.custom_session_duration_group_item_v2025_r0 import ( + CustomSessionDurationGroupItemV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class EnterpriseConfigurationSecurityV2025R0LastPasswordResetAtField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[DateTime] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param value: When an enterprise password reset was last applied., defaults to None + :type value: Optional[DateTime], optional + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationSecurityV2025R0ExternalCollabMultiFactorAuthSettingsField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[Optional[ExternalCollabSecuritySettingsV2025R0]] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationSecurityV2025R0KeysafeField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[Optional[KeysafeSettingsV2025R0]] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationSecurityV2025R0CustomSessionDurationGroupsField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[List[CustomSessionDurationGroupItemV2025R0]] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationSecurityV2025R0EnforcedMfaFrequencyFieldValueField( + BaseObject +): + def __init__( + self, *, days: Optional[int] = None, hours: Optional[int] = None, **kwargs + ): + """ + :param days: Number of days before the user is required to authenticate again., defaults to None + :type days: Optional[int], optional + :param hours: Number of hours before the user is required to authenticate again., defaults to None + :type hours: Optional[int], optional + """ + super().__init__(**kwargs) + self.days = days + self.hours = hours + + +class EnterpriseConfigurationSecurityV2025R0EnforcedMfaFrequencyField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[ + EnterpriseConfigurationSecurityV2025R0EnforcedMfaFrequencyFieldValueField + ] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationSecurityV2025R0(BaseObject): + def __init__( + self, + *, + is_managed_user_signup_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_managed_user_signup_notification_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_managed_user_signup_corporate_email_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_new_user_notification_daily_digest_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_managed_user_email_change_disabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_multi_factor_auth_required: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_weak_password_prevention_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_password_leak_detection_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + last_password_reset_at: Optional[ + EnterpriseConfigurationSecurityV2025R0LastPasswordResetAtField + ] = None, + is_password_request_notification_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_password_change_notification_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_strong_password_for_ext_collab_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_managed_user_migration_disabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + join_link: Optional[EnterpriseConfigurationItemStringV2025R0] = None, + join_url: Optional[EnterpriseConfigurationItemStringV2025R0] = None, + failed_login_attempts_to_trigger_admin_notification: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + password_min_length: Optional[EnterpriseConfigurationItemIntegerV2025R0] = None, + password_min_uppercase_characters: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + password_min_numeric_characters: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + password_min_special_characters: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + password_reset_frequency: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + previous_password_reuse_limit: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + session_duration: Optional[EnterpriseConfigurationItemStringV2025R0] = None, + external_collab_multi_factor_auth_settings: Optional[ + EnterpriseConfigurationSecurityV2025R0ExternalCollabMultiFactorAuthSettingsField + ] = None, + keysafe: Optional[EnterpriseConfigurationSecurityV2025R0KeysafeField] = None, + is_custom_session_duration_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + custom_session_duration_value: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + custom_session_duration_groups: Optional[ + EnterpriseConfigurationSecurityV2025R0CustomSessionDurationGroupsField + ] = None, + multi_factor_auth_type: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + enforced_mfa_frequency: Optional[ + EnterpriseConfigurationSecurityV2025R0EnforcedMfaFrequencyField + ] = None, + **kwargs + ): + super().__init__(**kwargs) + self.is_managed_user_signup_enabled = is_managed_user_signup_enabled + self.is_managed_user_signup_notification_enabled = ( + is_managed_user_signup_notification_enabled + ) + self.is_managed_user_signup_corporate_email_enabled = ( + is_managed_user_signup_corporate_email_enabled + ) + self.is_new_user_notification_daily_digest_enabled = ( + is_new_user_notification_daily_digest_enabled + ) + self.is_managed_user_email_change_disabled = ( + is_managed_user_email_change_disabled + ) + self.is_multi_factor_auth_required = is_multi_factor_auth_required + self.is_weak_password_prevention_enabled = is_weak_password_prevention_enabled + self.is_password_leak_detection_enabled = is_password_leak_detection_enabled + self.last_password_reset_at = last_password_reset_at + self.is_password_request_notification_enabled = ( + is_password_request_notification_enabled + ) + self.is_password_change_notification_enabled = ( + is_password_change_notification_enabled + ) + self.is_strong_password_for_ext_collab_enabled = ( + is_strong_password_for_ext_collab_enabled + ) + self.is_managed_user_migration_disabled = is_managed_user_migration_disabled + self.join_link = join_link + self.join_url = join_url + self.failed_login_attempts_to_trigger_admin_notification = ( + failed_login_attempts_to_trigger_admin_notification + ) + self.password_min_length = password_min_length + self.password_min_uppercase_characters = password_min_uppercase_characters + self.password_min_numeric_characters = password_min_numeric_characters + self.password_min_special_characters = password_min_special_characters + self.password_reset_frequency = password_reset_frequency + self.previous_password_reuse_limit = previous_password_reuse_limit + self.session_duration = session_duration + self.external_collab_multi_factor_auth_settings = ( + external_collab_multi_factor_auth_settings + ) + self.keysafe = keysafe + self.is_custom_session_duration_enabled = is_custom_session_duration_enabled + self.custom_session_duration_value = custom_session_duration_value + self.custom_session_duration_groups = custom_session_duration_groups + self.multi_factor_auth_type = multi_factor_auth_type + self.enforced_mfa_frequency = enforced_mfa_frequency diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_shield_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_shield_v2025_r0.py new file mode 100644 index 000000000..c5666ef5a --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_shield_v2025_r0.py @@ -0,0 +1,21 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.shield_rule_item_v2025_r0 import ShieldRuleItemV2025R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseConfigurationShieldV2025R0(BaseObject): + def __init__( + self, *, shield_rules: Optional[List[ShieldRuleItemV2025R0]] = None, **kwargs + ): + """ + :param shield_rules: The shield rules configuration for the enterprise., defaults to None + :type shield_rules: Optional[List[ShieldRuleItemV2025R0]], optional + """ + super().__init__(**kwargs) + self.shield_rules = shield_rules diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_user_settings_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_user_settings_v2025_r0.py new file mode 100644 index 000000000..e777d81a0 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_user_settings_v2025_r0.py @@ -0,0 +1,148 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.enterprise_feature_settings_item_v2025_r0 import ( + EnterpriseFeatureSettingsItemV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_string_v2025_r0 import ( + EnterpriseConfigurationItemStringV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_boolean_v2025_r0 import ( + EnterpriseConfigurationItemBooleanV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_integer_v2025_r0 import ( + EnterpriseConfigurationItemIntegerV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_v2025_r0 import ( + EnterpriseConfigurationItemV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.user_tracking_code_v2025_r0 import ( + UserTrackingCodeV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseConfigurationUserSettingsV2025R0UserTrackingCodesField( + EnterpriseConfigurationItemV2025R0 +): + def __init__( + self, + *, + value: Optional[List[UserTrackingCodeV2025R0]] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value + + +class EnterpriseConfigurationUserSettingsV2025R0(BaseObject): + def __init__( + self, + *, + enterprise_feature_settings: Optional[ + List[EnterpriseFeatureSettingsItemV2025R0] + ] = None, + user_invites_expiration_time_frame: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + is_username_change_restricted: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_box_sync_restricted_for_new_users: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_view_all_users_enabled_for_new_users: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_device_limit_exemption_enabled_for_new_users: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_external_collaboration_restricted_for_new_users: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_unlimited_storage_enabled_for_new_users: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + new_user_default_storage_limit: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + new_user_default_timezone: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + new_user_default_language: Optional[ + EnterpriseConfigurationItemStringV2025R0 + ] = None, + is_enterprise_sso_required: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_enterprise_sso_in_testing: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_sso_auto_add_groups_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_sso_auto_add_user_to_groups_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + is_sso_auto_remove_user_from_groups_enabled: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + user_tracking_codes: Optional[ + EnterpriseConfigurationUserSettingsV2025R0UserTrackingCodesField + ] = None, + number_of_user_tracking_codes_remaining: Optional[ + EnterpriseConfigurationItemIntegerV2025R0 + ] = None, + is_instant_login_restricted: Optional[ + EnterpriseConfigurationItemBooleanV2025R0 + ] = None, + **kwargs + ): + super().__init__(**kwargs) + self.enterprise_feature_settings = enterprise_feature_settings + self.user_invites_expiration_time_frame = user_invites_expiration_time_frame + self.is_username_change_restricted = is_username_change_restricted + self.is_box_sync_restricted_for_new_users = is_box_sync_restricted_for_new_users + self.is_view_all_users_enabled_for_new_users = ( + is_view_all_users_enabled_for_new_users + ) + self.is_device_limit_exemption_enabled_for_new_users = ( + is_device_limit_exemption_enabled_for_new_users + ) + self.is_external_collaboration_restricted_for_new_users = ( + is_external_collaboration_restricted_for_new_users + ) + self.is_unlimited_storage_enabled_for_new_users = ( + is_unlimited_storage_enabled_for_new_users + ) + self.new_user_default_storage_limit = new_user_default_storage_limit + self.new_user_default_timezone = new_user_default_timezone + self.new_user_default_language = new_user_default_language + self.is_enterprise_sso_required = is_enterprise_sso_required + self.is_enterprise_sso_in_testing = is_enterprise_sso_in_testing + self.is_sso_auto_add_groups_enabled = is_sso_auto_add_groups_enabled + self.is_sso_auto_add_user_to_groups_enabled = ( + is_sso_auto_add_user_to_groups_enabled + ) + self.is_sso_auto_remove_user_from_groups_enabled = ( + is_sso_auto_remove_user_from_groups_enabled + ) + self.user_tracking_codes = user_tracking_codes + self.number_of_user_tracking_codes_remaining = ( + number_of_user_tracking_codes_remaining + ) + self.is_instant_login_restricted = is_instant_login_restricted diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_v2025_r0.py new file mode 100644 index 000000000..4e91f3e49 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_v2025_r0.py @@ -0,0 +1,60 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_security_v2025_r0 import ( + EnterpriseConfigurationSecurityV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_content_and_sharing_v2025_r0 import ( + EnterpriseConfigurationContentAndSharingV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_user_settings_v2025_r0 import ( + EnterpriseConfigurationUserSettingsV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_shield_v2025_r0 import ( + EnterpriseConfigurationShieldV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseConfigurationV2025R0TypeField(str, Enum): + ENTERPRISE_CONFIGURATION = 'enterprise_configuration' + + +class EnterpriseConfigurationV2025R0(BaseObject): + _discriminator = 'type', {'enterprise_configuration'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[EnterpriseConfigurationV2025R0TypeField] = None, + security: Optional[Optional[EnterpriseConfigurationSecurityV2025R0]] = None, + content_and_sharing: Optional[ + Optional[EnterpriseConfigurationContentAndSharingV2025R0] + ] = None, + user_settings: Optional[ + Optional[EnterpriseConfigurationUserSettingsV2025R0] + ] = None, + shield: Optional[Optional[EnterpriseConfigurationShieldV2025R0]] = None, + **kwargs + ): + """ + :param id: The identifier of the enterprise configuration which is the ID of the enterprise., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `enterprise_configuration`., defaults to None + :type type: Optional[EnterpriseConfigurationV2025R0TypeField], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.security = security + self.content_and_sharing = content_and_sharing + self.user_settings = user_settings + self.shield = shield diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_feature_setting_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_feature_setting_v2025_r0.py new file mode 100644 index 000000000..fae485004 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_feature_setting_v2025_r0.py @@ -0,0 +1,60 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from typing import List + +from box_sdk_gen.schemas.v2025_r0.user_or_group_reference_v2025_r0 import ( + UserOrGroupReferenceV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseFeatureSettingV2025R0FeatureField(BaseObject): + def __init__(self, *, id: Optional[str] = None, **kwargs): + """ + :param id: The identifier of the feature., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + + +class EnterpriseFeatureSettingV2025R0(BaseObject): + def __init__( + self, + *, + id: Optional[str] = None, + feature: Optional[EnterpriseFeatureSettingV2025R0FeatureField] = None, + state: Optional[str] = None, + can_configure: Optional[bool] = None, + is_configured: Optional[bool] = None, + allowlist: Optional[List[UserOrGroupReferenceV2025R0]] = None, + denylist: Optional[List[UserOrGroupReferenceV2025R0]] = None, + **kwargs + ): + """ + :param id: The identifier of the enterprise feature setting., defaults to None + :type id: Optional[str], optional + :param feature: The feature., defaults to None + :type feature: Optional[EnterpriseFeatureSettingV2025R0FeatureField], optional + :param state: The state of the feature., defaults to None + :type state: Optional[str], optional + :param can_configure: Whether the feature can be configured., defaults to None + :type can_configure: Optional[bool], optional + :param is_configured: Whether the feature is configured., defaults to None + :type is_configured: Optional[bool], optional + :param allowlist: Enterprise feature setting is enabled for only this set of users and groups., defaults to None + :type allowlist: Optional[List[UserOrGroupReferenceV2025R0]], optional + :param denylist: Enterprise feature setting is enabled for everyone except this set of users and groups., defaults to None + :type denylist: Optional[List[UserOrGroupReferenceV2025R0]], optional + """ + super().__init__(**kwargs) + self.id = id + self.feature = feature + self.state = state + self.can_configure = can_configure + self.is_configured = is_configured + self.allowlist = allowlist + self.denylist = denylist diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_feature_settings_item_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_feature_settings_item_v2025_r0.py new file mode 100644 index 000000000..0b3e805e2 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_feature_settings_item_v2025_r0.py @@ -0,0 +1,27 @@ +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_v2025_r0 import ( + EnterpriseConfigurationItemV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_feature_setting_v2025_r0 import ( + EnterpriseFeatureSettingV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class EnterpriseFeatureSettingsItemV2025R0(EnterpriseConfigurationItemV2025R0): + def __init__( + self, + *, + value: Optional[EnterpriseFeatureSettingV2025R0] = None, + is_used: Optional[bool] = None, + **kwargs + ): + """ + :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None + :type is_used: Optional[bool], optional + """ + super().__init__(is_used=is_used, **kwargs) + self.value = value diff --git a/box_sdk_gen/schemas/v2025_r0/external_collab_security_settings_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/external_collab_security_settings_v2025_r0.py new file mode 100644 index 000000000..56b2cae58 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/external_collab_security_settings_v2025_r0.py @@ -0,0 +1,52 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class ExternalCollabSecuritySettingsV2025R0(BaseObject): + def __init__( + self, + *, + denylist_domains: Optional[List[str]] = None, + denylist_emails: Optional[List[str]] = None, + allowlist_domains: Optional[List[str]] = None, + allowlist_emails: Optional[List[str]] = None, + state: Optional[str] = None, + scheduled_status: Optional[str] = None, + scheduled_at: Optional[DateTime] = None, + factor_type_settings: Optional[str] = None, + **kwargs + ): + """ + :param denylist_domains: List of domains that are not allowed for external collaboration. Applies if state is `denylist`., defaults to None + :type denylist_domains: Optional[List[str]], optional + :param denylist_emails: List of email addresses that are not allowed for external collaboration. Applies if state is `denylist`., defaults to None + :type denylist_emails: Optional[List[str]], optional + :param allowlist_domains: List of domains that are allowed for external collaboration. Applies if state is `allowlist`., defaults to None + :type allowlist_domains: Optional[List[str]], optional + :param allowlist_emails: List of email addresses that are allowed for external collaboration. Applies if state is `allowlist`., defaults to None + :type allowlist_emails: Optional[List[str]], optional + :param state: The state of the external collaboration security settings. Possible values include `enabled`, `disabled`, `allowlist`, and `denylist`., defaults to None + :type state: Optional[str], optional + :param scheduled_status: The status of the scheduling to apply external collaboration security settings. Possible values include `in_progress`, `scheduled`, `completed`, `failed`, and `scheduled_immediate`., defaults to None + :type scheduled_status: Optional[str], optional + :param scheduled_at: Scheduled at., defaults to None + :type scheduled_at: Optional[DateTime], optional + :param factor_type_settings: Factor type for the external collaborators authentication. Possible values include `totp`, `any`, or `unknown`., defaults to None + :type factor_type_settings: Optional[str], optional + """ + super().__init__(**kwargs) + self.denylist_domains = denylist_domains + self.denylist_emails = denylist_emails + self.allowlist_domains = allowlist_domains + self.allowlist_emails = allowlist_emails + self.state = state + self.scheduled_status = scheduled_status + self.scheduled_at = scheduled_at + self.factor_type_settings = factor_type_settings diff --git a/box_sdk_gen/schemas/v2025_r0/keysafe_settings_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/keysafe_settings_v2025_r0.py new file mode 100644 index 000000000..59a025fad --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/keysafe_settings_v2025_r0.py @@ -0,0 +1,44 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class KeysafeSettingsV2025R0(BaseObject): + def __init__( + self, + *, + keysafe_enabled: Optional[bool] = None, + cloud_provider: Optional[str] = None, + key_id: Optional[str] = None, + account_id: Optional[str] = None, + location_id: Optional[str] = None, + project_id: Optional[str] = None, + keyring_id: Optional[str] = None, + **kwargs + ): + """ + :param keysafe_enabled: Whether KeySafe addon is enabled for the enterprise., defaults to None + :type keysafe_enabled: Optional[bool], optional + :param cloud_provider: The cloud provider., defaults to None + :type cloud_provider: Optional[str], optional + :param key_id: The key ID., defaults to None + :type key_id: Optional[str], optional + :param account_id: The account ID., defaults to None + :type account_id: Optional[str], optional + :param location_id: The location ID., defaults to None + :type location_id: Optional[str], optional + :param project_id: The project ID., defaults to None + :type project_id: Optional[str], optional + :param keyring_id: The key ring ID., defaults to None + :type keyring_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.keysafe_enabled = keysafe_enabled + self.cloud_provider = cloud_provider + self.key_id = key_id + self.account_id = account_id + self.location_id = location_id + self.project_id = project_id + self.keyring_id = keyring_id diff --git a/box_sdk_gen/schemas/v2025_r0/list_user_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/list_user_v2025_r0.py new file mode 100644 index 000000000..76f890b80 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/list_user_v2025_r0.py @@ -0,0 +1,28 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ListUserV2025R0(BaseObject): + def __init__( + self, + *, + id: Optional[int] = None, + name: Optional[str] = None, + email: Optional[str] = None, + **kwargs + ): + """ + :param id: The ID of the user., defaults to None + :type id: Optional[int], optional + :param name: The name of the user., defaults to None + :type name: Optional[str], optional + :param email: The email of the user., defaults to None + :type email: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.name = name + self.email = email diff --git a/box_sdk_gen/schemas/v2025_r0/shared_link_permissions_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shared_link_permissions_v2025_r0.py new file mode 100644 index 000000000..d0de01b3a --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shared_link_permissions_v2025_r0.py @@ -0,0 +1,32 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SharedLinkPermissionsV2025R0(BaseObject): + def __init__( + self, + *, + shared_links_option: Optional[str] = None, + default_shared_link_type: Optional[str] = None, + notes_shared_link_option: Optional[str] = None, + default_notes_shared_link_type: Optional[str] = None, + **kwargs + ): + """ + :param shared_links_option: The selected option for shared links permissions., defaults to None + :type shared_links_option: Optional[str], optional + :param default_shared_link_type: The default shared link type., defaults to None + :type default_shared_link_type: Optional[str], optional + :param notes_shared_link_option: The selected option for notes shared links permissions., defaults to None + :type notes_shared_link_option: Optional[str], optional + :param default_notes_shared_link_type: The default notes shared link type., defaults to None + :type default_notes_shared_link_type: Optional[str], optional + """ + super().__init__(**kwargs) + self.shared_links_option = shared_links_option + self.default_shared_link_type = default_shared_link_type + self.notes_shared_link_option = notes_shared_link_option + self.default_notes_shared_link_type = default_notes_shared_link_type diff --git a/box_sdk_gen/schemas/v2025_r0/shield_rule_item_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/shield_rule_item_v2025_r0.py new file mode 100644 index 000000000..27e4a21d3 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/shield_rule_item_v2025_r0.py @@ -0,0 +1,66 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class ShieldRuleItemV2025R0TypeField(str, Enum): + SHIELD_RULE = 'shield_rule' + + +class ShieldRuleItemV2025R0PriorityField(str, Enum): + INFORMATIONAL = 'informational' + LOW = 'low' + MEDIUM = 'medium' + HIGH = 'high' + CRITICAL = 'critical' + + +class ShieldRuleItemV2025R0(BaseObject): + _discriminator = 'type', {'shield_rule'} + + def __init__( + self, + *, + id: Optional[str] = None, + type: Optional[ShieldRuleItemV2025R0TypeField] = None, + rule_category: Optional[str] = None, + name: Optional[str] = None, + description: Optional[str] = None, + priority: Optional[ShieldRuleItemV2025R0PriorityField] = None, + created_at: Optional[DateTime] = None, + modified_at: Optional[DateTime] = None, + **kwargs + ): + """ + :param id: The identifier of the shield rule., defaults to None + :type id: Optional[str], optional + :param type: The value will always be `shield_rule`., defaults to None + :type type: Optional[ShieldRuleItemV2025R0TypeField], optional + :param rule_category: The category of the shield rule., defaults to None + :type rule_category: Optional[str], optional + :param name: The name of the shield rule., defaults to None + :type name: Optional[str], optional + :param description: The description of the shield rule., defaults to None + :type description: Optional[str], optional + :param priority: The priority level of the shield rule., defaults to None + :type priority: Optional[ShieldRuleItemV2025R0PriorityField], optional + :param created_at: The date and time when the shield rule was created., defaults to None + :type created_at: Optional[DateTime], optional + :param modified_at: The date and time when the shield rule was last modified., defaults to None + :type modified_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.rule_category = rule_category + self.name = name + self.description = description + self.priority = priority + self.created_at = created_at + self.modified_at = modified_at diff --git a/box_sdk_gen/schemas/v2025_r0/user_or_group_reference_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/user_or_group_reference_v2025_r0.py new file mode 100644 index 000000000..7a6933d41 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/user_or_group_reference_v2025_r0.py @@ -0,0 +1,33 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserOrGroupReferenceV2025R0TypeField(str, Enum): + USER = 'user' + GROUP = 'group' + + +class UserOrGroupReferenceV2025R0(BaseObject): + _discriminator = 'type', {'user', 'group'} + + def __init__( + self, + *, + type: Optional[UserOrGroupReferenceV2025R0TypeField] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The type `user` or `group`., defaults to None + :type type: Optional[UserOrGroupReferenceV2025R0TypeField], optional + :param id: The identifier of the user or group., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.id = id diff --git a/box_sdk_gen/schemas/v2025_r0/user_tracking_code_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/user_tracking_code_v2025_r0.py new file mode 100644 index 000000000..e0b85c724 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/user_tracking_code_v2025_r0.py @@ -0,0 +1,20 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserTrackingCodeV2025R0(BaseObject): + def __init__( + self, *, id: Optional[int] = None, name: Optional[str] = None, **kwargs + ): + """ + :param id: The ID of the user tracking code., defaults to None + :type id: Optional[int], optional + :param name: The name of the user tracking code., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.name = name diff --git a/docs/README.md b/docs/README.md index 3ed7efdee..5e353d405 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,6 +22,7 @@ the SDK are available by topic: - [Docgen template](docgen_template.md) - [Downloads](downloads.md) - [Email aliases](email_aliases.md) +- [Enterprise configurations](enterprise_configurations.md) - [Events](events.md) - [External users](external_users.md) - [File classifications](file_classifications.md) diff --git a/docs/enterprise_configurations.md b/docs/enterprise_configurations.md new file mode 100644 index 000000000..39c321590 --- /dev/null +++ b/docs/enterprise_configurations.md @@ -0,0 +1,31 @@ +# EnterpriseConfigurationsManager + +- [Get enterprise configuration](#get-enterprise-configuration) + +## Get enterprise configuration + +Retrieves the configuration for an enterprise. + +This operation is performed by calling function `get_enterprise_configuration_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-enterprise-configurations-id/). + +_Currently we don't have an example for calling `get_enterprise_configuration_by_id_v2025_r0` in integration tests_ + +### Arguments + +- enterprise_id `str` + - The ID of the enterprise. Example: "3442311" +- categories `str` + - The comma-delimited list of the enterprise configuration categories. Allowed values: `security`, `content_and_sharing`, `user_settings`, `shield`. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `EnterpriseConfigurationV2025R0`. + +Returns the enterprise configuration. From 4670a2356520776e8dc30b2021e1f70fc2e55fec Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:50:15 +0200 Subject: [PATCH 043/139] docs: Update README after `v4` release (box/box-codegen#869) (#1142) --- .codegen.json | 2 +- README.md | 181 ++++++++++-------- ...box-python-sdk-gen-v1-to-box-python-sdk.md | 2 +- migration-guides/from-v3-to-v4.md | 2 +- 4 files changed, 108 insertions(+), 79 deletions(-) diff --git a/.codegen.json b/.codegen.json index c22868b02..f2866b626 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "192deac", "specHash": "cf21406", "version": "10.0.1" } +{ "engineHash": "b98d1dc", "specHash": "cf21406", "version": "10.0.1" } diff --git a/README.md b/README.md index 08a417e20..0f991d473 100644 --- a/README.md +++ b/README.md @@ -2,29 +2,7 @@ “box-dev-logo”

-# Versioning Strategy - -Starting with v10 of the SDK, we’ve introduced a new generated codebase designed to enhance your experience with the Box API. -It is currently available on the [sdk-gen](https://github.com/box/box-python-sdk/tree/sdk-gen) branch and uses the new `box_sdk_gen` package instead of the old `boxsdk`. - -v10 is targeted at new users of Box Python SDK and users already working with the generated Box Python SDK previously available under the [Box Python SDK Gen repository](https://github.com/box/box-python-sdk-gen). - -For users of v3 of the Box Python SDK, no action is required at this time — we’ll be providing an upcoming v4 release that will include both `boxsdk` and `box_sdk_gen` packages for a smooth migration path. To avoid unintentional upgrades, pin your version to `boxsdk~=3.0`. - -For full guidance on SDK versioning, see the [Box SDK Versioning Guide](https://developer.box.com/guides/tooling/sdks/sdk-versioning/). - ---- - -## Which Version Should I Use? - -| Scenario | Recommended Version | Example `pip install` | -| --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------- | -| Creating a new application | Use [v10](https://github.com/box/box-python-sdk/tree/sdk-gen) | `pip install "boxsdk>=10"` | -| Existing app using [box-sdk-gen](https://pypi.org/project/box-sdk-gen/) artifact | Upgrade to [v10](https://github.com/box/box-python-sdk/tree/sdk-gen) | `pip install "boxsdk>=10"` | -| Existing app using [boxsdk](https://pypi.org/project/boxsdk/) artifact planning to use new features | Wait for v4 to start migration process | TBD | -| Existing app using [boxsdk](https://pypi.org/project/boxsdk/) artifact not planning any changes | Stay on `boxsdk~=3.0` | `pip install "boxsdk~=3.0"` | - -# Box Python SDK +# Box Python SDK v10 [![Project Status](http://opensource.box.com/badges/active.svg)](http://opensource.box.com/badges) ![build](https://github.com/box/box-python-sdk/actions/workflows/build.yml/badge.svg?branch=sdk-gen) @@ -33,10 +11,34 @@ For full guidance on SDK versioning, see the [Box SDK Versioning Guide](https:// ![Platform](https://img.shields.io/badge/python-3.8+-blue) [![Coverage](https://coveralls.io/repos/github/box/box-python-sdk/badge.svg?branch=sdk-gen)](https://coveralls.io/github/box/box-python-sdk?branch=sdk-gen) -We are excited to introduce the stable release of the latest generation of Box Python SDK, + + + +- [Introduction](#introduction) +- [Supported versions](#supported-versions) + - [Version v4](#version-v4) + - [Version v10](#version-v10) + - [Which Version Should I Use?](#which-version-should-i-use) +- [Installing](#installing) +- [Getting Started](#getting-started) +- [Authentication](#authentication) +- [Documentation](#documentation) +- [Migration guides](#migration-guides) +- [Versioning](#versioning) + - [Version schedule](#version-schedule) +- [Contributing](#contributing) +- [FIPS 140-2 Compliance](#fips-140-2-compliance) +- [Questions, Bugs, and Feature Requests?](#questions-bugs-and-feature-requests) +- [Copyright and License](#copyright-and-license) + + + +# Introduction + +We are excited to introduce the v10 major release of the Box Python SDK, designed to elevate the developer experience and streamline your integration with the Box Content Cloud. -With this SDK, you’ll have access to: +With this SDK version, we provide the `box_sdk_gen` package, which gives you access to: 1. Full API Support: The new generation of Box SDKs empowers developers with complete coverage of the Box API ecosystem. You can now access all the latest features and functionalities offered by Box, allowing you to build even more sophisticated and feature-rich applications. 2. Rapid API Updates: Say goodbye to waiting for new Box APIs to be incorporated into the SDK. With our new auto-generation development approach, we can now add new Box APIs to the SDK at a much faster pace (in a matter of days). This means you can leverage the most up-to-date features in your applications without delay. @@ -46,26 +48,44 @@ With this SDK, you’ll have access to: Embrace the new generation of Box SDKs and unlock the full potential of the Box Content Cloud. -# Table of contents +# Supported versions - - +To enhance developer experience, we have introduced the new generated codebase through the `box_sdk_gen` package. +The `box_sdk_gen` package is available in two major supported versions: v4 and v10. -- [Box Python SDK](#box-python-sdk) -- [Table of contents](#table-of-contents) -- [Installing](#installing) -- [Getting Started](#getting-started) -- [Documentation](#documentation) -- [Upgrades](#upgrades) -- [Integration Tests](#integration-tests) - - [Running integration tests locally](#running-integration-tests-locally) - - [Create Platform Application](#create-platform-application) - - [Export configuration](#export-configuration) - - [Running tests](#running-tests) -- [Questions, Bugs, and Feature Requests?](#questions-bugs-and-feature-requests) -- [Copyright and License](#copyright-and-license) +## Version v4 - +In v4 of the Box Python SDK, we are introducing a version that consolidates both the manually written package (`boxsdk`) +and the new generated package (`box_sdk_gen`). This allows developers to use both packages simultaneously within a single project. + +The codebase for v4 of the Box Python SDK is currently available on the [combined-sdk](https://github.com/box/box-python-sdk/tree/combined-sdk) branch. +Migration guide which would help with migration from `boxsdk` to `box_sdk_gen` can be found [here](./migration-guides/from-boxsdk-to-box_sdk_gen.md). + +Version v4 is intended for: + +- Existing developers of the Box Python SDK v3 who want to access new API features while keeping their current codebase largely unchanged. +- Existing developers who are in the process of migrating to `box_sdk_gen`, but do not want to move all their code to the new package immediately. + +## Version v10 + +Starting with v10, the SDK is built entirely on the generated `box_sdk_gen` package, which fully and exclusively replaces the old `boxsdk` package. +The codebase for v10 of the Box Python SDK is currently available on the [sdk-gen](https://github.com/box/box-python-sdk/tree/sdk-gen) branch. + +Version v10 is intended for: + +- New users of the Box Python SDK. +- Developers already working with the generated Box Python SDK previously available under the [Box Python SDK Gen repository](https://github.com/box/box-python-sdk-gen). + +## Which Version Should I Use? + +| Scenario | Recommended Version | Example `pip install` | +| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------- | +| Creating a new application | Use [v10](https://github.com/box/box-python-sdk/tree/sdk-gen) | `pip install "boxsdk>=10"` | +| App using [box-sdk-gen](https://pypi.org/project/box-sdk-gen/) artifact | Migrate to [v10](https://github.com/box/box-python-sdk/tree/sdk-gen) | `pip install "boxsdk>=10"` | +| App using both [box-sdk-gen](https://pypi.org/project/box-sdk-gen/) and [boxsdk](https://pypi.org/project/boxsdk/) artifacts | Upgrade to [v4](https://github.com/box/box-python-sdk/tree/combined-sdk) | `pip install "boxsdk~=4.0"` | +| App using v3 of [boxsdk](https://pypi.org/project/boxsdk/) artifact | Upgrade to [v4](https://github.com/box/box-python-sdk/tree/combined-sdk) | `pip install "boxsdk~=4.0"` | + +For full guidance on SDK versioning, see the [Box SDK Versioning Guide](https://developer.box.com/guides/tooling/sdks/sdk-versioning/). # Installing @@ -107,62 +127,71 @@ if __name__ == '__main__': main('INSERT YOUR DEVELOPER TOKEN HERE') ``` +# Authentication + +Box Python SDK v10 supports multiple authentication methods including Developer Token, OAuth 2.0, +Client Credentials Grant, and JSON Web Token (JWT). + +You can find detailed instructions and example code for each authentication method in +[Authentication](./docs/authentication.md) document. + # Documentation Browse the [docs](docs/README.md) or see [API Reference](https://developer.box.com/reference/) for more information. -# Upgrades +# Migration guides + +Migration guides which help you to migrate to supported major SDK versions can be found [here](./migration-guides). -The SDK is updated regularly to include new features, enhancements, and bug fixes. -If you are upgrading from manual v3 SDK version to this new generated v10, checkout the [migration guide](migration-guides/from-v3-to-v10.md) and [changelog](CHANGELOG.md) for more information. +# Versioning -# Integration Tests +We use a modified version of [Semantic Versioning](https://semver.org/) for all changes. See [version strategy](VERSIONS.md) for details which is effective from 30 July 2022. -## Running integration tests locally +A current release is on the leading edge of our SDK development, and is intended for customers who are in active development and want the latest and greatest features. +Instead of stating a release date for a new feature, we set a fixed minor or patch release cadence of maximum 2-3 months (while we may release more often). +At the same time, there is no schedule for major or breaking release. Instead, we will communicate one quarter in advance the upcoming breaking change to allow customers to plan for the upgrade. -### Create Platform Application +We always recommend that all users run the latest available minor release for whatever major version is in use. +We highly recommend upgrading to the latest SDK major release at the earliest convenient time and before the EOL date. -To run integration tests locally you will need a `Custom App` created in the [Box Developer -Console](https://app.box.com/developers/console) -with `Server Authentication (with JWT)` selected as authentication method. -Once created you can edit properties of the application: +## Version schedule -- In section `App Access Level` select `App + Enterprise Access`. You can enable all `Application Scopes`. -- In section `Advanced Features` enable `Make API calls using the as-user header` and `Generate user access tokens`. +| Version | Supported Environments | State | First Release | EOL/Terminated | +| ------- | ---------------------- | --------- | ------------- | ---------------------- | +| 10 | Python 3.8+ | Supported | 17 Sep 2025 | TBD | +| 4 | Python 3.8+ | Supported | 23 Oct 2025 | 2027 or v5 is released | +| 3 | Python 3.6+ | EOL | 17 Jan 2022 | 23 Oct 2025 | +| 2 | | EOL | 01 Nov 2018 | 17 Jan 2022 | +| 1 | | EOL | 10 Feb 2015 | 01 Nov 2018 | -Now select `Authorization` and submit application to be reviewed by account admin. +# Contributing -### Export configuration +See [CONTRIBUTING.md](./CONTRIBUTING.md). -1. Select `Configuration` tab and in the bottom in the section `App Settings` - download your app configuration settings as JSON. -2. Encode configuration file to Base64, e.g. using command: `base64 -i path_to_json_file` -3. Set environment variable: `JWT_CONFIG_BASE_64` with base64 encoded jwt configuration file -4. Set environment variable: `BOX_FILE_REQUEST_ID` with ID of file request already created in the user account, `BOX_EXTERNAL_USER_EMAIL` with email of free external user which not belongs to any enterprise and `BOX_EXTERNAL_USER_ID` with its ID. -5. Set environment variable: `WORKFLOW_FOLDER_ID` with the ID of the Relay workflow that deletes the file that triggered the workflow. The workflow should have a manual start to be able to start it from the API. -6. Set environment variable: `APP_ITEM_ASSOCIATION_FILE_ID` to the ID of the file with associated app item and `APP_ITEM_ASSOCIATION_FOLDER_ID` to the ID of the folder with associated app item. -7. Set environment variable: `APP_ITEM_SHARED_LINK` to the shared link associated with app item. -8. Set environment variable: `SLACK_AUTOMATION_USER_ID` to the ID of the user responsible for the Slack automation, `SLACK_ORG_ID` to the ID of the Slack organization and `SLACK_PARTNER_ITEM_ID` to the ID of the Slack partner item. +# FIPS 140-2 Compliance -### Running tests +The Python SDK allows the use of FIPS 140-2 validated SSL libraries, such as OpenSSL 3.0. +However, some actions are required to enable this functionality. -To run integration tests locally: +Currently, the latest distributions of Python default to OpenSSL v1.1.1, which is not FIPS compliant. +Therefore, if you want to use OpenSSL 3.0 in your network communication, +you need to ensure that Python uses a custom SSL library. +One way to achieve this is by creating a custom Python distribution with the ssl module replaced. -1. `pip install -r requirements-test.txt` -2. `pytest` +If you are using JWT for authentication, it is also necessary to ensure that the cryptography library, +which is one of the extra dependencies for JWT, uses OpenSSL 3.0. +To enable FIPS mode for the `cryptography` library, you need to install a FIPS-compliant version of OpenSSL +during the installation process of cryptography using the `pip` command. # Questions, Bugs, and Feature Requests? -Need to contact us directly? [Browse the issues -tickets](https://github.com/box/box-python-sdk/issues)! Or, if that -doesn't work, [file a new -one](https://github.com/box/box-python-sdk/issues/new) and we will get -back to you. If you have general questions about the Box API, you can -post to the [Box Developer Forum](https://forum.box.com/). +Need to contact us directly? [Browse the issues tickets](https://github.com/box/box-python-sdk/issues)! Or, if that +doesn't work, [file a new one](https://github.com/box/box-python-sdk/issues/new) and we will get +back to you. If you have general questions about the Box API, you can post to the [Box Developer Forum](https://community.box.com/box-platform-5). # Copyright and License -Copyright 2023 Box, Inc. All rights reserved. +Copyright 2025 Box, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk.md b/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk.md index 3c3eb270d..d8cb32b38 100644 --- a/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk.md +++ b/migration-guides/from-box-python-sdk-gen-v1-to-box-python-sdk.md @@ -101,7 +101,7 @@ def main(): folder: FolderOld = legacy_client.folder(folder_id="0").create_subfolder( "My Subfolder" ) - updated_folder: Folder = new_client.folders.update_folder( + updated_folder: Folder = new_client.folders.update_folder_by_id( folder_id=folder.id, name="My Updated Subfolder" ) print( diff --git a/migration-guides/from-v3-to-v4.md b/migration-guides/from-v3-to-v4.md index 452b15166..15a31d4f9 100644 --- a/migration-guides/from-v3-to-v4.md +++ b/migration-guides/from-v3-to-v4.md @@ -79,7 +79,7 @@ def main(): folder: FolderOld = legacy_client.folder(folder_id="0").create_subfolder( "My Subfolder" ) - updated_folder: Folder = new_client.folders.update_folder( + updated_folder: Folder = new_client.folders.update_folder_by_id( folder_id=folder.id, name="My Updated Subfolder" ) print( From 25e90d117e7bbf03f9f5dc84ed09bc9bd49b2727 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:19:09 +0100 Subject: [PATCH 044/139] fix: Remove enum types from GET enterprise configuration endpoint (box/box-openapi#560) (#1151) --- .codegen.json | 2 +- .../managers/enterprise_configurations.py | 8 +++--- box_sdk_gen/networking/retries.py | 6 ++++- box_sdk_gen/schemas/v2025_r0/__init__.py | 2 -- .../collaboration_restriction_v2025_r0.py | 8 ------ ...figuration_content_and_sharing_v2025_r0.py | 27 +++---------------- docs/enterprise_configurations.md | 4 +-- 7 files changed, 16 insertions(+), 41 deletions(-) delete mode 100644 box_sdk_gen/schemas/v2025_r0/collaboration_restriction_v2025_r0.py diff --git a/.codegen.json b/.codegen.json index f2866b626..771280e30 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "b98d1dc", "specHash": "cf21406", "version": "10.0.1" } +{ "engineHash": "4421f42", "specHash": "1715587", "version": "10.0.1" } diff --git a/box_sdk_gen/managers/enterprise_configurations.py b/box_sdk_gen/managers/enterprise_configurations.py index 56c09e02a..9fccb4610 100644 --- a/box_sdk_gen/managers/enterprise_configurations.py +++ b/box_sdk_gen/managers/enterprise_configurations.py @@ -1,5 +1,7 @@ from typing import Optional +from typing import List + from typing import Dict from box_sdk_gen.internal.utils import to_string @@ -54,7 +56,7 @@ def __init__( def get_enterprise_configuration_by_id_v2025_r0( self, enterprise_id: str, - categories: str, + categories: List[str], *, box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, extra_headers: Optional[Dict[str, Optional[str]]] = None @@ -64,9 +66,9 @@ def get_enterprise_configuration_by_id_v2025_r0( :param enterprise_id: The ID of the enterprise. Example: "3442311" :type enterprise_id: str - :param categories: The comma-delimited list of the enterprise configuration categories. + :param categories: A comma-separated list of the enterprise configuration categories. Allowed values: `security`, `content_and_sharing`, `user_settings`, `shield`. - :type categories: str + :type categories: List[str] :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 :type box_version: BoxVersionHeaderV2025R0, optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None diff --git a/box_sdk_gen/networking/retries.py b/box_sdk_gen/networking/retries.py index 4d9c6c257..82772f711 100644 --- a/box_sdk_gen/networking/retries.py +++ b/box_sdk_gen/networking/retries.py @@ -59,7 +59,11 @@ def should_retry( is_successful: bool = ( fetch_response.status >= 200 and fetch_response.status < 400 ) - retry_after_header: Optional[str] = fetch_response.headers.get('Retry-After') + retry_after_header: Optional[str] = ( + fetch_response.headers.get('Retry-After') + if 'Retry-After' in fetch_response.headers + else None + ) is_accepted_with_retry_after: bool = ( fetch_response.status == 202 and not retry_after_header == None ) diff --git a/box_sdk_gen/schemas/v2025_r0/__init__.py b/box_sdk_gen/schemas/v2025_r0/__init__.py index ed524c996..64d7b89b1 100644 --- a/box_sdk_gen/schemas/v2025_r0/__init__.py +++ b/box_sdk_gen/schemas/v2025_r0/__init__.py @@ -6,8 +6,6 @@ from box_sdk_gen.schemas.v2025_r0.collaboration_permissions_v2025_r0 import * -from box_sdk_gen.schemas.v2025_r0.collaboration_restriction_v2025_r0 import * - from box_sdk_gen.schemas.v2025_r0.custom_session_duration_group_item_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.doc_gen_batch_base_v2025_r0 import * diff --git a/box_sdk_gen/schemas/v2025_r0/collaboration_restriction_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/collaboration_restriction_v2025_r0.py deleted file mode 100644 index ee9f7ac7c..000000000 --- a/box_sdk_gen/schemas/v2025_r0/collaboration_restriction_v2025_r0.py +++ /dev/null @@ -1,8 +0,0 @@ -from enum import Enum - -from box_sdk_gen.box.errors import BoxSDKError - - -class CollaborationRestrictionV2025R0(str, Enum): - INTERNAL = 'internal' - EXTERNAL = 'external' diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_content_and_sharing_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_content_and_sharing_v2025_r0.py index 0fe0cfaa4..06ae2a645 100644 --- a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_content_and_sharing_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_content_and_sharing_v2025_r0.py @@ -2,8 +2,6 @@ from typing import List -from enum import Enum - from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.schemas.v2025_r0.enterprise_feature_settings_item_v2025_r0 import ( @@ -30,10 +28,6 @@ CollaborationPermissionsV2025R0, ) -from box_sdk_gen.schemas.v2025_r0.collaboration_restriction_v2025_r0 import ( - CollaborationRestrictionV2025R0, -) - from box_sdk_gen.schemas.v2025_r0.list_user_v2025_r0 import ListUserV2025R0 from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_item_integer_v2025_r0 import ( @@ -85,7 +79,7 @@ class EnterpriseConfigurationContentAndSharingV2025R0CollaborationRestrictionsFi def __init__( self, *, - value: Optional[List[CollaborationRestrictionV2025R0]] = None, + value: Optional[List[str]] = None, is_used: Optional[bool] = None, **kwargs ): @@ -97,30 +91,15 @@ def __init__( self.value = value -class EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationStatusFieldValueField( - str, Enum -): - ENABLE_EXTERNAL_COLLABORATION = 'enable_external_collaboration' - LIMIT_COLLABORATION_TO_ALLOWLISTED_DOMAINS = ( - 'limit_collaboration_to_allowlisted_domains' - ) - - class EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationStatusField( EnterpriseConfigurationItemV2025R0 ): def __init__( - self, - *, - value: Optional[ - EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationStatusFieldValueField - ] = None, - is_used: Optional[bool] = None, - **kwargs + self, *, value: Optional[str] = None, is_used: Optional[bool] = None, **kwargs ): """ :param value: The external collaboration status., defaults to None - :type value: Optional[EnterpriseConfigurationContentAndSharingV2025R0ExternalCollaborationStatusFieldValueField], optional + :type value: Optional[str], optional :param is_used: Indicates whether a configuration is used for a given enterprise., defaults to None :type is_used: Optional[bool], optional """ diff --git a/docs/enterprise_configurations.md b/docs/enterprise_configurations.md index 39c321590..751ae4d09 100644 --- a/docs/enterprise_configurations.md +++ b/docs/enterprise_configurations.md @@ -17,8 +17,8 @@ _Currently we don't have an example for calling `get_enterprise_configuration_by - enterprise_id `str` - The ID of the enterprise. Example: "3442311" -- categories `str` - - The comma-delimited list of the enterprise configuration categories. Allowed values: `security`, `content_and_sharing`, `user_settings`, `shield`. +- categories `List[str]` + - A comma-separated list of the enterprise configuration categories. Allowed values: `security`, `content_and_sharing`, `user_settings`, `shield`. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` From 92406e6eb53aa6993a9a47be299363c863a476ab Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:59:23 +0100 Subject: [PATCH 045/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1161) * chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] * chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 771280e30..591b26194 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "4421f42", "specHash": "1715587", "version": "10.0.1" } +{ "engineHash": "e6b1577", "specHash": "1715587", "version": "10.0.1" } From 6fac14632be5019defb06f7278bc647c5f7354f0 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 31 Oct 2025 15:40:06 +0100 Subject: [PATCH 046/139] test: add tests for `enterpriseConfigurations` manager (box/box-codegen#875) (#1167) * chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] * test: add tests for `enterpriseConfigurations` manager (box/box-codegen#875) --- .codegen.json | 2 +- docs/enterprise_configurations.md | 8 ++++- test/enterprise_configurations.py | 57 +++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 test/enterprise_configurations.py diff --git a/.codegen.json b/.codegen.json index 591b26194..748587d66 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "e6b1577", "specHash": "1715587", "version": "10.0.1" } +{ "engineHash": "8cdcb1b", "specHash": "1715587", "version": "10.0.1" } diff --git a/docs/enterprise_configurations.md b/docs/enterprise_configurations.md index 751ae4d09..b0d60a8a3 100644 --- a/docs/enterprise_configurations.md +++ b/docs/enterprise_configurations.md @@ -11,7 +11,13 @@ This operation is performed by calling function `get_enterprise_configuration_by See the endpoint docs at [API Reference](https://developer.box.com/reference/v2025.0/get-enterprise-configurations-id/). -_Currently we don't have an example for calling `get_enterprise_configuration_by_id_v2025_r0` in integration tests_ + + +```python +admin_client.enterprise_configurations.get_enterprise_configuration_by_id_v2025_r0( + enterprise_id, ["user_settings", "content_and_sharing", "security", "shield"] +) +``` ### Arguments diff --git a/test/enterprise_configurations.py b/test/enterprise_configurations.py new file mode 100644 index 000000000..bb9b0af86 --- /dev/null +++ b/test/enterprise_configurations.py @@ -0,0 +1,57 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_v2025_r0 import ( + EnterpriseConfigurationV2025R0, +) + +from box_sdk_gen.internal.utils import get_env_var + +from test.commons import get_default_client_with_user_subject + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_user_settings_v2025_r0 import ( + EnterpriseConfigurationUserSettingsV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_content_and_sharing_v2025_r0 import ( + EnterpriseConfigurationContentAndSharingV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_security_v2025_r0 import ( + EnterpriseConfigurationSecurityV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.enterprise_configuration_shield_v2025_r0 import ( + EnterpriseConfigurationShieldV2025R0, +) + +admin_client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + + +def testGetEnterpriseConfigurationById(): + enterprise_id: str = get_env_var('ENTERPRISE_ID') + enterprise_configuration: EnterpriseConfigurationV2025R0 = ( + admin_client.enterprise_configurations.get_enterprise_configuration_by_id_v2025_r0( + enterprise_id, + ['user_settings', 'content_and_sharing', 'security', 'shield'], + ) + ) + assert to_string(enterprise_configuration.type) == 'enterprise_configuration' + user_settings: EnterpriseConfigurationUserSettingsV2025R0 = ( + enterprise_configuration.user_settings + ) + assert user_settings.is_enterprise_sso_required.value == False + assert user_settings.new_user_default_language.value == 'English (US)' + assert user_settings.new_user_default_storage_limit.value == -1 + content_and_sharing: EnterpriseConfigurationContentAndSharingV2025R0 = ( + enterprise_configuration.content_and_sharing + ) + assert ( + content_and_sharing.collaboration_permissions.value.is_editor_role_enabled + == True + ) + security: EnterpriseConfigurationSecurityV2025R0 = enterprise_configuration.security + assert security.is_managed_user_signup_enabled.value == False + shield: EnterpriseConfigurationShieldV2025R0 = enterprise_configuration.shield + assert len(shield.shield_rules) == 0 From 44c1767049810de0163fd0636f96f8810d82e315 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:40:30 +0100 Subject: [PATCH 047/139] fix: update descriptions of the user deletion endpoint (box/box-openapi#562) (#1171) --- .codegen.json | 2 +- box_sdk_gen/managers/users.py | 10 +++++----- docs/users.md | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.codegen.json b/.codegen.json index 748587d66..d324a0463 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "8cdcb1b", "specHash": "1715587", "version": "10.0.1" } +{ "engineHash": "7c94f4f", "specHash": "a646ae6", "version": "10.0.1" } diff --git a/box_sdk_gen/managers/users.py b/box_sdk_gen/managers/users.py index 52df23cb1..0bc8a85da 100644 --- a/box_sdk_gen/managers/users.py +++ b/box_sdk_gen/managers/users.py @@ -653,12 +653,12 @@ def delete_user_by_id( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> None: """ - Deletes a user. By default this will fail if the user + Deletes a user. By default, this operation fails if the user - still owns any content. Move their owned content first + still owns any content, was recently active, or recently joined the enterprise from a free account. - before proceeding, or use the `force` field to delete + To proceed, move their owned content first, or use the `force` parameter to delete the user and their files. @@ -669,8 +669,8 @@ def delete_user_by_id( :param notify: Whether the user will receive email notification of the deletion., defaults to None :type notify: Optional[bool], optional - :param force: Whether the user should be deleted even if this user - still own files., defaults to None + :param force: Specifies whether to delete the user even if they still own files, + were recently active, or recently joined the enterprise from a free account., defaults to None :type force: Optional[bool], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional diff --git a/docs/users.md b/docs/users.md index 63a8191d2..45eaa9eb7 100644 --- a/docs/users.md +++ b/docs/users.md @@ -278,9 +278,9 @@ Returns the updated user object. ## Delete user -Deletes a user. By default this will fail if the user -still owns any content. Move their owned content first -before proceeding, or use the `force` field to delete +Deletes a user. By default, this operation fails if the user +still owns any content, was recently active, or recently joined the enterprise from a free account. +To proceed, move their owned content first, or use the `force` parameter to delete the user and their files. This operation is performed by calling function `delete_user_by_id`. @@ -301,7 +301,7 @@ client.users.delete_user_by_id(user.id) - notify `Optional[bool]` - Whether the user will receive email notification of the deletion. - force `Optional[bool]` - - Whether the user should be deleted even if this user still own files. + - Specifies whether to delete the user even if they still own files, were recently active, or recently joined the enterprise from a free account. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. From d21ec4df82ab43539ff07d306cf304f86bbb0593 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 6 Nov 2025 17:35:20 +0100 Subject: [PATCH 048/139] feat: Support update Archive API (box/box-openapi#563) (#1174) --- .codegen.json | 2 +- box_sdk_gen/managers/archives.py | 64 ++++++++++++++++++- .../schemas/v2025_r0/archive_v2025_r0.py | 25 +++++++- .../v2025_r0/weblink_reference_v2025_r0.py | 8 +-- docs/archives.md | 37 +++++++++++ 5 files changed, 129 insertions(+), 7 deletions(-) diff --git a/.codegen.json b/.codegen.json index d324a0463..c0f97a06b 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "7c94f4f", "specHash": "a646ae6", "version": "10.0.1" } +{ "engineHash": "7c94f4f", "specHash": "8b51a89", "version": "10.0.1" } diff --git a/box_sdk_gen/managers/archives.py b/box_sdk_gen/managers/archives.py index 6cfcf5258..b6bf5faf2 100644 --- a/box_sdk_gen/managers/archives.py +++ b/box_sdk_gen/managers/archives.py @@ -101,6 +101,8 @@ def create_archive_v2025_r0( self, name: str, *, + description: Optional[str] = None, + storage_policy_id: Optional[str] = None, box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> ArchiveV2025R0: @@ -111,6 +113,10 @@ def create_archive_v2025_r0( :param name: The name of the archive. :type name: str + :param description: The description of the archive., defaults to None + :type description: Optional[str], optional + :param storage_policy_id: The ID of the storage policy that the archive is assigned to., defaults to None + :type storage_policy_id: Optional[str], optional :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 :type box_version: BoxVersionHeaderV2025R0, optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None @@ -118,7 +124,11 @@ def create_archive_v2025_r0( """ if extra_headers is None: extra_headers = {} - request_body: Dict = {'name': name} + request_body: Dict = { + 'name': name, + 'description': description, + 'storage_policy_id': storage_policy_id, + } headers_map: Dict[str, str] = prepare_params( {'box-version': to_string(box_version), **extra_headers} ) @@ -178,3 +188,55 @@ def delete_archive_by_id_v2025_r0( ) ) return None + + def update_archive_by_id_v2025_r0( + self, + archive_id: str, + *, + name: Optional[str] = None, + description: Optional[str] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> ArchiveV2025R0: + """ + Updates an archive. + + To learn more about the archive APIs, see the [Archive API Guide](g://archives). + + :param archive_id: The ID of the archive. + Example: "982312" + :type archive_id: str + :param name: The name of the archive., defaults to None + :type name: Optional[str], optional + :param description: The description of the archive., defaults to None + :type description: Optional[str], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'name': name, 'description': description} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/archives/', + to_string(archive_id), + ] + ), + method='PUT', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, ArchiveV2025R0) diff --git a/box_sdk_gen/schemas/v2025_r0/archive_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/archive_v2025_r0.py index 4c65dafba..b59ac67ac 100644 --- a/box_sdk_gen/schemas/v2025_r0/archive_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/archive_v2025_r0.py @@ -2,6 +2,8 @@ from box_sdk_gen.internal.base_object import BaseObject +from typing import Optional + from box_sdk_gen.box.errors import BoxSDKError @@ -9,6 +11,19 @@ class ArchiveV2025R0TypeField(str, Enum): ARCHIVE = 'archive' +class ArchiveV2025R0OwnedByField(BaseObject): + def __init__(self, id: str, type: str, **kwargs): + """ + :param id: The unique identifier that represents a user who owns the archive. + :type id: str + :param type: The value is always `user`. + :type type: str + """ + super().__init__(**kwargs) + self.id = id + self.type = type + + class ArchiveV2025R0(BaseObject): _discriminator = 'type', {'archive'} @@ -19,6 +34,8 @@ def __init__( size: int, *, type: ArchiveV2025R0TypeField = ArchiveV2025R0TypeField.ARCHIVE, + description: Optional[str] = None, + owned_by: Optional[ArchiveV2025R0OwnedByField] = None, **kwargs ): r""" @@ -33,11 +50,17 @@ def __init__( :type name: str :param size: The size of the archive in bytes. :type size: int - :param type: The value will always be `archive`., defaults to ArchiveV2025R0TypeField.ARCHIVE + :param type: The value is always `archive`., defaults to ArchiveV2025R0TypeField.ARCHIVE :type type: ArchiveV2025R0TypeField, optional + :param description: The description of the archive., defaults to None + :type description: Optional[str], optional + :param owned_by: The part of an archive API response that describes the user who owns the archive., defaults to None + :type owned_by: Optional[ArchiveV2025R0OwnedByField], optional """ super().__init__(**kwargs) self.id = id self.name = name self.size = size self.type = type + self.description = description + self.owned_by = owned_by diff --git a/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py index d172f86e2..efef60c7b 100644 --- a/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/weblink_reference_v2025_r0.py @@ -6,23 +6,23 @@ class WeblinkReferenceV2025R0TypeField(str, Enum): - WEBLINK = 'weblink' + WEB_LINK = 'web_link' class WeblinkReferenceV2025R0(BaseObject): - _discriminator = 'type', {'weblink'} + _discriminator = 'type', {'web_link'} def __init__( self, id: str, *, - type: WeblinkReferenceV2025R0TypeField = WeblinkReferenceV2025R0TypeField.WEBLINK, + type: WeblinkReferenceV2025R0TypeField = WeblinkReferenceV2025R0TypeField.WEB_LINK, **kwargs ): """ :param id: ID of the web link. :type id: str - :param type: The value will always be `weblink`., defaults to WeblinkReferenceV2025R0TypeField.WEBLINK + :param type: The value will always be `web_link`., defaults to WeblinkReferenceV2025R0TypeField.WEB_LINK :type type: WeblinkReferenceV2025R0TypeField, optional """ super().__init__(**kwargs) diff --git a/docs/archives.md b/docs/archives.md index cdfd47f18..be0bd7892 100644 --- a/docs/archives.md +++ b/docs/archives.md @@ -3,6 +3,7 @@ - [List archives](#list-archives) - [Create archive](#create-archive) - [Delete archive](#delete-archive) +- [Update archive](#update-archive) ## List archives @@ -59,6 +60,10 @@ client.archives.create_archive_v2025_r0(archive_name) - name `str` - The name of the archive. +- description `Optional[str]` + - The description of the archive. +- storage_policy_id `Optional[str]` + - The ID of the storage policy that the archive is assigned to. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` @@ -101,3 +106,35 @@ client.archives.delete_archive_by_id_v2025_r0(archive.id) This function returns a value of type `None`. Returns an empty response when the archive has been deleted. + +## Update archive + +Updates an archive. + +To learn more about the archive APIs, see the [Archive API Guide](g://archives). + +This operation is performed by calling function `update_archive_by_id_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/put-archives-id/). + +_Currently we don't have an example for calling `update_archive_by_id_v2025_r0` in integration tests_ + +### Arguments + +- archive_id `str` + - The ID of the archive. Example: "982312" +- name `Optional[str]` + - The name of the archive. +- description `Optional[str]` + - The description of the archive. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `ArchiveV2025R0`. + +Returns the updated archive object. From fef446018a65d75aaad3a2e3814cfa4544b5c0cd Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 6 Nov 2025 17:37:35 +0100 Subject: [PATCH 049/139] chore: Rename Github spell check job (box/box-codegen#881) (#1177) --- .codegen.json | 2 +- .github/workflows/spell-check-lint.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index c0f97a06b..4430e22d0 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "7c94f4f", "specHash": "8b51a89", "version": "10.0.1" } +{ "engineHash": "0ca4cc8", "specHash": "8b51a89", "version": "10.0.1" } diff --git a/.github/workflows/spell-check-lint.yml b/.github/workflows/spell-check-lint.yml index 6bcfc58e4..8a4ebb0ed 100644 --- a/.github/workflows/spell-check-lint.yml +++ b/.github/workflows/spell-check-lint.yml @@ -5,7 +5,7 @@ on: branches: - sdk-gen jobs: - spellcheck-request: + spellcheck-request-title: runs-on: ubuntu-latest steps: - name: Checkout current repository From 222c043f09ad72ae4db4ffb1b1014c6ba4348751 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:46:59 +0100 Subject: [PATCH 050/139] test: Update archive tests (box/box-codegen#865) (#1187) --- .codegen.json | 2 +- docs/archives.md | 10 ++++++++-- test/archives.py | 13 ++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.codegen.json b/.codegen.json index 4430e22d0..bfc4d8d09 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "0ca4cc8", "specHash": "8b51a89", "version": "10.0.1" } +{ "engineHash": "87a3288", "specHash": "8b51a89", "version": "10.0.1" } diff --git a/docs/archives.md b/docs/archives.md index be0bd7892..76abb278b 100644 --- a/docs/archives.md +++ b/docs/archives.md @@ -53,7 +53,7 @@ See the endpoint docs at ```python -client.archives.create_archive_v2025_r0(archive_name) +client.archives.create_archive_v2025_r0(archive_name, description=archive_description) ``` ### Arguments @@ -118,7 +118,13 @@ This operation is performed by calling function `update_archive_by_id_v2025_r0`. See the endpoint docs at [API Reference](https://developer.box.com/reference/v2025.0/put-archives-id/). -_Currently we don't have an example for calling `update_archive_by_id_v2025_r0` in integration tests_ + + +```python +client.archives.update_archive_by_id_v2025_r0( + archive.id, name=new_archive_name, description=new_archive_description +) +``` ### Arguments diff --git a/test/archives.py b/test/archives.py index 49ffb2d5f..65c2e6ed1 100644 --- a/test/archives.py +++ b/test/archives.py @@ -21,9 +21,20 @@ def testArchivesCreateListDelete(): archive_name: str = get_uuid() - archive: ArchiveV2025R0 = client.archives.create_archive_v2025_r0(archive_name) + archive_description: str = 'Test Archive Description' + archive: ArchiveV2025R0 = client.archives.create_archive_v2025_r0( + archive_name, description=archive_description + ) assert to_string(archive.type) == 'archive' assert archive.name == archive_name + assert archive.description == archive_description + new_archive_name: str = get_uuid() + new_archive_description: str = 'Updated Archive Description' + updated_archive: ArchiveV2025R0 = client.archives.update_archive_by_id_v2025_r0( + archive.id, name=new_archive_name, description=new_archive_description + ) + assert updated_archive.name == new_archive_name + assert updated_archive.description == new_archive_description archives: ArchivesV2025R0 = client.archives.get_archives_v2025_r0(limit=100) assert len(archives.entries) > 0 client.archives.delete_archive_by_id_v2025_r0(archive.id) From 7e4e8787f45a9af1b6f2b8ca3c276f41160ecd27 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:44:42 +0100 Subject: [PATCH 051/139] chore: Adjust permissions for Github token (box/box-codegen#885) (#1193) --- .codegen.json | 2 +- .github/workflows/autoupdate-pr.yml | 2 ++ .github/workflows/build.yml | 4 ++++ .github/workflows/notify-changelog.yml | 2 ++ .github/workflows/semantic-pr.yml | 3 +++ .github/workflows/spell-check-lint.yml | 4 ++++ 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index bfc4d8d09..4d64e9136 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "87a3288", "specHash": "8b51a89", "version": "10.0.1" } +{ "engineHash": "2ac72c4", "specHash": "8b51a89", "version": "10.0.1" } diff --git a/.github/workflows/autoupdate-pr.yml b/.github/workflows/autoupdate-pr.yml index 030e9154c..085213119 100644 --- a/.github/workflows/autoupdate-pr.yml +++ b/.github/workflows/autoupdate-pr.yml @@ -5,6 +5,8 @@ on: - main - sdk-gen +permissions: {} + jobs: update_pull_requests: runs-on: ubuntu-latest diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3312f5637..47b5bf6e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,6 +3,10 @@ on: pull_request: branches: - sdk-gen + +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/notify-changelog.yml b/.github/workflows/notify-changelog.yml index be5f76102..478bc2345 100644 --- a/.github/workflows/notify-changelog.yml +++ b/.github/workflows/notify-changelog.yml @@ -10,6 +10,8 @@ on: types: - released +permissions: {} + jobs: notify: # This job can run on the latest Ubuntu diff --git a/.github/workflows/semantic-pr.yml b/.github/workflows/semantic-pr.yml index de1b2c83c..ee665e0e8 100644 --- a/.github/workflows/semantic-pr.yml +++ b/.github/workflows/semantic-pr.yml @@ -9,6 +9,9 @@ on: branches: - sdk-gen +permissions: + contents: read + jobs: main: name: Validate semantic PR title diff --git a/.github/workflows/spell-check-lint.yml b/.github/workflows/spell-check-lint.yml index 8a4ebb0ed..9aeb468e7 100644 --- a/.github/workflows/spell-check-lint.yml +++ b/.github/workflows/spell-check-lint.yml @@ -4,6 +4,10 @@ on: types: [opened, synchronize, edited] branches: - sdk-gen + +permissions: + contents: read + jobs: spellcheck-request-title: runs-on: ubuntu-latest From a54eee1131af53c3c2759059ec5c31db8ae7269f Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:26:04 +0100 Subject: [PATCH 052/139] docs: Update AI extract structured description with OCR support info (box/box-openapi#564) (#1199) --- .codegen.json | 2 +- box_sdk_gen/managers/ai.py | 8 ++++---- docs/ai.md | 11 +++++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.codegen.json b/.codegen.json index 4d64e9136..47a534f8e 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "2ac72c4", "specHash": "8b51a89", "version": "10.0.1" } +{ "engineHash": "2ac72c4", "specHash": "e7ce024", "version": "10.0.1" } diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index d75d5cbe6..6ac86c88a 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -431,16 +431,16 @@ def create_ai_extract_structured( """ Sends an AI request to supported Large Language Models (LLMs) and returns extracted metadata as a set of key-value pairs. - For this request, you either need a metadata template or a list of fields you want to extract. + To define the extraction structure, provide either a metadata template or a list of fields. To learn more about creating templates, see [Creating metadata templates in the Admin Console](https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates) - Input is **either** a metadata template or a list of fields to ensure the structure. + or use the [metadata template API](g://metadata/templates/create). - To learn more about creating templates, see [Creating metadata templates in the Admin Console](https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates) + This endpoint also supports [Enhanced Extract Agent](g://box-ai/ai-tutorials/extract-metadata-structured/#enhanced-extract-agent). - or use the [metadata template API](g://metadata/templates/create). This endpoint also supports [Enhanced Extract Agent](g://box-ai/ai-tutorials/extract-metadata-structured/#enhanced-extract-agent). + For information about supported file formats and languages, see the [Extract metadata from file (structured)](g://box-ai/ai-tutorials/extract-metadata-structured) API guide. :param items: The items to be processed by the LLM. Currently you can use files only. :type items: List[AiItemBase] diff --git a/docs/ai.md b/docs/ai.md index 93ff72218..acb7e2613 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -188,10 +188,13 @@ A response including the answer from the LLM. ## Extract metadata (structured) Sends an AI request to supported Large Language Models (LLMs) and returns extracted metadata as a set of key-value pairs. -For this request, you either need a metadata template or a list of fields you want to extract. -Input is **either** a metadata template or a list of fields to ensure the structure. -To learn more about creating templates, see [Creating metadata templates in the Admin Console](https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates) -or use the [metadata template API](g://metadata/templates/create). This endpoint also supports [Enhanced Extract Agent](g://box-ai/ai-tutorials/extract-metadata-structured/#enhanced-extract-agent). + +To define the extraction structure, provide either a metadata template or a list of fields. To learn more about creating templates, see [Creating metadata templates in the Admin Console](https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates) +or use the [metadata template API](g://metadata/templates/create). + +This endpoint also supports [Enhanced Extract Agent](g://box-ai/ai-tutorials/extract-metadata-structured/#enhanced-extract-agent). + +For information about supported file formats and languages, see the [Extract metadata from file (structured)](g://box-ai/ai-tutorials/extract-metadata-structured) API guide. This operation is performed by calling function `create_ai_extract_structured`. From 1c6641be81fb9688bb7f2b429cec896bbb6d2af2 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:50:16 +0100 Subject: [PATCH 053/139] test: Fix Box AI tests(box/box-codegen#886) (#1201) --- .codegen.json | 2 +- docs/ai.md | 21 +++++++-------- test/ai.py | 72 ++++++++++++++++----------------------------------- 3 files changed, 34 insertions(+), 61 deletions(-) diff --git a/.codegen.json b/.codegen.json index 47a534f8e..a78a79852 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "2ac72c4", "specHash": "e7ce024", "version": "10.0.1" } +{ "engineHash": "a17cbb6", "specHash": "e7ce024", "version": "10.0.1" } diff --git a/docs/ai.md b/docs/ai.md index acb7e2613..6c1f0686e 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -20,15 +20,15 @@ See the endpoint docs at ```python client.ai.create_ai_ask( CreateAiAskMode.SINGLE_ITEM_QA, - "which direction sun rises", + "Which direction does the Sun rise?", [ AiItemAsk( id=file_to_ask.id, type=AiItemAskTypeField.FILE, - content="Sun rises in the East", + content="The Sun rises in the east", ) ], - ai_agent=ai_ask_agent_config, + ai_agent=ai_ask_agent_basic_text_config, ) ``` @@ -67,27 +67,26 @@ See the endpoint docs at ```python client.ai.create_ai_text_gen( - "Parapharse the document.s", + "Paraphrase the documents", [ CreateAiTextGenItems( id=file_to_ask.id, type=CreateAiTextGenItemsTypeField.FILE, - content="The Earth goes around the sun. Sun rises in the East in the morning.", + content="The Earth goes around the Sun. The Sun rises in the east in the morning.", ) ], dialogue_history=[ AiDialogueHistory( prompt="What does the earth go around?", - answer="The sun", + answer="The Sun", created_at=date_time_from_string("2021-01-01T00:00:00Z"), ), AiDialogueHistory( - prompt="On Earth, where does the sun rise?", - answer="East", + prompt="On Earth, where does the Sun rise?", + answer="east", created_at=date_time_from_string("2021-01-01T00:00:00Z"), ), ], - ai_agent=ai_text_gen_agent_config, ) ``` @@ -165,7 +164,7 @@ See the endpoint docs at client.ai.create_ai_extract( "firstName, lastName, location, yearOfBirth, company", [AiItemBase(id=file.id)], - ai_agent=agent_ignoring_overriding_embeddings_model, + ai_agent=ai_extract_agent_basic_text_config, ) ``` @@ -247,7 +246,7 @@ client.ai.create_ai_extract_structured( ], ), ], - ai_agent=agent_ignoring_overriding_embeddings_model, + ai_agent=ai_extract_structured_agent_basic_text_config, ) ``` diff --git a/test/ai.py b/test/ai.py index d982ee16c..1658d4e6c 100644 --- a/test/ai.py +++ b/test/ai.py @@ -66,14 +66,8 @@ from box_sdk_gen.internal.utils import delay_in_seconds -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 box_sdk_gen.internal.utils import get_value_from_object_raw_data - from test.commons import upload_new_file from box_sdk_gen.schemas.ai_agent_ask import AiAgentAsk @@ -84,8 +78,6 @@ from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured -from box_sdk_gen.schemas.ai_agent_long_text_tool import AiAgentLongTextTool - client: BoxClient = get_default_client() @@ -94,20 +86,23 @@ def testAskAISingleItem(): GetAiAgentDefaultConfigMode.ASK, language='en-US' ) ai_ask_agent_config: AiAgentAsk = ai_agent_config + ai_ask_agent_basic_text_config: AiAgentAsk = AiAgentAsk( + basic_text=ai_ask_agent_config.basic_text + ) file_to_ask: FileFull = upload_new_file() response: Optional[AiResponseFull] = client.ai.create_ai_ask( CreateAiAskMode.SINGLE_ITEM_QA, - 'which direction sun rises', + 'Which direction does the Sun rise?', [ AiItemAsk( id=file_to_ask.id, type=AiItemAskTypeField.FILE, - content='Sun rises in the East', + content='The Sun rises in the east', ) ], - ai_agent=ai_ask_agent_config, + ai_agent=ai_ask_agent_basic_text_config, ) - assert 'East' in response.answer + assert 'east' in response.answer assert response.completion_reason == 'done' client.files.delete_file_by_id(file_to_ask.id) @@ -117,21 +112,21 @@ def testAskAIMultipleItems(): file_to_ask_2: FileFull = upload_new_file() response: Optional[AiResponseFull] = client.ai.create_ai_ask( CreateAiAskMode.MULTIPLE_ITEM_QA, - 'Which direction sun rises?', + 'Which direction does the Sun rise?', [ AiItemAsk( id=file_to_ask_1.id, type=AiItemAskTypeField.FILE, - content='Earth goes around the sun', + content='Earth goes around the Sun', ), AiItemAsk( id=file_to_ask_2.id, type=AiItemAskTypeField.FILE, - content='Sun rises in the East in the morning', + content='The Sun rises in the east in the morning', ), ], ) - assert 'East' in response.answer + 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) @@ -139,34 +134,29 @@ def testAskAIMultipleItems(): def testAITextGenWithDialogueHistory(): file_to_ask: FileFull = upload_new_file() - ai_agent_config: AiAgent = client.ai.get_ai_agent_default_config( - GetAiAgentDefaultConfigMode.TEXT_GEN, language='en-US' - ) - ai_text_gen_agent_config: AiAgentTextGen = ai_agent_config response: AiResponse = client.ai.create_ai_text_gen( - 'Parapharse the document.s', + 'Paraphrase the documents', [ CreateAiTextGenItems( id=file_to_ask.id, type=CreateAiTextGenItemsTypeField.FILE, - content='The Earth goes around the sun. Sun rises in the East in the morning.', + content='The Earth goes around the Sun. The Sun rises in the east in the morning.', ) ], dialogue_history=[ AiDialogueHistory( prompt='What does the earth go around?', - answer='The sun', + answer='The Sun', created_at=date_time_from_string('2021-01-01T00:00:00Z'), ), AiDialogueHistory( - prompt='On Earth, where does the sun rise?', - answer='East', + prompt='On Earth, where does the Sun rise?', + answer='east', created_at=date_time_from_string('2021-01-01T00:00:00Z'), ), ], - ai_agent=ai_text_gen_agent_config, ) - assert 'sun' in response.answer + assert 'Sun' in response.answer assert response.completion_reason == 'done' client.files.delete_file_by_id(file_to_ask.id) @@ -219,16 +209,8 @@ def testAIExtract(): GetAiAgentDefaultConfigMode.EXTRACT, language='en-US' ) ai_extract_agent_config: AiAgentExtract = ai_agent_config - long_text_config_with_no_embeddings: AiAgentLongTextTool = AiAgentLongTextTool( - system_message=ai_extract_agent_config.long_text.system_message, - prompt_template=ai_extract_agent_config.long_text.prompt_template, - model=ai_extract_agent_config.long_text.model, - num_tokens_for_completion=ai_extract_agent_config.long_text.num_tokens_for_completion, - llm_endpoint_params=ai_extract_agent_config.long_text.llm_endpoint_params, - ) - agent_ignoring_overriding_embeddings_model: AiAgentExtract = AiAgentExtract( - basic_text=ai_extract_agent_config.basic_text, - long_text=long_text_config_with_no_embeddings, + ai_extract_agent_basic_text_config: AiAgentExtract = AiAgentExtract( + basic_text=ai_extract_agent_config.basic_text ) uploaded_files: Files = client.uploads.upload_file( UploadFileAttributes( @@ -244,7 +226,7 @@ def testAIExtract(): response: AiResponse = client.ai.create_ai_extract( 'firstName, lastName, location, yearOfBirth, company', [AiItemBase(id=file.id)], - ai_agent=agent_ignoring_overriding_embeddings_model, + ai_agent=ai_extract_agent_basic_text_config, ) expected_response: str = ( '{"firstName": "John", "lastName": "Doe", "location": "San Francisco", "yearOfBirth": "1990", "company": "Box"}' @@ -259,17 +241,9 @@ def testAIExtractStructuredWithFields(): GetAiAgentDefaultConfigMode.EXTRACT_STRUCTURED, language='en-US' ) ai_extract_structured_agent_config: AiAgentExtractStructured = ai_agent_config - long_text_config_with_no_embeddings: AiAgentLongTextTool = AiAgentLongTextTool( - system_message=ai_extract_structured_agent_config.long_text.system_message, - prompt_template=ai_extract_structured_agent_config.long_text.prompt_template, - model=ai_extract_structured_agent_config.long_text.model, - num_tokens_for_completion=ai_extract_structured_agent_config.long_text.num_tokens_for_completion, - llm_endpoint_params=ai_extract_structured_agent_config.long_text.llm_endpoint_params, - ) - agent_ignoring_overriding_embeddings_model: AiAgentExtractStructured = ( + ai_extract_structured_agent_basic_text_config: AiAgentExtractStructured = ( AiAgentExtractStructured( - basic_text=ai_extract_structured_agent_config.basic_text, - long_text=long_text_config_with_no_embeddings, + basic_text=ai_extract_structured_agent_config.basic_text ) ) uploaded_files: Files = client.uploads.upload_file( @@ -326,7 +300,7 @@ def testAIExtractStructuredWithFields(): ], ), ], - ai_agent=agent_ignoring_overriding_embeddings_model, + ai_agent=ai_extract_structured_agent_basic_text_config, ) assert to_string(response.answer.get('hobby')) == to_string(['guitar']) assert to_string(response.answer.get('firstName')) == 'John' From a825bedf6603f94110c09da32d5f3c89c05adb59 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 18 Nov 2025 19:08:44 +0100 Subject: [PATCH 054/139] test: Improve Box AI Studio test (box/box-codegen#887) (#1203) --- .codegen.json | 2 +- test/ai_studio.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.codegen.json b/.codegen.json index a78a79852..ca4d36feb 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "a17cbb6", "specHash": "e7ce024", "version": "10.0.1" } +{ "engineHash": "35c5d34", "specHash": "e7ce024", "version": "10.0.1" } diff --git a/test/ai_studio.py b/test/ai_studio.py index 793965a1b..8c690f919 100644 --- a/test/ai_studio.py +++ b/test/ai_studio.py @@ -75,17 +75,17 @@ def testUseAIAgentReferenceInAIAsk(): file_to_ask: FileFull = upload_new_file() response: Optional[AiResponseFull] = client.ai.create_ai_ask( CreateAiAskMode.SINGLE_ITEM_QA, - 'which direction sun rises', + 'Which direction does the Sun rise?', [ AiItemAsk( id=file_to_ask.id, type=AiItemAskTypeField.FILE, - content='Sun rises in the East', + content='The Sun rises in the east.', ) ], ai_agent=AiAgentReference(id=created_agent.id), ) - assert 'East' in response.answer + assert 'east' in response.answer assert response.completion_reason == 'done' assert len(response.ai_agent_info.models) > 0 client.files.delete_file_by_id(file_to_ask.id) From 2c21df9df5530faf8a130e4b2b9a3d75d7d23161 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:30:17 +0100 Subject: [PATCH 055/139] chore: release version 10.1.0 (#1205) --- .codegen.json | 2 +- CHANGELOG.md | 8 ++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index ca4d36feb..11f973337 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "35c5d34", "specHash": "e7ce024", "version": "10.0.1" } +{ "engineHash": "35c5d34", "specHash": "e7ce024", "version": "10.1.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d47bb73c2..34ed4fbde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.1.0](https://github.com/box/box-python-sdk/compare/v10.0.1...v10.1.0) (2025-11-19) + + +### New Features and Enhancements + +* Support GET enterprise configuration API (box/box-openapi[#559](https://github.com/box/box-python-sdk/issues/559)) ([#1134](https://github.com/box/box-python-sdk/issues/1134)) ([1106d32](https://github.com/box/box-python-sdk/commit/1106d325973df9704f5102538ac0130bda6e9c38)) +* Support Archive API (box/box-openapi[#563](https://github.com/box/box-python-sdk/issues/563)) ([#1174](https://github.com/box/box-python-sdk/issues/1174)) ([d21ec4d](https://github.com/box/box-python-sdk/commit/d21ec4df82ab43539ff07d306cf304f86bbb0593)) + ### [10.0.1](https://github.com/box/box-python-sdk/compare/v10.0.0...v10.0.1) (2025-10-06) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 540d81da6..a06cd470e 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.0.1' +__version__ = '10.1.0' From ab6b1040f206a6b6cecc80b4168b74a703c4bbcb Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:37:21 +0100 Subject: [PATCH 056/139] docs: Update Readme for migrating `sdk-gen` to main (box/box-codegen#889) (#1207) --- .codegen.json | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/workflows/build-and-test-daily.yml | 107 +++++++++++++++++++++ .github/workflows/build.yml | 2 +- .github/workflows/semantic-pr.yml | 2 +- .github/workflows/spell-check-lint.yml | 2 +- README.md | 10 +- 7 files changed, 117 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/build-and-test-daily.yml diff --git a/.codegen.json b/.codegen.json index 11f973337..8e2d5e9da 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "35c5d34", "specHash": "e7ce024", "version": "10.1.0" } +{ "engineHash": "4147cc3", "specHash": "e7ce024", "version": "10.1.0" } diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 246e04877..7c4ec7317 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: false contact_links: - name: SDK documentation - url: https://github.com/box/box-python-sdk/tree/sdk-gen/docs + url: https://github.com/box/box-python-sdk/tree/main/docs about: Before creating an issue, I have checked that the SDK documentation doesn't solve my issue. - name: API documentation url: https://developer.box.com/docs diff --git a/.github/workflows/build-and-test-daily.yml b/.github/workflows/build-and-test-daily.yml new file mode 100644 index 000000000..2e8294281 --- /dev/null +++ b/.github/workflows/build-and-test-daily.yml @@ -0,0 +1,107 @@ +name: Build and Test daily +on: + schedule: + - cron: '20 2 * * 1-5' +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 1 + matrix: + python-version: + - '3.8' + - '3.11' + - '3.13' + name: Build with Python ${{ matrix.python-version }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: main + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e .[dev] + python -m pip install tox-gh-actions + - name: All Tests + env: + JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} + ADMIN_USER_ID: ${{ secrets.ADMIN_USER_ID }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + USER_ID: ${{ secrets.USER_ID }} + ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }} + BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }} + BOX_EXTERNAL_USER_EMAIL: ${{ secrets.BOX_EXTERNAL_USER_EMAIL }} + BOX_EXTERNAL_USER_ID: ${{ secrets.BOX_EXTERNAL_USER_ID }} + APP_ITEM_ASSOCIATION_FILE_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FILE_ID }} + APP_ITEM_ASSOCIATION_FOLDER_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FOLDER_ID }} + WORKFLOW_FOLDER_ID: ${{ secrets.WORKFLOW_FOLDER_ID }} + APP_ITEM_SHARED_LINK: ${{ secrets.APP_ITEM_SHARED_LINK }} + SLACK_AUTOMATION_USER_ID: ${{ secrets.SLACK_AUTOMATION_USER_ID }} + SLACK_ORG_ID: ${{ secrets.SLACK_ORG_ID }} + SLACK_PARTNER_ITEM_ID: ${{ secrets.SLACK_PARTNER_ITEM_ID }} + run: | + tox + coverage: + name: Coverage report + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: main + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e .[dev] + python -m pip install coveralls + - name: Send coverage report to Coveralls + run: | + tox -e coverage + coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} + ADMIN_USER_ID: ${{ secrets.ADMIN_USER_ID }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + USER_ID: ${{ secrets.USER_ID }} + ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }} + BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }} + BOX_EXTERNAL_USER_EMAIL: ${{ secrets.BOX_EXTERNAL_USER_EMAIL }} + WORKFLOW_FOLDER_ID: ${{ secrets.WORKFLOW_FOLDER_ID }} + APP_ITEM_ASSOCIATION_FILE_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FILE_ID }} + APP_ITEM_ASSOCIATION_FOLDER_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FOLDER_ID }} + APP_ITEM_SHARED_LINK: ${{ secrets.APP_ITEM_SHARED_LINK }} + SLACK_AUTOMATION_USER_ID: ${{ secrets.SLACK_AUTOMATION_USER_ID }} + SLACK_ORG_ID: ${{ secrets.SLACK_ORG_ID }} + SLACK_PARTNER_ITEM_ID: ${{ secrets.SLACK_PARTNER_ITEM_ID }} + notify: + name: Send Slack Notification + runs-on: ubuntu-latest + needs: [build, coverage] + if: always() + steps: + - name: Send Slack Notification + env: + SLACK_WEBHOOK_TEST_NOTIFICATION_URL: ${{ secrets.SLACK_WEBHOOK_TEST_NOTIFICATION_URL }} + BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + if [ "${{ needs.build.result }}" != "success" ] || [ "${{ needs.coverage.result }}" != "success" ]; then + STATUS="Failure ❌" + else + STATUS="Success ✅" + fi + + curl -X POST -H "Content-Type: application/json" \ + --data "{\"text\":\"<${BUILD_URL}|Daily Tests Job> in *${GITHUB_REPOSITORY}* finished with status: ${STATUS}\"}" \ + "$SLACK_WEBHOOK_TEST_NOTIFICATION_URL" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 47b5bf6e4..cf5460695 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build on: pull_request: branches: - - sdk-gen + - main permissions: contents: read diff --git a/.github/workflows/semantic-pr.yml b/.github/workflows/semantic-pr.yml index ee665e0e8..1bf079c95 100644 --- a/.github/workflows/semantic-pr.yml +++ b/.github/workflows/semantic-pr.yml @@ -7,7 +7,7 @@ on: - edited - synchronize branches: - - sdk-gen + - main permissions: contents: read diff --git a/.github/workflows/spell-check-lint.yml b/.github/workflows/spell-check-lint.yml index 9aeb468e7..1adbdeb74 100644 --- a/.github/workflows/spell-check-lint.yml +++ b/.github/workflows/spell-check-lint.yml @@ -3,7 +3,7 @@ on: pull_request_target: types: [opened, synchronize, edited] branches: - - sdk-gen + - main permissions: contents: read diff --git a/README.md b/README.md index 0f991d473..737d68388 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ # Box Python SDK v10 [![Project Status](http://opensource.box.com/badges/active.svg)](http://opensource.box.com/badges) -![build](https://github.com/box/box-python-sdk/actions/workflows/build.yml/badge.svg?branch=sdk-gen) +![build](https://github.com/box/box-python-sdk/actions/workflows/build.yml/badge.svg?branch=main) [![PyPI version](https://badge.fury.io/py/boxsdk.svg)](https://badge.fury.io/py/boxsdk) [![image](https://img.shields.io/pypi/dm/boxsdk.svg)](https://pypi.python.org/pypi/boxsdk) ![Platform](https://img.shields.io/badge/python-3.8+-blue) -[![Coverage](https://coveralls.io/repos/github/box/box-python-sdk/badge.svg?branch=sdk-gen)](https://coveralls.io/github/box/box-python-sdk?branch=sdk-gen) +[![Coverage](https://coveralls.io/repos/github/box/box-python-sdk/badge.svg?branch=main)](https://coveralls.io/github/box/box-python-sdk?branch=main) @@ -69,7 +69,7 @@ Version v4 is intended for: ## Version v10 Starting with v10, the SDK is built entirely on the generated `box_sdk_gen` package, which fully and exclusively replaces the old `boxsdk` package. -The codebase for v10 of the Box Python SDK is currently available on the [sdk-gen](https://github.com/box/box-python-sdk/tree/sdk-gen) branch. +The codebase for v10 of the Box Python SDK is currently available on the [main](https://github.com/box/box-python-sdk/tree/main) branch. Version v10 is intended for: @@ -80,8 +80,8 @@ Version v10 is intended for: | Scenario | Recommended Version | Example `pip install` | | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------- | -| Creating a new application | Use [v10](https://github.com/box/box-python-sdk/tree/sdk-gen) | `pip install "boxsdk>=10"` | -| App using [box-sdk-gen](https://pypi.org/project/box-sdk-gen/) artifact | Migrate to [v10](https://github.com/box/box-python-sdk/tree/sdk-gen) | `pip install "boxsdk>=10"` | +| Creating a new application | Use [v10](https://github.com/box/box-python-sdk/tree/main) | `pip install "boxsdk>=10"` | +| App using [box-sdk-gen](https://pypi.org/project/box-sdk-gen/) artifact | Migrate to [v10](https://github.com/box/box-python-sdk/tree/main) | `pip install "boxsdk>=10"` | | App using both [box-sdk-gen](https://pypi.org/project/box-sdk-gen/) and [boxsdk](https://pypi.org/project/boxsdk/) artifacts | Upgrade to [v4](https://github.com/box/box-python-sdk/tree/combined-sdk) | `pip install "boxsdk~=4.0"` | | App using v3 of [boxsdk](https://pypi.org/project/boxsdk/) artifact | Upgrade to [v4](https://github.com/box/box-python-sdk/tree/combined-sdk) | `pip install "boxsdk~=4.0"` | From ed27cd6f665a4471efc28266fb7a5ddf70b515bc Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:22:50 +0100 Subject: [PATCH 057/139] chore: Run integration tests in parallel (box/box-codegen#888) (#1211) --- .codegen.json | 2 +- .github/workflows/build.yml | 1 - setup.py | 8 +++++++- tox.ini | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.codegen.json b/.codegen.json index 8e2d5e9da..18b283643 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "4147cc3", "specHash": "e7ce024", "version": "10.1.0" } +{ "engineHash": "2fb2a6c", "specHash": "e7ce024", "version": "10.1.0" } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf5460695..2755672f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,6 @@ jobs: matrix: python-version: - '3.8' - - '3.11' - '3.13' name: Build with Python ${{ matrix.python-version }} steps: diff --git a/setup.py b/setup.py index 6dc91bfcc..1f89d3046 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,13 @@ def main(): install_requires = ['requests', 'requests-toolbelt'] - tests_require = ['pytest', 'pytest-timeout', 'pytest-cov', 'pytest-rerunfailures'] + tests_require = [ + 'pytest', + 'pytest-timeout', + 'pytest-cov', + 'pytest-rerunfailures', + 'pytest-xdist', + ] dev_requires = ['tox'] jwt_requires = ['pyjwt>=1.7.0', 'cryptography>=3'] version_file = open(join(dirname(__file__), 'box_sdk_gen/networking/version.py')) diff --git a/tox.ini b/tox.ini index 18a3da4b3..6e05d509f 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ envlist = [testenv] commands = - pytest {posargs} --disable-pytest-warnings --reruns 2 + pytest -n 5 {posargs} --disable-pytest-warnings --reruns 2 deps = -rrequirements-test.txt allowlist_externals = pytest passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,BOX_EXTERNAL_USER_ID,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID From 7b801e984c1efae07cf48c5f54eddd2841dedcb7 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:19:02 +0100 Subject: [PATCH 058/139] chore: Add missing environment variable to CI (box/box-codegen#891) (#1218) --- .codegen.json | 2 +- .github/workflows/build-and-test-daily.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 18b283643..def051524 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "2fb2a6c", "specHash": "e7ce024", "version": "10.1.0" } +{ "engineHash": "b311df0", "specHash": "e7ce024", "version": "10.1.0" } diff --git a/.github/workflows/build-and-test-daily.yml b/.github/workflows/build-and-test-daily.yml index 2e8294281..ee2d35182 100644 --- a/.github/workflows/build-and-test-daily.yml +++ b/.github/workflows/build-and-test-daily.yml @@ -78,9 +78,10 @@ jobs: ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }} BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }} BOX_EXTERNAL_USER_EMAIL: ${{ secrets.BOX_EXTERNAL_USER_EMAIL }} - WORKFLOW_FOLDER_ID: ${{ secrets.WORKFLOW_FOLDER_ID }} + BOX_EXTERNAL_USER_ID: ${{ secrets.BOX_EXTERNAL_USER_ID }} APP_ITEM_ASSOCIATION_FILE_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FILE_ID }} APP_ITEM_ASSOCIATION_FOLDER_ID: ${{ secrets.APP_ITEM_ASSOCIATION_FOLDER_ID }} + WORKFLOW_FOLDER_ID: ${{ secrets.WORKFLOW_FOLDER_ID }} APP_ITEM_SHARED_LINK: ${{ secrets.APP_ITEM_SHARED_LINK }} SLACK_AUTOMATION_USER_ID: ${{ secrets.SLACK_AUTOMATION_USER_ID }} SLACK_ORG_ID: ${{ secrets.SLACK_ORG_ID }} From 6c3d3325a19b3217225a80c2cd5d15c7bb068494 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:21:11 +0100 Subject: [PATCH 059/139] feat: Support new sign request metadata (box/box-openapi#565) (#1222) --- .codegen.json | 2 +- box_sdk_gen/schemas/__init__.py | 6 ++++-- box_sdk_gen/schemas/sign_request.py | 12 +++++++++++ box_sdk_gen/schemas/sign_request_signer.py | 8 ++++++++ .../schemas/sign_request_signer_attachment.py | 20 +++++++++++++++++++ 5 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 box_sdk_gen/schemas/sign_request_signer_attachment.py diff --git a/.codegen.json b/.codegen.json index def051524..30912e44a 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "b311df0", "specHash": "e7ce024", "version": "10.1.0" } +{ "engineHash": "49f3695", "specHash": "7e4908e", "version": "10.1.0" } diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index 1306426d7..641892bf7 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -274,12 +274,14 @@ from box_sdk_gen.schemas.sign_request_signer_input import * -from box_sdk_gen.schemas.sign_request_signer import * - from box_sdk_gen.schemas.sign_request_base import * from box_sdk_gen.schemas.sign_request_create_request import * +from box_sdk_gen.schemas.sign_request_signer_attachment import * + +from box_sdk_gen.schemas.sign_request_signer import * + from box_sdk_gen.schemas.sign_request import * from box_sdk_gen.schemas.sign_requests import * diff --git a/box_sdk_gen/schemas/sign_request.py b/box_sdk_gen/schemas/sign_request.py index 16d904d1c..d07c8ec41 100644 --- a/box_sdk_gen/schemas/sign_request.py +++ b/box_sdk_gen/schemas/sign_request.py @@ -79,6 +79,9 @@ def __init__( auto_expire_at: Optional[DateTime] = None, parent_folder: Optional[FolderMini] = None, collaborator_level: Optional[str] = None, + short_id: Optional[str] = None, + created_at: Optional[DateTime] = None, + finished_at: Optional[DateTime] = None, sender_email: Optional[str] = None, sender_id: Optional[int] = None, is_document_preparation_needed: Optional[bool] = None, @@ -124,6 +127,12 @@ def __init__( :type auto_expire_at: Optional[DateTime], optional :param collaborator_level: The collaborator level of the user to the sign request. Values can include "owner", "editor", and "viewer"., defaults to None :type collaborator_level: Optional[str], optional + :param short_id: Short identifier for the sign request., defaults to None + :type short_id: Optional[str], optional + :param created_at: Timestamp marking when the sign request was created., defaults to None + :type created_at: Optional[DateTime], optional + :param finished_at: Timestamp indicating when all signing actions completed., defaults to None + :type finished_at: Optional[DateTime], optional :param sender_email: The email address of the sender of the sign request., defaults to None :type sender_email: Optional[str], optional :param sender_id: The user ID of the sender of the sign request., defaults to None @@ -183,5 +192,8 @@ def __init__( self.auto_expire_at = auto_expire_at self.parent_folder = parent_folder self.collaborator_level = collaborator_level + self.short_id = short_id + self.created_at = created_at + self.finished_at = finished_at self.sender_email = sender_email self.sender_id = sender_id diff --git a/box_sdk_gen/schemas/sign_request_signer.py b/box_sdk_gen/schemas/sign_request_signer.py index e9575f17e..21e927bf9 100644 --- a/box_sdk_gen/schemas/sign_request_signer.py +++ b/box_sdk_gen/schemas/sign_request_signer.py @@ -14,6 +14,10 @@ from box_sdk_gen.schemas.sign_request_signer_input import SignRequestSignerInput +from box_sdk_gen.schemas.sign_request_signer_attachment import ( + SignRequestSignerAttachment, +) + from box_sdk_gen.box.errors import BoxSDKError from box_sdk_gen.internal.utils import DateTime @@ -58,6 +62,7 @@ def __init__( inputs: Optional[List[SignRequestSignerInput]] = None, embed_url: Optional[str] = None, iframeable_embed_url: Optional[str] = None, + attachments: Optional[List[SignRequestSignerAttachment]] = None, email: Optional[str] = None, role: Optional[SignRequestCreateSignerRoleField] = None, is_in_person: Optional[bool] = None, @@ -86,6 +91,8 @@ def __init__( parameter was passed in the `create Box Sign request` call., defaults to None :type iframeable_embed_url: Optional[str], optional + :param attachments: Attachments that the signer uploaded., defaults to None + :type attachments: Optional[List[SignRequestSignerAttachment]], optional :param email: Email address of the signer. The email address of the signer is required when making signature requests, except when using templates that are configured to include emails., defaults to None :type email: Optional[str], optional @@ -157,3 +164,4 @@ def __init__( self.inputs = inputs self.embed_url = embed_url self.iframeable_embed_url = iframeable_embed_url + self.attachments = attachments diff --git a/box_sdk_gen/schemas/sign_request_signer_attachment.py b/box_sdk_gen/schemas/sign_request_signer_attachment.py new file mode 100644 index 000000000..d1ba83bea --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_attachment.py @@ -0,0 +1,20 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerAttachment(BaseObject): + def __init__( + self, *, id: Optional[str] = None, name: Optional[str] = None, **kwargs + ): + """ + :param id: Identifier of the attachment file., defaults to None + :type id: Optional[str], optional + :param name: Display name of the attachment file., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.name = name From 1bf296177f4a9ae4e4ba2a608315be554d61e051 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 3 Dec 2025 12:36:15 +0100 Subject: [PATCH 060/139] docs: modify `can_view_path` description and add confidence scores for structured extract (box/box-openapi#566) (#1224) --- .codegen.json | 2 +- box_sdk_gen/managers/ai.py | 4 ++++ box_sdk_gen/managers/user_collaborations.py | 8 ++++---- box_sdk_gen/schemas/ai_extract_structured.py | 4 ++++ box_sdk_gen/schemas/ai_extract_structured_response.py | 4 ++++ docs/ai.md | 2 ++ docs/user_collaborations.md | 4 ++-- 7 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.codegen.json b/.codegen.json index 30912e44a..1f97c68c5 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "49f3695", "specHash": "7e4908e", "version": "10.1.0" } +{ "engineHash": "49f3695", "specHash": "5183b65", "version": "10.1.0" } diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index 6ac86c88a..9998d9f1d 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -425,6 +425,7 @@ def create_ai_extract_structured( *, metadata_template: Optional[CreateAiExtractStructuredMetadataTemplate] = None, fields: Optional[List[CreateAiExtractStructuredFields]] = None, + include_confidence_score: Optional[bool] = None, ai_agent: Optional[AiExtractStructuredAgent] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> AiExtractStructuredResponse: @@ -450,6 +451,8 @@ def create_ai_extract_structured( :param fields: The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both., defaults to None :type fields: Optional[List[CreateAiExtractStructuredFields]], optional + :param include_confidence_score: A flag to indicate whether confidence scores for every extracted field should be returned., defaults to None + :type include_confidence_score: Optional[bool], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ @@ -459,6 +462,7 @@ def create_ai_extract_structured( 'items': items, 'metadata_template': metadata_template, 'fields': fields, + 'include_confidence_score': include_confidence_score, 'ai_agent': ai_agent, } headers_map: Dict[str, str] = prepare_params({**extra_headers}) diff --git a/box_sdk_gen/managers/user_collaborations.py b/box_sdk_gen/managers/user_collaborations.py index 5b6b0e345..bd6e9c517 100644 --- a/box_sdk_gen/managers/user_collaborations.py +++ b/box_sdk_gen/managers/user_collaborations.py @@ -241,8 +241,8 @@ def update_collaboration_by_id( invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. - Only owner or co-owners can invite collaborators with a `can_view_path` of - `true`. + Only an owner or co-owners can invite collaborators with a `can_view_path` of + `true`. Only an owner can update `can_view_path` on existing collaborations. `can_view_path` can only be used for folder collaborations., defaults to None :type can_view_path: Optional[bool], optional @@ -381,8 +381,8 @@ def create_collaboration( invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. - Only owner or co-owners can invite collaborators with a `can_view_path` of - `true`. + Only an owner or co-owners can invite collaborators with a `can_view_path` of + `true`. Only an owner can update `can_view_path` on existing collaborations. `can_view_path` can only be used for folder collaborations., defaults to None :type can_view_path: Optional[bool], optional diff --git a/box_sdk_gen/schemas/ai_extract_structured.py b/box_sdk_gen/schemas/ai_extract_structured.py index 5a742b529..172e90003 100644 --- a/box_sdk_gen/schemas/ai_extract_structured.py +++ b/box_sdk_gen/schemas/ai_extract_structured.py @@ -114,6 +114,7 @@ def __init__( *, metadata_template: Optional[AiExtractStructuredMetadataTemplateField] = None, fields: Optional[List[AiExtractStructuredFieldsField]] = None, + include_confidence_score: Optional[bool] = None, ai_agent: Optional[AiExtractStructuredAgent] = None, **kwargs ): @@ -126,9 +127,12 @@ def __init__( :param fields: The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both., defaults to None :type fields: Optional[List[AiExtractStructuredFieldsField]], optional + :param include_confidence_score: A flag to indicate whether confidence scores for every extracted field should be returned., defaults to None + :type include_confidence_score: Optional[bool], optional """ super().__init__(**kwargs) self.items = items self.metadata_template = metadata_template self.fields = fields + self.include_confidence_score = include_confidence_score self.ai_agent = ai_agent diff --git a/box_sdk_gen/schemas/ai_extract_structured_response.py b/box_sdk_gen/schemas/ai_extract_structured_response.py index 76ae79525..5c5e7dc05 100644 --- a/box_sdk_gen/schemas/ai_extract_structured_response.py +++ b/box_sdk_gen/schemas/ai_extract_structured_response.py @@ -18,6 +18,7 @@ def __init__( created_at: DateTime, *, completion_reason: Optional[str] = None, + confidence_score: Optional[Dict] = None, ai_agent_info: Optional[AiAgentInfo] = None, **kwargs ): @@ -26,9 +27,12 @@ def __init__( :type created_at: DateTime :param completion_reason: The reason the response finishes., defaults to None :type completion_reason: Optional[str], optional + :param confidence_score: The confidence score numeric values for each extracted field as a JSON dictionary. This can be empty if no field could be extracted., defaults to None + :type confidence_score: Optional[Dict], optional """ super().__init__(**kwargs) self.answer = answer self.created_at = created_at self.completion_reason = completion_reason + self.confidence_score = confidence_score self.ai_agent_info = ai_agent_info diff --git a/docs/ai.md b/docs/ai.md index 6c1f0686e..8b1dcf7f7 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -258,6 +258,8 @@ client.ai.create_ai_extract_structured( - The metadata template containing the fields to extract. For your request to work, you must provide either `metadata_template` or `fields`, but not both. - fields `Optional[List[CreateAiExtractStructuredFields]]` - The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both. +- include_confidence_score `Optional[bool]` + - A flag to indicate whether confidence scores for every extracted field should be returned. - ai_agent `Optional[AiExtractStructuredAgent]` - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/user_collaborations.md b/docs/user_collaborations.md index 193e6b2e6..22de67213 100644 --- a/docs/user_collaborations.md +++ b/docs/user_collaborations.md @@ -65,7 +65,7 @@ client.user_collaborations.update_collaboration_by_id( - expires_at `Optional[DateTime]` - Update the expiration date for the collaboration. At this date, the collaboration will be automatically removed from the item. This feature will only work if the **Automatically remove invited collaborators: Allow folder owners to extend the expiry date** setting has been enabled in the **Enterprise Settings** of the **Admin Console**. When the setting is not enabled, collaborations can not have an expiry date and a value for this field will be result in an error. Additionally, a collaboration can only be given an expiration if it was created after the **Automatically remove invited collaborator** setting was enabled. - can_view_path `Optional[bool]` - - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only owner or co-owners can invite collaborators with a `can_view_path` of `true`. `can_view_path` can only be used for folder collaborations. + - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only an owner or co-owners can invite collaborators with a `can_view_path` of `true`. Only an owner can update `can_view_path` on existing collaborations. `can_view_path` can only be used for folder collaborations. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -152,7 +152,7 @@ client.user_collaborations.create_collaboration( - is_access_only `Optional[bool]` - If set to `true`, collaborators have access to shared items, but such items won't be visible in the All Files list. Additionally, collaborators won't see the path to the root folder for the shared item. - can_view_path `Optional[bool]` - - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only owner or co-owners can invite collaborators with a `can_view_path` of `true`. `can_view_path` can only be used for folder collaborations. + - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only an owner or co-owners can invite collaborators with a `can_view_path` of `true`. Only an owner can update `can_view_path` on existing collaborations. `can_view_path` can only be used for folder collaborations. - expires_at `Optional[DateTime]` - Set the expiration date for the collaboration. At this date, the collaboration will be automatically removed from the item. This feature will only work if the **Automatically remove invited collaborators: Allow folder owners to extend the expiry date** setting has been enabled in the **Enterprise Settings** of the **Admin Console**. When the setting is not enabled, collaborations can not have an expiry date and a value for this field will be result in an error. - fields `Optional[List[str]]` From 66674fce0e8a5f7383fa5bf3e4a6a53ab8fbeb4c Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:13:56 +0100 Subject: [PATCH 061/139] refactor: Eliminate redundant Optional type hint (box/box-codegen#898) (#1235) --- .codegen.json | 2 +- .../schemas/v2025_r0/doc_gen_job_full_v2025_r0.py | 4 ++-- box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py | 4 ++-- .../enterprise_configuration_security_v2025_r0.py | 4 ++-- .../v2025_r0/enterprise_configuration_v2025_r0.py | 10 ++++------ 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.codegen.json b/.codegen.json index 1f97c68c5..bb0528c0b 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "49f3695", "specHash": "5183b65", "version": "10.1.0" } +{ "engineHash": "3d9d391", "specHash": "5183b65", "version": "10.1.0" } diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py index 9debfb02f..003e192b2 100644 --- a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py @@ -45,8 +45,8 @@ def __init__( id: str, *, created_at: Optional[str] = None, - output_file: Optional[Optional[FileReferenceV2025R0]] = None, - output_file_version: Optional[Optional[FileVersionBaseV2025R0]] = None, + output_file: Optional[FileReferenceV2025R0] = None, + output_file_version: Optional[FileVersionBaseV2025R0] = None, type: DocGenJobBaseV2025R0TypeField = DocGenJobBaseV2025R0TypeField.DOCGEN_JOB, **kwargs ): diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py index 43e37b347..dd1134d17 100644 --- a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py @@ -39,8 +39,8 @@ def __init__( output_type: str, id: str, *, - output_file: Optional[Optional[FileReferenceV2025R0]] = None, - output_file_version: Optional[Optional[FileVersionBaseV2025R0]] = None, + output_file: Optional[FileReferenceV2025R0] = None, + output_file_version: Optional[FileVersionBaseV2025R0] = None, type: DocGenJobBaseV2025R0TypeField = DocGenJobBaseV2025R0TypeField.DOCGEN_JOB, **kwargs ): diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_security_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_security_v2025_r0.py index 41d50498e..c5754bbb2 100644 --- a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_security_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_security_v2025_r0.py @@ -63,7 +63,7 @@ class EnterpriseConfigurationSecurityV2025R0ExternalCollabMultiFactorAuthSetting def __init__( self, *, - value: Optional[Optional[ExternalCollabSecuritySettingsV2025R0]] = None, + value: Optional[ExternalCollabSecuritySettingsV2025R0] = None, is_used: Optional[bool] = None, **kwargs ): @@ -81,7 +81,7 @@ class EnterpriseConfigurationSecurityV2025R0KeysafeField( def __init__( self, *, - value: Optional[Optional[KeysafeSettingsV2025R0]] = None, + value: Optional[KeysafeSettingsV2025R0] = None, is_used: Optional[bool] = None, **kwargs ): diff --git a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_v2025_r0.py index 4e91f3e49..274194696 100644 --- a/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/enterprise_configuration_v2025_r0.py @@ -35,14 +35,12 @@ def __init__( *, id: Optional[str] = None, type: Optional[EnterpriseConfigurationV2025R0TypeField] = None, - security: Optional[Optional[EnterpriseConfigurationSecurityV2025R0]] = None, + security: Optional[EnterpriseConfigurationSecurityV2025R0] = None, content_and_sharing: Optional[ - Optional[EnterpriseConfigurationContentAndSharingV2025R0] + EnterpriseConfigurationContentAndSharingV2025R0 ] = None, - user_settings: Optional[ - Optional[EnterpriseConfigurationUserSettingsV2025R0] - ] = None, - shield: Optional[Optional[EnterpriseConfigurationShieldV2025R0]] = None, + user_settings: Optional[EnterpriseConfigurationUserSettingsV2025R0] = None, + shield: Optional[EnterpriseConfigurationShieldV2025R0] = None, **kwargs ): """ From 66267bae7395ce2886cc4cd7504a51d22394b738 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:17:36 +0100 Subject: [PATCH 062/139] feat: Remove `confidence_score` and `include_confidence_score` from AI extract (box/box-openapi#567) (#1238) --- .codegen.json | 2 +- box_sdk_gen/managers/ai.py | 4 ---- box_sdk_gen/managers/transfer.py | 2 +- box_sdk_gen/managers/user_collaborations.py | 12 ++++++++++-- box_sdk_gen/schemas/ai_extract_structured.py | 4 ---- .../schemas/ai_extract_structured_response.py | 4 ---- docs/ai.md | 2 -- docs/transfer.md | 2 +- docs/user_collaborations.md | 4 ++-- 9 files changed, 15 insertions(+), 21 deletions(-) diff --git a/.codegen.json b/.codegen.json index bb0528c0b..f1d2ad595 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "3d9d391", "specHash": "5183b65", "version": "10.1.0" } +{ "engineHash": "3d9d391", "specHash": "31c41d5", "version": "10.1.0" } diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index 9998d9f1d..6ac86c88a 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -425,7 +425,6 @@ def create_ai_extract_structured( *, metadata_template: Optional[CreateAiExtractStructuredMetadataTemplate] = None, fields: Optional[List[CreateAiExtractStructuredFields]] = None, - include_confidence_score: Optional[bool] = None, ai_agent: Optional[AiExtractStructuredAgent] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> AiExtractStructuredResponse: @@ -451,8 +450,6 @@ def create_ai_extract_structured( :param fields: The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both., defaults to None :type fields: Optional[List[CreateAiExtractStructuredFields]], optional - :param include_confidence_score: A flag to indicate whether confidence scores for every extracted field should be returned., defaults to None - :type include_confidence_score: Optional[bool], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ @@ -462,7 +459,6 @@ def create_ai_extract_structured( 'items': items, 'metadata_template': metadata_template, 'fields': fields, - 'include_confidence_score': include_confidence_score, 'ai_agent': ai_agent, } headers_map: Dict[str, str] = prepare_params({**extra_headers}) diff --git a/box_sdk_gen/managers/transfer.py b/box_sdk_gen/managers/transfer.py index 866934f48..1461f26bb 100644 --- a/box_sdk_gen/managers/transfer.py +++ b/box_sdk_gen/managers/transfer.py @@ -74,7 +74,7 @@ def transfer_owned_folder( """ Move all of the items (files, folders and workflows) owned by a user into - another user's account + another user's account. Only the root folder (`0`) can be transferred. diff --git a/box_sdk_gen/managers/user_collaborations.py b/box_sdk_gen/managers/user_collaborations.py index bd6e9c517..4c2bae562 100644 --- a/box_sdk_gen/managers/user_collaborations.py +++ b/box_sdk_gen/managers/user_collaborations.py @@ -244,7 +244,11 @@ def update_collaboration_by_id( Only an owner or co-owners can invite collaborators with a `can_view_path` of `true`. Only an owner can update `can_view_path` on existing collaborations. - `can_view_path` can only be used for folder collaborations., defaults to None + `can_view_path` can only be used for folder collaborations. + + When you delete a folder with `can_view_path=true`, collaborators may still see the parent path. + For instructions on how to remove this, see + [Even though a folder invited via can_view_path is deleted, the path remains displayed](https://support.box.com/hc/en-us/articles/37472814319891-Even-though-a-folder-invited-via-can-view-path-is-deleted-the-path-remains-displayed)., defaults to None :type can_view_path: Optional[bool], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional @@ -384,7 +388,11 @@ def create_collaboration( Only an owner or co-owners can invite collaborators with a `can_view_path` of `true`. Only an owner can update `can_view_path` on existing collaborations. - `can_view_path` can only be used for folder collaborations., defaults to None + `can_view_path` can only be used for folder collaborations. + + When you delete a folder with `can_view_path=true`, collaborators may still see the parent path. + For instructions on how to remove this, see + [Even though a folder invited via can_view_path is deleted, the path remains displayed](https://support.box.com/hc/en-us/articles/37472814319891-Even-though-a-folder-invited-via-can-view-path-is-deleted-the-path-remains-displayed)., defaults to None :type can_view_path: Optional[bool], optional :param expires_at: Set the expiration date for the collaboration. At this date, the collaboration will be automatically removed from the item. diff --git a/box_sdk_gen/schemas/ai_extract_structured.py b/box_sdk_gen/schemas/ai_extract_structured.py index 172e90003..5a742b529 100644 --- a/box_sdk_gen/schemas/ai_extract_structured.py +++ b/box_sdk_gen/schemas/ai_extract_structured.py @@ -114,7 +114,6 @@ def __init__( *, metadata_template: Optional[AiExtractStructuredMetadataTemplateField] = None, fields: Optional[List[AiExtractStructuredFieldsField]] = None, - include_confidence_score: Optional[bool] = None, ai_agent: Optional[AiExtractStructuredAgent] = None, **kwargs ): @@ -127,12 +126,9 @@ def __init__( :param fields: The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both., defaults to None :type fields: Optional[List[AiExtractStructuredFieldsField]], optional - :param include_confidence_score: A flag to indicate whether confidence scores for every extracted field should be returned., defaults to None - :type include_confidence_score: Optional[bool], optional """ super().__init__(**kwargs) self.items = items self.metadata_template = metadata_template self.fields = fields - self.include_confidence_score = include_confidence_score self.ai_agent = ai_agent diff --git a/box_sdk_gen/schemas/ai_extract_structured_response.py b/box_sdk_gen/schemas/ai_extract_structured_response.py index 5c5e7dc05..76ae79525 100644 --- a/box_sdk_gen/schemas/ai_extract_structured_response.py +++ b/box_sdk_gen/schemas/ai_extract_structured_response.py @@ -18,7 +18,6 @@ def __init__( created_at: DateTime, *, completion_reason: Optional[str] = None, - confidence_score: Optional[Dict] = None, ai_agent_info: Optional[AiAgentInfo] = None, **kwargs ): @@ -27,12 +26,9 @@ def __init__( :type created_at: DateTime :param completion_reason: The reason the response finishes., defaults to None :type completion_reason: Optional[str], optional - :param confidence_score: The confidence score numeric values for each extracted field as a JSON dictionary. This can be empty if no field could be extracted., defaults to None - :type confidence_score: Optional[Dict], optional """ super().__init__(**kwargs) self.answer = answer self.created_at = created_at self.completion_reason = completion_reason - self.confidence_score = confidence_score self.ai_agent_info = ai_agent_info diff --git a/docs/ai.md b/docs/ai.md index 8b1dcf7f7..6c1f0686e 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -258,8 +258,6 @@ client.ai.create_ai_extract_structured( - The metadata template containing the fields to extract. For your request to work, you must provide either `metadata_template` or `fields`, but not both. - fields `Optional[List[CreateAiExtractStructuredFields]]` - The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both. -- include_confidence_score `Optional[bool]` - - A flag to indicate whether confidence scores for every extracted field should be returned. - ai_agent `Optional[AiExtractStructuredAgent]` - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/transfer.md b/docs/transfer.md index d3e6b3c5c..cc6a8d2a8 100644 --- a/docs/transfer.md +++ b/docs/transfer.md @@ -5,7 +5,7 @@ ## Transfer owned folders Move all of the items (files, folders and workflows) owned by a user into -another user's account +another user's account. Only the root folder (`0`) can be transferred. diff --git a/docs/user_collaborations.md b/docs/user_collaborations.md index 22de67213..2884021a8 100644 --- a/docs/user_collaborations.md +++ b/docs/user_collaborations.md @@ -65,7 +65,7 @@ client.user_collaborations.update_collaboration_by_id( - expires_at `Optional[DateTime]` - Update the expiration date for the collaboration. At this date, the collaboration will be automatically removed from the item. This feature will only work if the **Automatically remove invited collaborators: Allow folder owners to extend the expiry date** setting has been enabled in the **Enterprise Settings** of the **Admin Console**. When the setting is not enabled, collaborations can not have an expiry date and a value for this field will be result in an error. Additionally, a collaboration can only be given an expiration if it was created after the **Automatically remove invited collaborator** setting was enabled. - can_view_path `Optional[bool]` - - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only an owner or co-owners can invite collaborators with a `can_view_path` of `true`. Only an owner can update `can_view_path` on existing collaborations. `can_view_path` can only be used for folder collaborations. + - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only an owner or co-owners can invite collaborators with a `can_view_path` of `true`. Only an owner can update `can_view_path` on existing collaborations. `can_view_path` can only be used for folder collaborations. When you delete a folder with `can_view_path=true`, collaborators may still see the parent path. For instructions on how to remove this, see [Even though a folder invited via can_view_path is deleted, the path remains displayed](https://support.box.com/hc/en-us/articles/37472814319891-Even-though-a-folder-invited-via-can-view-path-is-deleted-the-path-remains-displayed). - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -152,7 +152,7 @@ client.user_collaborations.create_collaboration( - is_access_only `Optional[bool]` - If set to `true`, collaborators have access to shared items, but such items won't be visible in the All Files list. Additionally, collaborators won't see the path to the root folder for the shared item. - can_view_path `Optional[bool]` - - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only an owner or co-owners can invite collaborators with a `can_view_path` of `true`. Only an owner can update `can_view_path` on existing collaborations. `can_view_path` can only be used for folder collaborations. + - Determines if the invited users can see the entire parent path to the associated folder. The user will not gain privileges in any parent folder and therefore can not see content the user is not collaborated on. Be aware that this meaningfully increases the time required to load the invitee's **All Files** page. We recommend you limit the number of collaborations with `can_view_path` enabled to 1,000 per user. Only an owner or co-owners can invite collaborators with a `can_view_path` of `true`. Only an owner can update `can_view_path` on existing collaborations. `can_view_path` can only be used for folder collaborations. When you delete a folder with `can_view_path=true`, collaborators may still see the parent path. For instructions on how to remove this, see [Even though a folder invited via can_view_path is deleted, the path remains displayed](https://support.box.com/hc/en-us/articles/37472814319891-Even-though-a-folder-invited-via-can-view-path-is-deleted-the-path-remains-displayed). - expires_at `Optional[DateTime]` - Set the expiration date for the collaboration. At this date, the collaboration will be automatically removed from the item. This feature will only work if the **Automatically remove invited collaborators: Allow folder owners to extend the expiry date** setting has been enabled in the **Enterprise Settings** of the **Admin Console**. When the setting is not enabled, collaborations can not have an expiry date and a value for this field will be result in an error. - fields `Optional[List[str]]` From ed11cf4124761c103624ae4fdceaa2d75135ed68 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:50:14 +0100 Subject: [PATCH 063/139] chore: release version 10.2.0 (#1240) --- .codegen.json | 2 +- CHANGELOG.md | 8 ++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index f1d2ad595..7b881e098 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "3d9d391", "specHash": "31c41d5", "version": "10.1.0" } +{ "engineHash": "3d9d391", "specHash": "31c41d5", "version": "10.2.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 34ed4fbde..173c03f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.2.0](https://github.com/box/box-python-sdk/compare/v10.1.0...v10.2.0) (2025-12-10) + + +### New Features and Enhancements + +* Remove `confidence_score` and `include_confidence_score` from AI extract (box/box-openapi[#567](https://github.com/box/box-python-sdk/issues/567)) ([#1238](https://github.com/box/box-python-sdk/issues/1238)) ([66267ba](https://github.com/box/box-python-sdk/commit/66267bae7395ce2886cc4cd7504a51d22394b738)) +* Support new sign request metadata (box/box-openapi[#565](https://github.com/box/box-python-sdk/issues/565)) ([#1222](https://github.com/box/box-python-sdk/issues/1222)) ([6c3d332](https://github.com/box/box-python-sdk/commit/6c3d3325a19b3217225a80c2cd5d15c7bb068494)) + ## [10.1.0](https://github.com/box/box-python-sdk/compare/v10.0.1...v10.1.0) (2025-11-19) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index a06cd470e..335d3f854 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.1.0' +__version__ = '10.2.0' From d7cc019dd186ef3cdb6214a6cf1625ec49c1fd37 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:42:18 +0100 Subject: [PATCH 064/139] fix: fix `RetryAfter` function when there's no `Retry-After` header (box/box-codegen#903) (#1244) --- .codegen.json | 2 +- box_sdk_gen/networking/retries.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 7b881e098..8ec3ba2ac 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "3d9d391", "specHash": "31c41d5", "version": "10.2.0" } +{ "engineHash": "f97463c", "specHash": "31c41d5", "version": "10.2.0" } diff --git a/box_sdk_gen/networking/retries.py b/box_sdk_gen/networking/retries.py index 82772f711..63f6f809e 100644 --- a/box_sdk_gen/networking/retries.py +++ b/box_sdk_gen/networking/retries.py @@ -90,7 +90,11 @@ def retry_after( fetch_response: FetchResponse, attempt_number: int, ) -> float: - retry_after_header: Optional[str] = fetch_response.headers.get('Retry-After') + retry_after_header: Optional[str] = ( + fetch_response.headers.get('Retry-After') + if 'Retry-After' in fetch_response.headers + else None + ) if not retry_after_header == None: return float(retry_after_header) randomization: float = random( From f99512fcfca75edae48bb5a1b6a1897d330f5dd3 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:43:39 +0100 Subject: [PATCH 065/139] feat: text input validation for Box Sign (box/box-openapi#568) (#1246) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/schemas/__init__.py | 36 ++++++-- .../schemas/sign_request_signer_input.py | 85 +++++++++++++++---- ..._request_signer_input_custom_validation.py | 33 +++++++ ...quest_signer_input_date_asia_validation.py | 28 ++++++ ...request_signer_input_date_eu_validation.py | 28 ++++++ ...equest_signer_input_date_iso_validation.py | 28 ++++++ ...request_signer_input_date_us_validation.py | 28 ++++++ ...n_request_signer_input_email_validation.py | 24 ++++++ ...gner_input_number_with_comma_validation.py | 28 ++++++ ...ner_input_number_with_period_validation.py | 28 ++++++ ...ign_request_signer_input_ssn_validation.py | 24 ++++++ .../sign_request_signer_input_validation.py | 61 +++++++++++++ ...n_request_signer_input_zip_4_validation.py | 24 ++++++ ...ign_request_signer_input_zip_validation.py | 24 ++++++ box_sdk_gen/schemas/template_signer_input.py | 55 +++++++++++- 16 files changed, 512 insertions(+), 24 deletions(-) create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_custom_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_date_asia_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_date_eu_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_date_iso_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_date_us_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_email_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_number_with_comma_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_number_with_period_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_ssn_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_zip_4_validation.py create mode 100644 box_sdk_gen/schemas/sign_request_signer_input_zip_validation.py diff --git a/.codegen.json b/.codegen.json index 8ec3ba2ac..3b6511760 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "f97463c", "specHash": "31c41d5", "version": "10.2.0" } +{ "engineHash": "f97463c", "specHash": "b7abe0d", "version": "10.2.0" } diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index 641892bf7..5a2ff543c 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -264,6 +264,36 @@ from box_sdk_gen.schemas.sign_request_prefill_tag import * +from box_sdk_gen.schemas.sign_request_base import * + +from box_sdk_gen.schemas.sign_request_create_request import * + +from box_sdk_gen.schemas.sign_request_signer_attachment import * + +from box_sdk_gen.schemas.sign_request_signer_input_custom_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_date_asia_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_date_eu_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_date_iso_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_date_us_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_email_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_number_with_comma_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_number_with_period_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_ssn_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_zip_4_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_zip_validation import * + +from box_sdk_gen.schemas.sign_request_signer_input_validation import * + from box_sdk_gen.schemas.template_signer_input import * from box_sdk_gen.schemas.template_signer import * @@ -274,12 +304,6 @@ from box_sdk_gen.schemas.sign_request_signer_input import * -from box_sdk_gen.schemas.sign_request_base import * - -from box_sdk_gen.schemas.sign_request_create_request import * - -from box_sdk_gen.schemas.sign_request_signer_attachment import * - from box_sdk_gen.schemas.sign_request_signer import * from box_sdk_gen.schemas.sign_request import * diff --git a/box_sdk_gen/schemas/sign_request_signer_input.py b/box_sdk_gen/schemas/sign_request_signer_input.py index 20067e3f3..8218fbca2 100644 --- a/box_sdk_gen/schemas/sign_request_signer_input.py +++ b/box_sdk_gen/schemas/sign_request_signer_input.py @@ -4,8 +4,56 @@ from box_sdk_gen.internal.utils import Date +from box_sdk_gen.schemas.sign_request_signer_input_email_validation import ( + SignRequestSignerInputEmailValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_custom_validation import ( + SignRequestSignerInputCustomValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_zip_validation import ( + SignRequestSignerInputZipValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_zip_4_validation import ( + SignRequestSignerInputZip4Validation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_ssn_validation import ( + SignRequestSignerInputSsnValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_number_with_period_validation import ( + SignRequestSignerInputNumberWithPeriodValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_number_with_comma_validation import ( + SignRequestSignerInputNumberWithCommaValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_iso_validation import ( + SignRequestSignerInputDateIsoValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_us_validation import ( + SignRequestSignerInputDateUsValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_eu_validation import ( + SignRequestSignerInputDateEuValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_asia_validation import ( + SignRequestSignerInputDateAsiaValidation, +) + from box_sdk_gen.schemas.sign_request_prefill_tag import SignRequestPrefillTag +from box_sdk_gen.schemas.sign_request_signer_input_validation import ( + SignRequestSignerInputValidation, +) + from box_sdk_gen.box.errors import BoxSDKError @@ -44,6 +92,7 @@ def __init__( type: Optional[SignRequestSignerInputTypeField] = None, content_type: Optional[SignRequestSignerInputContentTypeField] = None, read_only: Optional[bool] = None, + validation: Optional[SignRequestSignerInputValidation] = None, document_tag_id: Optional[str] = None, text_value: Optional[str] = None, checkbox_value: Optional[bool] = None, @@ -51,22 +100,25 @@ def __init__( **kwargs ): """ - :param page_index: Index of page that the input is on. - :type page_index: int - :param type: Type of input., defaults to None - :type type: Optional[SignRequestSignerInputTypeField], optional - :param content_type: Content type of input., defaults to None - :type content_type: Optional[SignRequestSignerInputContentTypeField], optional - :param read_only: Whether this input was defined as read-only(immutable by signers) or not., defaults to None - :type read_only: Optional[bool], optional - :param document_tag_id: This references the ID of a specific tag contained in a file of the signature request., defaults to None - :type document_tag_id: Optional[str], optional - :param text_value: Text prefill value., defaults to None - :type text_value: Optional[str], optional - :param checkbox_value: Checkbox prefill value., defaults to None - :type checkbox_value: Optional[bool], optional - :param date_value: Date prefill value., defaults to None - :type date_value: Optional[Date], optional + :param page_index: Index of page that the input is on. + :type page_index: int + :param type: Type of input., defaults to None + :type type: Optional[SignRequestSignerInputTypeField], optional + :param content_type: Content type of input., defaults to None + :type content_type: Optional[SignRequestSignerInputContentTypeField], optional + :param read_only: Indicates whether this input is read-only (cannot be modified by signers)., defaults to None + :type read_only: Optional[bool], optional + :param validation: Specifies the formatting rules that signers must follow for text field inputs. + If set, this validation is mandatory., defaults to None + :type validation: Optional[SignRequestSignerInputValidation], optional + :param document_tag_id: This references the ID of a specific tag contained in a file of the signature request., defaults to None + :type document_tag_id: Optional[str], optional + :param text_value: Text prefill value., defaults to None + :type text_value: Optional[str], optional + :param checkbox_value: Checkbox prefill value., defaults to None + :type checkbox_value: Optional[bool], optional + :param date_value: Date prefill value., defaults to None + :type date_value: Optional[Date], optional """ super().__init__( document_tag_id=document_tag_id, @@ -79,3 +131,4 @@ def __init__( self.type = type self.content_type = content_type self.read_only = read_only + self.validation = validation diff --git a/box_sdk_gen/schemas/sign_request_signer_input_custom_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_custom_validation.py new file mode 100644 index 000000000..2b2188899 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_custom_validation.py @@ -0,0 +1,33 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputCustomValidationValidationTypeField(str, Enum): + CUSTOM = 'custom' + + +class SignRequestSignerInputCustomValidation(BaseObject): + def __init__( + self, + custom_regex: str, + custom_error_message: str, + *, + validation_type: SignRequestSignerInputCustomValidationValidationTypeField = SignRequestSignerInputCustomValidationValidationTypeField.CUSTOM, + **kwargs + ): + """ + :param custom_regex: Regular expression used for validation. + :type custom_regex: str + :param custom_error_message: Error message shown if input fails custom regular expression validation. + :type custom_error_message: str + :param validation_type: Defines the validation format for the text input as custom. + A custom regular expression is used for validation., defaults to SignRequestSignerInputCustomValidationValidationTypeField.CUSTOM + :type validation_type: SignRequestSignerInputCustomValidationValidationTypeField, optional + """ + super().__init__(**kwargs) + self.custom_regex = custom_regex + self.custom_error_message = custom_error_message + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/sign_request_signer_input_date_asia_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_date_asia_validation.py new file mode 100644 index 000000000..bd786668f --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_date_asia_validation.py @@ -0,0 +1,28 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputDateAsiaValidationValidationTypeField(str, Enum): + DATE_ASIA = 'date_asia' + + +class SignRequestSignerInputDateAsiaValidation(BaseObject): + def __init__( + self, + *, + validation_type: Optional[ + SignRequestSignerInputDateAsiaValidationValidationTypeField + ] = None, + **kwargs + ): + """ + :param validation_type: Validates that the text input uses the Asian date format `YYYY/MM/DD`., defaults to None + :type validation_type: Optional[SignRequestSignerInputDateAsiaValidationValidationTypeField], optional + """ + super().__init__(**kwargs) + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/sign_request_signer_input_date_eu_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_date_eu_validation.py new file mode 100644 index 000000000..d1905f2dd --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_date_eu_validation.py @@ -0,0 +1,28 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputDateEuValidationValidationTypeField(str, Enum): + DATE_EU = 'date_eu' + + +class SignRequestSignerInputDateEuValidation(BaseObject): + def __init__( + self, + *, + validation_type: Optional[ + SignRequestSignerInputDateEuValidationValidationTypeField + ] = None, + **kwargs + ): + """ + :param validation_type: Validates that the text input uses the European date format `DD/MM/YYYY`., defaults to None + :type validation_type: Optional[SignRequestSignerInputDateEuValidationValidationTypeField], optional + """ + super().__init__(**kwargs) + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/sign_request_signer_input_date_iso_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_date_iso_validation.py new file mode 100644 index 000000000..6f7472200 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_date_iso_validation.py @@ -0,0 +1,28 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputDateIsoValidationValidationTypeField(str, Enum): + DATE_ISO = 'date_iso' + + +class SignRequestSignerInputDateIsoValidation(BaseObject): + def __init__( + self, + *, + validation_type: Optional[ + SignRequestSignerInputDateIsoValidationValidationTypeField + ] = None, + **kwargs + ): + """ + :param validation_type: Validates that the text input uses the ISO date format `YYYY-MM-DD`., defaults to None + :type validation_type: Optional[SignRequestSignerInputDateIsoValidationValidationTypeField], optional + """ + super().__init__(**kwargs) + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/sign_request_signer_input_date_us_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_date_us_validation.py new file mode 100644 index 000000000..d5307b85e --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_date_us_validation.py @@ -0,0 +1,28 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputDateUsValidationValidationTypeField(str, Enum): + DATE_US = 'date_us' + + +class SignRequestSignerInputDateUsValidation(BaseObject): + def __init__( + self, + *, + validation_type: Optional[ + SignRequestSignerInputDateUsValidationValidationTypeField + ] = None, + **kwargs + ): + """ + :param validation_type: Validates that the text input uses the US date format `MM/DD/YYYY`., defaults to None + :type validation_type: Optional[SignRequestSignerInputDateUsValidationValidationTypeField], optional + """ + super().__init__(**kwargs) + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/sign_request_signer_input_email_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_email_validation.py new file mode 100644 index 000000000..8f833f9ea --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_email_validation.py @@ -0,0 +1,24 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputEmailValidationValidationTypeField(str, Enum): + EMAIL = 'email' + + +class SignRequestSignerInputEmailValidation(BaseObject): + def __init__( + self, + *, + validation_type: SignRequestSignerInputEmailValidationValidationTypeField = SignRequestSignerInputEmailValidationValidationTypeField.EMAIL, + **kwargs + ): + """ + :param validation_type: Validates that the text input is an email address., defaults to SignRequestSignerInputEmailValidationValidationTypeField.EMAIL + :type validation_type: SignRequestSignerInputEmailValidationValidationTypeField, optional + """ + super().__init__(**kwargs) + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/sign_request_signer_input_number_with_comma_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_number_with_comma_validation.py new file mode 100644 index 000000000..c31789852 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_number_with_comma_validation.py @@ -0,0 +1,28 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputNumberWithCommaValidationValidationTypeField(str, Enum): + NUMBER_WITH_COMMA = 'number_with_comma' + + +class SignRequestSignerInputNumberWithCommaValidation(BaseObject): + def __init__( + self, + *, + validation_type: Optional[ + SignRequestSignerInputNumberWithCommaValidationValidationTypeField + ] = None, + **kwargs + ): + """ + :param validation_type: Validates that the text input uses a number format with a comma as the decimal separator (for example, 1,23)., defaults to None + :type validation_type: Optional[SignRequestSignerInputNumberWithCommaValidationValidationTypeField], optional + """ + super().__init__(**kwargs) + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/sign_request_signer_input_number_with_period_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_number_with_period_validation.py new file mode 100644 index 000000000..62c8ee202 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_number_with_period_validation.py @@ -0,0 +1,28 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputNumberWithPeriodValidationValidationTypeField(str, Enum): + NUMBER_WITH_PERIOD = 'number_with_period' + + +class SignRequestSignerInputNumberWithPeriodValidation(BaseObject): + def __init__( + self, + *, + validation_type: Optional[ + SignRequestSignerInputNumberWithPeriodValidationValidationTypeField + ] = None, + **kwargs + ): + """ + :param validation_type: Validates that the text input uses a number format with a period as the decimal separator (for example, 1.23)., defaults to None + :type validation_type: Optional[SignRequestSignerInputNumberWithPeriodValidationValidationTypeField], optional + """ + super().__init__(**kwargs) + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/sign_request_signer_input_ssn_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_ssn_validation.py new file mode 100644 index 000000000..2960af441 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_ssn_validation.py @@ -0,0 +1,24 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputSsnValidationValidationTypeField(str, Enum): + SSN = 'ssn' + + +class SignRequestSignerInputSsnValidation(BaseObject): + def __init__( + self, + *, + validation_type: SignRequestSignerInputSsnValidationValidationTypeField = SignRequestSignerInputSsnValidationValidationTypeField.SSN, + **kwargs + ): + """ + :param validation_type: Validates that the text input is a Social Security Number (SSN)., defaults to SignRequestSignerInputSsnValidationValidationTypeField.SSN + :type validation_type: SignRequestSignerInputSsnValidationValidationTypeField, optional + """ + super().__init__(**kwargs) + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/sign_request_signer_input_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_validation.py new file mode 100644 index 000000000..b186cf633 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_validation.py @@ -0,0 +1,61 @@ +from typing import Union + +from box_sdk_gen.schemas.sign_request_signer_input_email_validation import ( + SignRequestSignerInputEmailValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_custom_validation import ( + SignRequestSignerInputCustomValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_zip_validation import ( + SignRequestSignerInputZipValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_zip_4_validation import ( + SignRequestSignerInputZip4Validation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_ssn_validation import ( + SignRequestSignerInputSsnValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_number_with_period_validation import ( + SignRequestSignerInputNumberWithPeriodValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_number_with_comma_validation import ( + SignRequestSignerInputNumberWithCommaValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_iso_validation import ( + SignRequestSignerInputDateIsoValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_us_validation import ( + SignRequestSignerInputDateUsValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_eu_validation import ( + SignRequestSignerInputDateEuValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_asia_validation import ( + SignRequestSignerInputDateAsiaValidation, +) + +from box_sdk_gen.box.errors import BoxSDKError + +SignRequestSignerInputValidation = Union[ + SignRequestSignerInputEmailValidation, + SignRequestSignerInputCustomValidation, + SignRequestSignerInputZipValidation, + SignRequestSignerInputZip4Validation, + SignRequestSignerInputSsnValidation, + SignRequestSignerInputNumberWithPeriodValidation, + SignRequestSignerInputNumberWithCommaValidation, + SignRequestSignerInputDateIsoValidation, + SignRequestSignerInputDateUsValidation, + SignRequestSignerInputDateEuValidation, + SignRequestSignerInputDateAsiaValidation, +] diff --git a/box_sdk_gen/schemas/sign_request_signer_input_zip_4_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_zip_4_validation.py new file mode 100644 index 000000000..47d8bf0f3 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_zip_4_validation.py @@ -0,0 +1,24 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputZip4ValidationValidationTypeField(str, Enum): + ZIP_4 = 'zip_4' + + +class SignRequestSignerInputZip4Validation(BaseObject): + def __init__( + self, + *, + validation_type: SignRequestSignerInputZip4ValidationValidationTypeField = SignRequestSignerInputZip4ValidationValidationTypeField.ZIP_4, + **kwargs + ): + """ + :param validation_type: Validates that the text input is a ZIP+4 code., defaults to SignRequestSignerInputZip4ValidationValidationTypeField.ZIP_4 + :type validation_type: SignRequestSignerInputZip4ValidationValidationTypeField, optional + """ + super().__init__(**kwargs) + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/sign_request_signer_input_zip_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_zip_validation.py new file mode 100644 index 000000000..a0eb13771 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_signer_input_zip_validation.py @@ -0,0 +1,24 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestSignerInputZipValidationValidationTypeField(str, Enum): + ZIP = 'zip' + + +class SignRequestSignerInputZipValidation(BaseObject): + def __init__( + self, + *, + validation_type: SignRequestSignerInputZipValidationValidationTypeField = SignRequestSignerInputZipValidationValidationTypeField.ZIP, + **kwargs + ): + """ + :param validation_type: Validates that the text input is a ZIP code., defaults to SignRequestSignerInputZipValidationValidationTypeField.ZIP + :type validation_type: SignRequestSignerInputZipValidationValidationTypeField, optional + """ + super().__init__(**kwargs) + self.validation_type = validation_type diff --git a/box_sdk_gen/schemas/template_signer_input.py b/box_sdk_gen/schemas/template_signer_input.py index 32702404a..845999bc8 100644 --- a/box_sdk_gen/schemas/template_signer_input.py +++ b/box_sdk_gen/schemas/template_signer_input.py @@ -8,8 +8,56 @@ from box_sdk_gen.internal.utils import Date +from box_sdk_gen.schemas.sign_request_signer_input_email_validation import ( + SignRequestSignerInputEmailValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_custom_validation import ( + SignRequestSignerInputCustomValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_zip_validation import ( + SignRequestSignerInputZipValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_zip_4_validation import ( + SignRequestSignerInputZip4Validation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_ssn_validation import ( + SignRequestSignerInputSsnValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_number_with_period_validation import ( + SignRequestSignerInputNumberWithPeriodValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_number_with_comma_validation import ( + SignRequestSignerInputNumberWithCommaValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_iso_validation import ( + SignRequestSignerInputDateIsoValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_us_validation import ( + SignRequestSignerInputDateUsValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_eu_validation import ( + SignRequestSignerInputDateEuValidation, +) + +from box_sdk_gen.schemas.sign_request_signer_input_date_asia_validation import ( + SignRequestSignerInputDateAsiaValidation, +) + from box_sdk_gen.schemas.sign_request_prefill_tag import SignRequestPrefillTag +from box_sdk_gen.schemas.sign_request_signer_input_validation import ( + SignRequestSignerInputValidation, +) + from box_sdk_gen.box.errors import BoxSDKError @@ -86,6 +134,7 @@ def __init__( dimensions: Optional[TemplateSignerInputDimensionsField] = None, label: Optional[str] = None, read_only: Optional[bool] = None, + validation: Optional[SignRequestSignerInputValidation] = None, document_tag_id: Optional[str] = None, text_value: Optional[str] = None, checkbox_value: Optional[bool] = None, @@ -116,8 +165,11 @@ def __init__( :type dimensions: Optional[TemplateSignerInputDimensionsField], optional :param label: The label field is used especially for text, attachment, radio, and checkbox type inputs., defaults to None :type label: Optional[str], optional - :param read_only: Whether this input was defined as read-only(immutable by signers) or not., defaults to None + :param read_only: Indicates whether this input is read-only (cannot be modified by signers)., defaults to None :type read_only: Optional[bool], optional + :param validation: Specifies the formatting rules that signers must follow for text field inputs. + If set, this validation is mandatory., defaults to None + :type validation: Optional[SignRequestSignerInputValidation], optional :param document_tag_id: This references the ID of a specific tag contained in a file of the signature request., defaults to None :type document_tag_id: Optional[str], optional :param text_value: Text prefill value., defaults to None @@ -145,3 +197,4 @@ def __init__( self.dimensions = dimensions self.label = label self.read_only = read_only + self.validation = validation From 7850463204ef9f7802a759ef69a3b7f4b6dd328b Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:13:22 +0100 Subject: [PATCH 066/139] feat: add Metadata Taxonomies api (box/box-openapi#569) (#1252) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/client.py | 5 + box_sdk_gen/managers/__init__.py | 2 + box_sdk_gen/managers/file_metadata.py | 23 +- box_sdk_gen/managers/folder_metadata.py | 9 + box_sdk_gen/managers/metadata_taxonomies.py | 901 ++++++++++++++++++ box_sdk_gen/managers/metadata_templates.py | 65 +- box_sdk_gen/schemas/__init__.py | 14 + box_sdk_gen/schemas/metadata_taxonomies.py | 38 + box_sdk_gen/schemas/metadata_taxonomy.py | 52 + .../schemas/metadata_taxonomy_ancestor.py | 39 + .../schemas/metadata_taxonomy_level.py | 39 + .../schemas/metadata_taxonomy_levels.py | 21 + box_sdk_gen/schemas/metadata_taxonomy_node.py | 61 ++ .../schemas/metadata_taxonomy_nodes.py | 38 + docs/README.md | 1 + docs/authentication.md | 4 +- docs/file_metadata.md | 4 + docs/folder_metadata.md | 2 + docs/metadata_taxonomies.md | 482 ++++++++++ 20 files changed, 1797 insertions(+), 5 deletions(-) create mode 100644 box_sdk_gen/managers/metadata_taxonomies.py create mode 100644 box_sdk_gen/schemas/metadata_taxonomies.py create mode 100644 box_sdk_gen/schemas/metadata_taxonomy.py create mode 100644 box_sdk_gen/schemas/metadata_taxonomy_ancestor.py create mode 100644 box_sdk_gen/schemas/metadata_taxonomy_level.py create mode 100644 box_sdk_gen/schemas/metadata_taxonomy_levels.py create mode 100644 box_sdk_gen/schemas/metadata_taxonomy_node.py create mode 100644 box_sdk_gen/schemas/metadata_taxonomy_nodes.py create mode 100644 docs/metadata_taxonomies.md diff --git a/.codegen.json b/.codegen.json index 3b6511760..873459285 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "f97463c", "specHash": "b7abe0d", "version": "10.2.0" } +{ "engineHash": "b5860f1", "specHash": "1e0848d", "version": "10.2.0" } diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py index 914844877..155301d2e 100644 --- a/box_sdk_gen/client.py +++ b/box_sdk_gen/client.py @@ -168,6 +168,8 @@ from box_sdk_gen.managers.ai_studio import AiStudioManager +from box_sdk_gen.managers.metadata_taxonomies import MetadataTaxonomiesManager + from box_sdk_gen.managers.docgen_template import DocgenTemplateManager from box_sdk_gen.managers.docgen import DocgenManager @@ -427,6 +429,9 @@ def __init__(self, auth: Authentication, *, network_session: NetworkSession = No self.ai_studio = AiStudioManager( auth=self.auth, network_session=self.network_session ) + self.metadata_taxonomies = MetadataTaxonomiesManager( + auth=self.auth, network_session=self.network_session + ) self.docgen_template = DocgenTemplateManager( auth=self.auth, network_session=self.network_session ) diff --git a/box_sdk_gen/managers/__init__.py b/box_sdk_gen/managers/__init__.py index 59e3e15f2..9ab97dc30 100644 --- a/box_sdk_gen/managers/__init__.py +++ b/box_sdk_gen/managers/__init__.py @@ -142,6 +142,8 @@ from box_sdk_gen.managers.ai_studio import * +from box_sdk_gen.managers.metadata_taxonomies import * + from box_sdk_gen.managers.docgen_template import * from box_sdk_gen.managers.docgen import * diff --git a/box_sdk_gen/managers/file_metadata.py b/box_sdk_gen/managers/file_metadata.py index 8dc0577f2..21b7236af 100644 --- a/box_sdk_gen/managers/file_metadata.py +++ b/box_sdk_gen/managers/file_metadata.py @@ -132,7 +132,11 @@ def __init__( self.network_session = network_session def get_file_metadata( - self, file_id: str, *, extra_headers: Optional[Dict[str, Optional[str]]] = None + self, + file_id: str, + *, + view: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> Metadatas: """ Retrieves all metadata for a given file. @@ -145,11 +149,18 @@ def get_file_metadata( the `file_id` is `123`. Example: "12345" :type file_id: str + :param view: Taxonomy field values are returned in `API view` by default, meaning + the value is represented with a taxonomy node identifier. + To retrieve the `Hydrated view`, where taxonomy values are represented + with the full taxonomy node information, set this parameter to `hydrated`. + This is the only supported value for this parameter., defaults to None + :type view: Optional[str], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ if extra_headers is None: extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'view': to_string(view)}) headers_map: Dict[str, str] = prepare_params({**extra_headers}) response: FetchResponse = self.network_session.network_client.fetch( FetchOptions( @@ -162,6 +173,7 @@ def get_file_metadata( ] ), method='GET', + params=query_params_map, headers=headers_map, response_format=ResponseFormat.JSON, auth=self.auth, @@ -176,6 +188,7 @@ def get_file_metadata_by_id( scope: GetFileMetadataByIdScope, template_key: str, *, + view: Optional[str] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> MetadataFull: """ @@ -198,11 +211,18 @@ def get_file_metadata_by_id( :param template_key: The name of the metadata template. Example: "properties" :type template_key: str + :param view: Taxonomy field values are returned in `API view` by default, meaning + the value is represented with a taxonomy node identifier. + To retrieve the `Hydrated view`, where taxonomy values are represented + with the full taxonomy node information, set this parameter to `hydrated`. + This is the only supported value for this parameter., defaults to None + :type view: Optional[str], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ if extra_headers is None: extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'view': to_string(view)}) headers_map: Dict[str, str] = prepare_params({**extra_headers}) response: FetchResponse = self.network_session.network_client.fetch( FetchOptions( @@ -218,6 +238,7 @@ def get_file_metadata_by_id( ] ), method='GET', + params=query_params_map, headers=headers_map, response_format=ResponseFormat.JSON, auth=self.auth, diff --git a/box_sdk_gen/managers/folder_metadata.py b/box_sdk_gen/managers/folder_metadata.py index 602effef4..3a88749a9 100644 --- a/box_sdk_gen/managers/folder_metadata.py +++ b/box_sdk_gen/managers/folder_metadata.py @@ -135,6 +135,7 @@ def get_folder_metadata( self, folder_id: str, *, + view: Optional[str] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> Metadatas: """ @@ -154,11 +155,18 @@ def get_folder_metadata( always represented by the ID `0`. Example: "12345" :type folder_id: str + :param view: Taxonomy field values are returned in `API view` by default, meaning + the value is represented with a taxonomy node identifier. + To retrieve the `Hydrated view`, where taxonomy values are represented + with the full taxonomy node information, set this parameter to `hydrated`. + This is the only supported value for this parameter., defaults to None + :type view: Optional[str], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ if extra_headers is None: extra_headers = {} + query_params_map: Dict[str, str] = prepare_params({'view': to_string(view)}) headers_map: Dict[str, str] = prepare_params({**extra_headers}) response: FetchResponse = self.network_session.network_client.fetch( FetchOptions( @@ -171,6 +179,7 @@ def get_folder_metadata( ] ), method='GET', + params=query_params_map, headers=headers_map, response_format=ResponseFormat.JSON, auth=self.auth, diff --git a/box_sdk_gen/managers/metadata_taxonomies.py b/box_sdk_gen/managers/metadata_taxonomies.py new file mode 100644 index 000000000..868e22249 --- /dev/null +++ b/box_sdk_gen/managers/metadata_taxonomies.py @@ -0,0 +1,901 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.internal.utils import to_string + +from typing import List + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.metadata_taxonomy import MetadataTaxonomy + +from box_sdk_gen.schemas.client_error import ClientError + +from box_sdk_gen.schemas.metadata_taxonomies import MetadataTaxonomies + +from box_sdk_gen.schemas.metadata_taxonomy_levels import MetadataTaxonomyLevels + +from box_sdk_gen.schemas.metadata_taxonomy_level import MetadataTaxonomyLevel + +from box_sdk_gen.schemas.metadata_taxonomy_nodes import MetadataTaxonomyNodes + +from box_sdk_gen.schemas.metadata_taxonomy_node import MetadataTaxonomyNode + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import SerializedData + +from box_sdk_gen.serialization.json import sd_to_json + + +class GetMetadataTemplateFieldOptionsScope(str, Enum): + GLOBAL = 'global' + ENTERPRISE = 'enterprise' + + +class MetadataTaxonomiesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_metadata_taxonomy( + self, + display_name: str, + namespace: str, + *, + key: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomy: + """ + Creates a new metadata taxonomy that can be used in + + metadata templates. + + :param display_name: The display name of the taxonomy. + :type display_name: str + :param namespace: The namespace of the metadata taxonomy to create. + :type namespace: str + :param key: The taxonomy key. If it is not provided in the request body, it will be + generated from the `displayName`. The `displayName` would be converted + to lower case, and all spaces and non-alphanumeric characters replaced + with underscores., defaults to None + :type key: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'key': key, + 'displayName': display_name, + 'namespace': namespace, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomy) + + def get_metadata_taxonomies( + self, + namespace: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomies: + """ + Used to retrieve all metadata taxonomies in a namespace. + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + {'marker': to_string(marker), 'limit': to_string(limit)} + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomies) + + def get_metadata_taxonomy_by_key( + self, + namespace: str, + taxonomy_key: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomy: + """ + Used to retrieve a metadata taxonomy by taxonomy key. + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomy) + + def update_metadata_taxonomy( + self, + namespace: str, + taxonomy_key: str, + display_name: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomy: + """ + Updates an existing metadata taxonomy. + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param display_name: The display name of the taxonomy. + :type display_name: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'displayName': display_name} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + ] + ), + method='PATCH', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomy) + + def delete_metadata_taxonomy( + self, + namespace: str, + taxonomy_key: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Delete a metadata taxonomy. + + This deletion is permanent and cannot be reverted. + + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def create_metadata_taxonomy_level( + self, + namespace: str, + taxonomy_key: str, + request_body: List[MetadataTaxonomyLevel], + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomyLevels: + """ + Creates new metadata taxonomy levels. + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param request_body: Request body of createMetadataTaxonomyLevel method + :type request_body: List[MetadataTaxonomyLevel] + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + '/levels', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomyLevels) + + def patch_metadata_taxonomies_id_id_levels_id( + self, + namespace: str, + taxonomy_key: str, + level_index: int, + display_name: str, + *, + description: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomyLevel: + """ + Updates an existing metadata taxonomy level. + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param level_index: The index of the metadata taxonomy level. + Example: 1 + :type level_index: int + :param display_name: The display name of the taxonomy level. + :type display_name: str + :param description: The description of the taxonomy level., defaults to None + :type description: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'displayName': display_name, 'description': description} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + '/levels/', + to_string(level_index), + ] + ), + method='PATCH', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomyLevel) + + def add_metadata_taxonomy_level( + self, + namespace: str, + taxonomy_key: str, + display_name: str, + *, + description: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomyLevels: + """ + Creates a new metadata taxonomy level and appends it to the existing levels. + + If there are no levels defined yet, this will create the first level. + + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param display_name: The display name of the taxonomy level. + :type display_name: str + :param description: The description of the taxonomy level., defaults to None + :type description: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'displayName': display_name, 'description': description} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + '/levels:append', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomyLevels) + + def delete_metadata_taxonomy_level( + self, + namespace: str, + taxonomy_key: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomyLevels: + """ + Deletes the last level of the metadata taxonomy. + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + '/levels:trim', + ] + ), + method='POST', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomyLevels) + + def get_metadata_taxonomy_nodes( + self, + namespace: str, + taxonomy_key: str, + *, + level: Optional[List[int]] = None, + parent: Optional[List[str]] = None, + ancestor: Optional[List[str]] = None, + query: Optional[str] = None, + include_total_result_count: Optional[bool] = None, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomyNodes: + """ + Used to retrieve metadata taxonomy nodes based on the parameters specified. + + Results are sorted in lexicographic order unless a `query` parameter is passed. + + + With a `query` parameter specified, results are sorted in order of relevance. + + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param level: Filters results by taxonomy level. Multiple values can be provided. + Results include nodes that match any of the specified values., defaults to None + :type level: Optional[List[int]], optional + :param parent: Node identifier of a direct parent node. Multiple values can be provided. + Results include nodes that match any of the specified values., defaults to None + :type parent: Optional[List[str]], optional + :param ancestor: Node identifier of any ancestor node. Multiple values can be provided. + Results include nodes that match any of the specified values., defaults to None + :type ancestor: Optional[List[str]], optional + :param query: Query text to search for the taxonomy nodes., defaults to None + :type query: Optional[str], optional + :param include_total_result_count: When set to `true` this provides the total number of nodes that matched the query. + The response will compute counts of up to 10,000 elements. Defaults to `false`., defaults to None + :type include_total_result_count: Optional[bool], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'level': to_string(level), + 'parent': to_string(parent), + 'ancestor': to_string(ancestor), + 'query': to_string(query), + 'include-total-result-count': to_string(include_total_result_count), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + '/nodes', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomyNodes) + + def create_metadata_taxonomy_node( + self, + namespace: str, + taxonomy_key: str, + display_name: str, + level: int, + *, + parent_id: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomyNode: + """ + Creates a new metadata taxonomy node. + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param display_name: The display name of the taxonomy node. + :type display_name: str + :param level: The level of the taxonomy node. + :type level: int + :param parent_id: The identifier of the parent taxonomy node. + Omit this field for root-level nodes., defaults to None + :type parent_id: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'displayName': display_name, + 'level': level, + 'parentId': parent_id, + } + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + '/nodes', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomyNode) + + def get_metadata_taxonomy_node_by_id( + self, + namespace: str, + taxonomy_key: str, + node_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomyNode: + """ + Retrieves a metadata taxonomy node by its identifier. + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param node_id: The identifier of the metadata taxonomy node. + Example: "14d3d433-c77f-49c5-b146-9dea370f6e32" + :type node_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + '/nodes/', + to_string(node_id), + ] + ), + method='GET', + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomyNode) + + def update_metadata_taxonomy_node( + self, + namespace: str, + taxonomy_key: str, + node_id: str, + *, + display_name: Optional[str] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomyNode: + """ + Updates an existing metadata taxonomy node. + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param node_id: The identifier of the metadata taxonomy node. + Example: "14d3d433-c77f-49c5-b146-9dea370f6e32" + :type node_id: str + :param display_name: The display name of the taxonomy node., defaults to None + :type display_name: Optional[str], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'displayName': display_name} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + '/nodes/', + to_string(node_id), + ] + ), + method='PATCH', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomyNode) + + def delete_metadata_taxonomy_node( + self, + namespace: str, + taxonomy_key: str, + node_id: str, + *, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Delete a metadata taxonomy node. + + This deletion is permanent and cannot be reverted. + + + Only metadata taxonomy nodes without any children can be deleted. + + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str + :param taxonomy_key: The key of the metadata taxonomy. + Example: "geography" + :type taxonomy_key: str + :param node_id: The identifier of the metadata taxonomy node. + Example: "14d3d433-c77f-49c5-b146-9dea370f6e32" + :type node_id: str + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_taxonomies/', + to_string(namespace), + '/', + to_string(taxonomy_key), + '/nodes/', + to_string(node_id), + ] + ), + method='DELETE', + headers=headers_map, + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None + + def get_metadata_template_field_options( + self, + scope: GetMetadataTemplateFieldOptionsScope, + template_key: str, + field_key: str, + *, + level: Optional[List[int]] = None, + parent: Optional[List[str]] = None, + ancestor: Optional[List[str]] = None, + query: Optional[str] = None, + include_total_result_count: Optional[bool] = None, + only_selectable_options: Optional[bool] = None, + marker: Optional[str] = None, + limit: Optional[int] = None, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> MetadataTaxonomyNodes: + """ + Used to retrieve metadata taxonomy nodes which are available for the taxonomy field based + + on its configuration and the parameters specified. + + + Results are sorted in lexicographic order unless a `query` parameter is passed. + + + With a `query` parameter specified, results are sorted in order of relevance. + + :param scope: The scope of the metadata template. + Example: "global" + :type scope: GetMetadataTemplateFieldOptionsScope + :param template_key: The name of the metadata template. + Example: "properties" + :type template_key: str + :param field_key: The key of the metadata taxonomy field in the template. + Example: "geography" + :type field_key: str + :param level: Filters results by taxonomy level. Multiple values can be provided. + Results include nodes that match any of the specified values., defaults to None + :type level: Optional[List[int]], optional + :param parent: Node identifier of a direct parent node. Multiple values can be provided. + Results include nodes that match any of the specified values., defaults to None + :type parent: Optional[List[str]], optional + :param ancestor: Node identifier of any ancestor node. Multiple values can be provided. + Results include nodes that match any of the specified values., defaults to None + :type ancestor: Optional[List[str]], optional + :param query: Query text to search for the taxonomy nodes., defaults to None + :type query: Optional[str], optional + :param include_total_result_count: When set to `true` this provides the total number of nodes that matched the query. + The response will compute counts of up to 10,000 elements. Defaults to `false`., defaults to None + :type include_total_result_count: Optional[bool], optional + :param only_selectable_options: When set to `true`, this only returns valid selectable options for this template + taxonomy field. Otherwise, it returns all taxonomy nodes, whether or not they are selectable. + Defaults to `true`., defaults to None + :type only_selectable_options: Optional[bool], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination. + + This requires `usemarker` to be set to `true`., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'level': to_string(level), + 'parent': to_string(parent), + 'ancestor': to_string(ancestor), + 'query': to_string(query), + 'include-total-result-count': to_string(include_total_result_count), + 'only-selectable-options': to_string(only_selectable_options), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params({**extra_headers}) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/metadata_templates/', + to_string(scope), + '/', + to_string(template_key), + '/fields/', + to_string(field_key), + '/options', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, MetadataTaxonomyNodes) diff --git a/box_sdk_gen/managers/metadata_templates.py b/box_sdk_gen/managers/metadata_templates.py index 445c66a57..3c6e12b83 100644 --- a/box_sdk_gen/managers/metadata_templates.py +++ b/box_sdk_gen/managers/metadata_templates.py @@ -150,6 +150,7 @@ class CreateMetadataTemplateFieldsTypeField(str, Enum): DATE = 'date' ENUM = 'enum' MULTISELECT = 'multiSelect' + TAXONOMY = 'taxonomy' class CreateMetadataTemplateFieldsOptionsField(BaseObject): @@ -163,16 +164,58 @@ def __init__(self, key: str, **kwargs): self.key = key +class CreateMetadataTemplateFieldsOptionsRulesField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'multi_select': 'multiSelect', + 'selectable_levels': 'selectableLevels', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'multiSelect': 'multi_select', + 'selectableLevels': 'selectable_levels', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + multi_select: Optional[bool] = None, + selectable_levels: Optional[List[int]] = None, + **kwargs + ): + """ + :param multi_select: Whether to allow users to select multiple values., defaults to None + :type multi_select: Optional[bool], optional + :param selectable_levels: An array of integers defining which levels of the taxonomy are + selectable by users., defaults to None + :type selectable_levels: Optional[List[int]], optional + """ + super().__init__(**kwargs) + self.multi_select = multi_select + self.selectable_levels = selectable_levels + + class CreateMetadataTemplateFields(BaseObject): _fields_to_json_mapping: Dict[str, str] = { 'display_name': 'displayName', + 'taxonomy_key': 'taxonomyKey', + 'options_rules': 'optionsRules', **BaseObject._fields_to_json_mapping, } _json_to_fields_mapping: Dict[str, str] = { 'displayName': 'display_name', + 'taxonomyKey': 'taxonomy_key', + 'optionsRules': 'options_rules', **BaseObject._json_to_fields_mapping, } - _discriminator = 'type', {'string', 'float', 'date', 'enum', 'multiSelect'} + _discriminator = 'type', { + 'string', + 'float', + 'date', + 'enum', + 'multiSelect', + 'taxonomy', + } def __init__( self, @@ -183,16 +226,22 @@ def __init__( description: Optional[str] = None, hidden: Optional[bool] = None, options: Optional[List[CreateMetadataTemplateFieldsOptionsField]] = None, + taxonomy_key: Optional[str] = None, + namespace: Optional[str] = None, + options_rules: Optional[CreateMetadataTemplateFieldsOptionsRulesField] = None, **kwargs ): """ :param type: The type of field. The basic fields are a `string` field for text, a - `float` field for numbers, and a `date` fields to present the user with a + `float` field for numbers, and a `date` field to present the user with a date-time picker. Additionally, metadata templates support an `enum` field for a basic list of items, and ` multiSelect` field for a similar list of items where the user can select more than one value. + + Metadata taxonomies are also supported as a `taxonomy` field type + with a specific set of additional properties, which describe its structure. :type type: CreateMetadataTemplateFieldsTypeField :param key: A unique identifier for the field. The identifier must be unique within the template to which it belongs. @@ -208,6 +257,15 @@ def __init__( :param options: A list of options for this field. This is used in combination with the `enum` and `multiSelect` field types., defaults to None :type options: Optional[List[CreateMetadataTemplateFieldsOptionsField]], optional + :param taxonomy_key: The unique key of the metadata taxonomy to use for this taxonomy field. + This property is required when the field `type` is set to `taxonomy`., defaults to None + :type taxonomy_key: Optional[str], optional + :param namespace: The namespace of the metadata taxonomy to use for this taxonomy field. + This property is required when the field `type` is set to `taxonomy`., defaults to None + :type namespace: Optional[str], optional + :param options_rules: An object defining additional rules for the options of the taxonomy field. + This property is required when the field `type` is set to `taxonomy`., defaults to None + :type options_rules: Optional[CreateMetadataTemplateFieldsOptionsRulesField], optional """ super().__init__(**kwargs) self.type = type @@ -216,6 +274,9 @@ def __init__( self.description = description self.hidden = hidden self.options = options + self.taxonomy_key = taxonomy_key + self.namespace = namespace + self.options_rules = options_rules class MetadataTemplatesManager: diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index 5a2ff543c..023aac177 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -216,6 +216,20 @@ from box_sdk_gen.schemas.metadata_query import * +from box_sdk_gen.schemas.metadata_taxonomy_ancestor import * + +from box_sdk_gen.schemas.metadata_taxonomy_node import * + +from box_sdk_gen.schemas.metadata_taxonomy_nodes import * + +from box_sdk_gen.schemas.metadata_taxonomy_level import * + +from box_sdk_gen.schemas.metadata_taxonomy_levels import * + +from box_sdk_gen.schemas.metadata_taxonomy import * + +from box_sdk_gen.schemas.metadata_taxonomies import * + from box_sdk_gen.schemas.metadata_template import * from box_sdk_gen.schemas.metadata_templates import * diff --git a/box_sdk_gen/schemas/metadata_taxonomies.py b/box_sdk_gen/schemas/metadata_taxonomies.py new file mode 100644 index 000000000..fcfb9e6a3 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_taxonomies.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.metadata_taxonomy import MetadataTaxonomy + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataTaxonomies(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[MetadataTaxonomy]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of metadata taxonomies., defaults to None + :type entries: Optional[List[MetadataTaxonomy]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/box_sdk_gen/schemas/metadata_taxonomy.py b/box_sdk_gen/schemas/metadata_taxonomy.py new file mode 100644 index 000000000..796d43822 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_taxonomy.py @@ -0,0 +1,52 @@ +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.metadata_taxonomy_level import MetadataTaxonomyLevel + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataTaxonomy(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + id: str, + display_name: str, + namespace: str, + *, + key: Optional[str] = None, + levels: Optional[List[MetadataTaxonomyLevel]] = None, + **kwargs + ): + """ + :param id: A unique identifier of the metadata taxonomy. + :type id: str + :param display_name: The display name of the metadata taxonomy. This can be seen in the Box web app. + :type display_name: str + :param namespace: A namespace that the metadata taxonomy is associated with. + :type namespace: str + :param key: A unique identifier of the metadata taxonomy. The identifier must be unique within + the namespace to which it belongs., defaults to None + :type key: Optional[str], optional + :param levels: Levels of the metadata taxonomy., defaults to None + :type levels: Optional[List[MetadataTaxonomyLevel]], optional + """ + super().__init__(**kwargs) + self.id = id + self.display_name = display_name + self.namespace = namespace + self.key = key + self.levels = levels diff --git a/box_sdk_gen/schemas/metadata_taxonomy_ancestor.py b/box_sdk_gen/schemas/metadata_taxonomy_ancestor.py new file mode 100644 index 000000000..b781c0d5e --- /dev/null +++ b/box_sdk_gen/schemas/metadata_taxonomy_ancestor.py @@ -0,0 +1,39 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataTaxonomyAncestor(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + id: Optional[str] = None, + display_name: Optional[str] = None, + level: Optional[int] = None, + **kwargs + ): + """ + :param id: A unique identifier of the metadata taxonomy node., defaults to None + :type id: Optional[str], optional + :param display_name: The display name of the metadata taxonomy node., defaults to None + :type display_name: Optional[str], optional + :param level: An index of the level to which the node belongs., defaults to None + :type level: Optional[int], optional + """ + super().__init__(**kwargs) + self.id = id + self.display_name = display_name + self.level = level diff --git a/box_sdk_gen/schemas/metadata_taxonomy_level.py b/box_sdk_gen/schemas/metadata_taxonomy_level.py new file mode 100644 index 000000000..91f2d7f18 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_taxonomy_level.py @@ -0,0 +1,39 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataTaxonomyLevel(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + display_name: Optional[str] = None, + description: Optional[str] = None, + level: Optional[int] = None, + **kwargs + ): + """ + :param display_name: The display name of the level as it is shown to the user., defaults to None + :type display_name: Optional[str], optional + :param description: A description of the level., defaults to None + :type description: Optional[str], optional + :param level: An index of the level within the taxonomy. Levels are indexed starting from 1., defaults to None + :type level: Optional[int], optional + """ + super().__init__(**kwargs) + self.display_name = display_name + self.description = description + self.level = level diff --git a/box_sdk_gen/schemas/metadata_taxonomy_levels.py b/box_sdk_gen/schemas/metadata_taxonomy_levels.py new file mode 100644 index 000000000..e2582a8d1 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_taxonomy_levels.py @@ -0,0 +1,21 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.metadata_taxonomy_level import MetadataTaxonomyLevel + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataTaxonomyLevels(BaseObject): + def __init__( + self, *, entries: Optional[List[MetadataTaxonomyLevel]] = None, **kwargs + ): + """ + :param entries: An array of all taxonomy levels., defaults to None + :type entries: Optional[List[MetadataTaxonomyLevel]], optional + """ + super().__init__(**kwargs) + self.entries = entries diff --git a/box_sdk_gen/schemas/metadata_taxonomy_node.py b/box_sdk_gen/schemas/metadata_taxonomy_node.py new file mode 100644 index 000000000..eabe4c15a --- /dev/null +++ b/box_sdk_gen/schemas/metadata_taxonomy_node.py @@ -0,0 +1,61 @@ +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.metadata_taxonomy_ancestor import MetadataTaxonomyAncestor + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataTaxonomyNode(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + 'parent_id': 'parentId', + 'node_path': 'nodePath', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + 'parentId': 'parent_id', + 'nodePath': 'node_path', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + id: str, + display_name: str, + level: int, + *, + parent_id: Optional[str] = None, + node_path: Optional[List[str]] = None, + ancestors: Optional[List[MetadataTaxonomyAncestor]] = None, + **kwargs + ): + """ + :param id: A unique identifier of the metadata taxonomy node. + :type id: str + :param display_name: The display name of the metadata taxonomy node. + :type display_name: str + :param level: An index of the level to which the node belongs. + :type level: int + :param parent_id: The identifier of the parent node., defaults to None + :type parent_id: Optional[str], optional + :param node_path: An array of identifiers for all ancestor nodes. + Not returned for root-level nodes., defaults to None + :type node_path: Optional[List[str]], optional + :param ancestors: An array of objects for all ancestor nodes. + Not returned for root-level nodes., defaults to None + :type ancestors: Optional[List[MetadataTaxonomyAncestor]], optional + """ + super().__init__(**kwargs) + self.id = id + self.display_name = display_name + self.level = level + self.parent_id = parent_id + self.node_path = node_path + self.ancestors = ancestors diff --git a/box_sdk_gen/schemas/metadata_taxonomy_nodes.py b/box_sdk_gen/schemas/metadata_taxonomy_nodes.py new file mode 100644 index 000000000..d678d0925 --- /dev/null +++ b/box_sdk_gen/schemas/metadata_taxonomy_nodes.py @@ -0,0 +1,38 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.metadata_taxonomy_node import MetadataTaxonomyNode + +from box_sdk_gen.box.errors import BoxSDKError + + +class MetadataTaxonomyNodes(BaseObject): + def __init__( + self, + *, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + prev_marker: Optional[str] = None, + entries: Optional[List[MetadataTaxonomyNode]] = None, + **kwargs + ): + """ + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + :param prev_marker: The marker for the start of the previous page of results., defaults to None + :type prev_marker: Optional[str], optional + :param entries: A list of metadata taxonomy nodes., defaults to None + :type entries: Optional[List[MetadataTaxonomyNode]], optional + """ + super().__init__(**kwargs) + self.limit = limit + self.next_marker = next_marker + self.prev_marker = prev_marker + self.entries = entries diff --git a/docs/README.md b/docs/README.md index 5e353d405..61d2c712a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -49,6 +49,7 @@ the SDK are available by topic: - [List collaborations](list_collaborations.md) - [Memberships](memberships.md) - [Metadata cascade policies](metadata_cascade_policies.md) +- [Metadata taxonomies](metadata_taxonomies.md) - [Metadata templates](metadata_templates.md) - [Recent items](recent_items.md) - [Retention policies](retention_policies.md) diff --git a/docs/authentication.md b/docs/authentication.md index cbe78f305..115db3994 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -60,9 +60,11 @@ print(f"My user ID is {me.id}") Authenticating with a JWT requires some extra dependencies. To get them, use ``` -pip install "box-sdk-gen[jwt]" +pip install "boxsdk[jwt]" ``` +You may need to specify a version to install. + Before using JWT Auth make sure you set up correctly your Box platform app. The guide with all required steps can be found here: [Setup with JWT][jwt_guide] diff --git a/docs/file_metadata.md b/docs/file_metadata.md index 5f611f001..3d1a48cd6 100644 --- a/docs/file_metadata.md +++ b/docs/file_metadata.md @@ -25,6 +25,8 @@ client.file_metadata.get_file_metadata(file.id) - file_id `str` - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" +- view `Optional[str]` + - Taxonomy field values are returned in `API view` by default, meaning the value is represented with a taxonomy node identifier. To retrieve the `Hydrated view`, where taxonomy values are represented with the full taxonomy node information, set this parameter to `hydrated`. This is the only supported value for this parameter. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -63,6 +65,8 @@ client.file_metadata.get_file_metadata_by_id( - The scope of the metadata template. Example: "global" - template_key `str` - The name of the metadata template. Example: "properties" +- view `Optional[str]` + - Taxonomy field values are returned in `API view` by default, meaning the value is represented with a taxonomy node identifier. To retrieve the `Hydrated view`, where taxonomy values are represented with the full taxonomy node information, set this parameter to `hydrated`. This is the only supported value for this parameter. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/folder_metadata.md b/docs/folder_metadata.md index 0f6b729cb..b3d5e63fc 100644 --- a/docs/folder_metadata.md +++ b/docs/folder_metadata.md @@ -26,6 +26,8 @@ client.folder_metadata.get_folder_metadata(folder.id) - folder_id `str` - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" +- view `Optional[str]` + - Taxonomy field values are returned in `API view` by default, meaning the value is represented with a taxonomy node identifier. To retrieve the `Hydrated view`, where taxonomy values are represented with the full taxonomy node information, set this parameter to `hydrated`. This is the only supported value for this parameter. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/metadata_taxonomies.md b/docs/metadata_taxonomies.md new file mode 100644 index 000000000..9c03b697e --- /dev/null +++ b/docs/metadata_taxonomies.md @@ -0,0 +1,482 @@ +# MetadataTaxonomiesManager + +- [Create metadata taxonomy](#create-metadata-taxonomy) +- [Get metadata taxonomies for namespace](#get-metadata-taxonomies-for-namespace) +- [Get metadata taxonomy by taxonomy key](#get-metadata-taxonomy-by-taxonomy-key) +- [Update metadata taxonomy](#update-metadata-taxonomy) +- [Remove metadata taxonomy](#remove-metadata-taxonomy) +- [Create metadata taxonomy levels](#create-metadata-taxonomy-levels) +- [Update metadata taxonomy level](#update-metadata-taxonomy-level) +- [Add metadata taxonomy level](#add-metadata-taxonomy-level) +- [Delete metadata taxonomy level](#delete-metadata-taxonomy-level) +- [List metadata taxonomy nodes](#list-metadata-taxonomy-nodes) +- [Create metadata taxonomy node](#create-metadata-taxonomy-node) +- [Get metadata taxonomy node by ID](#get-metadata-taxonomy-node-by-id) +- [Update metadata taxonomy node](#update-metadata-taxonomy-node) +- [Remove metadata taxonomy node](#remove-metadata-taxonomy-node) +- [List metadata template's options for taxonomy field](#list-metadata-templates-options-for-taxonomy-field) + +## Create metadata taxonomy + +Creates a new metadata taxonomy that can be used in +metadata templates. + +This operation is performed by calling function `create_metadata_taxonomy`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-metadata-taxonomies/). + +_Currently we don't have an example for calling `create_metadata_taxonomy` in integration tests_ + +### Arguments + +- key `Optional[str]` + - The taxonomy key. If it is not provided in the request body, it will be generated from the `displayName`. The `displayName` would be converted to lower case, and all spaces and non-alphanumeric characters replaced with underscores. +- display_name `str` + - The display name of the taxonomy. +- namespace `str` + - The namespace of the metadata taxonomy to create. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomy`. + +The schema representing the metadata taxonomy created. + +## Get metadata taxonomies for namespace + +Used to retrieve all metadata taxonomies in a namespace. + +This operation is performed by calling function `get_metadata_taxonomies`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-taxonomies-id/). + +_Currently we don't have an example for calling `get_metadata_taxonomies` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomies`. + +Returns all of the metadata taxonomies within a namespace +and their corresponding schema. + +## Get metadata taxonomy by taxonomy key + +Used to retrieve a metadata taxonomy by taxonomy key. + +This operation is performed by calling function `get_metadata_taxonomy_by_key`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-taxonomies-id-id/). + +_Currently we don't have an example for calling `get_metadata_taxonomy_by_key` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomy`. + +Returns the metadata taxonomy identified by the taxonomy key. + +## Update metadata taxonomy + +Updates an existing metadata taxonomy. + +This operation is performed by calling function `update_metadata_taxonomy`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/patch-metadata-taxonomies-id-id/). + +_Currently we don't have an example for calling `update_metadata_taxonomy` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- display_name `str` + - The display name of the taxonomy. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomy`. + +The schema representing the updated metadata taxonomy. + +## Remove metadata taxonomy + +Delete a metadata taxonomy. +This deletion is permanent and cannot be reverted. + +This operation is performed by calling function `delete_metadata_taxonomy`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-metadata-taxonomies-id-id/). + +_Currently we don't have an example for calling `delete_metadata_taxonomy` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the metadata taxonomy is successfully deleted. + +## Create metadata taxonomy levels + +Creates new metadata taxonomy levels. + +This operation is performed by calling function `create_metadata_taxonomy_level`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-metadata-taxonomies-id-id-levels/). + +_Currently we don't have an example for calling `create_metadata_taxonomy_level` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- request_body `List[MetadataTaxonomyLevel]` + - Request body of createMetadataTaxonomyLevel method +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomyLevels`. + +Returns an array of all taxonomy levels. + +## Update metadata taxonomy level + +Updates an existing metadata taxonomy level. + +This operation is performed by calling function `patch_metadata_taxonomies_id_id_levels_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/patch-metadata-taxonomies-id-id-levels-id/). + +_Currently we don't have an example for calling `patch_metadata_taxonomies_id_id_levels_id` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- level_index `int` + - The index of the metadata taxonomy level. Example: 1 +- display_name `str` + - The display name of the taxonomy level. +- description `Optional[str]` + - The description of the taxonomy level. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomyLevel`. + +The updated taxonomy level. + +## Add metadata taxonomy level + +Creates a new metadata taxonomy level and appends it to the existing levels. +If there are no levels defined yet, this will create the first level. + +This operation is performed by calling function `add_metadata_taxonomy_level`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-metadata-taxonomies-id-id-levels:append/). + +_Currently we don't have an example for calling `add_metadata_taxonomy_level` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- display_name `str` + - The display name of the taxonomy level. +- description `Optional[str]` + - The description of the taxonomy level. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomyLevels`. + +Returns an array of all taxonomy levels. + +## Delete metadata taxonomy level + +Deletes the last level of the metadata taxonomy. + +This operation is performed by calling function `delete_metadata_taxonomy_level`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-metadata-taxonomies-id-id-levels:trim/). + +_Currently we don't have an example for calling `delete_metadata_taxonomy_level` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomyLevels`. + +Returns an array of all taxonomy levels. + +## List metadata taxonomy nodes + +Used to retrieve metadata taxonomy nodes based on the parameters specified. +Results are sorted in lexicographic order unless a `query` parameter is passed. +With a `query` parameter specified, results are sorted in order of relevance. + +This operation is performed by calling function `get_metadata_taxonomy_nodes`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-taxonomies-id-id-nodes/). + +_Currently we don't have an example for calling `get_metadata_taxonomy_nodes` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- level `Optional[List[int]]` + - Filters results by taxonomy level. Multiple values can be provided. Results include nodes that match any of the specified values. +- parent `Optional[List[str]]` + - Node identifier of a direct parent node. Multiple values can be provided. Results include nodes that match any of the specified values. +- ancestor `Optional[List[str]]` + - Node identifier of any ancestor node. Multiple values can be provided. Results include nodes that match any of the specified values. +- query `Optional[str]` + - Query text to search for the taxonomy nodes. +- include_total_result_count `Optional[bool]` + - When set to `true` this provides the total number of nodes that matched the query. The response will compute counts of up to 10,000 elements. Defaults to `false`. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomyNodes`. + +Returns a list of the taxonomy nodes that match the specified parameters. + +## Create metadata taxonomy node + +Creates a new metadata taxonomy node. + +This operation is performed by calling function `create_metadata_taxonomy_node`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/post-metadata-taxonomies-id-id-nodes/). + +_Currently we don't have an example for calling `create_metadata_taxonomy_node` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- display_name `str` + - The display name of the taxonomy node. +- level `int` + - The level of the taxonomy node. +- parent_id `Optional[str]` + - The identifier of the parent taxonomy node. Omit this field for root-level nodes. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomyNode`. + +The schema representing the taxonomy node created. + +## Get metadata taxonomy node by ID + +Retrieves a metadata taxonomy node by its identifier. + +This operation is performed by calling function `get_metadata_taxonomy_node_by_id`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-taxonomies-id-id-nodes-id/). + +_Currently we don't have an example for calling `get_metadata_taxonomy_node_by_id` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- node_id `str` + - The identifier of the metadata taxonomy node. Example: "14d3d433-c77f-49c5-b146-9dea370f6e32" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomyNode`. + +Returns the metadata taxonomy node that matches the identifier. + +## Update metadata taxonomy node + +Updates an existing metadata taxonomy node. + +This operation is performed by calling function `update_metadata_taxonomy_node`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/patch-metadata-taxonomies-id-id-nodes-id/). + +_Currently we don't have an example for calling `update_metadata_taxonomy_node` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- node_id `str` + - The identifier of the metadata taxonomy node. Example: "14d3d433-c77f-49c5-b146-9dea370f6e32" +- display_name `Optional[str]` + - The display name of the taxonomy node. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomyNode`. + +The schema representing the updated taxonomy node. + +## Remove metadata taxonomy node + +Delete a metadata taxonomy node. +This deletion is permanent and cannot be reverted. +Only metadata taxonomy nodes without any children can be deleted. + +This operation is performed by calling function `delete_metadata_taxonomy_node`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/delete-metadata-taxonomies-id-id-nodes-id/). + +_Currently we don't have an example for calling `delete_metadata_taxonomy_node` in integration tests_ + +### Arguments + +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" +- taxonomy_key `str` + - The key of the metadata taxonomy. Example: "geography" +- node_id `str` + - The identifier of the metadata taxonomy node. Example: "14d3d433-c77f-49c5-b146-9dea370f6e32" +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Returns an empty response when the metadata taxonomy node is successfully deleted. + +## List metadata template's options for taxonomy field + +Used to retrieve metadata taxonomy nodes which are available for the taxonomy field based +on its configuration and the parameters specified. +Results are sorted in lexicographic order unless a `query` parameter is passed. +With a `query` parameter specified, results are sorted in order of relevance. + +This operation is performed by calling function `get_metadata_template_field_options`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/get-metadata-templates-id-id-fields-id-options/). + +_Currently we don't have an example for calling `get_metadata_template_field_options` in integration tests_ + +### Arguments + +- scope `GetMetadataTemplateFieldOptionsScope` + - The scope of the metadata template. Example: "global" +- template_key `str` + - The name of the metadata template. Example: "properties" +- field_key `str` + - The key of the metadata taxonomy field in the template. Example: "geography" +- level `Optional[List[int]]` + - Filters results by taxonomy level. Multiple values can be provided. Results include nodes that match any of the specified values. +- parent `Optional[List[str]]` + - Node identifier of a direct parent node. Multiple values can be provided. Results include nodes that match any of the specified values. +- ancestor `Optional[List[str]]` + - Node identifier of any ancestor node. Multiple values can be provided. Results include nodes that match any of the specified values. +- query `Optional[str]` + - Query text to search for the taxonomy nodes. +- include_total_result_count `Optional[bool]` + - When set to `true` this provides the total number of nodes that matched the query. The response will compute counts of up to 10,000 elements. Defaults to `false`. +- only_selectable_options `Optional[bool]` + - When set to `true`, this only returns valid selectable options for this template taxonomy field. Otherwise, it returns all taxonomy nodes, whether or not they are selectable. Defaults to `true`. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `MetadataTaxonomyNodes`. + +Returns a list of the taxonomy nodes that match the specified parameters. From dcfc504d5feb3c141d99dd57c34fbfb0c07f4144 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:29:59 +0100 Subject: [PATCH 067/139] test: fix DocGen test (box/box-codegen#907) (#1254) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- docs/docgen.md | 2 +- test/docgen.py | 13 ++++++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.codegen.json b/.codegen.json index 873459285..d40adfb75 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "b5860f1", "specHash": "1e0848d", "version": "10.2.0" } +{ "engineHash": "576c827", "specHash": "1e0848d", "version": "10.2.0" } diff --git a/docs/docgen.md b/docs/docgen.md index 6ba00e607..d0994c7ec 100644 --- a/docs/docgen.md +++ b/docs/docgen.md @@ -114,7 +114,7 @@ See the endpoint docs at ```python client.docgen.create_docgen_batch_v2025_r0( - FileReferenceV2025R0(id=uploaded_file.id), + FileReferenceV2025R0(id=uploaded_file_docx.id), "api", CreateDocgenBatchV2025R0DestinationFolder(id=folder.id), "pdf", diff --git a/test/docgen.py b/test/docgen.py index d1f7d1765..fb3b3d80d 100644 --- a/test/docgen.py +++ b/test/docgen.py @@ -42,15 +42,18 @@ def testDocgenBatchAndJobs(): - uploaded_file: FileFull = upload_new_file() + uploaded_file_pdf: FileFull = upload_new_file() + uploaded_file_docx: FileFull = client.files.update_file_by_id( + uploaded_file_pdf.id, name=''.join([uploaded_file_pdf.name, '.docx']) + ) folder: FolderFull = create_new_folder() created_docgen_template: DocGenTemplateBaseV2025R0 = ( client.docgen_template.create_docgen_template_v2025_r0( - FileReferenceV2025R0(id=uploaded_file.id) + FileReferenceV2025R0(id=uploaded_file_docx.id) ) ) docgen_batch: DocGenBatchBaseV2025R0 = client.docgen.create_docgen_batch_v2025_r0( - FileReferenceV2025R0(id=uploaded_file.id), + FileReferenceV2025R0(id=uploaded_file_docx.id), 'api', CreateDocgenBatchV2025R0DestinationFolder(id=folder.id), 'pdf', @@ -70,7 +73,7 @@ def testDocgenBatchAndJobs(): assert to_string(docgen_batch_jobs.entries[0].type) == 'docgen_job' assert docgen_batch_jobs.entries[0].output_type == 'pdf' assert not to_string(docgen_batch_jobs.entries[0].status) == '' - assert docgen_batch_jobs.entries[0].template_file.id == uploaded_file.id + assert docgen_batch_jobs.entries[0].template_file.id == uploaded_file_docx.id assert docgen_batch_jobs.entries[0].batch.id == docgen_batch.id docgen_jobs: DocGenJobsFullV2025R0 = client.docgen.get_docgen_jobs_v2025_r0( limit=10000 @@ -105,4 +108,4 @@ def testDocgenBatchAndJobs(): assert not docgen_job.template_file_version.id == '' assert to_string(docgen_job.type) == 'docgen_job' client.folders.delete_folder_by_id(folder.id) - client.files.delete_file_by_id(uploaded_file.id) + client.files.delete_file_by_id(uploaded_file_docx.id) From 12c05dcebae13a696956288f41ec55fed8a4011e Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:54:22 +0100 Subject: [PATCH 068/139] feat: Treat `nullable` fields as Optional (box/box-codegen#906) (#1256) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- ...ign_request_signer_input_custom_validation.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.codegen.json b/.codegen.json index d40adfb75..ae1d027ab 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "576c827", "specHash": "1e0848d", "version": "10.2.0" } +{ "engineHash": "de55410", "specHash": "1e0848d", "version": "10.2.0" } diff --git a/box_sdk_gen/schemas/sign_request_signer_input_custom_validation.py b/box_sdk_gen/schemas/sign_request_signer_input_custom_validation.py index 2b2188899..5c0d7d729 100644 --- a/box_sdk_gen/schemas/sign_request_signer_input_custom_validation.py +++ b/box_sdk_gen/schemas/sign_request_signer_input_custom_validation.py @@ -1,5 +1,7 @@ from enum import Enum +from typing import Optional + from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.box.errors import BoxSDKError @@ -12,22 +14,22 @@ class SignRequestSignerInputCustomValidationValidationTypeField(str, Enum): class SignRequestSignerInputCustomValidation(BaseObject): def __init__( self, - custom_regex: str, - custom_error_message: str, *, validation_type: SignRequestSignerInputCustomValidationValidationTypeField = SignRequestSignerInputCustomValidationValidationTypeField.CUSTOM, + custom_regex: Optional[str] = None, + custom_error_message: Optional[str] = None, **kwargs ): """ - :param custom_regex: Regular expression used for validation. - :type custom_regex: str - :param custom_error_message: Error message shown if input fails custom regular expression validation. - :type custom_error_message: str :param validation_type: Defines the validation format for the text input as custom. A custom regular expression is used for validation., defaults to SignRequestSignerInputCustomValidationValidationTypeField.CUSTOM :type validation_type: SignRequestSignerInputCustomValidationValidationTypeField, optional + :param custom_regex: Regular expression used for validation., defaults to None + :type custom_regex: Optional[str], optional + :param custom_error_message: Error message shown if input fails custom regular expression validation., defaults to None + :type custom_error_message: Optional[str], optional """ super().__init__(**kwargs) + self.validation_type = validation_type self.custom_regex = custom_regex self.custom_error_message = custom_error_message - self.validation_type = validation_type From 132a1b88af780f3feacbf26f9024e31103929416 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:42:03 +0100 Subject: [PATCH 069/139] chore: Make `dependabot` builds run without secrets (box/box-codegen#904) (#1260) --- .codegen.json | 2 +- .github/workflows/build.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.codegen.json b/.codegen.json index ae1d027ab..c96fa681e 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "de55410", "specHash": "1e0848d", "version": "10.2.0" } +{ "engineHash": "467757c", "specHash": "1e0848d", "version": "10.2.0" } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2755672f5..68c075193 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: python -m pip install -e .[dev] python -m pip install tox-gh-actions - name: All Tests - if: startsWith(github.head_ref, 'codegen-release') + if: ${{ startsWith(github.head_ref, 'codegen-release') && github.actor != 'dependabot[bot]' }} env: JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} ADMIN_USER_ID: ${{ secrets.ADMIN_USER_ID }} @@ -51,7 +51,7 @@ jobs: run: | tox - name: Smoke Tests - if: "!startsWith(github.head_ref, 'codegen-release')" + if: ${{ !startsWith(github.head_ref, 'codegen-release') && github.actor != 'dependabot[bot]' }} env: JWT_CONFIG_BASE_64: ${{ secrets.JWT_CONFIG_BASE_64 }} CLIENT_ID: ${{ secrets.CLIENT_ID }} From 2bb54cfba20cee38c98cb8d54ce86a5f5c8faf71 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:43:02 +0100 Subject: [PATCH 070/139] test: Metadata Taxonomies integration tests (box/box-codegen#897) (#1264) Co-authored-by: Minh Nguyen Cong --- docs/metadata_taxonomies.md | 99 ++++++++++++++++++++--- test/metadata_taxonomies.py | 156 ++++++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 13 deletions(-) create mode 100644 test/metadata_taxonomies.py diff --git a/docs/metadata_taxonomies.md b/docs/metadata_taxonomies.md index 9c03b697e..ec7bf6b61 100644 --- a/docs/metadata_taxonomies.md +++ b/docs/metadata_taxonomies.md @@ -26,7 +26,13 @@ This operation is performed by calling function `create_metadata_taxonomy`. See the endpoint docs at [API Reference](https://developer.box.com/reference/post-metadata-taxonomies/). -_Currently we don't have an example for calling `create_metadata_taxonomy` in integration tests_ + + +```python +client.metadata_taxonomies.create_metadata_taxonomy( + display_name, namespace, key=taxonomy_key +) +``` ### Arguments @@ -54,7 +60,11 @@ This operation is performed by calling function `get_metadata_taxonomies`. See the endpoint docs at [API Reference](https://developer.box.com/reference/get-metadata-taxonomies-id/). -_Currently we don't have an example for calling `get_metadata_taxonomies` in integration tests_ + + +```python +client.metadata_taxonomies.get_metadata_taxonomies(namespace) +``` ### Arguments @@ -83,7 +93,11 @@ This operation is performed by calling function `get_metadata_taxonomy_by_key`. See the endpoint docs at [API Reference](https://developer.box.com/reference/get-metadata-taxonomies-id-id/). -_Currently we don't have an example for calling `get_metadata_taxonomy_by_key` in integration tests_ + + +```python +client.metadata_taxonomies.get_metadata_taxonomy_by_key(namespace, taxonomy_key) +``` ### Arguments @@ -109,7 +123,13 @@ This operation is performed by calling function `update_metadata_taxonomy`. See the endpoint docs at [API Reference](https://developer.box.com/reference/patch-metadata-taxonomies-id-id/). -_Currently we don't have an example for calling `update_metadata_taxonomy` in integration tests_ + + +```python +client.metadata_taxonomies.update_metadata_taxonomy( + namespace, taxonomy_key, updated_display_name +) +``` ### Arguments @@ -138,7 +158,11 @@ This operation is performed by calling function `delete_metadata_taxonomy`. See the endpoint docs at [API Reference](https://developer.box.com/reference/delete-metadata-taxonomies-id-id/). -_Currently we don't have an example for calling `delete_metadata_taxonomy` in integration tests_ + + +```python +client.metadata_taxonomies.delete_metadata_taxonomy(namespace, taxonomy_key) +``` ### Arguments @@ -164,7 +188,18 @@ This operation is performed by calling function `create_metadata_taxonomy_level` See the endpoint docs at [API Reference](https://developer.box.com/reference/post-metadata-taxonomies-id-id-levels/). -_Currently we don't have an example for calling `create_metadata_taxonomy_level` in integration tests_ + + +```python +client.metadata_taxonomies.create_metadata_taxonomy_level( + namespace, + taxonomy_key, + [ + MetadataTaxonomyLevel(display_name="Continent", description="Continent Level"), + MetadataTaxonomyLevel(display_name="Country", description="Country Level"), + ], +) +``` ### Arguments @@ -225,7 +260,13 @@ This operation is performed by calling function `add_metadata_taxonomy_level`. See the endpoint docs at [API Reference](https://developer.box.com/reference/post-metadata-taxonomies-id-id-levels:append/). -_Currently we don't have an example for calling `add_metadata_taxonomy_level` in integration tests_ + + +```python +client.metadata_taxonomies.add_metadata_taxonomy_level( + namespace, taxonomy_key, "Region", description="Region Description" +) +``` ### Arguments @@ -255,7 +296,11 @@ This operation is performed by calling function `delete_metadata_taxonomy_level` See the endpoint docs at [API Reference](https://developer.box.com/reference/post-metadata-taxonomies-id-id-levels:trim/). -_Currently we don't have an example for calling `delete_metadata_taxonomy_level` in integration tests_ + + +```python +client.metadata_taxonomies.delete_metadata_taxonomy_level(namespace, taxonomy_key) +``` ### Arguments @@ -283,7 +328,11 @@ This operation is performed by calling function `get_metadata_taxonomy_nodes`. See the endpoint docs at [API Reference](https://developer.box.com/reference/get-metadata-taxonomies-id-id-nodes/). -_Currently we don't have an example for calling `get_metadata_taxonomy_nodes` in integration tests_ + + +```python +client.metadata_taxonomies.get_metadata_taxonomy_nodes(namespace, taxonomy_key) +``` ### Arguments @@ -323,7 +372,13 @@ This operation is performed by calling function `create_metadata_taxonomy_node`. See the endpoint docs at [API Reference](https://developer.box.com/reference/post-metadata-taxonomies-id-id-nodes/). -_Currently we don't have an example for calling `create_metadata_taxonomy_node` in integration tests_ + + +```python +client.metadata_taxonomies.create_metadata_taxonomy_node( + namespace, taxonomy_key, "Europe", 1 +) +``` ### Arguments @@ -355,7 +410,13 @@ This operation is performed by calling function `get_metadata_taxonomy_node_by_i See the endpoint docs at [API Reference](https://developer.box.com/reference/get-metadata-taxonomies-id-id-nodes-id/). -_Currently we don't have an example for calling `get_metadata_taxonomy_node_by_id` in integration tests_ + + +```python +client.metadata_taxonomies.get_metadata_taxonomy_node_by_id( + namespace, taxonomy_key, country_node.id +) +``` ### Arguments @@ -383,7 +444,13 @@ This operation is performed by calling function `update_metadata_taxonomy_node`. See the endpoint docs at [API Reference](https://developer.box.com/reference/patch-metadata-taxonomies-id-id-nodes-id/). -_Currently we don't have an example for calling `update_metadata_taxonomy_node` in integration tests_ + + +```python +client.metadata_taxonomies.update_metadata_taxonomy_node( + namespace, taxonomy_key, country_node.id, display_name="Poland UPDATED" +) +``` ### Arguments @@ -415,7 +482,13 @@ This operation is performed by calling function `delete_metadata_taxonomy_node`. See the endpoint docs at [API Reference](https://developer.box.com/reference/delete-metadata-taxonomies-id-id-nodes-id/). -_Currently we don't have an example for calling `delete_metadata_taxonomy_node` in integration tests_ + + +```python +client.metadata_taxonomies.delete_metadata_taxonomy_node( + namespace, taxonomy_key, country_node.id +) +``` ### Arguments diff --git a/test/metadata_taxonomies.py b/test/metadata_taxonomies.py new file mode 100644 index 000000000..13613a9e9 --- /dev/null +++ b/test/metadata_taxonomies.py @@ -0,0 +1,156 @@ +import pytest + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.metadata_taxonomy import MetadataTaxonomy + +from box_sdk_gen.schemas.metadata_taxonomies import MetadataTaxonomies + +from box_sdk_gen.schemas.metadata_taxonomy_levels import MetadataTaxonomyLevels + +from box_sdk_gen.schemas.metadata_taxonomy_level import MetadataTaxonomyLevel + +from box_sdk_gen.schemas.metadata_taxonomy_node import MetadataTaxonomyNode + +from box_sdk_gen.schemas.metadata_taxonomy_nodes import MetadataTaxonomyNodes + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import delay_in_seconds + +from test.commons import get_default_client + +client: BoxClient = get_default_client() + + +def testMetadataTaxonomiesCRUD(): + namespace: str = ''.join(['enterprise_', get_env_var('ENTERPRISE_ID')]) + uuid: str = get_uuid() + taxonomy_key: str = ''.join(['geography', uuid]) + display_name: str = ''.join(['Geography Taxonomy', uuid]) + created_taxonomy: MetadataTaxonomy = ( + client.metadata_taxonomies.create_metadata_taxonomy( + display_name, namespace, key=taxonomy_key + ) + ) + assert created_taxonomy.display_name == display_name + assert created_taxonomy.namespace == namespace + taxonomies: MetadataTaxonomies = client.metadata_taxonomies.get_metadata_taxonomies( + namespace + ) + assert len(taxonomies.entries) > 0 + assert taxonomies.entries[0].namespace == namespace + updated_display_name: str = ''.join(['Geography Taxonomy UPDATED', uuid]) + updated_taxonomy: MetadataTaxonomy = ( + client.metadata_taxonomies.update_metadata_taxonomy( + namespace, taxonomy_key, updated_display_name + ) + ) + assert updated_taxonomy.display_name == updated_display_name + assert updated_taxonomy.namespace == namespace + assert updated_taxonomy.id == created_taxonomy.id + get_taxonomy: MetadataTaxonomy = ( + client.metadata_taxonomies.get_metadata_taxonomy_by_key(namespace, taxonomy_key) + ) + assert get_taxonomy.display_name == updated_display_name + assert get_taxonomy.namespace == namespace + assert get_taxonomy.id == created_taxonomy.id + client.metadata_taxonomies.delete_metadata_taxonomy(namespace, taxonomy_key) + with pytest.raises(Exception): + client.metadata_taxonomies.get_metadata_taxonomy_by_key(namespace, taxonomy_key) + + +def testMetadataTaxonomiesNodes(): + namespace: str = ''.join(['enterprise_', get_env_var('ENTERPRISE_ID')]) + uuid: str = get_uuid() + taxonomy_key: str = ''.join(['geography', uuid]) + display_name: str = ''.join(['Geography Taxonomy', uuid]) + created_taxonomy: MetadataTaxonomy = ( + client.metadata_taxonomies.create_metadata_taxonomy( + display_name, namespace, key=taxonomy_key + ) + ) + assert created_taxonomy.display_name == display_name + assert created_taxonomy.namespace == namespace + taxonomy_levels: MetadataTaxonomyLevels = ( + client.metadata_taxonomies.create_metadata_taxonomy_level( + namespace, + taxonomy_key, + [ + MetadataTaxonomyLevel( + display_name='Continent', description='Continent Level' + ), + MetadataTaxonomyLevel( + display_name='Country', description='Country Level' + ), + ], + ) + ) + assert len(taxonomy_levels.entries) == 2 + assert taxonomy_levels.entries[0].display_name == 'Continent' + assert taxonomy_levels.entries[1].display_name == 'Country' + taxonomy_levels_after_addition: MetadataTaxonomyLevels = ( + client.metadata_taxonomies.add_metadata_taxonomy_level( + namespace, taxonomy_key, 'Region', description='Region Description' + ) + ) + assert len(taxonomy_levels_after_addition.entries) == 3 + assert taxonomy_levels_after_addition.entries[2].display_name == 'Region' + taxonomy_levels_after_deletion: MetadataTaxonomyLevels = ( + client.metadata_taxonomies.delete_metadata_taxonomy_level( + namespace, taxonomy_key + ) + ) + assert len(taxonomy_levels_after_deletion.entries) == 2 + assert taxonomy_levels_after_deletion.entries[0].display_name == 'Continent' + assert taxonomy_levels_after_deletion.entries[1].display_name == 'Country' + continent_node: MetadataTaxonomyNode = ( + client.metadata_taxonomies.create_metadata_taxonomy_node( + namespace, taxonomy_key, 'Europe', 1 + ) + ) + assert continent_node.display_name == 'Europe' + assert continent_node.level == 1 + country_node: MetadataTaxonomyNode = ( + client.metadata_taxonomies.create_metadata_taxonomy_node( + namespace, taxonomy_key, 'Poland', 2, parent_id=continent_node.id + ) + ) + assert country_node.display_name == 'Poland' + assert country_node.level == 2 + assert country_node.parent_id == continent_node.id + delay_in_seconds(5) + all_nodes: MetadataTaxonomyNodes = ( + client.metadata_taxonomies.get_metadata_taxonomy_nodes(namespace, taxonomy_key) + ) + assert len(all_nodes.entries) == 2 + updated_country_node: MetadataTaxonomyNode = ( + client.metadata_taxonomies.update_metadata_taxonomy_node( + namespace, taxonomy_key, country_node.id, display_name='Poland UPDATED' + ) + ) + assert updated_country_node.display_name == 'Poland UPDATED' + assert updated_country_node.level == 2 + assert updated_country_node.parent_id == country_node.parent_id + assert updated_country_node.id == country_node.id + get_country_node: MetadataTaxonomyNode = ( + client.metadata_taxonomies.get_metadata_taxonomy_node_by_id( + namespace, taxonomy_key, country_node.id + ) + ) + assert get_country_node.display_name == 'Poland UPDATED' + assert get_country_node.id == country_node.id + client.metadata_taxonomies.delete_metadata_taxonomy_node( + namespace, taxonomy_key, country_node.id + ) + client.metadata_taxonomies.delete_metadata_taxonomy_node( + namespace, taxonomy_key, continent_node.id + ) + delay_in_seconds(5) + all_nodes_after_deletion: MetadataTaxonomyNodes = ( + client.metadata_taxonomies.get_metadata_taxonomy_nodes(namespace, taxonomy_key) + ) + assert len(all_nodes_after_deletion.entries) == 0 + client.metadata_taxonomies.delete_metadata_taxonomy(namespace, taxonomy_key) From 5ad6980ef59a42aaa23bbfa896f1f6d52e81b03d Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:46:51 +0100 Subject: [PATCH 071/139] docs: replace internal links with absolute links (box/box-openapi#570) (#1266) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/managers/ai.py | 6 ++-- box_sdk_gen/managers/archives.py | 8 ++--- box_sdk_gen/managers/chunked_uploads.py | 36 +++++++++---------- box_sdk_gen/managers/file_classifications.py | 4 +-- .../managers/file_version_retentions.py | 4 +-- box_sdk_gen/managers/files.py | 2 +- .../managers/folder_classifications.py | 4 +-- box_sdk_gen/managers/folders.py | 6 ++-- .../managers/metadata_cascade_policies.py | 6 ++-- box_sdk_gen/managers/search.py | 2 +- box_sdk_gen/managers/sign_requests.py | 2 +- box_sdk_gen/managers/trashed_files.py | 4 +-- box_sdk_gen/managers/trashed_folders.py | 4 +-- box_sdk_gen/managers/zip_downloads.py | 4 +-- .../schemas/ai_agent_basic_gen_tool.py | 2 +- .../schemas/ai_agent_basic_text_tool.py | 2 +- .../schemas/ai_agent_basic_text_tool_base.py | 2 +- .../ai_agent_basic_text_tool_text_gen.py | 2 +- .../schemas/ai_agent_long_text_tool.py | 2 +- .../ai_agent_long_text_tool_text_gen.py | 2 +- box_sdk_gen/schemas/ai_agent_reference.py | 2 +- .../schemas/ai_agent_spreadsheet_tool.py | 2 +- .../schemas/ai_studio_agent_basic_gen_tool.py | 2 +- ...ai_studio_agent_basic_gen_tool_response.py | 2 +- .../ai_studio_agent_basic_text_tool.py | 2 +- ...i_studio_agent_basic_text_tool_response.py | 2 +- .../schemas/ai_studio_agent_long_text_tool.py | 2 +- ...ai_studio_agent_long_text_tool_response.py | 2 +- .../ai_studio_agent_spreadsheet_tool.py | 2 +- ..._studio_agent_spreadsheet_tool_response.py | 2 +- .../schemas/metadata_cascade_policy.py | 6 ++-- box_sdk_gen/schemas/metadata_filter.py | 6 ++-- .../schemas/sign_request_create_request.py | 2 +- box_sdk_gen/schemas/sign_template.py | 2 +- docs/ai.md | 6 ++-- docs/archives.md | 8 ++--- docs/chunked_uploads.md | 36 +++++++++---------- docs/downloads.md | 4 +-- docs/file_classifications.md | 2 +- docs/file_version_retentions.md | 4 +-- docs/files.md | 2 +- docs/folder_classifications.md | 2 +- docs/folders.md | 8 ++--- docs/metadata_cascade_policies.md | 2 +- docs/search.md | 2 +- docs/sign_requests.md | 2 +- docs/trashed_files.md | 4 +-- docs/trashed_folders.md | 4 +-- docs/zip_downloads.md | 4 +-- 50 files changed, 116 insertions(+), 116 deletions(-) diff --git a/.codegen.json b/.codegen.json index c96fa681e..e5be0f20c 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "467757c", "specHash": "1e0848d", "version": "10.2.0" } +{ "engineHash": "c370934", "specHash": "9d069ca", "version": "10.2.0" } diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index 6ac86c88a..11b1d6f37 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -434,13 +434,13 @@ def create_ai_extract_structured( To define the extraction structure, provide either a metadata template or a list of fields. To learn more about creating templates, see [Creating metadata templates in the Admin Console](https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates) - or use the [metadata template API](g://metadata/templates/create). + or use the [metadata template API](https://developer.box.com/guides/metadata/templates/create). - This endpoint also supports [Enhanced Extract Agent](g://box-ai/ai-tutorials/extract-metadata-structured/#enhanced-extract-agent). + This endpoint also supports [Enhanced Extract Agent](https://developer.box.com/guides/box-ai/ai-tutorials/extract-metadata-structured#enhanced-extract-agent). - For information about supported file formats and languages, see the [Extract metadata from file (structured)](g://box-ai/ai-tutorials/extract-metadata-structured) API guide. + For information about supported file formats and languages, see the [Extract metadata from file (structured)](https://developer.box.com/guides/box-ai/ai-tutorials/extract-metadata-structured) API guide. :param items: The items to be processed by the LLM. Currently you can use files only. :type items: List[AiItemBase] diff --git a/box_sdk_gen/managers/archives.py b/box_sdk_gen/managers/archives.py index b6bf5faf2..146b58f82 100644 --- a/box_sdk_gen/managers/archives.py +++ b/box_sdk_gen/managers/archives.py @@ -64,7 +64,7 @@ def get_archives_v2025_r0( """ Retrieves archives for an enterprise. - To learn more about the archive APIs, see the [Archive API Guide](g://archives). + To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). :param limit: The maximum number of items to return per page., defaults to None :type limit: Optional[int], optional @@ -109,7 +109,7 @@ def create_archive_v2025_r0( """ Creates an archive. - To learn more about the archive APIs, see the [Archive API Guide](g://archives). + To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). :param name: The name of the archive. :type name: str @@ -156,7 +156,7 @@ def delete_archive_by_id_v2025_r0( """ Permanently deletes an archive. - To learn more about the archive APIs, see the [Archive API Guide](g://archives). + To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). :param archive_id: The ID of the archive. Example: "982312" @@ -201,7 +201,7 @@ def update_archive_by_id_v2025_r0( """ Updates an archive. - To learn more about the archive APIs, see the [Archive API Guide](g://archives). + To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). :param archive_id: The ID of the archive. Example: "982312" diff --git a/box_sdk_gen/managers/chunked_uploads.py b/box_sdk_gen/managers/chunked_uploads.py index 1ff148681..aed8f1fd8 100644 --- a/box_sdk_gen/managers/chunked_uploads.py +++ b/box_sdk_gen/managers/chunked_uploads.py @@ -204,7 +204,7 @@ def get_file_upload_session_by_url( Return information about an upload session. - The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) endpoint. + The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) endpoint. :param url: URL of getFileUploadSessionById method :type url: str @@ -235,7 +235,7 @@ def get_file_upload_session_by_id( """ Return information about an upload session. - The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) endpoint. + The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) endpoint. :param upload_session_id: The ID of the upload session. Example: "D5E3F7A" @@ -282,10 +282,10 @@ def upload_file_part_by_url( Uploads a chunk of a file for an upload session. - The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) - and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. :param url: URL of uploadFilePart method :type url: str @@ -355,10 +355,10 @@ def upload_file_part( """ Uploads a chunk of a file for an upload session. - The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) - and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. :param upload_session_id: The ID of the upload session. Example: "D5E3F7A" @@ -438,10 +438,10 @@ def delete_file_upload_session_by_url( This cannot be reversed. - The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) - and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. :param url: URL of deleteFileUploadSessionById method :type url: str @@ -475,10 +475,10 @@ def delete_file_upload_session_by_id( This cannot be reversed. - The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) - and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. :param upload_session_id: The ID of the upload session. Example: "D5E3F7A" @@ -524,10 +524,10 @@ def get_file_upload_session_parts_by_url( Return a list of the chunks uploaded to the upload session so far. - The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) - and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. :param url: URL of getFileUploadSessionParts method :type url: str @@ -572,10 +572,10 @@ def get_file_upload_session_parts( """ Return a list of the chunks uploaded to the upload session so far. - The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) - and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. :param upload_session_id: The ID of the upload session. Example: "D5E3F7A" @@ -636,10 +636,10 @@ def create_file_upload_session_commit_by_url( Close an upload session and create a file from the uploaded chunks. - The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) - and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. :param url: URL of createFileUploadSessionCommit method :type url: str @@ -711,10 +711,10 @@ def create_file_upload_session_commit( """ Close an upload session and create a file from the uploaded chunks. - The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) + The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) - and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. + and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. :param upload_session_id: The ID of the upload session. Example: "D5E3F7A" diff --git a/box_sdk_gen/managers/file_classifications.py b/box_sdk_gen/managers/file_classifications.py index c38a2bbff..58836c38e 100644 --- a/box_sdk_gen/managers/file_classifications.py +++ b/box_sdk_gen/managers/file_classifications.py @@ -63,7 +63,7 @@ def __init__( To list the available classifications in an enterprise, use the classification API to retrieve the - [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) + [classification template](https://developer.box.com/reference/get-metadata-templates-enterprise-securityClassification-6VMVochwUWo-schema) which lists all available classification keys. :type value: str :param op: The value will always be `replace`., defaults to UpdateClassificationOnFileRequestBodyOpField.REPLACE @@ -175,7 +175,7 @@ def add_classification_to_file( To list the available classifications in an enterprise, use the classification API to retrieve the - [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) + [classification template](https://developer.box.com/reference/get-metadata-templates-enterprise-securityClassification-6VMVochwUWo-schema) which lists all available classification keys., defaults to None :type box_security_classification_key: Optional[str], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None diff --git a/box_sdk_gen/managers/file_version_retentions.py b/box_sdk_gen/managers/file_version_retentions.py index 8c2ddf75c..9598a3ddd 100644 --- a/box_sdk_gen/managers/file_version_retentions.py +++ b/box_sdk_gen/managers/file_version_retentions.py @@ -79,7 +79,7 @@ def get_file_version_retentions( To get information about files and file versions under retention, - see [files under retention](e://get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](e://get-retention-policy-assignments-id-file-versions-under-retention) endpoints. + see [files under retention](https://developer.box.com/reference/get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](https://developer.box.com/reference/get-retention-policy-assignments-id-file-versions-under-retention) endpoints. :param file_id: Filters results by files with this ID., defaults to None :type file_id: Optional[str], optional @@ -157,7 +157,7 @@ def get_file_version_retention_by_id( To get information about files and file versions under retention, - see [files under retention](e://get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](e://get-retention-policy-assignments-id-file-versions-under-retention) endpoints. + see [files under retention](https://developer.box.com/reference/get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](https://developer.box.com/reference/get-retention-policy-assignments-id-file-versions-under-retention) endpoints. :param file_version_retention_id: The ID of the file version retention. Example: "3424234" diff --git a/box_sdk_gen/managers/files.py b/box_sdk_gen/managers/files.py index b153e2a61..6175976ca 100644 --- a/box_sdk_gen/managers/files.py +++ b/box_sdk_gen/managers/files.py @@ -402,7 +402,7 @@ def update_file_by_id( Passing an empty array `[]` or `null` will remove the file from all collections. - [1]: e://get-collections, defaults to None + [1]: https://developer.box.com/reference/get-collections, defaults to None :type collections: Union[Optional[List[UpdateFileByIdCollections]], NullValue], optional :param tags: The tags for this item. These tags are shown in the Box web app and mobile apps next to an item. diff --git a/box_sdk_gen/managers/folder_classifications.py b/box_sdk_gen/managers/folder_classifications.py index 4192936a7..4241588f9 100644 --- a/box_sdk_gen/managers/folder_classifications.py +++ b/box_sdk_gen/managers/folder_classifications.py @@ -63,7 +63,7 @@ def __init__( To list the available classifications in an enterprise, use the classification API to retrieve the - [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) + [classification template](https://developer.box.com/reference/get-metadata-templates-enterprise-securityClassification-6VMVochwUWo-schema) which lists all available classification keys. :type value: str :param op: The value will always be `replace`., defaults to UpdateClassificationOnFolderRequestBodyOpField.REPLACE @@ -184,7 +184,7 @@ def add_classification_to_folder( To list the available classifications in an enterprise, use the classification API to retrieve the - [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) + [classification template](https://developer.box.com/reference/get-metadata-templates-enterprise-securityClassification-6VMVochwUWo-schema) which lists all available classification keys., defaults to None :type box_security_classification_key: Optional[str], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None diff --git a/box_sdk_gen/managers/folders.py b/box_sdk_gen/managers/folders.py index 50772507b..f7b3e2c3f 100644 --- a/box_sdk_gen/managers/folders.py +++ b/box_sdk_gen/managers/folders.py @@ -308,13 +308,13 @@ def get_folder_by_id( list of returned - [folder items](r://folder--full#param-item-collection). + [folder items](https://developer.box.com/reference/resources/folder--full#param-item-collection). To fetch more items within the folder, use the - [Get items in a folder](e://get-folders-id-items) endpoint. + [Get items in a folder](https://developer.box.com/reference/get-folders-id-items) endpoint. :param folder_id: The unique identifier that represent a folder. @@ -524,7 +524,7 @@ def update_folder_by_id( Passing an empty array `[]` or `null` will remove the folder from all collections. - [1]: e://get-collections, defaults to None + [1]: https://developer.box.com/reference/get-collections, defaults to None :type collections: Union[Optional[List[UpdateFolderByIdCollections]], NullValue], optional :param can_non_owners_view_collaborators: Restricts collaborators who are not the owner of this folder from viewing other collaborations on diff --git a/box_sdk_gen/managers/metadata_cascade_policies.py b/box_sdk_gen/managers/metadata_cascade_policies.py index 2d254c871..e6cf00629 100644 --- a/box_sdk_gen/managers/metadata_cascade_policies.py +++ b/box_sdk_gen/managers/metadata_cascade_policies.py @@ -171,9 +171,9 @@ def create_metadata_cascade_policy( get all instances on a [file][file] or [folder][folder] to inspect a template's key. - [list]: e://get-metadata-templates-enterprise - [file]: e://get-files-id-metadata - [folder]: e://get-folders-id-metadata + [list]: https://developer.box.com/reference/get-metadata-templates-enterprise + [file]: https://developer.box.com/reference/get-files-id-metadata + [folder]: https://developer.box.com/reference/get-folders-id-metadata :type template_key: str :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional diff --git a/box_sdk_gen/managers/search.py b/box_sdk_gen/managers/search.py index f5adde965..dfb1a16df 100644 --- a/box_sdk_gen/managers/search.py +++ b/box_sdk_gen/managers/search.py @@ -462,7 +462,7 @@ def search_for_content( When this parameter has been set to true, the format of the response of this API changes to return a list of [Search Results with - Shared Links](r://search_results_with_shared_links)., defaults to None + Shared Links](https://developer.box.com/reference/resources/search-results-with-shared-links)., defaults to None :type include_recent_shared_links: Optional[bool], optional :param fields: A comma-separated list of attributes to include in the response. This can be used to request fields that are diff --git a/box_sdk_gen/managers/sign_requests.py b/box_sdk_gen/managers/sign_requests.py index bef15282c..75e4cf0e4 100644 --- a/box_sdk_gen/managers/sign_requests.py +++ b/box_sdk_gen/managers/sign_requests.py @@ -270,7 +270,7 @@ def create_sign_request( :param signers: Array of signers for the signature request. 35 is the max number of signers permitted. - **Note**: It may happen that some signers belong to conflicting [segments](r://shield-information-barrier-segment-member) (user groups). + **Note**: It may happen that some signers belong to conflicting [segments](https://developer.box.com/reference/resources/shield-information-barrier-segment-member) (user groups). This means that due to the security policies, users are assigned to segments to prevent exchanges or communication that could lead to ethical conflicts. In such a case, an attempt to send the sign request will result in an error. diff --git a/box_sdk_gen/managers/trashed_files.py b/box_sdk_gen/managers/trashed_files.py index 340d93b32..6aa6eddf0 100644 --- a/box_sdk_gen/managers/trashed_files.py +++ b/box_sdk_gen/managers/trashed_files.py @@ -152,13 +152,13 @@ def get_trashed_file_by_id( can be inspected using the - [`GET /folders/:id/trash`](e://get_folders_id_trash) API. + [`GET /folders/:id/trash`](https://developer.box.com/reference/get-folders-id-trash) API. To list all items that have been moved to the trash, please - use the [`GET /folders/trash/items`](e://get-folders-trash-items/) + use the [`GET /folders/trash/items`](https://developer.box.com/reference/get-folders-trash-items/) API. diff --git a/box_sdk_gen/managers/trashed_folders.py b/box_sdk_gen/managers/trashed_folders.py index b41e66dd1..0796cc262 100644 --- a/box_sdk_gen/managers/trashed_folders.py +++ b/box_sdk_gen/managers/trashed_folders.py @@ -170,13 +170,13 @@ def get_trashed_folder_by_id( can be inspected using the - [`GET /folders/:id/trash`](e://get_folders_id_trash) API. + [`GET /folders/:id/trash`](https://developer.box.com/reference/get-folders-id-trash) API. To list all items that have been moved to the trash, please - use the [`GET /folders/trash/items`](e://get-folders-trash-items/) + use the [`GET /folders/trash/items`](https://developer.box.com/reference/get-folders-trash-items/) API. diff --git a/box_sdk_gen/managers/zip_downloads.py b/box_sdk_gen/managers/zip_downloads.py index 7813cbdc2..c056430a5 100644 --- a/box_sdk_gen/managers/zip_downloads.py +++ b/box_sdk_gen/managers/zip_downloads.py @@ -204,7 +204,7 @@ def get_zip_download_content( The URL of this endpoint should not be considered as fixed. Instead, use - the [Create zip download](e://post_zip_downloads) API to request to create a + the [Create zip download](https://developer.box.com/reference/post-zip-downloads) API to request to create a `zip` archive, and then follow the `download_url` field in the response to @@ -260,7 +260,7 @@ def get_zip_download_status( The URL of this endpoint should not be considered as fixed. Instead, use - the [Create zip download](e://post_zip_downloads) API to request to create a + the [Create zip download](https://developer.box.com/reference/post-zip-downloads) API to request to create a `zip` archive, and then follow the `status_url` field in the response to diff --git a/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py b/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py index 6aef8bb0c..2bb60b4e0 100644 --- a/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py +++ b/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py @@ -52,7 +52,7 @@ def __init__( When using the `prompt_template` parameter, you **must include** input for `{user_question}`. Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_basic_text_tool.py b/box_sdk_gen/schemas/ai_agent_basic_text_tool.py index 5c48ae509..f9722bb05 100644 --- a/box_sdk_gen/schemas/ai_agent_basic_text_tool.py +++ b/box_sdk_gen/schemas/ai_agent_basic_text_tool.py @@ -33,7 +33,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py b/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py index 0090d4218..e077b03cc 100644 --- a/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py +++ b/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py @@ -25,7 +25,7 @@ def __init__( **kwargs ): """ - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py b/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py index 2d9a73f01..cc49bd6a6 100644 --- a/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py +++ b/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py @@ -35,7 +35,7 @@ def __init__( When using the `prompt_template` parameter, you **must include** input for `{user_question}`. Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_long_text_tool.py b/box_sdk_gen/schemas/ai_agent_long_text_tool.py index 57773078c..de45f4b52 100644 --- a/box_sdk_gen/schemas/ai_agent_long_text_tool.py +++ b/box_sdk_gen/schemas/ai_agent_long_text_tool.py @@ -74,7 +74,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py b/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py index 445162945..b168df4f6 100644 --- a/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py +++ b/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py @@ -78,7 +78,7 @@ def __init__( When using the `prompt_template` parameter, you **must include** input for `{user_question}`. Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_reference.py b/box_sdk_gen/schemas/ai_agent_reference.py index f8b2bb38b..9f7dd1dc9 100644 --- a/box_sdk_gen/schemas/ai_agent_reference.py +++ b/box_sdk_gen/schemas/ai_agent_reference.py @@ -26,7 +26,7 @@ def __init__( :type type: AiAgentReferenceTypeField, optional :param id: The ID of an Agent. This can be a numeric ID for custom agents (for example, `14031`) or a unique identifier for pre-built agents (for example, `enhanced_extract_agent` - for the [Enhanced Extract Agent](g://box-ai/ai-tutorials/extract-metadata-structured/#enhanced-extract-agent))., defaults to None + for the [Enhanced Extract Agent](https://developer.box.com/guides/box-ai/ai-tutorials/extract-metadata-structured#enhanced-extract-agent))., defaults to None :type id: Optional[str], optional """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py b/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py index fe7942124..1dbb1f497 100644 --- a/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py +++ b/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py @@ -25,7 +25,7 @@ def __init__( **kwargs ): """ - :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py index b3ed99e08..5e017d116 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py @@ -57,7 +57,7 @@ def __init__( When using the `prompt_template` parameter, you **must include** input for `{user_question}`. Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py index 4e418497c..7c006939d 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py @@ -64,7 +64,7 @@ def __init__( When using the `prompt_template` parameter, you **must include** input for `{user_question}`. Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py index bb1435fe5..a910fb58b 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py @@ -38,7 +38,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py index 47b4eaf25..5f5e1a931 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py @@ -47,7 +47,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py index 5419f0b12..1e31c862b 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py +++ b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py @@ -45,7 +45,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py index b521b3751..350093abc 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py +++ b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py @@ -52,7 +52,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py index 9d207998f..c453f3ca6 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py +++ b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py @@ -25,7 +25,7 @@ def __init__( **kwargs ): """ - :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py index bf6323140..888b52cbe 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py +++ b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py @@ -34,7 +34,7 @@ def __init__( """ :param warnings: Warnings concerning tool., defaults to None :type warnings: Optional[List[str]], optional - :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](g://box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/metadata_cascade_policy.py b/box_sdk_gen/schemas/metadata_cascade_policy.py index a3096866f..d24ce5994 100644 --- a/box_sdk_gen/schemas/metadata_cascade_policy.py +++ b/box_sdk_gen/schemas/metadata_cascade_policy.py @@ -112,9 +112,9 @@ def __init__( get all instances on a [file][file] or [folder][folder] to inspect a template's key. - [list]: e://get-metadata-templates-enterprise - [file]: e://get-files-id-metadata - [folder]: e://get-folders-id-metadata, defaults to None + [list]: https://developer.box.com/reference/get-metadata-templates-enterprise + [file]: https://developer.box.com/reference/get-files-id-metadata + [folder]: https://developer.box.com/reference/get-folders-id-metadata, defaults to None :type template_key: Optional[str], optional """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/metadata_filter.py b/box_sdk_gen/schemas/metadata_filter.py index e25881cf3..2f0fb2660 100644 --- a/box_sdk_gen/schemas/metadata_filter.py +++ b/box_sdk_gen/schemas/metadata_filter.py @@ -61,9 +61,9 @@ def __init__( get all instances on a [file][file] or [folder][folder] to inspect a template's key. - [list]: e://get-metadata-templates-enterprise - [file]: e://get-files-id-metadata - [folder]: e://get-folders-id-metadata, defaults to None + [list]: https://developer.box.com/reference/get-metadata-templates-enterprise + [file]: https://developer.box.com/reference/get-files-id-metadata + [folder]: https://developer.box.com/reference/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 diff --git a/box_sdk_gen/schemas/sign_request_create_request.py b/box_sdk_gen/schemas/sign_request_create_request.py index 8d18ce00b..7222098ae 100644 --- a/box_sdk_gen/schemas/sign_request_create_request.py +++ b/box_sdk_gen/schemas/sign_request_create_request.py @@ -50,7 +50,7 @@ def __init__( :param signers: Array of signers for the signature request. 35 is the max number of signers permitted. - **Note**: It may happen that some signers belong to conflicting [segments](r://shield-information-barrier-segment-member) (user groups). + **Note**: It may happen that some signers belong to conflicting [segments](https://developer.box.com/reference/resources/shield-information-barrier-segment-member) (user groups). This means that due to the security policies, users are assigned to segments to prevent exchanges or communication that could lead to ethical conflicts. In such a case, an attempt to send the sign request will result in an error. diff --git a/box_sdk_gen/schemas/sign_template.py b/box_sdk_gen/schemas/sign_template.py index 08f7ca684..f96b30cad 100644 --- a/box_sdk_gen/schemas/sign_template.py +++ b/box_sdk_gen/schemas/sign_template.py @@ -208,7 +208,7 @@ def __init__( :type are_files_locked: Optional[bool], optional :param signers: Array of signers for the template. - **Note**: It may happen that some signers specified in the template belong to conflicting [segments](r://shield-information-barrier-segment-member) (user groups). + **Note**: It may happen that some signers specified in the template belong to conflicting [segments](https://developer.box.com/reference/resources/shield-information-barrier-segment-member) (user groups). This means that due to the security policies, users are assigned to segments to prevent exchanges or communication that could lead to ethical conflicts. In such a case, an attempt to send a sign request based on a template that lists signers in conflicting segments will result in an error. diff --git a/docs/ai.md b/docs/ai.md index 6c1f0686e..47d4c6349 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -189,11 +189,11 @@ A response including the answer from the LLM. Sends an AI request to supported Large Language Models (LLMs) and returns extracted metadata as a set of key-value pairs. To define the extraction structure, provide either a metadata template or a list of fields. To learn more about creating templates, see [Creating metadata templates in the Admin Console](https://support.box.com/hc/en-us/articles/360044194033-Customizing-Metadata-Templates) -or use the [metadata template API](g://metadata/templates/create). +or use the [metadata template API](https://developer.box.com/guides/metadata/templates/create). -This endpoint also supports [Enhanced Extract Agent](g://box-ai/ai-tutorials/extract-metadata-structured/#enhanced-extract-agent). +This endpoint also supports [Enhanced Extract Agent](https://developer.box.com/guides/box-ai/ai-tutorials/extract-metadata-structured#enhanced-extract-agent). -For information about supported file formats and languages, see the [Extract metadata from file (structured)](g://box-ai/ai-tutorials/extract-metadata-structured) API guide. +For information about supported file formats and languages, see the [Extract metadata from file (structured)](https://developer.box.com/guides/box-ai/ai-tutorials/extract-metadata-structured) API guide. This operation is performed by calling function `create_ai_extract_structured`. diff --git a/docs/archives.md b/docs/archives.md index 76abb278b..33a2dd122 100644 --- a/docs/archives.md +++ b/docs/archives.md @@ -9,7 +9,7 @@ Retrieves archives for an enterprise. -To learn more about the archive APIs, see the [Archive API Guide](g://archives). +To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). This operation is performed by calling function `get_archives_v2025_r0`. @@ -43,7 +43,7 @@ Returns a list of archives in the enterprise. Creates an archive. -To learn more about the archive APIs, see the [Archive API Guide](g://archives). +To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). This operation is performed by calling function `create_archive_v2025_r0`. @@ -79,7 +79,7 @@ Returns a new archive object. Permanently deletes an archive. -To learn more about the archive APIs, see the [Archive API Guide](g://archives). +To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). This operation is performed by calling function `delete_archive_by_id_v2025_r0`. @@ -111,7 +111,7 @@ Returns an empty response when the archive has been deleted. Updates an archive. -To learn more about the archive APIs, see the [Archive API Guide](g://archives). +To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). This operation is performed by calling function `update_archive_by_id_v2025_r0`. diff --git a/docs/chunked_uploads.md b/docs/chunked_uploads.md index 2d965c739..d5d5d75a9 100644 --- a/docs/chunked_uploads.md +++ b/docs/chunked_uploads.md @@ -82,7 +82,7 @@ Returns a new upload session. Return information about an upload session. -The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) endpoint. +The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) endpoint. This operation is performed by calling function `get_file_upload_session_by_url`. @@ -112,7 +112,7 @@ Returns an upload session object. Return information about an upload session. -The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) endpoint. +The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) endpoint. This operation is performed by calling function `get_file_upload_session_by_id`. @@ -142,8 +142,8 @@ Returns an upload session object. Uploads a chunk of a file for an upload session. -The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) -and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. +The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) +and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. This operation is performed by calling function `upload_file_part_by_url`. @@ -184,8 +184,8 @@ Chunk has been uploaded successfully. Uploads a chunk of a file for an upload session. -The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) -and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. +The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) +and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. This operation is performed by calling function `upload_file_part`. @@ -228,8 +228,8 @@ Abort an upload session and discard all data uploaded. This cannot be reversed. -The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) -and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. +The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) +and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. This operation is performed by calling function `delete_file_upload_session_by_url`. @@ -262,8 +262,8 @@ Abort an upload session and discard all data uploaded. This cannot be reversed. -The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) -and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. +The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) +and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. This operation is performed by calling function `delete_file_upload_session_by_id`. @@ -294,8 +294,8 @@ successfully aborted. Return a list of the chunks uploaded to the upload session so far. -The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) -and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. +The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) +and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. This operation is performed by calling function `get_file_upload_session_parts_by_url`. @@ -329,8 +329,8 @@ Returns a list of parts that have been uploaded. Return a list of the chunks uploaded to the upload session so far. -The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) -and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. +The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) +and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. This operation is performed by calling function `get_file_upload_session_parts`. @@ -364,8 +364,8 @@ Returns a list of parts that have been uploaded. Close an upload session and create a file from the uploaded chunks. -The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) -and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. +The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) +and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. This operation is performed by calling function `create_file_upload_session_commit_by_url`. @@ -409,8 +409,8 @@ when all chunks have processed. Close an upload session and create a file from the uploaded chunks. -The actual endpoint URL is returned by the [`Create upload session`](e://post-files-upload-sessions) -and [`Get upload session`](e://get-files-upload-sessions-id) endpoints. +The actual endpoint URL is returned by the [`Create upload session`](https://developer.box.com/reference/post-files-upload-sessions) +and [`Get upload session`](https://developer.box.com/reference/get-files-upload-sessions-id) endpoints. This operation is performed by calling function `create_file_upload_session_commit`. diff --git a/docs/downloads.md b/docs/downloads.md index 46ea036eb..c8be4bd88 100644 --- a/docs/downloads.md +++ b/docs/downloads.md @@ -43,7 +43,7 @@ redirects** setting enabled to automatically follow HTTP `3xx` responses as redirects. If not, the request will return `302` instead. For details, see -the [download file guide](g://downloads/file#download-url).If the file is not ready to be downloaded yet `Retry-After` header will +the [download file guide](https://developer.box.com/guides/downloads/file#download-url).If the file is not ready to be downloaded yet `Retry-After` header will be returned indicating the time in seconds after which the file will be available for the client to download. @@ -89,7 +89,7 @@ redirects** setting enabled to automatically follow HTTP `3xx` responses as redirects. If not, the request will return `302` instead. For details, see -the [download file guide](g://downloads/file#download-url).If the file is not ready to be downloaded yet `Retry-After` header will +the [download file guide](https://developer.box.com/guides/downloads/file#download-url).If the file is not ready to be downloaded yet `Retry-After` header will be returned indicating the time in seconds after which the file will be available for the client to download. diff --git a/docs/file_classifications.md b/docs/file_classifications.md index 113e50d7d..f502708cb 100644 --- a/docs/file_classifications.md +++ b/docs/file_classifications.md @@ -68,7 +68,7 @@ client.file_classifications.add_classification_to_file( - file_id `str` - The unique identifier that represents a file. The ID for any file can be determined by visiting a file in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/files/123` the `file_id` is `123`. Example: "12345" - box_security_classification_key `Optional[str]` - - The name of the classification to apply to this file. To list the available classifications in an enterprise, use the classification API to retrieve the [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) which lists all available classification keys. + - The name of the classification to apply to this file. To list the available classifications in an enterprise, use the classification API to retrieve the [classification template](https://developer.box.com/reference/get-metadata-templates-enterprise-securityClassification-6VMVochwUWo-schema) which lists all available classification keys. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/file_version_retentions.md b/docs/file_version_retentions.md index 890fac999..a8e09144a 100644 --- a/docs/file_version_retentions.md +++ b/docs/file_version_retentions.md @@ -10,7 +10,7 @@ Retrieves all file version retentions for the given enterprise. **Note**: File retention API is now **deprecated**. To get information about files and file versions under retention, -see [files under retention](e://get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](e://get-retention-policy-assignments-id-file-versions-under-retention) endpoints. +see [files under retention](https://developer.box.com/reference/get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](https://developer.box.com/reference/get-retention-policy-assignments-id-file-versions-under-retention) endpoints. This operation is performed by calling function `get_file_version_retentions`. @@ -57,7 +57,7 @@ Returns information about a file version retention. **Note**: File retention API is now **deprecated**. To get information about files and file versions under retention, -see [files under retention](e://get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](e://get-retention-policy-assignments-id-file-versions-under-retention) endpoints. +see [files under retention](https://developer.box.com/reference/get-retention-policy-assignments-id-files-under-retention) or [file versions under retention](https://developer.box.com/reference/get-retention-policy-assignments-id-file-versions-under-retention) endpoints. This operation is performed by calling function `get_file_version_retention_by_id`. diff --git a/docs/files.md b/docs/files.md index 9522994ab..664f051b5 100644 --- a/docs/files.md +++ b/docs/files.md @@ -84,7 +84,7 @@ client.files.update_file_by_id( - permissions `Optional[UpdateFileByIdPermissions]` - Defines who can download a file. - collections `Optional[List[UpdateFileByIdCollections]]` - - An array of collections to make this file a member of. Currently we only support the `favorites` collection. To get the ID for a collection, use the [List all collections][1] endpoint. Passing an empty array `[]` or `null` will remove the file from all collections. [1]: e://get-collections + - An array of collections to make this file a member of. Currently we only support the `favorites` collection. To get the ID for a collection, use the [List all collections][1] endpoint. Passing an empty array `[]` or `null` will remove the file from all collections. [1]: https://developer.box.com/reference/get-collections - tags `Optional[List[str]]` - The tags for this item. These tags are shown in the Box web app and mobile apps next to an item. To add or remove a tag, retrieve the item's current tags, modify them, and then update this field. There is a limit of 100 tags per item, and 10,000 unique tags per enterprise. - fields `Optional[List[str]]` diff --git a/docs/folder_classifications.md b/docs/folder_classifications.md index a10765c44..58fe5373e 100644 --- a/docs/folder_classifications.md +++ b/docs/folder_classifications.md @@ -68,7 +68,7 @@ client.folder_classifications.add_classification_to_folder( - folder_id `str` - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. Example: "12345" - box_security_classification_key `Optional[str]` - - The name of the classification to apply to this folder. To list the available classifications in an enterprise, use the classification API to retrieve the [classification template](e://get_metadata_templates_enterprise_securityClassification-6VMVochwUWo_schema) which lists all available classification keys. + - The name of the classification to apply to this folder. To list the available classifications in an enterprise, use the classification API to retrieve the [classification template](https://developer.box.com/reference/get-metadata-templates-enterprise-securityClassification-6VMVochwUWo-schema) which lists all available classification keys. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/folders.md b/docs/folders.md index fa2d33321..b79664416 100644 --- a/docs/folders.md +++ b/docs/folders.md @@ -15,10 +15,10 @@ in the folder. Passing `sort`, `direction`, `offset`, and `limit` parameters in query allows you to manage the list of returned -[folder items](r://folder--full#param-item-collection). +[folder items](https://developer.box.com/reference/resources/folder--full#param-item-collection). To fetch more items within the folder, use the -[Get items in a folder](e://get-folders-id-items) endpoint. +[Get items in a folder](https://developer.box.com/reference/get-folders-id-items) endpoint. This operation is performed by calling function `get_folder_by_id`. @@ -62,7 +62,7 @@ If you used query parameters like the _folder items list_ will be affected accordingly. To fetch more items within the folder, use the -[Get items in a folder](e://get-folders-id-items)) endpoint. +[Get items in a folder](https://developer.box.com/reference/get-folders-id-items)) endpoint. Not all available fields are returned by default. Use the [fields](#param-fields) query parameter to explicitly request @@ -106,7 +106,7 @@ client.folders.update_folder_by_id( - is_collaboration_restricted_to_enterprise `Optional[bool]` - Specifies if new invites to this folder are restricted to users within the enterprise. This does not affect existing collaborations. - collections `Optional[List[UpdateFolderByIdCollections]]` - - An array of collections to make this folder a member of. Currently we only support the `favorites` collection. To get the ID for a collection, use the [List all collections][1] endpoint. Passing an empty array `[]` or `null` will remove the folder from all collections. [1]: e://get-collections + - An array of collections to make this folder a member of. Currently we only support the `favorites` collection. To get the ID for a collection, use the [List all collections][1] endpoint. Passing an empty array `[]` or `null` will remove the folder from all collections. [1]: https://developer.box.com/reference/get-collections - can_non_owners_view_collaborators `Optional[bool]` - Restricts collaborators who are not the owner of this folder from viewing other collaborations on this folder. It also restricts non-owners from inviting new collaborators. When setting this field to `false`, it is required to also set `can_non_owners_invite_collaborators` to `false` if it has not already been set. - fields `Optional[List[str]]` diff --git a/docs/metadata_cascade_policies.md b/docs/metadata_cascade_policies.md index 0f7604fe2..b1d7b0bb7 100644 --- a/docs/metadata_cascade_policies.md +++ b/docs/metadata_cascade_policies.md @@ -71,7 +71,7 @@ client.metadata_cascade_policies.create_metadata_cascade_policy( - scope `CreateMetadataCascadePolicyScope` - The scope of the targeted metadata template. This template will need to already have an instance applied to the targeted folder. - template_key `str` - - The key of the targeted metadata template. This template will need to already have an instance applied to the targeted folder. In many cases the template key is automatically derived of its display name, for example `Contract Template` would become `contractTemplate`. In some cases the creator of the template will have provided its own template key. Please [list the templates for an enterprise][list], or get all instances on a [file][file] or [folder][folder] to inspect a template's key. [list]: e://get-metadata-templates-enterprise [file]: e://get-files-id-metadata [folder]: e://get-folders-id-metadata + - The key of the targeted metadata template. This template will need to already have an instance applied to the targeted folder. In many cases the template key is automatically derived of its display name, for example `Contract Template` would become `contractTemplate`. In some cases the creator of the template will have provided its own template key. Please [list the templates for an enterprise][list], or get all instances on a [file][file] or [folder][folder] to inspect a template's key. [list]: https://developer.box.com/reference/get-metadata-templates-enterprise [file]: https://developer.box.com/reference/get-files-id-metadata [folder]: https://developer.box.com/reference/get-folders-id-metadata - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/search.md b/docs/search.md index 803050620..c2c2e27de 100644 --- a/docs/search.md +++ b/docs/search.md @@ -121,7 +121,7 @@ client.search.search_for_content( - limit `Optional[int]` - Defines the maximum number of items to return as part of a page of results. - include_recent_shared_links `Optional[bool]` - - Defines whether the search results should include any items that the user recently accessed through a shared link. When this parameter has been set to true, the format of the response of this API changes to return a list of [Search Results with Shared Links](r://search_results_with_shared_links). + - Defines whether the search results should include any items that the user recently accessed through a shared link. When this parameter has been set to true, the format of the response of this API changes to return a list of [Search Results with Shared Links](https://developer.box.com/reference/resources/search-results-with-shared-links). - fields `Optional[List[str]]` - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. - offset `Optional[int]` diff --git a/docs/sign_requests.md b/docs/sign_requests.md index bee346654..56a7dbfde 100644 --- a/docs/sign_requests.md +++ b/docs/sign_requests.md @@ -176,7 +176,7 @@ client.sign_requests.create_sign_request( - signature_color `Optional[CreateSignRequestSignatureColor]` - Force a specific color for the signature (blue, black, or red). - signers `List[SignRequestCreateSigner]` - - Array of signers for the signature request. 35 is the max number of signers permitted. **Note**: It may happen that some signers belong to conflicting [segments](r://shield-information-barrier-segment-member) (user groups). This means that due to the security policies, users are assigned to segments to prevent exchanges or communication that could lead to ethical conflicts. In such a case, an attempt to send the sign request will result in an error. Read more about [segments and ethical walls](https://support.box.com/hc/en-us/articles/9920431507603-Understanding-Information-Barriers#h_01GFVJEHQA06N7XEZ4GCZ9GFAQ). + - Array of signers for the signature request. 35 is the max number of signers permitted. **Note**: It may happen that some signers belong to conflicting [segments](https://developer.box.com/reference/resources/shield-information-barrier-segment-member) (user groups). This means that due to the security policies, users are assigned to segments to prevent exchanges or communication that could lead to ethical conflicts. In such a case, an attempt to send the sign request will result in an error. Read more about [segments and ethical walls](https://support.box.com/hc/en-us/articles/9920431507603-Understanding-Information-Barriers#h_01GFVJEHQA06N7XEZ4GCZ9GFAQ). - parent_folder `Optional[FolderMini]` - is_document_preparation_needed `Optional[bool]` - Indicates if the sender should receive a `prepare_url` in the response to complete document preparation using the UI. diff --git a/docs/trashed_files.md b/docs/trashed_files.md index 3b77196f2..0afadfa88 100644 --- a/docs/trashed_files.md +++ b/docs/trashed_files.md @@ -48,10 +48,10 @@ Please note that only if the file itself has been moved to the trash can it be retrieved with this API call. If instead one of its parent folders was moved to the trash, only that folder can be inspected using the -[`GET /folders/:id/trash`](e://get_folders_id_trash) API. +[`GET /folders/:id/trash`](https://developer.box.com/reference/get-folders-id-trash) API. To list all items that have been moved to the trash, please -use the [`GET /folders/trash/items`](e://get-folders-trash-items/) +use the [`GET /folders/trash/items`](https://developer.box.com/reference/get-folders-trash-items/) API. This operation is performed by calling function `get_trashed_file_by_id`. diff --git a/docs/trashed_folders.md b/docs/trashed_folders.md index 66a0f27fc..8be203502 100644 --- a/docs/trashed_folders.md +++ b/docs/trashed_folders.md @@ -55,10 +55,10 @@ Please note that only if the folder itself has been moved to the trash can it be retrieved with this API call. If instead one of its parent folders was moved to the trash, only that folder can be inspected using the -[`GET /folders/:id/trash`](e://get_folders_id_trash) API. +[`GET /folders/:id/trash`](https://developer.box.com/reference/get-folders-id-trash) API. To list all items that have been moved to the trash, please -use the [`GET /folders/trash/items`](e://get-folders-trash-items/) +use the [`GET /folders/trash/items`](https://developer.box.com/reference/get-folders-trash-items/) API. This operation is performed by calling function `get_trashed_folder_by_id`. diff --git a/docs/zip_downloads.md b/docs/zip_downloads.md index 2a4b8fc57..6cfcffcfa 100644 --- a/docs/zip_downloads.md +++ b/docs/zip_downloads.md @@ -71,7 +71,7 @@ stopped and resumed, instead a new request for a zip archive would need to be created. The URL of this endpoint should not be considered as fixed. Instead, use -the [Create zip download](e://post_zip_downloads) API to request to create a +the [Create zip download](https://developer.box.com/reference/post-zip-downloads) API to request to create a `zip` archive, and then follow the `download_url` field in the response to this endpoint. @@ -111,7 +111,7 @@ Subsequently this endpoint is valid for 12 hours from the start of the download. The URL of this endpoint should not be considered as fixed. Instead, use -the [Create zip download](e://post_zip_downloads) API to request to create a +the [Create zip download](https://developer.box.com/reference/post-zip-downloads) API to request to create a `zip` archive, and then follow the `status_url` field in the response to this endpoint. From 7bb9bdc894cb6d765a09ecbedda58b6edb9181d0 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:05:58 +0100 Subject: [PATCH 072/139] fix: add taxonomy to Metadata Field (read) definition (box/box-openapi#572) (#1269) --- .codegen.json | 2 +- box_sdk_gen/schemas/metadata_template.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index e5be0f20c..6ff2c6686 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "c370934", "specHash": "9d069ca", "version": "10.2.0" } +{ "engineHash": "c370934", "specHash": "a4bdc72", "version": "10.2.0" } diff --git a/box_sdk_gen/schemas/metadata_template.py b/box_sdk_gen/schemas/metadata_template.py index 3596a105f..d844ab9ab 100644 --- a/box_sdk_gen/schemas/metadata_template.py +++ b/box_sdk_gen/schemas/metadata_template.py @@ -22,6 +22,7 @@ class MetadataTemplateFieldsTypeField(str, Enum): ENUM = 'enum' MULTISELECT = 'multiSelect' INTEGER = 'integer' + TAXONOMY = 'taxonomy' class MetadataTemplateFieldsOptionsField(BaseObject): @@ -54,6 +55,7 @@ class MetadataTemplateFieldsField(BaseObject): 'enum', 'multiSelect', 'integer', + 'taxonomy', } def __init__( From 36e4e6f5bf91912d690bc29028cdb2d9881d7776 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:56:47 +0100 Subject: [PATCH 073/139] test: Update Metadata Taxonomies tests (box/box-codegen#909) (#1277) --- .codegen.json | 2 +- .github/workflows/build-and-test-daily.yml | 4 ++ box_sdk_gen/managers/metadata_taxonomies.py | 19 ++----- docs/metadata_taxonomies.md | 26 +++++++-- test/metadata_taxonomies.py | 63 ++++++++++++++++++++- 5 files changed, 94 insertions(+), 20 deletions(-) diff --git a/.codegen.json b/.codegen.json index 6ff2c6686..43c85758c 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "c370934", "specHash": "a4bdc72", "version": "10.2.0" } +{ "engineHash": "41c28e1", "specHash": "1617aaf", "version": "10.2.0" } diff --git a/.github/workflows/build-and-test-daily.yml b/.github/workflows/build-and-test-daily.yml index ee2d35182..7ed858860 100644 --- a/.github/workflows/build-and-test-daily.yml +++ b/.github/workflows/build-and-test-daily.yml @@ -2,6 +2,10 @@ name: Build and Test daily on: schedule: - cron: '20 2 * * 1-5' + +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/box_sdk_gen/managers/metadata_taxonomies.py b/box_sdk_gen/managers/metadata_taxonomies.py index 868e22249..fe6421d68 100644 --- a/box_sdk_gen/managers/metadata_taxonomies.py +++ b/box_sdk_gen/managers/metadata_taxonomies.py @@ -1,5 +1,3 @@ -from enum import Enum - from typing import Optional from typing import Dict @@ -49,11 +47,6 @@ from box_sdk_gen.serialization.json import sd_to_json -class GetMetadataTemplateFieldOptionsScope(str, Enum): - GLOBAL = 'global' - ENTERPRISE = 'enterprise' - - class MetadataTaxonomiesManager: def __init__( self, @@ -345,7 +338,7 @@ def create_metadata_taxonomy_level( ) return deserialize(response.data, MetadataTaxonomyLevels) - def patch_metadata_taxonomies_id_id_levels_id( + def update_metadata_taxonomy_level_by_id( self, namespace: str, taxonomy_key: str, @@ -799,7 +792,7 @@ def delete_metadata_taxonomy_node( def get_metadata_template_field_options( self, - scope: GetMetadataTemplateFieldOptionsScope, + namespace: str, template_key: str, field_key: str, *, @@ -824,9 +817,9 @@ def get_metadata_template_field_options( With a `query` parameter specified, results are sorted in order of relevance. - :param scope: The scope of the metadata template. - Example: "global" - :type scope: GetMetadataTemplateFieldOptionsScope + :param namespace: The namespace of the metadata taxonomy. + Example: "enterprise_123456" + :type namespace: str :param template_key: The name of the metadata template. Example: "properties" :type template_key: str @@ -882,7 +875,7 @@ def get_metadata_template_field_options( [ self.network_session.base_urls.base_url, '/2.0/metadata_templates/', - to_string(scope), + to_string(namespace), '/', to_string(template_key), '/fields/', diff --git a/docs/metadata_taxonomies.md b/docs/metadata_taxonomies.md index ec7bf6b61..c84fd2910 100644 --- a/docs/metadata_taxonomies.md +++ b/docs/metadata_taxonomies.md @@ -222,12 +222,22 @@ Returns an array of all taxonomy levels. Updates an existing metadata taxonomy level. -This operation is performed by calling function `patch_metadata_taxonomies_id_id_levels_id`. +This operation is performed by calling function `update_metadata_taxonomy_level_by_id`. See the endpoint docs at [API Reference](https://developer.box.com/reference/patch-metadata-taxonomies-id-id-levels-id/). -_Currently we don't have an example for calling `patch_metadata_taxonomies_id_id_levels_id` in integration tests_ + + +```python +client.metadata_taxonomies.update_metadata_taxonomy_level_by_id( + namespace, + taxonomy_key, + 1, + "Continent UPDATED", + description="Continent Level UPDATED", +) +``` ### Arguments @@ -519,12 +529,18 @@ This operation is performed by calling function `get_metadata_template_field_opt See the endpoint docs at [API Reference](https://developer.box.com/reference/get-metadata-templates-id-id-fields-id-options/). -_Currently we don't have an example for calling `get_metadata_template_field_options` in integration tests_ + + +```python +client.metadata_taxonomies.get_metadata_template_field_options( + namespace, metadata_template_key, "taxonomy" +) +``` ### Arguments -- scope `GetMetadataTemplateFieldOptionsScope` - - The scope of the metadata template. Example: "global" +- namespace `str` + - The namespace of the metadata taxonomy. Example: "enterprise_123456" - template_key `str` - The name of the metadata template. Example: "properties" - field_key `str` diff --git a/test/metadata_taxonomies.py b/test/metadata_taxonomies.py index 13613a9e9..eda959118 100644 --- a/test/metadata_taxonomies.py +++ b/test/metadata_taxonomies.py @@ -1,5 +1,7 @@ import pytest +from box_sdk_gen.internal.utils import to_string + from box_sdk_gen.client import BoxClient from box_sdk_gen.schemas.metadata_taxonomy import MetadataTaxonomy @@ -14,6 +16,20 @@ from box_sdk_gen.schemas.metadata_taxonomy_nodes import MetadataTaxonomyNodes +from box_sdk_gen.schemas.metadata_template import MetadataTemplate + +from box_sdk_gen.managers.metadata_templates import CreateMetadataTemplateFields + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsTypeField, +) + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsOptionsRulesField, +) + +from box_sdk_gen.managers.metadata_templates import DeleteMetadataTemplateScope + from box_sdk_gen.internal.utils import get_uuid from box_sdk_gen.internal.utils import get_env_var @@ -91,6 +107,18 @@ def testMetadataTaxonomiesNodes(): assert len(taxonomy_levels.entries) == 2 assert taxonomy_levels.entries[0].display_name == 'Continent' assert taxonomy_levels.entries[1].display_name == 'Country' + updated_taxonomy_levels: MetadataTaxonomyLevel = ( + client.metadata_taxonomies.update_metadata_taxonomy_level_by_id( + namespace, + taxonomy_key, + 1, + 'Continent UPDATED', + description='Continent Level UPDATED', + ) + ) + assert updated_taxonomy_levels.display_name == 'Continent UPDATED' + assert updated_taxonomy_levels.description == 'Continent Level UPDATED' + assert updated_taxonomy_levels.level == taxonomy_levels.entries[0].level taxonomy_levels_after_addition: MetadataTaxonomyLevels = ( client.metadata_taxonomies.add_metadata_taxonomy_level( namespace, taxonomy_key, 'Region', description='Region Description' @@ -104,7 +132,7 @@ def testMetadataTaxonomiesNodes(): ) ) assert len(taxonomy_levels_after_deletion.entries) == 2 - assert taxonomy_levels_after_deletion.entries[0].display_name == 'Continent' + assert taxonomy_levels_after_deletion.entries[0].display_name == 'Continent UPDATED' assert taxonomy_levels_after_deletion.entries[1].display_name == 'Country' continent_node: MetadataTaxonomyNode = ( client.metadata_taxonomies.create_metadata_taxonomy_node( @@ -142,6 +170,39 @@ def testMetadataTaxonomiesNodes(): ) assert get_country_node.display_name == 'Poland UPDATED' assert get_country_node.id == country_node.id + metadata_template_key: str = ''.join(['templateKey', get_uuid()]) + metadata_template: MetadataTemplate = ( + client.metadata_templates.create_metadata_template( + 'enterprise', + metadata_template_key, + template_key=metadata_template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.TAXONOMY, + key='taxonomy', + display_name='taxonomy', + taxonomy_key=taxonomy_key, + namespace=namespace, + options_rules=CreateMetadataTemplateFieldsOptionsRulesField( + multi_select=True, selectable_levels=[1] + ), + ) + ], + ) + ) + assert metadata_template.template_key == metadata_template_key + assert metadata_template.display_name == metadata_template_key + assert len(metadata_template.fields) == 1 + assert to_string(metadata_template.fields[0].type) == 'taxonomy' + options: MetadataTaxonomyNodes = ( + client.metadata_taxonomies.get_metadata_template_field_options( + namespace, metadata_template_key, 'taxonomy' + ) + ) + assert len(options.entries) == 1 + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, metadata_template_key + ) client.metadata_taxonomies.delete_metadata_taxonomy_node( namespace, taxonomy_key, country_node.id ) From 360b840ec5ac893e4e36d873b3697e856e0d86d5 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:28:02 +0100 Subject: [PATCH 074/139] chore: release version 10.3.0 (#1284) --- .codegen.json | 2 +- CHANGELOG.md | 15 +++++++++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 43c85758c..d5071b09e 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "41c28e1", "specHash": "1617aaf", "version": "10.2.0" } +{ "engineHash": "41c28e1", "specHash": "1617aaf", "version": "10.3.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 173c03f9b..5c924b4e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.3.0](https://github.com/box/box-python-sdk/compare/v10.2.0...v10.3.0) (2025-12-19) + + +### Bug Fixes + +* add taxonomy to Metadata Field (read) definition (box/box-openapi[#572](https://github.com/box/box-python-sdk/issues/572)) ([#1269](https://github.com/box/box-python-sdk/issues/1269)) ([7bb9bdc](https://github.com/box/box-python-sdk/commit/7bb9bdc894cb6d765a09ecbedda58b6edb9181d0)) +* fix `RetryAfter` function when there's no `Retry-After` header (box/box-codegen[#903](https://github.com/box/box-python-sdk/issues/903)) ([#1244](https://github.com/box/box-python-sdk/issues/1244)) ([d7cc019](https://github.com/box/box-python-sdk/commit/d7cc019dd186ef3cdb6214a6cf1625ec49c1fd37)) + + +### New Features and Enhancements + +* add Metadata Taxonomies api (box/box-openapi[#569](https://github.com/box/box-python-sdk/issues/569)) ([#1252](https://github.com/box/box-python-sdk/issues/1252)) ([7850463](https://github.com/box/box-python-sdk/commit/7850463204ef9f7802a759ef69a3b7f4b6dd328b)) +* text input validation for Box Sign (box/box-openapi[#568](https://github.com/box/box-python-sdk/issues/568)) ([#1246](https://github.com/box/box-python-sdk/issues/1246)) ([f99512f](https://github.com/box/box-python-sdk/commit/f99512fcfca75edae48bb5a1b6a1897d330f5dd3)) +* Treat `nullable` fields as Optional (box/box-codegen[#906](https://github.com/box/box-python-sdk/issues/906)) ([#1256](https://github.com/box/box-python-sdk/issues/1256)) ([12c05dc](https://github.com/box/box-python-sdk/commit/12c05dcebae13a696956288f41ec55fed8a4011e)) + ## [10.2.0](https://github.com/box/box-python-sdk/compare/v10.1.0...v10.2.0) (2025-12-10) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 335d3f854..518fc4237 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.2.0' +__version__ = '10.3.0' From 103b85870f6bc36e3623e3d052f1f1f9d2a11c04 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 31 Dec 2025 15:52:51 +0100 Subject: [PATCH 075/139] test: Remove integration mappings tests (box/box-codegen#912) (#1294) --- .codegen.json | 2 +- test/integration_mappings.py | 145 ----------------------------------- 2 files changed, 1 insertion(+), 146 deletions(-) delete mode 100644 test/integration_mappings.py diff --git a/.codegen.json b/.codegen.json index d5071b09e..969750adc 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "41c28e1", "specHash": "1617aaf", "version": "10.3.0" } +{ "engineHash": "06ab9c3", "specHash": "425a724", "version": "10.3.0" } diff --git a/test/integration_mappings.py b/test/integration_mappings.py deleted file mode 100644 index a7d938cc3..000000000 --- a/test/integration_mappings.py +++ /dev/null @@ -1,145 +0,0 @@ -from box_sdk_gen.internal.utils import to_string - -import pytest - -from box_sdk_gen.client import BoxClient - -from box_sdk_gen.schemas.folder_full import FolderFull - -from box_sdk_gen.managers.folders import CreateFolderParent - -from box_sdk_gen.managers.user_collaborations import CreateCollaborationItem - -from box_sdk_gen.managers.user_collaborations import CreateCollaborationItemTypeField - -from box_sdk_gen.managers.user_collaborations import CreateCollaborationAccessibleBy - -from box_sdk_gen.managers.user_collaborations import ( - CreateCollaborationAccessibleByTypeField, -) - -from box_sdk_gen.managers.user_collaborations import CreateCollaborationRole - -from box_sdk_gen.schemas.integration_mappings import IntegrationMappings - -from box_sdk_gen.schemas.integration_mapping_partner_item_slack import ( - IntegrationMappingPartnerItemSlack, -) - -from box_sdk_gen.schemas.integration_mapping_box_item_slack import ( - IntegrationMappingBoxItemSlack, -) - -from box_sdk_gen.schemas.integration_mapping import IntegrationMapping - -from box_sdk_gen.schemas.integration_mapping_partner_item_teams_create_request import ( - IntegrationMappingPartnerItemTeamsCreateRequest, -) - -from box_sdk_gen.schemas.integration_mapping_partner_item_teams_create_request import ( - IntegrationMappingPartnerItemTeamsCreateRequestTypeField, -) - -from box_sdk_gen.schemas.folder_reference import FolderReference - -from box_sdk_gen.internal.utils import generate_byte_stream - -from box_sdk_gen.internal.utils import get_uuid - -from box_sdk_gen.internal.utils import get_env_var - -from box_sdk_gen.internal.utils import to_string - -from test.commons import get_default_client - -from test.commons import get_default_client_with_user_subject - -client: BoxClient = get_default_client() - - -def testSlackIntegrationMappings(): - user_id: str = get_env_var('USER_ID') - slack_automation_user_id: str = get_env_var('SLACK_AUTOMATION_USER_ID') - slack_org_id: str = get_env_var('SLACK_ORG_ID') - slack_partner_item_id: str = get_env_var('SLACK_PARTNER_ITEM_ID') - user_client: BoxClient = get_default_client_with_user_subject(user_id) - folder: FolderFull = user_client.folders.create_folder( - get_uuid(), CreateFolderParent(id='0') - ) - user_client.user_collaborations.create_collaboration( - CreateCollaborationItem( - type=CreateCollaborationItemTypeField.FOLDER, id=folder.id - ), - CreateCollaborationAccessibleBy( - type=CreateCollaborationAccessibleByTypeField.USER, - id=slack_automation_user_id, - ), - CreateCollaborationRole.CO_OWNER, - ) - slack_integrations: IntegrationMappings = ( - user_client.integration_mappings.get_slack_integration_mapping() - ) - if len(slack_integrations.entries) == 0: - user_client.integration_mappings.create_slack_integration_mapping( - IntegrationMappingPartnerItemSlack( - id=slack_partner_item_id, slack_org_id=slack_org_id - ), - IntegrationMappingBoxItemSlack(id=folder.id), - ) - slack_mappings: IntegrationMappings = ( - user_client.integration_mappings.get_slack_integration_mapping() - ) - assert len(slack_mappings.entries) >= 1 - slack_integration_mapping: IntegrationMapping = slack_mappings.entries[0] - assert to_string(slack_integration_mapping.integration_type) == 'slack' - assert to_string(slack_integration_mapping.type) == 'integration_mapping' - assert to_string(slack_integration_mapping.box_item.type) == 'folder' - assert slack_integration_mapping.partner_item.id == slack_partner_item_id - assert slack_integration_mapping.partner_item.slack_workspace_id == slack_org_id - assert to_string(slack_integration_mapping.partner_item.type) == 'channel' - updated_slack_mapping: IntegrationMapping = ( - user_client.integration_mappings.update_slack_integration_mapping_by_id( - slack_integration_mapping.id, - box_item=IntegrationMappingBoxItemSlack(id=folder.id), - ) - ) - assert to_string(updated_slack_mapping.box_item.type) == 'folder' - assert updated_slack_mapping.box_item.id == folder.id - if len(slack_mappings.entries) > 2: - user_client.integration_mappings.delete_slack_integration_mapping_by_id( - slack_integration_mapping.id - ) - user_client.folders.delete_folder_by_id(folder.id) - - -def testTeamsIntegrationMappings(): - folder: FolderFull = client.folders.create_folder( - get_uuid(), CreateFolderParent(id='0') - ) - tenant_id: str = '1' - team_id: str = '2' - partner_item_id: str = '3' - user_id: str = get_env_var('USER_ID') - user_client: BoxClient = get_default_client_with_user_subject(user_id) - with pytest.raises(Exception): - user_client.integration_mappings.create_teams_integration_mapping( - IntegrationMappingPartnerItemTeamsCreateRequest( - type=IntegrationMappingPartnerItemTeamsCreateRequestTypeField.CHANNEL, - id=partner_item_id, - tenant_id=tenant_id, - team_id=team_id, - ), - FolderReference(id=folder.id), - ) - with pytest.raises(Exception): - user_client.integration_mappings.get_teams_integration_mapping() - integration_mapping_id: str = '123456' - with pytest.raises(Exception): - user_client.integration_mappings.update_teams_integration_mapping_by_id( - integration_mapping_id, box_item=FolderReference(id='1234567') - ) - with pytest.raises(Exception): - user_client.integration_mappings.delete_teams_integration_mapping_by_id( - integration_mapping_id - ) - client.folders.delete_folder_by_id(folder.id) From 3849f9878a00d7b5dea05851aad4df0222749131 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:57:21 +0100 Subject: [PATCH 076/139] test: Remove `box.com` domain from tests (box/box-codegen#914) (#1298) --- .codegen.json | 2 +- test/shield_lists.py | 2 +- test/sign_requests.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.codegen.json b/.codegen.json index 969750adc..95e1c618b 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "06ab9c3", "specHash": "425a724", "version": "10.3.0" } +{ "engineHash": "3e11b6f", "specHash": "425a724", "version": "10.3.0" } diff --git a/test/shield_lists.py b/test/shield_lists.py index 43baf9731..f45da5f4e 100644 --- a/test/shield_lists.py +++ b/test/shield_lists.py @@ -84,7 +84,7 @@ def testCreateGetUpdateDeleteShieldList(): shield_list_content_email_name, ShieldListContentEmailV2025R0( type=ShieldListContentEmailV2025R0TypeField.EMAIL, - email_addresses=['test@box.com', 'test@example.com'], + email_addresses=['test@boxdemo.com', 'test@example.com'], ), description='A list of things that are shielded', ) diff --git a/test/sign_requests.py b/test/sign_requests.py index 753985901..8a295df8a 100644 --- a/test/sign_requests.py +++ b/test/sign_requests.py @@ -38,7 +38,7 @@ def testCreateGetCancelAndListSignRequest(): - signer_email: str = ''.join([get_uuid(), '@box.com']) + signer_email: str = ''.join([get_uuid(), '@boxdemo.com']) file_to_sign: FileFull = upload_new_file() destination_folder: FolderFull = create_new_folder() created_sign_request: SignRequest = client.sign_requests.create_sign_request( @@ -115,8 +115,8 @@ def testCreateGetCancelAndListSignRequest(): def testCreateSignRequestWithSignerGroupId(): - signer_1_email: str = ''.join([get_uuid(), '@box.com']) - signer_2_email: str = ''.join([get_uuid(), '@box.com']) + signer_1_email: str = ''.join([get_uuid(), '@boxdemo.com']) + signer_2_email: str = ''.join([get_uuid(), '@boxdemo.com']) file_to_sign: FileFull = upload_new_file() destination_folder: FolderFull = create_new_folder() created_sign_request: SignRequest = client.sign_requests.create_sign_request( From 7a2c7e3596c352a3f4d1f73f57c8032bd02f8cdf Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:00:17 +0100 Subject: [PATCH 077/139] feat: Add watermarking permissions and sources information (box/box-openapi#579) (#1300) --- .codegen.json | 2 +- box_sdk_gen/schemas/file_full.py | 24 +++++++++++++++++++++--- box_sdk_gen/schemas/folder_full.py | 24 +++++++++++++++++++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/.codegen.json b/.codegen.json index 95e1c618b..09a90978c 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "3e11b6f", "specHash": "425a724", "version": "10.3.0" } +{ "engineHash": "3e11b6f", "specHash": "4beaa19", "version": "10.3.0" } diff --git a/box_sdk_gen/schemas/file_full.py b/box_sdk_gen/schemas/file_full.py index 615fac845..3396821d6 100644 --- a/box_sdk_gen/schemas/file_full.py +++ b/box_sdk_gen/schemas/file_full.py @@ -1,9 +1,9 @@ +from typing import Optional + from box_sdk_gen.internal.base_object import BaseObject from enum import Enum -from typing import Optional - from typing import List from box_sdk_gen.schemas.file_base import FileBaseTypeField @@ -50,6 +50,8 @@ def __init__( can_upload: bool, can_view_annotations_all: bool, can_view_annotations_self: bool, + *, + can_apply_watermark: Optional[bool] = None, **kwargs ): """ @@ -82,6 +84,8 @@ def __init__( :param can_view_annotations_self: Specifies if the user view annotations placed by themselves on this file. :type can_view_annotations_self: bool + :param can_apply_watermark: Specifies if the user can apply a watermark to this file., defaults to None + :type can_apply_watermark: Optional[bool], optional """ super().__init__(**kwargs) self.can_delete = can_delete @@ -96,6 +100,7 @@ def __init__( self.can_upload = can_upload self.can_view_annotations_all = can_view_annotations_all self.can_view_annotations_self = can_view_annotations_self + self.can_apply_watermark = can_apply_watermark class FileFullLockTypeField(str, Enum): @@ -190,13 +195,26 @@ def __init__( class FileFullWatermarkInfoField(BaseObject): - def __init__(self, *, is_watermarked: Optional[bool] = None, **kwargs): + def __init__( + self, + *, + is_watermarked: Optional[bool] = None, + is_watermark_inherited: Optional[bool] = None, + is_watermarked_by_access_policy: Optional[bool] = None, + **kwargs + ): """ :param is_watermarked: Specifies if this item has a watermark applied., defaults to None :type is_watermarked: Optional[bool], optional + :param is_watermark_inherited: Specifies if the watermark is inherited from any parent folder in the hierarchy., defaults to None + :type is_watermark_inherited: Optional[bool], optional + :param is_watermarked_by_access_policy: Specifies if the watermark is enforced by an access policy., defaults to None + :type is_watermarked_by_access_policy: Optional[bool], optional """ super().__init__(**kwargs) self.is_watermarked = is_watermarked + self.is_watermark_inherited = is_watermark_inherited + self.is_watermarked_by_access_policy = is_watermarked_by_access_policy class FileFullAllowedInviteeRolesField(str, Enum): diff --git a/box_sdk_gen/schemas/folder_full.py b/box_sdk_gen/schemas/folder_full.py index dff8b9f69..b2594ff12 100644 --- a/box_sdk_gen/schemas/folder_full.py +++ b/box_sdk_gen/schemas/folder_full.py @@ -1,9 +1,9 @@ from enum import Enum -from box_sdk_gen.internal.base_object import BaseObject - from typing import Optional +from box_sdk_gen.internal.base_object import BaseObject + from typing import List from box_sdk_gen.schemas.folder_base import FolderBaseTypeField @@ -49,6 +49,8 @@ def __init__( can_set_share_access: bool, can_share: bool, can_upload: bool, + *, + can_apply_watermark: Optional[bool] = None, **kwargs ): """ @@ -70,6 +72,8 @@ def __init__( :type can_share: bool :param can_upload: Specifies if the user can upload into this folder. :type can_upload: bool + :param can_apply_watermark: Specifies if the user can apply a watermark to this folder and its contents., defaults to None + :type can_apply_watermark: Optional[bool], optional """ super().__init__(**kwargs) self.can_delete = can_delete @@ -79,6 +83,7 @@ def __init__( self.can_set_share_access = can_set_share_access self.can_share = can_share self.can_upload = can_upload + self.can_apply_watermark = can_apply_watermark class FolderFullMetadataField(BaseObject): @@ -104,13 +109,26 @@ class FolderFullAllowedInviteeRolesField(str, Enum): class FolderFullWatermarkInfoField(BaseObject): - def __init__(self, *, is_watermarked: Optional[bool] = None, **kwargs): + def __init__( + self, + *, + is_watermarked: Optional[bool] = None, + is_watermark_inherited: Optional[bool] = None, + is_watermarked_by_access_policy: Optional[bool] = None, + **kwargs + ): """ :param is_watermarked: Specifies if this item has a watermark applied., defaults to None :type is_watermarked: Optional[bool], optional + :param is_watermark_inherited: Specifies if the watermark is inherited from any parent folder in the hierarchy., defaults to None + :type is_watermark_inherited: Optional[bool], optional + :param is_watermarked_by_access_policy: Specifies if the watermark is enforced by an access policy., defaults to None + :type is_watermarked_by_access_policy: Optional[bool], optional """ super().__init__(**kwargs) self.is_watermarked = is_watermarked + self.is_watermark_inherited = is_watermark_inherited + self.is_watermarked_by_access_policy = is_watermarked_by_access_policy class FolderFullClassificationField(BaseObject): From 7a0d35af46ab0f94aa255fea52638e59c5ffbdcb Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:04:19 +0100 Subject: [PATCH 078/139] docs: Add missing tag to API resources (box/box-openapi#580) (#1301) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/managers/authorization.py | 2 +- box_sdk_gen/managers/events.py | 4 ++-- box_sdk_gen/managers/folders.py | 2 +- box_sdk_gen/managers/users.py | 4 ++-- box_sdk_gen/schemas/realtime_server.py | 2 +- box_sdk_gen/schemas/user.py | 2 +- box_sdk_gen/schemas/user_full.py | 2 +- docs/authorization.md | 2 +- docs/comments.md | 2 +- docs/events.md | 4 ++-- docs/file_versions.md | 2 +- docs/files.md | 6 +++--- docs/folders.md | 10 +++++----- docs/users.md | 8 ++++---- 15 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.codegen.json b/.codegen.json index 09a90978c..66004b28c 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "3e11b6f", "specHash": "4beaa19", "version": "10.3.0" } +{ "engineHash": "3e11b6f", "specHash": "ad08e8c", "version": "10.3.0" } diff --git a/box_sdk_gen/managers/authorization.py b/box_sdk_gen/managers/authorization.py index 7bcb2af9c..e03ae65e5 100644 --- a/box_sdk_gen/managers/authorization.py +++ b/box_sdk_gen/managers/authorization.py @@ -218,7 +218,7 @@ def request_access_token( operations you will begin by requesting authentication from the - [authorize](#get-authorize) endpoint and Box will send you an + [authorize](https://developer.box.com/reference/get-authorize) endpoint and Box will send you an authorization code. diff --git a/box_sdk_gen/managers/events.py b/box_sdk_gen/managers/events.py index 1ccd76a85..e929f8d83 100644 --- a/box_sdk_gen/managers/events.py +++ b/box_sdk_gen/managers/events.py @@ -420,7 +420,7 @@ def get_events_with_long_polling( """ Returns a list of real-time servers that can be used for long-polling updates - to the [event stream](#get-events). + to the [event stream](https://developer.box.com/reference/get-events). Long polling is the concept where a HTTP request is kept open until the @@ -453,7 +453,7 @@ def get_events_with_long_polling( it only serves as a prompt to take further action such as sending a - request to the [events endpoint](#get-events) with the last known + request to the [events endpoint](https://developer.box.com/reference/get-events) with the last known `stream_position`. diff --git a/box_sdk_gen/managers/folders.py b/box_sdk_gen/managers/folders.py index f7b3e2c3f..3f6c67dfe 100644 --- a/box_sdk_gen/managers/folders.py +++ b/box_sdk_gen/managers/folders.py @@ -689,7 +689,7 @@ def get_folder_items( To request more information about the folder itself, like its size, - use the [Get a folder](#get-folders-id) endpoint instead. + use the [Get a folder](https://developer.box.com/reference/get-folders-id) endpoint instead. :param folder_id: The unique identifier that represent a folder. diff --git a/box_sdk_gen/managers/users.py b/box_sdk_gen/managers/users.py index 0bc8a85da..69549f8cc 100644 --- a/box_sdk_gen/managers/users.py +++ b/box_sdk_gen/managers/users.py @@ -262,7 +262,7 @@ def create_user( :param role: The user’s enterprise role., defaults to None :type role: Optional[CreateUserRole], optional :param language: The language of the user, formatted in modified version of the - [ISO 639-1](/guides/api-calls/language-codes) format., defaults to None + [ISO 639-1](https://developer.box.com/guides/api-calls/language-codes) format., defaults to None :type language: Optional[str], optional :param is_sync_enabled: Whether the user can use Box Sync., defaults to None :type is_sync_enabled: Optional[bool], optional @@ -535,7 +535,7 @@ def update_user_by_id( :param role: The user’s enterprise role., defaults to None :type role: Optional[UpdateUserByIdRole], optional :param language: The language of the user, formatted in modified version of the - [ISO 639-1](/guides/api-calls/language-codes) format., defaults to None + [ISO 639-1](https://developer.box.com/guides/api-calls/language-codes) format., defaults to None :type language: Optional[str], optional :param is_sync_enabled: Whether the user can use Box Sync., defaults to None :type is_sync_enabled: Optional[bool], optional diff --git a/box_sdk_gen/schemas/realtime_server.py b/box_sdk_gen/schemas/realtime_server.py index f8228268e..b9198d27e 100644 --- a/box_sdk_gen/schemas/realtime_server.py +++ b/box_sdk_gen/schemas/realtime_server.py @@ -25,7 +25,7 @@ def __init__( :type ttl: Optional[str], optional :param max_retries: The maximum number of retries this server will allow before a new long poll should be started by - getting a [new list of server](#options-events)., defaults to None + getting a [new list of server](https://developer.box.com/reference/options-events)., defaults to None :type max_retries: Optional[str], optional :param retry_timeout: The maximum number of seconds without a response after which you should retry the long poll connection. diff --git a/box_sdk_gen/schemas/user.py b/box_sdk_gen/schemas/user.py index 58eb548fc..8e98fec97 100644 --- a/box_sdk_gen/schemas/user.py +++ b/box_sdk_gen/schemas/user.py @@ -72,7 +72,7 @@ def __init__( :param modified_at: When the user object was last modified., defaults to None :type modified_at: Optional[DateTime], optional :param language: The language of the user, formatted in modified version of the - [ISO 639-1](/guides/api-calls/language-codes) format., defaults to None + [ISO 639-1](https://developer.box.com/guides/api-calls/language-codes) format., defaults to None :type language: Optional[str], optional :param timezone: The user's timezone., defaults to None :type timezone: Optional[str], optional diff --git a/box_sdk_gen/schemas/user_full.py b/box_sdk_gen/schemas/user_full.py index b35ae441f..6ae220de1 100644 --- a/box_sdk_gen/schemas/user_full.py +++ b/box_sdk_gen/schemas/user_full.py @@ -133,7 +133,7 @@ def __init__( :param modified_at: When the user object was last modified., defaults to None :type modified_at: Optional[DateTime], optional :param language: The language of the user, formatted in modified version of the - [ISO 639-1](/guides/api-calls/language-codes) format., defaults to None + [ISO 639-1](https://developer.box.com/guides/api-calls/language-codes) format., defaults to None :type language: Optional[str], optional :param timezone: The user's timezone., defaults to None :type timezone: Optional[str], optional diff --git a/docs/authorization.md b/docs/authorization.md index 65da2275c..2fb961360 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -51,7 +51,7 @@ authorization code or a server-side JWT assertion. An Access Token is a string that enables Box to verify that a request belongs to an authorized session. In the normal order of operations you will begin by requesting authentication from the -[authorize](#get-authorize) endpoint and Box will send you an +[authorize](https://developer.box.com/reference/get-authorize) endpoint and Box will send you an authorization code. You will then send this code to this endpoint to exchange it for diff --git a/docs/comments.md b/docs/comments.md index b5434eee3..cddc33ac9 100644 --- a/docs/comments.md +++ b/docs/comments.md @@ -170,5 +170,5 @@ This function returns a value of type `CommentFull`. Returns the newly created comment object. Not all available fields are returned by default. Use the -[fields](#param-fields) query parameter to explicitly request +[fields](#parameter-fields) query parameter to explicitly request any specific fields. diff --git a/docs/events.md b/docs/events.md index 7e4e45cfd..80a656e42 100644 --- a/docs/events.md +++ b/docs/events.md @@ -7,7 +7,7 @@ ## Get events long poll endpoint Returns a list of real-time servers that can be used for long-polling updates -to the [event stream](#get-events). +to the [event stream](https://developer.box.com/reference/get-events). Long polling is the concept where a HTTP request is kept open until the server sends a response, then repeating the process over and over to receive @@ -22,7 +22,7 @@ URLs. Next, make a long poll request to any of the provided URLs. When an event occurs in monitored account a response with the value `new_change` will be sent. The response contains no other details as it only serves as a prompt to take further action such as sending a -request to the [events endpoint](#get-events) with the last known +request to the [events endpoint](https://developer.box.com/reference/get-events) with the last known `stream_position`. After the server sends this response it closes the connection. You must now diff --git a/docs/file_versions.md b/docs/file_versions.md index b217d2af5..f9c13063f 100644 --- a/docs/file_versions.md +++ b/docs/file_versions.md @@ -78,7 +78,7 @@ This function returns a value of type `FileVersionFull`. Returns a specific version of a file. Not all available fields are returned by default. Use the -[fields](#param-fields) query parameter to explicitly request +[fields](#parameter-fields) query parameter to explicitly request any specific fields. ## Remove file version diff --git a/docs/files.md b/docs/files.md index 664f051b5..5698c052d 100644 --- a/docs/files.md +++ b/docs/files.md @@ -46,7 +46,7 @@ This function returns a value of type `FileFull`. Returns a file object. Not all available fields are returned by default. Use the -[fields](#param-fields) query parameter to explicitly request +[fields](#parameter-fields) query parameter to explicitly request any specific fields. ## Update file @@ -101,7 +101,7 @@ This function returns a value of type `FileFull`. Returns a file object. Not all available fields are returned by default. Use the -[fields](#param-fields) query parameter to explicitly request +[fields](#parameter-fields) query parameter to explicitly request any specific fields. ## Delete file @@ -176,7 +176,7 @@ This function returns a value of type `FileFull`. Returns a new file object representing the copied file. Not all available fields are returned by default. Use the -[fields](#param-fields) query parameter to explicitly request +[fields](#parameter-fields) query parameter to explicitly request any specific fields. ## Get file thumbnail URL diff --git a/docs/folders.md b/docs/folders.md index b79664416..7c210a50b 100644 --- a/docs/folders.md +++ b/docs/folders.md @@ -65,7 +65,7 @@ To fetch more items within the folder, use the [Get items in a folder](https://developer.box.com/reference/get-folders-id-items)) endpoint. Not all available fields are returned by default. Use the -[fields](#param-fields) query parameter to explicitly request +[fields](#parameter-fields) query parameter to explicitly request any specific fields. ## Update folder @@ -123,7 +123,7 @@ This function returns a value of type `FolderFull`. Returns a folder object for the updated folder Not all available fields are returned by default. Use the -[fields](#param-fields) query parameter to explicitly request +[fields](#parameter-fields) query parameter to explicitly request any specific fields. If the user is moving folders with a large number of items in all of @@ -171,7 +171,7 @@ Retrieves a page of items in a folder. These items can be files, folders, and web links. To request more information about the folder itself, like its size, -use the [Get a folder](#get-folders-id) endpoint instead. +use the [Get a folder](https://developer.box.com/reference/get-folders-id) endpoint instead. This operation is performed by calling function `get_folder_items`. @@ -249,7 +249,7 @@ This function returns a value of type `FolderFull`. Returns a folder object. Not all available fields are returned by default. Use the -[fields](#param-fields) query parameter to explicitly request +[fields](#parameter-fields) query parameter to explicitly request any specific fields. ## Copy folder @@ -291,5 +291,5 @@ This function returns a value of type `FolderFull`. Returns a new folder object representing the copied folder. Not all available fields are returned by default. Use the -[fields](#param-fields) query parameter to explicitly request +[fields](#parameter-fields) query parameter to explicitly request any specific fields. diff --git a/docs/users.md b/docs/users.md index 45eaa9eb7..725d9d912 100644 --- a/docs/users.md +++ b/docs/users.md @@ -82,7 +82,7 @@ client.users.create_user(user_name, login=user_login, is_platform_access_only=Tr - role `Optional[CreateUserRole]` - The user’s enterprise role. - language `Optional[str]` - - The language of the user, formatted in modified version of the [ISO 639-1](/guides/api-calls/language-codes) format. + - The language of the user, formatted in modified version of the [ISO 639-1](https://developer.box.com/guides/api-calls/language-codes) format. - is_sync_enabled `Optional[bool]` - Whether the user can use Box Sync. - job_title `Optional[str]` @@ -198,8 +198,8 @@ This function returns a value of type `UserFull`. Returns a single user object. Not all available fields are returned by default. Use the -[fields](#param-fields) query parameter to explicitly request -any specific fields using the [fields](#get-users-id--request--fields) +[fields](#parameter-fields) query parameter to explicitly request +any specific fields using the [fields](#parameter-fields) parameter. ## Update user @@ -234,7 +234,7 @@ client.users.update_user_by_id(user.id, name=updated_user_name) - role `Optional[UpdateUserByIdRole]` - The user’s enterprise role. - language `Optional[str]` - - The language of the user, formatted in modified version of the [ISO 639-1](/guides/api-calls/language-codes) format. + - The language of the user, formatted in modified version of the [ISO 639-1](https://developer.box.com/guides/api-calls/language-codes) format. - is_sync_enabled `Optional[bool]` - Whether the user can use Box Sync. - job_title `Optional[str]` From d51b4eac0a0e2cbb593871bd35e48f0e4cbe6693 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:13:40 +0100 Subject: [PATCH 079/139] fix: Correct enum value for legal hold policy changes (box/box-openapi#581) (#1304) --- .codegen.json | 2 +- .../managers/legal_hold_policy_assignments.py | 6 +++--- box_sdk_gen/schemas/legal_hold_policy.py | 12 ++++++++++-- docs/legal_hold_policy_assignments.md | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.codegen.json b/.codegen.json index 66004b28c..3b7525eee 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "3e11b6f", "specHash": "ad08e8c", "version": "10.3.0" } +{ "engineHash": "f9e2519", "specHash": "f8fb08c", "version": "10.3.0" } diff --git a/box_sdk_gen/managers/legal_hold_policy_assignments.py b/box_sdk_gen/managers/legal_hold_policy_assignments.py index 3e6812726..2653ffd8d 100644 --- a/box_sdk_gen/managers/legal_hold_policy_assignments.py +++ b/box_sdk_gen/managers/legal_hold_policy_assignments.py @@ -60,7 +60,7 @@ class CreateLegalHoldPolicyAssignmentAssignToTypeField(str, Enum): FOLDER = 'folder' USER = 'user' OWNERSHIP = 'ownership' - INTERACTION = 'interaction' + INTERACTIONS = 'interactions' class CreateLegalHoldPolicyAssignmentAssignTo(BaseObject): @@ -70,7 +70,7 @@ class CreateLegalHoldPolicyAssignmentAssignTo(BaseObject): 'folder', 'user', 'ownership', - 'interaction', + 'interactions', } def __init__( @@ -179,7 +179,7 @@ def create_legal_hold_policy_assignment( extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> LegalHoldPolicyAssignment: """ - Assign a legal hold to a file, file version, folder, or user. + Assign a legal hold to an item type of: file, file version, folder, user, ownership, or interactions. :param policy_id: The ID of the policy to assign. :type policy_id: str :param assign_to: The item to assign the policy to. diff --git a/box_sdk_gen/schemas/legal_hold_policy.py b/box_sdk_gen/schemas/legal_hold_policy.py index 5a91fa884..f3e85921e 100644 --- a/box_sdk_gen/schemas/legal_hold_policy.py +++ b/box_sdk_gen/schemas/legal_hold_policy.py @@ -30,10 +30,12 @@ def __init__( folder: Optional[int] = None, file: Optional[int] = None, file_version: Optional[int] = None, + ownership: Optional[int] = None, + interactions: Optional[int] = None, **kwargs ): """ - :param user: The number of users this policy is applied to., defaults to None + :param user: The number of users this policy is applied to with the `access` type assignment., defaults to None :type user: Optional[int], optional :param folder: The number of folders this policy is applied to., defaults to None :type folder: Optional[int], optional @@ -41,12 +43,18 @@ def __init__( :type file: Optional[int], optional :param file_version: The number of file versions this policy is applied to., defaults to None :type file_version: Optional[int], optional + :param ownership: The number of users this policy is applied to with the `ownership` type assignment., defaults to None + :type ownership: Optional[int], optional + :param interactions: The number of users this policy is applied to with the `interactions` type assignment., defaults to None + :type interactions: Optional[int], optional """ super().__init__(**kwargs) self.user = user self.folder = folder self.file = file self.file_version = file_version + self.ownership = ownership + self.interactions = interactions class LegalHoldPolicy(LegalHoldPolicyMini): @@ -84,7 +92,7 @@ def __init__( of being released. * 'released' - the policy is no longer active., defaults to None :type status: Optional[LegalHoldPolicyStatusField], optional - :param assignment_counts: Counts of assignments within this a legal hold policy by item type., defaults to None + :param assignment_counts: Counts of assignments within a legal hold policy by item type., defaults to None :type assignment_counts: Optional[LegalHoldPolicyAssignmentCountsField], optional :param created_at: When the legal hold policy object was created., defaults to None :type created_at: Optional[DateTime], optional diff --git a/docs/legal_hold_policy_assignments.md b/docs/legal_hold_policy_assignments.md index cd14c2aef..c23119a45 100644 --- a/docs/legal_hold_policy_assignments.md +++ b/docs/legal_hold_policy_assignments.md @@ -48,7 +48,7 @@ Returns a list of legal hold policy assignments. ## Assign legal hold policy -Assign a legal hold to a file, file version, folder, or user. +Assign a legal hold to an item type of: file, file version, folder, user, ownership, or interactions. This operation is performed by calling function `create_legal_hold_policy_assignment`. From 9db7a80514e4d0c45baf05cd6823c88e6666b395 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:32:28 +0100 Subject: [PATCH 080/139] feat: add confidence scores for structured extract (box/box-openapi#582) (#1311) --- .codegen.json | 2 +- box_sdk_gen/managers/ai.py | 4 ++++ box_sdk_gen/schemas/ai_extract_structured.py | 4 ++++ box_sdk_gen/schemas/ai_extract_structured_response.py | 4 ++++ docs/ai.md | 2 ++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 3b7525eee..ff89bc9f1 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "f9e2519", "specHash": "f8fb08c", "version": "10.3.0" } +{ "engineHash": "f9e2519", "specHash": "ccdb456", "version": "10.3.0" } diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index 11b1d6f37..483c0e7ed 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -425,6 +425,7 @@ def create_ai_extract_structured( *, metadata_template: Optional[CreateAiExtractStructuredMetadataTemplate] = None, fields: Optional[List[CreateAiExtractStructuredFields]] = None, + include_confidence_score: Optional[bool] = None, ai_agent: Optional[AiExtractStructuredAgent] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> AiExtractStructuredResponse: @@ -450,6 +451,8 @@ def create_ai_extract_structured( :param fields: The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both., defaults to None :type fields: Optional[List[CreateAiExtractStructuredFields]], optional + :param include_confidence_score: A flag to indicate whether confidence scores for every extracted field should be returned., defaults to None + :type include_confidence_score: Optional[bool], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ @@ -459,6 +462,7 @@ def create_ai_extract_structured( 'items': items, 'metadata_template': metadata_template, 'fields': fields, + 'include_confidence_score': include_confidence_score, 'ai_agent': ai_agent, } headers_map: Dict[str, str] = prepare_params({**extra_headers}) diff --git a/box_sdk_gen/schemas/ai_extract_structured.py b/box_sdk_gen/schemas/ai_extract_structured.py index 5a742b529..172e90003 100644 --- a/box_sdk_gen/schemas/ai_extract_structured.py +++ b/box_sdk_gen/schemas/ai_extract_structured.py @@ -114,6 +114,7 @@ def __init__( *, metadata_template: Optional[AiExtractStructuredMetadataTemplateField] = None, fields: Optional[List[AiExtractStructuredFieldsField]] = None, + include_confidence_score: Optional[bool] = None, ai_agent: Optional[AiExtractStructuredAgent] = None, **kwargs ): @@ -126,9 +127,12 @@ def __init__( :param fields: The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both., defaults to None :type fields: Optional[List[AiExtractStructuredFieldsField]], optional + :param include_confidence_score: A flag to indicate whether confidence scores for every extracted field should be returned., defaults to None + :type include_confidence_score: Optional[bool], optional """ super().__init__(**kwargs) self.items = items self.metadata_template = metadata_template self.fields = fields + self.include_confidence_score = include_confidence_score self.ai_agent = ai_agent diff --git a/box_sdk_gen/schemas/ai_extract_structured_response.py b/box_sdk_gen/schemas/ai_extract_structured_response.py index 76ae79525..5c5e7dc05 100644 --- a/box_sdk_gen/schemas/ai_extract_structured_response.py +++ b/box_sdk_gen/schemas/ai_extract_structured_response.py @@ -18,6 +18,7 @@ def __init__( created_at: DateTime, *, completion_reason: Optional[str] = None, + confidence_score: Optional[Dict] = None, ai_agent_info: Optional[AiAgentInfo] = None, **kwargs ): @@ -26,9 +27,12 @@ def __init__( :type created_at: DateTime :param completion_reason: The reason the response finishes., defaults to None :type completion_reason: Optional[str], optional + :param confidence_score: The confidence score numeric values for each extracted field as a JSON dictionary. This can be empty if no field could be extracted., defaults to None + :type confidence_score: Optional[Dict], optional """ super().__init__(**kwargs) self.answer = answer self.created_at = created_at self.completion_reason = completion_reason + self.confidence_score = confidence_score self.ai_agent_info = ai_agent_info diff --git a/docs/ai.md b/docs/ai.md index 47d4c6349..1c9ab630d 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -258,6 +258,8 @@ client.ai.create_ai_extract_structured( - The metadata template containing the fields to extract. For your request to work, you must provide either `metadata_template` or `fields`, but not both. - fields `Optional[List[CreateAiExtractStructuredFields]]` - The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both. +- include_confidence_score `Optional[bool]` + - A flag to indicate whether confidence scores for every extracted field should be returned. - ai_agent `Optional[AiExtractStructuredAgent]` - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. From d52fa05e56b57f74fcc69fc55e45ec917b933070 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:43:25 +0100 Subject: [PATCH 081/139] test: test AI confidence score (box/box-codegen#919) (#1312) --- .codegen.json | 2 +- docs/ai.md | 1 + test/ai.py | 16 ++++++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.codegen.json b/.codegen.json index ff89bc9f1..9969bcb45 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "f9e2519", "specHash": "ccdb456", "version": "10.3.0" } +{ "engineHash": "b181eba", "specHash": "ccdb456", "version": "10.3.0" } diff --git a/docs/ai.md b/docs/ai.md index 1c9ab630d..96e3d76b5 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -246,6 +246,7 @@ client.ai.create_ai_extract_structured( ], ), ], + include_confidence_score=True, ai_agent=ai_extract_structured_agent_basic_text_config, ) ``` diff --git a/test/ai.py b/test/ai.py index 1658d4e6c..00e1100ca 100644 --- a/test/ai.py +++ b/test/ai.py @@ -252,7 +252,12 @@ def testAIExtractStructuredWithFields(): parent=UploadFileAttributesParentField(id='0'), ), string_to_byte_stream( - 'My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar.' + ''.join( + [ + 'My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar. My UUID is ', + get_uuid(), + ] + ) ), ) file: FileFull = uploaded_files.entries[0] @@ -300,8 +305,10 @@ def testAIExtractStructuredWithFields(): ], ), ], + include_confidence_score=True, ai_agent=ai_extract_structured_agent_basic_text_config, ) + assert not response.confidence_score == None assert to_string(response.answer.get('hobby')) == to_string(['guitar']) assert to_string(response.answer.get('firstName')) == 'John' assert to_string(response.answer.get('lastName')) == 'Doe' @@ -318,7 +325,12 @@ def testAIExtractStructuredWithMetadataTemplate(): parent=UploadFileAttributesParentField(id='0'), ), string_to_byte_stream( - 'My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar.' + ''.join( + [ + 'My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar. My UUID is ', + get_uuid(), + ] + ) ), ) file: FileFull = uploaded_files.entries[0] From 02e3b137a7cce4f111d7d084289b0d6609ef2103 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:43:57 +0100 Subject: [PATCH 082/139] docs: Link configuration docs in Readme (box/box-codegen#917) (#1313) Co-authored-by: Minh Nguyen Cong --- docs/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 61d2c712a..85c0f0ec5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,14 @@ # Documentation -## High-level Documentation +## Configuration + +This section covers the documentation required to get up and running with the SDK, including configuration, authentication, and client initialization for making API calls. + +- [Configuration](Configuration.md) +- [Authentication](Authentication.md) +- [Client](Client.md) + +## Box API Usage General explanations of the available functionality and examples of how to use the SDK are available by topic: From dd276be46ea543f846b8b301e19c42be4938f11e Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:19:49 +0100 Subject: [PATCH 083/139] docs: Adjust docs filenames to language convention (box/box-codegen#921) (#1316) --- .codegen.json | 2 +- docs/README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.codegen.json b/.codegen.json index 9969bcb45..dd56f9e69 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "b181eba", "specHash": "ccdb456", "version": "10.3.0" } +{ "engineHash": "29cb063", "specHash": "ccdb456", "version": "10.3.0" } diff --git a/docs/README.md b/docs/README.md index 85c0f0ec5..c626b6118 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,9 +4,9 @@ This section covers the documentation required to get up and running with the SDK, including configuration, authentication, and client initialization for making API calls. -- [Configuration](Configuration.md) -- [Authentication](Authentication.md) -- [Client](Client.md) +- [Configuration](configuration.md) +- [Authentication](authentication.md) +- [Client](client.md) ## Box API Usage From 672ac0fdaa09b0738f0b79aadff828a5ae3fd61f Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 10 Feb 2026 18:15:05 +0100 Subject: [PATCH 084/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1327) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index dd56f9e69..7b24eff2a 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "29cb063", "specHash": "ccdb456", "version": "10.3.0" } +{ "engineHash": "bfb97cc", "specHash": "ccdb456", "version": "10.3.0" } From fcca2a47439189c2f4986596cdfd7d3c303c9ec2 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 11 Feb 2026 12:39:00 +0100 Subject: [PATCH 085/139] feat: add signer language, cancel sign request reason (box/box-openapi#584) (#1331) --- .codegen.json | 2 +- box_sdk_gen/managers/folders.py | 12 ++++++------ box_sdk_gen/managers/sign_requests.py | 12 ++++++++++-- box_sdk_gen/schemas/__init__.py | 2 ++ .../schemas/ai_extract_structured_response.py | 2 +- .../schemas/sign_request_cancel_request.py | 15 +++++++++++++++ box_sdk_gen/schemas/sign_request_create_signer.py | 5 +++++ box_sdk_gen/schemas/sign_request_signer.py | 5 +++++ docs/folders.md | 4 ++-- docs/sign_requests.md | 2 ++ 10 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 box_sdk_gen/schemas/sign_request_cancel_request.py diff --git a/.codegen.json b/.codegen.json index 7b24eff2a..451b29035 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "bfb97cc", "specHash": "ccdb456", "version": "10.3.0" } +{ "engineHash": "bfb97cc", "specHash": "77eac4b", "version": "10.3.0" } diff --git a/box_sdk_gen/managers/folders.py b/box_sdk_gen/managers/folders.py index 3f6c67dfe..fc55bef41 100644 --- a/box_sdk_gen/managers/folders.py +++ b/box_sdk_gen/managers/folders.py @@ -377,9 +377,10 @@ def get_folder_by_id( :type direction: Optional[GetFolderByIdDirection], optional :param offset: The offset of the item at which to begin the response. - Queries with offset parameter value - exceeding 10000 will be rejected - with a 400 response., defaults to None + Offset-based pagination is not guaranteed to work reliably for high offset values and may fail for large datasets. In + those cases, reduce the number of items in the folder (for example, by + restructuring the folder into smaller subfolders) before retrying the + request., defaults to None :type offset: Optional[int], optional :param limit: The maximum number of items to return per page., defaults to None :type limit: Optional[int], optional @@ -733,9 +734,8 @@ def get_folder_items( :type marker: Optional[str], optional :param offset: The offset of the item at which to begin the response. - Queries with offset parameter value - exceeding 10000 will be rejected - with a 400 response., defaults to None + Offset-based pagination is not guaranteed to work reliably for high offset values and may fail for large datasets. In + those cases, use marker-based pagination by setting `usemarker` to `true`., defaults to None :type offset: Optional[int], optional :param limit: The maximum number of items to return per page., defaults to None :type limit: Optional[int], optional diff --git a/box_sdk_gen/managers/sign_requests.py b/box_sdk_gen/managers/sign_requests.py index 75e4cf0e4..09e00bf54 100644 --- a/box_sdk_gen/managers/sign_requests.py +++ b/box_sdk_gen/managers/sign_requests.py @@ -6,6 +6,8 @@ from box_sdk_gen.internal.utils import to_string +from box_sdk_gen.serialization.json import serialize + from box_sdk_gen.serialization.json import deserialize from typing import List @@ -14,8 +16,6 @@ from typing import Union -from box_sdk_gen.serialization.json import serialize - from box_sdk_gen.networking.fetch_options import ResponseFormat from box_sdk_gen.schemas.file_base import FileBase @@ -30,6 +30,8 @@ from box_sdk_gen.schemas.client_error import ClientError +from box_sdk_gen.schemas.sign_request_cancel_request import SignRequestCancelRequest + from box_sdk_gen.schemas.sign_requests import SignRequests from box_sdk_gen.schemas.sign_request_create_request import SignRequestCreateRequest @@ -77,6 +79,7 @@ def cancel_sign_request( self, sign_request_id: str, *, + reason: Optional[str] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> SignRequest: """ @@ -84,11 +87,14 @@ def cancel_sign_request( :param sign_request_id: The ID of the signature request. Example: "33243242" :type sign_request_id: str + :param reason: An optional reason for cancelling the sign request., defaults to None + :type reason: Optional[str], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ if extra_headers is None: extra_headers = {} + request_body: Dict = {'reason': reason} headers_map: Dict[str, str] = prepare_params({**extra_headers}) response: FetchResponse = self.network_session.network_client.fetch( FetchOptions( @@ -102,6 +108,8 @@ def cancel_sign_request( ), method='POST', headers=headers_map, + data=serialize(request_body) if not request_body == None else None, + content_type='application/json', response_format=ResponseFormat.JSON, auth=self.auth, network_session=self.network_session, diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index 023aac177..a4132c26b 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -274,6 +274,8 @@ from box_sdk_gen.schemas.shield_information_barrier_segment_restriction_mini import * +from box_sdk_gen.schemas.sign_request_cancel_request import * + from box_sdk_gen.schemas.sign_request_create_signer import * from box_sdk_gen.schemas.sign_request_prefill_tag import * diff --git a/box_sdk_gen/schemas/ai_extract_structured_response.py b/box_sdk_gen/schemas/ai_extract_structured_response.py index 5c5e7dc05..d583e50b7 100644 --- a/box_sdk_gen/schemas/ai_extract_structured_response.py +++ b/box_sdk_gen/schemas/ai_extract_structured_response.py @@ -27,7 +27,7 @@ def __init__( :type created_at: DateTime :param completion_reason: The reason the response finishes., defaults to None :type completion_reason: Optional[str], optional - :param confidence_score: The confidence score numeric values for each extracted field as a JSON dictionary. This can be empty if no field could be extracted., defaults to None + :param confidence_score: The confidence score levels and numeric values for each extracted field as a JSON dictionary. This can be empty if no field could be extracted., defaults to None :type confidence_score: Optional[Dict], optional """ super().__init__(**kwargs) diff --git a/box_sdk_gen/schemas/sign_request_cancel_request.py b/box_sdk_gen/schemas/sign_request_cancel_request.py new file mode 100644 index 000000000..f7de1a575 --- /dev/null +++ b/box_sdk_gen/schemas/sign_request_cancel_request.py @@ -0,0 +1,15 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class SignRequestCancelRequest(BaseObject): + def __init__(self, *, reason: Optional[str] = None, **kwargs): + """ + :param reason: An optional reason for cancelling the sign request., defaults to None + :type reason: Optional[str], optional + """ + super().__init__(**kwargs) + self.reason = reason diff --git a/box_sdk_gen/schemas/sign_request_create_signer.py b/box_sdk_gen/schemas/sign_request_create_signer.py index ed62e5cb1..4c5563488 100644 --- a/box_sdk_gen/schemas/sign_request_create_signer.py +++ b/box_sdk_gen/schemas/sign_request_create_signer.py @@ -29,6 +29,7 @@ def __init__( password: Optional[str] = None, signer_group_id: Optional[str] = None, suppress_notifications: Optional[bool] = None, + language: Optional[str] = None, **kwargs ): """ @@ -82,6 +83,9 @@ def __init__( :type signer_group_id: Optional[str], optional :param suppress_notifications: If true, no emails about the sign request will be sent., defaults to None :type suppress_notifications: Optional[bool], optional + :param language: The language of the user, formatted in modified version of the + [ISO 639-1](https://developer.box.com/guides/api-calls/language-codes) format., defaults to None + :type language: Optional[str], optional """ super().__init__(**kwargs) self.email = email @@ -96,3 +100,4 @@ def __init__( self.password = password self.signer_group_id = signer_group_id self.suppress_notifications = suppress_notifications + self.language = language diff --git a/box_sdk_gen/schemas/sign_request_signer.py b/box_sdk_gen/schemas/sign_request_signer.py index 21e927bf9..35b708261 100644 --- a/box_sdk_gen/schemas/sign_request_signer.py +++ b/box_sdk_gen/schemas/sign_request_signer.py @@ -75,6 +75,7 @@ def __init__( password: Optional[str] = None, signer_group_id: Optional[str] = None, suppress_notifications: Optional[bool] = None, + language: Optional[str] = None, **kwargs ): """ @@ -143,6 +144,9 @@ def __init__( :type signer_group_id: Optional[str], optional :param suppress_notifications: If true, no emails about the sign request will be sent., defaults to None :type suppress_notifications: Optional[bool], optional + :param language: The language of the user, formatted in modified version of the + [ISO 639-1](https://developer.box.com/guides/api-calls/language-codes) format., defaults to None + :type language: Optional[str], optional """ super().__init__( email=email, @@ -157,6 +161,7 @@ def __init__( password=password, signer_group_id=signer_group_id, suppress_notifications=suppress_notifications, + language=language, **kwargs ) self.has_viewed_document = has_viewed_document diff --git a/docs/folders.md b/docs/folders.md index 7c210a50b..62db04d5b 100644 --- a/docs/folders.md +++ b/docs/folders.md @@ -42,7 +42,7 @@ client.folders.get_folder_by_id("0") - direction `Optional[GetFolderByIdDirection]` - The direction to sort results in. This can be either in alphabetical ascending (`ASC`) or descending (`DESC`) order. - offset `Optional[int]` - - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. + - The offset of the item at which to begin the response. Offset-based pagination is not guaranteed to work reliably for high offset values and may fail for large datasets. In those cases, reduce the number of items in the folder (for example, by restructuring the folder into smaller subfolders) before retrying the request. - limit `Optional[int]` - The maximum number of items to return per page. - if_none_match `Optional[str]` @@ -195,7 +195,7 @@ client.folders.get_folder_items(folder_origin.id) - marker `Optional[str]` - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. - offset `Optional[int]` - - The offset of the item at which to begin the response. Queries with offset parameter value exceeding 10000 will be rejected with a 400 response. + - The offset of the item at which to begin the response. Offset-based pagination is not guaranteed to work reliably for high offset values and may fail for large datasets. In those cases, use marker-based pagination by setting `usemarker` to `true`. - limit `Optional[int]` - The maximum number of items to return per page. - sort `Optional[GetFolderItemsSort]` diff --git a/docs/sign_requests.md b/docs/sign_requests.md index 56a7dbfde..1abe8c888 100644 --- a/docs/sign_requests.md +++ b/docs/sign_requests.md @@ -25,6 +25,8 @@ client.sign_requests.cancel_sign_request(created_sign_request.id) - sign_request_id `str` - The ID of the signature request. Example: "33243242" +- reason `Optional[str]` + - An optional reason for cancelling the sign request. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. From 5d3cdfc6acb1d449d2e9b68e6e8bf8f80941b07f Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:55:20 +0100 Subject: [PATCH 086/139] chore: release version 10.4.0 (#1333) --- .codegen.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 451b29035..cf002cd64 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "bfb97cc", "specHash": "77eac4b", "version": "10.3.0" } +{ "engineHash": "bfb97cc", "specHash": "77eac4b", "version": "10.4.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c924b4e3..febf44ed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.4.0](https://github.com/box/box-python-sdk/compare/v10.3.0...v10.4.0) (2026-02-11) + + +### Bug Fixes + +* Correct enum value for legal hold policy changes (box/box-openapi[#581](https://github.com/box/box-python-sdk/issues/581)) ([#1304](https://github.com/box/box-python-sdk/issues/1304)) ([d51b4ea](https://github.com/box/box-python-sdk/commit/d51b4eac0a0e2cbb593871bd35e48f0e4cbe6693)) + + +### New Features and Enhancements + +* add confidence scores for structured extract (box/box-openapi[#582](https://github.com/box/box-python-sdk/issues/582)) ([#1311](https://github.com/box/box-python-sdk/issues/1311)) ([9db7a80](https://github.com/box/box-python-sdk/commit/9db7a80514e4d0c45baf05cd6823c88e6666b395)) +* add signer language, cancel sign request reason (box/box-openapi[#584](https://github.com/box/box-python-sdk/issues/584)) ([#1331](https://github.com/box/box-python-sdk/issues/1331)) ([fcca2a4](https://github.com/box/box-python-sdk/commit/fcca2a47439189c2f4986596cdfd7d3c303c9ec2)) +* Add watermarking permissions and sources information (box/box-openapi[#579](https://github.com/box/box-python-sdk/issues/579)) ([#1300](https://github.com/box/box-python-sdk/issues/1300)) ([7a2c7e3](https://github.com/box/box-python-sdk/commit/7a2c7e3596c352a3f4d1f73f57c8032bd02f8cdf)) + ## [10.3.0](https://github.com/box/box-python-sdk/compare/v10.2.0...v10.3.0) (2025-12-19) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 518fc4237..00cefb785 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.3.0' +__version__ = '10.4.0' From d33db7cc5630540c140b9e06521a76c09d7268fb Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:31:47 +0100 Subject: [PATCH 087/139] test: assume that exception is thrown from delete archive (box/box-codegen#927) (#1335) --- .codegen.json | 2 +- test/archives.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index cf002cd64..16f59e2de 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "bfb97cc", "specHash": "77eac4b", "version": "10.4.0" } +{ "engineHash": "f36ed52", "specHash": "77eac4b", "version": "10.4.0" } diff --git a/test/archives.py b/test/archives.py index 65c2e6ed1..83d3ef39f 100644 --- a/test/archives.py +++ b/test/archives.py @@ -37,6 +37,5 @@ def testArchivesCreateListDelete(): assert updated_archive.description == new_archive_description archives: ArchivesV2025R0 = client.archives.get_archives_v2025_r0(limit=100) assert len(archives.entries) > 0 - client.archives.delete_archive_by_id_v2025_r0(archive.id) with pytest.raises(Exception): client.archives.delete_archive_by_id_v2025_r0(archive.id) From 1402f6cfbbb691ad320f9039c32dc1bfdcd3e962 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:10:37 +0100 Subject: [PATCH 088/139] docs: Improve documentation for retry strategies (box/box-codegen#925) (#1347) --- .codegen.json | 2 +- docs/configuration.md | 145 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 136 insertions(+), 11 deletions(-) diff --git a/.codegen.json b/.codegen.json index 16f59e2de..f8eaf4e6a 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "f36ed52", "specHash": "77eac4b", "version": "10.4.0" } +{ "engineHash": "482939a", "specHash": "77eac4b", "version": "10.4.0" } diff --git a/docs/configuration.md b/docs/configuration.md index 4c10d7f8e..3ec5fa9c9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -3,15 +3,134 @@ -- [Max retry attempts](#max-retry-attempts) -- [Custom retry strategy](#custom-retry-strategy) +- [Retry Strategy](#retry-strategy) + - [Overview](#overview) + - [Default Configuration](#default-configuration) + - [Retry Decision Flow](#retry-decision-flow) + - [Exponential Backoff Algorithm](#exponential-backoff-algorithm) + - [Example Delays (with default settings)](#example-delays-with-default-settings) + - [Retry-After Header](#retry-after-header) + - [Network Exception Handling](#network-exception-handling) + - [Customizing Retry Parameters](#customizing-retry-parameters) + - [Custom Retry Strategy](#custom-retry-strategy) -## Max retry attempts +## Retry Strategy -The default maximum number of retries in case of failed API call is 5. -To change this number you should initialize `BoxRetryStrategy` with the new value and pass it to `NetworkSession`. +### Overview + +The SDK ships with a built-in retry strategy (`BoxRetryStrategy`) that implements the `RetryStrategy` interface. The `BoxNetworkClient`, which serves as the default network client, uses this strategy to automatically retry failed API requests with exponential backoff. + +The retry strategy exposes two methods: + +- **`should_retry`** — Determines whether a failed request should be retried based on the HTTP status code, response headers, attempt count, and authentication state. +- **`retry_after`** — Computes the delay (in seconds) before the next retry attempt, using either the server-provided `Retry-After` header or an exponential backoff formula. + +### Default Configuration + +| Parameter | Default | Description | +| ---------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `max_attempts` | `5` | Maximum number of retry attempts for HTTP error responses (status 4xx/5xx). | +| `retry_base_interval` | `1` (second) | Base interval used in the exponential backoff calculation. | +| `retry_randomization_factor` | `0.5` | Jitter factor applied to the backoff delay. The actual delay is multiplied by a random value between `1 - factor` and `1 + factor`. | +| `max_retries_on_exception` | `2` | Maximum number of retries for network-level exceptions (connection failures, timeouts). These are tracked by a separate counter from HTTP error retries. | + +### Retry Decision Flow + +The following diagram shows how `BoxRetryStrategy.should_retry` decides whether to retry a request: + +``` + should_retry(fetch_options, fetch_response, attempt_number) + | + v + +-----------------------+ + | status == 0 | Yes + | (network exception)? |----------> attempt_number <= max_retries_on_exception? + +-----------------------+ | | + | No Yes No + v | | + +-----------------------+ [RETRY] [NO RETRY] + | attempt_number >= | + | max_attempts? | + +-----------------------+ + | | + Yes No + | | + [NO RETRY] v + +-----------------------+ + | status == 202 AND | Yes + | Retry-After header? |----------> [RETRY] + +-----------------------+ + | No + v + +-----------------------+ + | status >= 500 | Yes + | (server error)? |----------> [RETRY] + +-----------------------+ + | No + v + +-----------------------+ + | status == 429 | Yes + | (rate limited)? |----------> [RETRY] + +-----------------------+ + | No + v + +-----------------------+ + | status == 401 AND | Yes + | auth available? |----------> Refresh token, then [RETRY] + +-----------------------+ + | No + v + [NO RETRY] +``` + +### Exponential Backoff Algorithm + +When the response does not include a `Retry-After` header, the retry delay is computed using exponential backoff with randomized jitter: + +``` +delay = 2^attempt_number * retry_base_interval * random(1 - factor, 1 + factor) +``` + +Where: + +- `attempt_number` is the current attempt (1-based) +- `retry_base_interval` defaults to `1` second +- `factor` is `retry_randomization_factor` (default `0.5`) +- `random(min, max)` returns a uniformly distributed value in `[min, max]` + +#### Example Delays (with default settings) + +| Attempt | Base Delay | Min Delay (factor=0.5) | Max Delay (factor=0.5) | +| ------- | ---------- | ---------------------- | ---------------------- | +| 1 | 2s | 1.0s | 3.0s | +| 2 | 4s | 2.0s | 6.0s | +| 3 | 8s | 4.0s | 12.0s | +| 4 | 16s | 8.0s | 24.0s | + +### Retry-After Header + +When the server includes a `Retry-After` header in the response, the SDK uses the header value directly as the delay in seconds instead of computing an exponential backoff delay. This applies to any retryable response that includes the header, including: + +- `202 Accepted` with `Retry-After` (long-running operations) +- `429 Too Many Requests` with `Retry-After` +- `5xx` server errors with `Retry-After` + +The header value is parsed as a floating-point number representing seconds. + +### Network Exception Handling + +Network-level failures (connection refused, DNS resolution errors, timeouts, TLS errors) are represented internally as responses with status `0`. These exceptions are tracked by a **separate counter** (`max_retries_on_exception`, default `2`) from the regular HTTP error retry counter (`max_attempts`). + +This means: + +- Network exception retries are tracked independently from HTTP error retries, each with their own counter and backoff progression. +- A request can fail up to `max_retries_on_exception` times due to network exceptions, but each exception retry also increments the overall attempt counter, so the total number of retries across both exception and HTTP error types is bounded by `max_attempts`. + +### Customizing Retry Parameters + +You can customize all retry parameters by initializing `BoxRetryStrategy` with the desired values and passing it to `NetworkSession`: ```python from box_sdk_gen import ( @@ -22,14 +141,20 @@ from box_sdk_gen import ( ) auth = BoxDeveloperTokenAuth(token="DEVELOPER_TOKEN_GOES_HERE") -network_session = NetworkSession(retry_strategy=BoxRetryStrategy(max_attempts=6)) +network_session = NetworkSession( + retry_strategy=BoxRetryStrategy( + max_attempts=3, + retry_base_interval=2, + retry_randomization_factor=0.3, + max_retries_on_exception=1, + ) +) client = BoxClient(auth=auth, network_session=network_session) ``` -## Custom retry strategy +### Custom Retry Strategy -You can also implement your own retry strategy by subclassing `RetryStrategy` and overriding `should_retry` and `retry_after` methods. -This example shows how to set custom strategy that retries on 5xx status codes and waits 1 second between retries. +You can implement your own retry strategy by subclassing `RetryStrategy` and overriding the `should_retry` and `retry_after` methods: ```python from box_sdk_gen import ( @@ -49,7 +174,7 @@ class CustomRetryStrategy(RetryStrategy): fetch_response: FetchResponse, attempt_number: int, ) -> bool: - return fetch_response.status_code >= 500 + return fetch_response.status >= 500 and attempt_number < 3 def retry_after( self, From 6c556ccf4ce097b503209c055c9871c39944cdbd Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 27 Feb 2026 18:07:26 +0100 Subject: [PATCH 089/139] feat: Add configurable timeouts for SDKs (box/box-codegen#924) (#1349) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/client.py | 13 ++++++ box_sdk_gen/networking/__init__.py | 2 + box_sdk_gen/networking/box_network_client.py | 46 ++++++++++++++++++-- box_sdk_gen/networking/network.py | 31 +++++++++++++ box_sdk_gen/networking/timeout_config.py | 12 +++++ docs/client.md | 10 +++++ docs/configuration.md | 28 ++++++++++++ test/box_network_client.py | 25 ++++++++++- test/client.py | 19 ++++++++ 10 files changed, 181 insertions(+), 7 deletions(-) create mode 100644 box_sdk_gen/networking/timeout_config.py diff --git a/.codegen.json b/.codegen.json index f8eaf4e6a..a18e284bf 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "482939a", "specHash": "77eac4b", "version": "10.4.0" } +{ "engineHash": "bc04b80", "specHash": "77eac4b", "version": "10.4.0" } diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py index 155301d2e..f141a1306 100644 --- a/box_sdk_gen/client.py +++ b/box_sdk_gen/client.py @@ -202,6 +202,8 @@ from box_sdk_gen.networking.base_urls import BaseUrls +from box_sdk_gen.networking.timeout_config import TimeoutConfig + from box_sdk_gen.networking.proxy_config import ProxyConfig @@ -545,3 +547,14 @@ def with_proxy(self, config: ProxyConfig) -> 'BoxClient': return BoxClient( auth=self.auth, network_session=self.network_session.with_proxy(config) ) + + def with_timeouts(self, config: TimeoutConfig) -> 'BoxClient': + """ + Create a new client with custom timeouts that will be used for every API call + :param config: Timeout configuration. + :type config: TimeoutConfig + """ + return BoxClient( + auth=self.auth, + network_session=self.network_session.with_timeout_config(config), + ) diff --git a/box_sdk_gen/networking/__init__.py b/box_sdk_gen/networking/__init__.py index c12260c17..84da5517f 100644 --- a/box_sdk_gen/networking/__init__.py +++ b/box_sdk_gen/networking/__init__.py @@ -1,5 +1,7 @@ from box_sdk_gen.networking.box_network_client import * +from box_sdk_gen.networking.timeout_config import * + from box_sdk_gen.networking.proxy_config import * from box_sdk_gen.networking.network import * diff --git a/box_sdk_gen/networking/box_network_client.py b/box_sdk_gen/networking/box_network_client.py index 7a4f44647..3edeef995 100644 --- a/box_sdk_gen/networking/box_network_client.py +++ b/box_sdk_gen/networking/box_network_client.py @@ -3,7 +3,7 @@ import time from collections import OrderedDict from dataclasses import dataclass -from typing import Optional, Dict, Union +from typing import Optional, Dict, Union, Tuple from sys import version_info as py_version import requests @@ -17,6 +17,7 @@ from ..box.errors import BoxAPIError, BoxSDKError, RequestInfo, ResponseInfo from ..internal.utils import ByteStream, ResponseByteStream from ..networking.network_client import NetworkClient +from ..networking.timeout_config import TimeoutConfig from ..serialization.json import ( sd_to_json, sd_to_url_params, @@ -40,6 +41,7 @@ class APIRequest: params: Dict[str, str] data: Optional[Union[str, ByteStream, MultipartEncoder]] allow_redirects: bool = True + timeout: Optional[Tuple[Optional[float], Optional[float]]] = None @dataclass @@ -151,6 +153,7 @@ def _prepare_request( options.content_type, options.file_stream or options.data ) allow_redirects = options.follow_redirects + timeout = self._get_request_timeout(options) if options.content_type: if options.content_type == 'multipart/form-data': @@ -178,8 +181,43 @@ def _prepare_request( params=params, data=data, allow_redirects=allow_redirects, + timeout=timeout, ) + @staticmethod + def _get_request_timeout( + options: 'FetchOptions', + ) -> Optional[Tuple[Optional[float], Optional[float]]]: + """ + Derive requests timeout tuple (connect, read) in seconds. + + Uses `options.network_session.timeout_config` when present. + The timeout config values are expected to be in milliseconds. + """ + network_session = options.network_session + timeout_config = network_session.timeout_config if network_session else None + if timeout_config is None: + return None + + connection_timeout_ms, read_timeout_ms = ( + timeout_config.connection_timeout_ms, + timeout_config.read_timeout_ms, + ) + + if connection_timeout_ms is None and read_timeout_ms is None: + return None + + connection_timeout_sec = ( + connection_timeout_ms / 1000.0 + if connection_timeout_ms is not None + else None + ) + read_timeout_sec = ( + read_timeout_ms / 1000.0 if read_timeout_ms is not None else None + ) + + return (connection_timeout_sec, read_timeout_sec) + @staticmethod def _prepare_headers( options: 'FetchOptions', reauthenticate: bool = False @@ -216,12 +254,12 @@ def _prepare_body( or content_type == 'application/octet-stream' ): return data - raise + raise ValueError(f'Unsupported content type: {content_type}') def _make_request(self, request: APIRequest) -> APIResponse: raised_exception = None reauthentication_needed = False - default_timeout = (5, 60) # connect, read timeout + timeout = request.timeout try: network_response = self.requests_session.request( method=request.method, @@ -231,7 +269,7 @@ def _make_request(self, request: APIRequest) -> APIResponse: params=request.params, allow_redirects=request.allow_redirects, stream=True, - timeout=default_timeout, + timeout=timeout, ) except RequestException as request_exc: raised_exception = request_exc diff --git a/box_sdk_gen/networking/network.py b/box_sdk_gen/networking/network.py index c51f8722e..a3ed2fb23 100644 --- a/box_sdk_gen/networking/network.py +++ b/box_sdk_gen/networking/network.py @@ -6,6 +6,7 @@ from .proxy_config import ProxyConfig from .base_urls import BaseUrls from .retries import RetryStrategy, BoxRetryStrategy +from .timeout_config import TimeoutConfig class NetworkSession: @@ -18,6 +19,7 @@ def __init__( base_urls: BaseUrls = None, proxy_url: str = None, data_sanitizer: DataSanitizer = None, + timeout_config: TimeoutConfig = None, ): if additional_headers is None: additional_headers = {} @@ -38,12 +40,18 @@ def __init__( } if data_sanitizer is None: data_sanitizer = DataSanitizer() + if timeout_config is None: + timeout_config = TimeoutConfig( + connection_timeout_ms=5000, + read_timeout_ms=60000, + ) self.additional_headers = additional_headers self.base_urls = base_urls self.proxy_url = proxy_url self.network_client = network_client self.retry_strategy = retry_strategy self.data_sanitizer = data_sanitizer + self.timeout_config = timeout_config def with_additional_headers( self, additional_headers: Dict[str, str] = None @@ -61,6 +69,7 @@ def with_additional_headers( proxy_url=self.proxy_url, retry_strategy=self.retry_strategy, data_sanitizer=self.data_sanitizer, + timeout_config=self.timeout_config, ) def with_custom_base_urls(self, base_urls: BaseUrls) -> 'NetworkSession': @@ -77,6 +86,7 @@ def with_custom_base_urls(self, base_urls: BaseUrls) -> 'NetworkSession': proxy_url=self.proxy_url, retry_strategy=self.retry_strategy, data_sanitizer=self.data_sanitizer, + timeout_config=self.timeout_config, ) def with_proxy(self, config: ProxyConfig) -> 'NetworkSession': @@ -103,6 +113,7 @@ def with_proxy(self, config: ProxyConfig) -> 'NetworkSession': proxy_url=proxy_url, retry_strategy=self.retry_strategy, data_sanitizer=self.data_sanitizer, + timeout_config=self.timeout_config, ) def with_network_client(self, network_client: NetworkClient) -> 'NetworkSession': @@ -119,6 +130,7 @@ def with_network_client(self, network_client: NetworkClient) -> 'NetworkSession' proxy_url=self.proxy_url, retry_strategy=self.retry_strategy, data_sanitizer=self.data_sanitizer, + timeout_config=self.timeout_config, ) def with_retry_strategy(self, retry_strategy: RetryStrategy) -> 'NetworkSession': @@ -135,6 +147,7 @@ def with_retry_strategy(self, retry_strategy: RetryStrategy) -> 'NetworkSession' proxy_url=self.proxy_url, retry_strategy=retry_strategy, data_sanitizer=self.data_sanitizer, + timeout_config=self.timeout_config, ) def with_data_sanitizer(self, data_sanitizer: DataSanitizer) -> 'NetworkSession': @@ -151,4 +164,22 @@ def with_data_sanitizer(self, data_sanitizer: DataSanitizer) -> 'NetworkSession' proxy_url=self.proxy_url, retry_strategy=self.retry_strategy, data_sanitizer=data_sanitizer, + timeout_config=self.timeout_config, + ) + + def with_timeout_config(self, timeout_config: TimeoutConfig) -> 'NetworkSession': + """ + Generate a fresh network session by duplicating the existing configuration and network parameters, + while also including timeout config to be used for every API call. + :param timeout_config: TimeoutConfig object, which contains the timeout config + :return: a new instance of NetworkSession + """ + return NetworkSession( + network_client=self.network_client, + additional_headers=self.additional_headers, + base_urls=self.base_urls, + proxy_url=self.proxy_url, + retry_strategy=self.retry_strategy, + data_sanitizer=self.data_sanitizer, + timeout_config=timeout_config, ) diff --git a/box_sdk_gen/networking/timeout_config.py b/box_sdk_gen/networking/timeout_config.py new file mode 100644 index 000000000..79772f7b3 --- /dev/null +++ b/box_sdk_gen/networking/timeout_config.py @@ -0,0 +1,12 @@ +from typing import Optional + + +class TimeoutConfig: + def __init__( + self, + *, + connection_timeout_ms: Optional[int] = None, + read_timeout_ms: Optional[int] = None + ): + self.connection_timeout_ms = connection_timeout_ms + self.read_timeout_ms = read_timeout_ms diff --git a/docs/client.md b/docs/client.md index 48b11a454..7fa552e2d 100644 --- a/docs/client.md +++ b/docs/client.md @@ -15,6 +15,7 @@ divided across resource managers. - [Suppress notifications](#suppress-notifications) - [Custom headers](#custom-headers) - [Custom Base URLs](#custom-base-urls) +- [Use Timeouts for API calls](#use-timeouts-for-api-calls) - [Use Proxy for API calls](#use-proxy-for-api-calls) @@ -153,6 +154,15 @@ new_client = client.with_custom_base_urls( ) ``` +# Use Timeouts for API calls + +In order to configure timeout for API calls, calling the `client.with_timeouts(config)` method creates a new client with timeout settings, leaving the original client unmodified. + +```python +timeout_config = TimeoutConfig(connection_timeout_ms=10000, read_timeout_ms=30000) +new_client = client.with_timeouts(timeout_config) +``` + # Use Proxy for API calls In order to use a proxy for API calls, calling the `client.with_proxy(proxyConfig)` method creates a new client, leaving the original client unmodified, with the username and password being optional. diff --git a/docs/configuration.md b/docs/configuration.md index 3ec5fa9c9..6e9500871 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -13,6 +13,7 @@ - [Network Exception Handling](#network-exception-handling) - [Customizing Retry Parameters](#customizing-retry-parameters) - [Custom Retry Strategy](#custom-retry-strategy) +- [Timeouts](#timeouts) @@ -189,3 +190,30 @@ auth = BoxDeveloperTokenAuth(token="DEVELOPER_TOKEN_GOES_HERE") network_session = NetworkSession(retry_strategy=CustomRetryStrategy()) client = BoxClient(auth=auth, network_session=network_session) ``` + +## Timeouts + +You can configure network timeouts with `TimeoutConfig` on `NetworkSession`. +Python SDK supports separate connection and read timeout values in milliseconds. + +```python +from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth, NetworkSession, TimeoutConfig + +auth = BoxDeveloperTokenAuth(token="DEVELOPER_TOKEN_GOES_HERE") +timeout_config = TimeoutConfig( + connection_timeout_ms=10000, + read_timeout_ms=30000, +) +network_session = NetworkSession(timeout_config=timeout_config) +client = BoxClient(auth=auth, network_session=network_session) +``` + +How timeout handling works: + +- Timeout values are configured in milliseconds and converted to seconds internally for HTTP requests. +- The SDK uses default timeouts when timeout config is not provided: `connection_timeout_ms=5000` and `read_timeout_ms=60000`. +- To disable all SDK timeouts, pass `TimeoutConfig(connection_timeout_ms=None, read_timeout_ms=None)` explicitly to `NetworkSession`. +- You can also disable only one timeout by setting one value to `None` (for example, `connection_timeout_ms=None` or `read_timeout_ms=None`). If you provide only the other value (for example, `read_timeout_ms=30000`) and leave one unspecified, the unspecified field remains `None` and that timeout stays disabled. +- Timeout failures are treated as network exceptions, and retry behavior is controlled by the configured retry strategy. +- Timeout applies to a single HTTP request attempt to the Box API (not the total time across all retries). +- If retries are exhausted, the SDK raises `BoxSDKError` with the underlying request exception. diff --git a/test/box_network_client.py b/test/box_network_client.py index ab95206ee..bfd003846 100644 --- a/test/box_network_client.py +++ b/test/box_network_client.py @@ -175,6 +175,25 @@ def network_session_mock(): return NetworkSession() +def test_network_session_uses_default_timeout_config_values(): + network_session = NetworkSession() + + assert network_session.timeout_config.connection_timeout_ms == 5000 + assert network_session.timeout_config.read_timeout_ms == 60000 + + +def test_prepare_request_uses_default_network_session_timeouts(network_client): + options = FetchOptions( + url="https://example.com", + method="GET", + network_session=NetworkSession(), + ) + + api_request = network_client._prepare_request(options=options) + + assert api_request.timeout == (5, 60) + + @pytest.fixture def network_client(mock_requests_session): return BoxNetworkClient(mock_requests_session) @@ -295,7 +314,7 @@ def test_prepare_body_invalid_content_type(network_client): network_client._prepare_body("invalid_content_type", {}) -def test_prepare_json_request(network_client): +def test_prepare_json_request(network_client, network_session_mock): options = FetchOptions( url="https://example.com", method="POST", @@ -303,6 +322,7 @@ def test_prepare_json_request(network_client): headers={"header": "test"}, params={"param": "value"}, content_type="application/json", + network_session=network_session_mock, ) api_request = network_client._prepare_request(options=options) @@ -318,6 +338,7 @@ def test_prepare_json_request(network_client): }, params={"param": "value"}, data='{"key": "value"}', + timeout=(5, 60), ) @@ -379,7 +400,7 @@ def test_make_request(network_client, mock_requests_session, response_200): ) assert mock_requests_session.request.call_count == 1 mock_requests_session.request.assert_called_once_with( - **request_params, stream=True, timeout=(5, 60) + **request_params, stream=True, timeout=None ) diff --git a/test/client.py b/test/client.py index 91eeb8ef9..53853faf7 100644 --- a/test/client.py +++ b/test/client.py @@ -26,6 +26,8 @@ from box_sdk_gen.schemas.user_full import UserFull +from box_sdk_gen.networking.timeout_config import TimeoutConfig + from box_sdk_gen.internal.utils import get_uuid from box_sdk_gen.internal.utils import generate_byte_stream @@ -218,3 +220,20 @@ def testWithCustomBaseUrls(): custom_base_client: BoxClient = client.with_custom_base_urls(new_base_urls) with pytest.raises(Exception): custom_base_client.users.get_user_me() + + +def testWithTimeoutWhenTimeoutOccurs(): + read_timeout_ms: int = 1 + client_with_timeout: BoxClient = client.with_timeouts( + TimeoutConfig(read_timeout_ms=read_timeout_ms) + ) + with pytest.raises(Exception): + client_with_timeout.users.get_user_me() + + +def testWithTimeoutWhenTimeoutDoesNotOccur(): + read_timeout_ms: int = 10000 + client_with_timeout: BoxClient = client.with_timeouts( + TimeoutConfig(read_timeout_ms=read_timeout_ms) + ) + client_with_timeout.users.get_user_me() From ee00aaf3d7f7c78d80534557f8de593fd4249658 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:14:04 +0100 Subject: [PATCH 090/139] docs: Update description for delete archives endpoint (box/box-openapi#585) (#1351) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/managers/archives.py | 9 +++++++++ box_sdk_gen/schemas/__init__.py | 16 ++++++++-------- docs/archives.md | 4 ++++ 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.codegen.json b/.codegen.json index a18e284bf..6c17bf8dd 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "bc04b80", "specHash": "77eac4b", "version": "10.4.0" } +{ "engineHash": "bc04b80", "specHash": "f2523d5", "version": "10.4.0" } diff --git a/box_sdk_gen/managers/archives.py b/box_sdk_gen/managers/archives.py index 146b58f82..24c64bd29 100644 --- a/box_sdk_gen/managers/archives.py +++ b/box_sdk_gen/managers/archives.py @@ -158,6 +158,15 @@ def delete_archive_by_id_v2025_r0( To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). + + + + + This endpoint is currently unavailable. Please contact support for assistance. + + + + :param archive_id: The ID of the archive. Example: "982312" :type archive_id: str diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index a4132c26b..5ed9f1f5f 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -358,10 +358,10 @@ from box_sdk_gen.schemas.upload_part import * -from box_sdk_gen.schemas.uploaded_part import * - from box_sdk_gen.schemas.upload_parts import * +from box_sdk_gen.schemas.uploaded_part import * + from box_sdk_gen.schemas.upload_session import * from box_sdk_gen.schemas.upload_url import * @@ -500,6 +500,12 @@ from box_sdk_gen.schemas.watermark import * +from box_sdk_gen.schemas.webhook_mini import * + +from box_sdk_gen.schemas.webhooks import * + +from box_sdk_gen.schemas.webhook import * + from box_sdk_gen.schemas.web_link_base import * from box_sdk_gen.schemas.web_link_mini import * @@ -568,12 +574,6 @@ from box_sdk_gen.schemas.app_item_associations import * -from box_sdk_gen.schemas.webhook_mini import * - -from box_sdk_gen.schemas.webhooks import * - -from box_sdk_gen.schemas.webhook import * - from box_sdk_gen.schemas.workflow_mini import * from box_sdk_gen.schemas.workflow import * diff --git a/docs/archives.md b/docs/archives.md index 33a2dd122..da8f30baf 100644 --- a/docs/archives.md +++ b/docs/archives.md @@ -81,6 +81,10 @@ Permanently deletes an archive. To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives). + +This endpoint is currently unavailable. Please contact support for assistance. + + This operation is performed by calling function `delete_archive_by_id_v2025_r0`. See the endpoint docs at From 6af2956c1cccf864a16b1b854b49c9058ebb9102 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 18 Mar 2026 15:06:40 +0100 Subject: [PATCH 091/139] feat: add `canPublicSharedLinkBeCreated` to `Hubs` (box/box-codegen#928) (#1356) --- .codegen.json | 2 +- box_sdk_gen/managers/hubs.py | 4 ++++ box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py | 4 ++++ box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py | 4 ++++ docs/hubs.md | 2 ++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 6c17bf8dd..4e8db47b9 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "bc04b80", "specHash": "f2523d5", "version": "10.4.0" } +{ "engineHash": "65f6eab", "specHash": "57b3004", "version": "10.4.0" } diff --git a/box_sdk_gen/managers/hubs.py b/box_sdk_gen/managers/hubs.py index 47669e810..26e34bca1 100644 --- a/box_sdk_gen/managers/hubs.py +++ b/box_sdk_gen/managers/hubs.py @@ -303,6 +303,7 @@ def update_hub_by_id_v2025_r0( is_collaboration_restricted_to_enterprise: Optional[bool] = None, can_non_owners_invite: Optional[bool] = None, can_shared_link_be_created: Optional[bool] = None, + can_public_shared_link_be_created: Optional[bool] = None, box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubV2025R0: @@ -329,6 +330,8 @@ def update_hub_by_id_v2025_r0( :type can_non_owners_invite: Optional[bool], optional :param can_shared_link_be_created: Indicates if a shared link can be created for the Box Hub., defaults to None :type can_shared_link_be_created: Optional[bool], optional + :param can_public_shared_link_be_created: Indicates if a public shared link can be created for the Box Hub., defaults to None + :type can_public_shared_link_be_created: Optional[bool], optional :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 :type box_version: BoxVersionHeaderV2025R0, optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None @@ -345,6 +348,7 @@ def update_hub_by_id_v2025_r0( ), 'can_non_owners_invite': can_non_owners_invite, 'can_shared_link_be_created': can_shared_link_be_created, + 'can_public_shared_link_be_created': can_public_shared_link_be_created, } headers_map: Dict[str, str] = prepare_params( {'box-version': to_string(box_version), **extra_headers} diff --git a/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py index 67a29953d..cf120b7cd 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py @@ -15,6 +15,7 @@ def __init__( is_collaboration_restricted_to_enterprise: Optional[bool] = None, can_non_owners_invite: Optional[bool] = None, can_shared_link_be_created: Optional[bool] = None, + can_public_shared_link_be_created: Optional[bool] = None, **kwargs ): """ @@ -30,6 +31,8 @@ def __init__( :type can_non_owners_invite: Optional[bool], optional :param can_shared_link_be_created: Indicates if a shared link can be created for the Box Hub., defaults to None :type can_shared_link_be_created: Optional[bool], optional + :param can_public_shared_link_be_created: Indicates if a public shared link can be created for the Box Hub., defaults to None + :type can_public_shared_link_be_created: Optional[bool], optional """ super().__init__(**kwargs) self.title = title @@ -40,3 +43,4 @@ def __init__( ) self.can_non_owners_invite = can_non_owners_invite self.can_shared_link_be_created = can_shared_link_be_created + self.can_public_shared_link_be_created = can_public_shared_link_be_created diff --git a/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py index 51ed529aa..e2559bba4 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py @@ -27,6 +27,7 @@ def __init__( is_collaboration_restricted_to_enterprise: Optional[bool] = None, can_non_owners_invite: Optional[bool] = None, can_shared_link_be_created: Optional[bool] = None, + can_public_shared_link_be_created: Optional[bool] = None, type: HubBaseV2025R0TypeField = HubBaseV2025R0TypeField.HUBS, **kwargs ): @@ -59,6 +60,8 @@ def __init__( :type can_non_owners_invite: Optional[bool], optional :param can_shared_link_be_created: Indicates if a shared link can be created for the Box Hub., defaults to None :type can_shared_link_be_created: Optional[bool], optional + :param can_public_shared_link_be_created: Indicates if a public shared link can be created for the Box Hub., defaults to None + :type can_public_shared_link_be_created: Optional[bool], optional :param type: The value will always be `hubs`., defaults to HubBaseV2025R0TypeField.HUBS :type type: HubBaseV2025R0TypeField, optional """ @@ -76,3 +79,4 @@ def __init__( ) self.can_non_owners_invite = can_non_owners_invite self.can_shared_link_be_created = can_shared_link_be_created + self.can_public_shared_link_be_created = can_public_shared_link_be_created diff --git a/docs/hubs.md b/docs/hubs.md index 10b1459b1..54e0de223 100644 --- a/docs/hubs.md +++ b/docs/hubs.md @@ -188,6 +188,8 @@ client.hubs.update_hub_by_id_v2025_r0( - Indicates if non-owners can invite others to the Box Hub. - can_shared_link_be_created `Optional[bool]` - Indicates if a shared link can be created for the Box Hub. +- can_public_shared_link_be_created `Optional[bool]` + - Indicates if a public shared link can be created for the Box Hub. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` From 7536f1879d12393a66c61db2ec8c584fd7ebc3cc Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 18 Mar 2026 15:47:22 +0100 Subject: [PATCH 092/139] chore: release version 10.5.0 (#1358) --- .codegen.json | 2 +- CHANGELOG.md | 8 ++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 4e8db47b9..043fa35bb 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "65f6eab", "specHash": "57b3004", "version": "10.4.0" } +{ "engineHash": "65f6eab", "specHash": "57b3004", "version": "10.5.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index febf44ed1..9c9cd4265 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.5.0](https://github.com/box/box-python-sdk/compare/v10.4.0...v10.5.0) (2026-03-18) + + +### New Features and Enhancements + +* add `canPublicSharedLinkBeCreated` to `Hubs` (box/box-codegen[#928](https://github.com/box/box-python-sdk/issues/928)) ([#1356](https://github.com/box/box-python-sdk/issues/1356)) ([6af2956](https://github.com/box/box-python-sdk/commit/6af2956c1cccf864a16b1b854b49c9058ebb9102)) +* Add configurable timeouts for SDKs (box/box-codegen[#924](https://github.com/box/box-python-sdk/issues/924)) ([#1349](https://github.com/box/box-python-sdk/issues/1349)) ([6c556cc](https://github.com/box/box-python-sdk/commit/6c556ccf4ce097b503209c055c9871c39944cdbd)) + ## [10.4.0](https://github.com/box/box-python-sdk/compare/v10.3.0...v10.4.0) (2026-02-11) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 00cefb785..98d96db57 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.4.0' +__version__ = '10.5.0' From 59c1d3c156cc079aa1ad66f7bb2476d48c67a57c Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:38:59 +0100 Subject: [PATCH 093/139] feat: Add `include_reference` to `AI` and `parent_id` to `HubsItems` (box/box-openapi#588) (#1360) --- .codegen.json | 2 +- box_sdk_gen/managers/ai.py | 4 ++++ box_sdk_gen/managers/hub_items.py | 8 ++++++++ box_sdk_gen/schemas/ai_extract_structured.py | 4 ++++ box_sdk_gen/schemas/ai_extract_structured_response.py | 4 ++++ .../schemas/v2025_r0/hub_item_operation_v2025_r0.py | 7 +++++++ docs/ai.md | 2 ++ docs/hub_items.md | 2 ++ 8 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 043fa35bb..c0bf37423 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "65f6eab", "specHash": "57b3004", "version": "10.5.0" } +{ "engineHash": "65f6eab", "specHash": "ca63e5e", "version": "10.5.0" } diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index 483c0e7ed..b50eb57c0 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -426,6 +426,7 @@ def create_ai_extract_structured( metadata_template: Optional[CreateAiExtractStructuredMetadataTemplate] = None, fields: Optional[List[CreateAiExtractStructuredFields]] = None, include_confidence_score: Optional[bool] = None, + include_reference: Optional[bool] = None, ai_agent: Optional[AiExtractStructuredAgent] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> AiExtractStructuredResponse: @@ -453,6 +454,8 @@ def create_ai_extract_structured( :type fields: Optional[List[CreateAiExtractStructuredFields]], optional :param include_confidence_score: A flag to indicate whether confidence scores for every extracted field should be returned., defaults to None :type include_confidence_score: Optional[bool], optional + :param include_reference: A flag to indicate whether references for every extracted field should be returned., defaults to None + :type include_reference: Optional[bool], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ @@ -463,6 +466,7 @@ def create_ai_extract_structured( 'metadata_template': metadata_template, 'fields': fields, 'include_confidence_score': include_confidence_score, + 'include_reference': include_reference, 'ai_agent': ai_agent, } headers_map: Dict[str, str] = prepare_params({**extra_headers}) diff --git a/box_sdk_gen/managers/hub_items.py b/box_sdk_gen/managers/hub_items.py index a05cc2ef5..46a829d7b 100644 --- a/box_sdk_gen/managers/hub_items.py +++ b/box_sdk_gen/managers/hub_items.py @@ -69,6 +69,7 @@ def get_hub_items_v2025_r0( self, hub_id: str, *, + parent_id: Optional[str] = None, marker: Optional[str] = None, limit: Optional[int] = None, box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, @@ -84,6 +85,12 @@ def get_hub_items_v2025_r0( for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. :type hub_id: str + :param parent_id: The unique identifier of an item list block within the Box Hub. + + When provided, the response will only include items that belong + to the specified item list, allowing you to filter results to + items on a specific page or section., defaults to None + :type parent_id: Optional[str], optional :param marker: Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. @@ -101,6 +108,7 @@ def get_hub_items_v2025_r0( query_params_map: Dict[str, str] = prepare_params( { 'hub_id': to_string(hub_id), + 'parent_id': to_string(parent_id), 'marker': to_string(marker), 'limit': to_string(limit), } diff --git a/box_sdk_gen/schemas/ai_extract_structured.py b/box_sdk_gen/schemas/ai_extract_structured.py index 172e90003..a6bafd0ce 100644 --- a/box_sdk_gen/schemas/ai_extract_structured.py +++ b/box_sdk_gen/schemas/ai_extract_structured.py @@ -115,6 +115,7 @@ def __init__( metadata_template: Optional[AiExtractStructuredMetadataTemplateField] = None, fields: Optional[List[AiExtractStructuredFieldsField]] = None, include_confidence_score: Optional[bool] = None, + include_reference: Optional[bool] = None, ai_agent: Optional[AiExtractStructuredAgent] = None, **kwargs ): @@ -129,10 +130,13 @@ def __init__( :type fields: Optional[List[AiExtractStructuredFieldsField]], optional :param include_confidence_score: A flag to indicate whether confidence scores for every extracted field should be returned., defaults to None :type include_confidence_score: Optional[bool], optional + :param include_reference: A flag to indicate whether references for every extracted field should be returned., defaults to None + :type include_reference: Optional[bool], optional """ super().__init__(**kwargs) self.items = items self.metadata_template = metadata_template self.fields = fields self.include_confidence_score = include_confidence_score + self.include_reference = include_reference self.ai_agent = ai_agent diff --git a/box_sdk_gen/schemas/ai_extract_structured_response.py b/box_sdk_gen/schemas/ai_extract_structured_response.py index d583e50b7..08c41e2fc 100644 --- a/box_sdk_gen/schemas/ai_extract_structured_response.py +++ b/box_sdk_gen/schemas/ai_extract_structured_response.py @@ -19,6 +19,7 @@ def __init__( *, completion_reason: Optional[str] = None, confidence_score: Optional[Dict] = None, + reference: Optional[Dict] = None, ai_agent_info: Optional[AiAgentInfo] = None, **kwargs ): @@ -29,10 +30,13 @@ def __init__( :type completion_reason: Optional[str], optional :param confidence_score: The confidence score levels and numeric values for each extracted field as a JSON dictionary. This can be empty if no field could be extracted., defaults to None :type confidence_score: Optional[Dict], optional + :param reference: The reference for each extracted field as a JSON dictionary. This can be empty if no field could be extracted., defaults to None + :type reference: Optional[Dict], optional """ super().__init__(**kwargs) self.answer = answer self.created_at = created_at self.completion_reason = completion_reason self.confidence_score = confidence_score + self.reference = reference self.ai_agent_info = ai_agent_info diff --git a/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py index 9c6f99759..17737d625 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_v2025_r0.py @@ -1,5 +1,7 @@ from enum import Enum +from typing import Optional + from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.schemas.v2025_r0.file_reference_v2025_r0 import FileReferenceV2025R0 @@ -29,12 +31,17 @@ def __init__( self, action: HubItemOperationV2025R0ActionField, item: HubItemReferenceV2025R0, + *, + parent_id: Optional[str] = None, **kwargs ): """ :param action: The action to perform on a Box Hub item. :type action: HubItemOperationV2025R0ActionField + :param parent_id: The ID of the parent block to add the item to. Must be an Item List block. If not provided, the item will be added to the first page's first Item List block., defaults to None + :type parent_id: Optional[str], optional """ super().__init__(**kwargs) self.action = action self.item = item + self.parent_id = parent_id diff --git a/docs/ai.md b/docs/ai.md index 96e3d76b5..7e0e7b005 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -261,6 +261,8 @@ client.ai.create_ai_extract_structured( - The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both. - include_confidence_score `Optional[bool]` - A flag to indicate whether confidence scores for every extracted field should be returned. +- include_reference `Optional[bool]` + - A flag to indicate whether references for every extracted field should be returned. - ai_agent `Optional[AiExtractStructuredAgent]` - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/hub_items.md b/docs/hub_items.md index 966349d66..c05929a8c 100644 --- a/docs/hub_items.md +++ b/docs/hub_items.md @@ -22,6 +22,8 @@ client.hub_items.get_hub_items_v2025_r0(created_hub.id) - hub_id `str` - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. +- parent_id `Optional[str]` + - The unique identifier of an item list block within the Box Hub. When provided, the response will only include items that belong to the specified item list, allowing you to filter results to items on a specific page or section. - marker `Optional[str]` - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. This requires `usemarker` to be set to `true`. - limit `Optional[int]` From b1cac228c2ff705e359115a17b6b1e84b46605f3 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:44:01 +0100 Subject: [PATCH 094/139] docs: Fix links to the Supported AI models page (box/box-openapi#589) (#1364) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/schemas/ai_agent_basic_gen_tool.py | 2 +- box_sdk_gen/schemas/ai_agent_basic_text_tool.py | 2 +- box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py | 2 +- box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py | 2 +- box_sdk_gen/schemas/ai_agent_long_text_tool.py | 2 +- box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py | 2 +- box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py | 2 +- box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py | 2 +- box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py | 2 +- box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py | 2 +- box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py | 2 +- box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py | 2 +- box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py | 2 +- box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py | 2 +- .../schemas/ai_studio_agent_spreadsheet_tool_response.py | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.codegen.json b/.codegen.json index c0bf37423..871c0b89d 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "65f6eab", "specHash": "ca63e5e", "version": "10.5.0" } +{ "engineHash": "7c01dda", "specHash": "2bd751f", "version": "10.5.0" } diff --git a/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py b/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py index 2bb60b4e0..454cc6858 100644 --- a/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py +++ b/box_sdk_gen/schemas/ai_agent_basic_gen_tool.py @@ -52,7 +52,7 @@ def __init__( When using the `prompt_template` parameter, you **must include** input for `{user_question}`. Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_basic_text_tool.py b/box_sdk_gen/schemas/ai_agent_basic_text_tool.py index f9722bb05..70c6df6c6 100644 --- a/box_sdk_gen/schemas/ai_agent_basic_text_tool.py +++ b/box_sdk_gen/schemas/ai_agent_basic_text_tool.py @@ -33,7 +33,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py b/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py index e077b03cc..48958e90c 100644 --- a/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py +++ b/box_sdk_gen/schemas/ai_agent_basic_text_tool_base.py @@ -25,7 +25,7 @@ def __init__( **kwargs ): """ - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py b/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py index cc49bd6a6..567566731 100644 --- a/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py +++ b/box_sdk_gen/schemas/ai_agent_basic_text_tool_text_gen.py @@ -35,7 +35,7 @@ def __init__( When using the `prompt_template` parameter, you **must include** input for `{user_question}`. Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_long_text_tool.py b/box_sdk_gen/schemas/ai_agent_long_text_tool.py index de45f4b52..5398c5713 100644 --- a/box_sdk_gen/schemas/ai_agent_long_text_tool.py +++ b/box_sdk_gen/schemas/ai_agent_long_text_tool.py @@ -74,7 +74,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py b/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py index b168df4f6..1fffdc8ed 100644 --- a/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py +++ b/box_sdk_gen/schemas/ai_agent_long_text_tool_text_gen.py @@ -78,7 +78,7 @@ def __init__( When using the `prompt_template` parameter, you **must include** input for `{user_question}`. Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py b/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py index 1dbb1f497..e4b66dffd 100644 --- a/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py +++ b/box_sdk_gen/schemas/ai_agent_spreadsheet_tool.py @@ -25,7 +25,7 @@ def __init__( **kwargs ): """ - :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py index 5e017d116..b59aa7eaa 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool.py @@ -57,7 +57,7 @@ def __init__( When using the `prompt_template` parameter, you **must include** input for `{user_question}`. Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py index 7c006939d..9c00ac512 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_gen_tool_response.py @@ -64,7 +64,7 @@ def __init__( When using the `prompt_template` parameter, you **must include** input for `{user_question}`. Inputs for `{current_date}` and `{content}` are optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py index a910fb58b..74a8e99fb 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool.py @@ -38,7 +38,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py index 5f5e1a931..dc0375e33 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py +++ b/box_sdk_gen/schemas/ai_studio_agent_basic_text_tool_response.py @@ -47,7 +47,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py index 1e31c862b..c86c9f0be 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py +++ b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool.py @@ -45,7 +45,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py index 350093abc..73a634fa4 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py +++ b/box_sdk_gen/schemas/ai_studio_agent_long_text_tool_response.py @@ -52,7 +52,7 @@ def __init__( When passing `prompt_template` parameters, you **must include** inputs for `{user_question}` and `{content}`. `{current_date}` is optional, depending on the use., defaults to None :type prompt_template: Optional[str], optional - :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for basic text. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py index c453f3ca6..4c66d6287 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py +++ b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool.py @@ -25,7 +25,7 @@ def __init__( **kwargs ): """ - :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional diff --git a/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py index 888b52cbe..9adba3cb7 100644 --- a/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py +++ b/box_sdk_gen/schemas/ai_studio_agent_spreadsheet_tool_response.py @@ -34,7 +34,7 @@ def __init__( """ :param warnings: Warnings concerning tool., defaults to None :type warnings: Optional[List[str]], optional - :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/supported-models)., defaults to None + :param model: The model used for the AI agent for spreadsheets. For specific model values, see the [available models list](https://developer.box.com/guides/box-ai/ai-models)., defaults to None :type model: Optional[str], optional :param num_tokens_for_completion: The number of tokens for completion., defaults to None :type num_tokens_for_completion: Optional[int], optional From feb201068f851701ecd6f2dfea522182a33a6846 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:13:16 +0200 Subject: [PATCH 095/139] fix: Align `LegalHoldPolicyAssignedItem` with API (box/box-openapi#1554) (box/box-openapi#590) (#1365) --- .codegen.json | 2 +- box_sdk_gen/managers/trashed_items.py | 3 ++ box_sdk_gen/schemas/__init__.py | 20 +++++----- .../legal_hold_policy_assigned_item.py | 37 ++++++++++++++++--- .../schemas/legal_hold_policy_assignment.py | 6 --- docs/trashed_items.md | 2 + 6 files changed, 47 insertions(+), 23 deletions(-) diff --git a/.codegen.json b/.codegen.json index 871c0b89d..183f4913a 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "7c01dda", "specHash": "2bd751f", "version": "10.5.0" } +{ "engineHash": "7c01dda", "specHash": "74a1e4a", "version": "10.5.0" } diff --git a/box_sdk_gen/managers/trashed_items.py b/box_sdk_gen/managers/trashed_items.py index 242cd2ed3..0b2551174 100644 --- a/box_sdk_gen/managers/trashed_items.py +++ b/box_sdk_gen/managers/trashed_items.py @@ -92,6 +92,9 @@ def get_trashed_items( marker-based pagination using the `marker` parameter. + + The number of entries returned may be less than `total_count`. For example, if a user deletes items from a shared folder and is later removed as a collaborator, those deleted items will no longer appear in this endpoint’s results, even though they are still included in `total_count`. + :param fields: A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index 5ed9f1f5f..d43309f4d 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -188,6 +188,8 @@ from box_sdk_gen.schemas.legal_hold_policy_mini import * +from box_sdk_gen.schemas.legal_hold_policy_assigned_item import * + from box_sdk_gen.schemas.legal_hold_policy_assignment_base import * from box_sdk_gen.schemas.metadata_base import * @@ -414,6 +416,14 @@ from box_sdk_gen.schemas.retention_policies import * +from box_sdk_gen.schemas.legal_hold_policy_assignment import * + +from box_sdk_gen.schemas.legal_hold_policy_assignments import * + +from box_sdk_gen.schemas.file_version_legal_hold import * + +from box_sdk_gen.schemas.file_version_legal_holds import * + from box_sdk_gen.schemas.legal_hold_policy import * from box_sdk_gen.schemas.legal_hold_policies import * @@ -520,16 +530,6 @@ from box_sdk_gen.schemas.folder import * -from box_sdk_gen.schemas.legal_hold_policy_assigned_item import * - -from box_sdk_gen.schemas.legal_hold_policy_assignment import * - -from box_sdk_gen.schemas.legal_hold_policy_assignments import * - -from box_sdk_gen.schemas.file_version_legal_hold import * - -from box_sdk_gen.schemas.file_version_legal_holds import * - from box_sdk_gen.schemas.folder_full import * from box_sdk_gen.schemas.search_result_with_shared_link_item import * diff --git a/box_sdk_gen/schemas/legal_hold_policy_assigned_item.py b/box_sdk_gen/schemas/legal_hold_policy_assigned_item.py index b1c950182..77479f9ab 100644 --- a/box_sdk_gen/schemas/legal_hold_policy_assigned_item.py +++ b/box_sdk_gen/schemas/legal_hold_policy_assigned_item.py @@ -1,11 +1,36 @@ -from typing import Union +from enum import Enum -from box_sdk_gen.schemas.file import File +from box_sdk_gen.internal.base_object import BaseObject -from box_sdk_gen.schemas.folder import Folder +from box_sdk_gen.box.errors import BoxSDKError -from box_sdk_gen.schemas.web_link import WebLink -from box_sdk_gen.box.errors import BoxSDKError +class LegalHoldPolicyAssignedItemTypeField(str, Enum): + FILE = 'file' + FILE_VERSION = 'file_version' + FOLDER = 'folder' + USER = 'user' + OWNERSHIP = 'ownership' + INTERACTIONS = 'interactions' + + +class LegalHoldPolicyAssignedItem(BaseObject): + _discriminator = 'type', { + 'file', + 'file_version', + 'folder', + 'user', + 'ownership', + 'interactions', + } -LegalHoldPolicyAssignedItem = Union[File, Folder, WebLink] + def __init__(self, type: LegalHoldPolicyAssignedItemTypeField, id: str, **kwargs): + """ + :param type: The type of item the policy is assigned to. + :type type: LegalHoldPolicyAssignedItemTypeField + :param id: The ID of the item the policy is assigned to. + :type id: str + """ + super().__init__(**kwargs) + self.type = type + self.id = id diff --git a/box_sdk_gen/schemas/legal_hold_policy_assignment.py b/box_sdk_gen/schemas/legal_hold_policy_assignment.py index 3b00d1af6..55892ffff 100644 --- a/box_sdk_gen/schemas/legal_hold_policy_assignment.py +++ b/box_sdk_gen/schemas/legal_hold_policy_assignment.py @@ -4,12 +4,6 @@ LegalHoldPolicyAssignmentBaseTypeField, ) -from box_sdk_gen.schemas.file import File - -from box_sdk_gen.schemas.folder import Folder - -from box_sdk_gen.schemas.web_link import WebLink - from box_sdk_gen.schemas.legal_hold_policy_assignment_base import ( LegalHoldPolicyAssignmentBase, ) diff --git a/docs/trashed_items.md b/docs/trashed_items.md index 22b41a270..c2ca2e9d5 100644 --- a/docs/trashed_items.md +++ b/docs/trashed_items.md @@ -14,6 +14,8 @@ attributes that are not returned by default. This endpoint defaults to use offset-based pagination, yet also supports marker-based pagination using the `marker` parameter. +The number of entries returned may be less than `total_count`. For example, if a user deletes items from a shared folder and is later removed as a collaborator, those deleted items will no longer appear in this endpoint’s results, even though they are still included in `total_count`. + This operation is performed by calling function `get_trashed_items`. See the endpoint docs at From fe0a3b86b292d7304c244e4b6933612bbce5f332 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:19:38 +0200 Subject: [PATCH 096/139] fix: Add missing discriminators for schema types (box/box-codegen#931) (#1368) --- .codegen.json | 2 +- box_sdk_gen/schemas/ai_single_agent_response_full.py | 2 ++ box_sdk_gen/schemas/comment.py | 2 ++ box_sdk_gen/schemas/comment_full.py | 2 ++ box_sdk_gen/schemas/conflict_error.py | 2 ++ box_sdk_gen/schemas/file.py | 2 ++ box_sdk_gen/schemas/file_conflict.py | 2 ++ box_sdk_gen/schemas/file_full.py | 2 ++ box_sdk_gen/schemas/file_mini.py | 1 + box_sdk_gen/schemas/file_version.py | 2 ++ box_sdk_gen/schemas/file_version_full.py | 2 ++ box_sdk_gen/schemas/file_version_mini.py | 1 + box_sdk_gen/schemas/folder.py | 2 ++ box_sdk_gen/schemas/folder_full.py | 2 ++ box_sdk_gen/schemas/folder_mini.py | 2 ++ box_sdk_gen/schemas/group.py | 2 ++ box_sdk_gen/schemas/group_full.py | 2 ++ box_sdk_gen/schemas/group_mini.py | 2 ++ box_sdk_gen/schemas/integration_mapping.py | 2 ++ box_sdk_gen/schemas/integration_mapping_teams.py | 2 ++ box_sdk_gen/schemas/legal_hold_policy.py | 2 ++ box_sdk_gen/schemas/legal_hold_policy_assignment.py | 2 ++ box_sdk_gen/schemas/retention_policy.py | 2 ++ box_sdk_gen/schemas/retention_policy_mini.py | 2 ++ .../schemas/shield_information_barrier_report.py | 2 ++ .../shield_information_barrier_segment_member.py | 2 ++ .../shield_information_barrier_segment_member_mini.py | 2 ++ .../shield_information_barrier_segment_restriction.py | 2 ++ ...eld_information_barrier_segment_restriction_mini.py | 2 ++ box_sdk_gen/schemas/sign_request.py | 2 ++ box_sdk_gen/schemas/sign_request_signer_input.py | 9 +++++++++ box_sdk_gen/schemas/storage_policy.py | 2 ++ box_sdk_gen/schemas/template_signer_input.py | 10 ++++++++++ box_sdk_gen/schemas/terms_of_service.py | 2 ++ box_sdk_gen/schemas/user.py | 2 ++ box_sdk_gen/schemas/user_collaborations.py | 2 ++ box_sdk_gen/schemas/user_full.py | 2 ++ box_sdk_gen/schemas/user_integration_mappings.py | 2 ++ box_sdk_gen/schemas/user_mini.py | 2 ++ .../schemas/v2025_r0/doc_gen_job_full_v2025_r0.py | 2 ++ box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py | 2 ++ box_sdk_gen/schemas/v2025_r0/group_mini_v2025_r0.py | 2 ++ .../v2025_r0/hub_collaboration_user_v2025_r0.py | 2 ++ box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py | 2 ++ box_sdk_gen/schemas/v2025_r0/user_mini_v2025_r0.py | 2 ++ box_sdk_gen/schemas/web_link.py | 2 ++ box_sdk_gen/schemas/web_link_mini.py | 2 ++ box_sdk_gen/schemas/webhook.py | 2 ++ box_sdk_gen/schemas/workflow.py | 2 ++ 49 files changed, 110 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 183f4913a..6521d1b3d 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "7c01dda", "specHash": "74a1e4a", "version": "10.5.0" } +{ "engineHash": "02fdae4", "specHash": "74a1e4a", "version": "10.5.0" } diff --git a/box_sdk_gen/schemas/ai_single_agent_response_full.py b/box_sdk_gen/schemas/ai_single_agent_response_full.py index 1898fd5e7..9086422a6 100644 --- a/box_sdk_gen/schemas/ai_single_agent_response_full.py +++ b/box_sdk_gen/schemas/ai_single_agent_response_full.py @@ -28,6 +28,8 @@ class AiSingleAgentResponseFull(AiSingleAgentResponse): + _discriminator = 'type', {'ai_agent'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/comment.py b/box_sdk_gen/schemas/comment.py index a67ba328f..de1237868 100644 --- a/box_sdk_gen/schemas/comment.py +++ b/box_sdk_gen/schemas/comment.py @@ -29,6 +29,8 @@ def __init__( class Comment(CommentBase): + _discriminator = 'type', {'comment'} + def __init__( self, *, diff --git a/box_sdk_gen/schemas/comment_full.py b/box_sdk_gen/schemas/comment_full.py index 1b3b7b69f..fcd699608 100644 --- a/box_sdk_gen/schemas/comment_full.py +++ b/box_sdk_gen/schemas/comment_full.py @@ -16,6 +16,8 @@ class CommentFull(Comment): + _discriminator = 'type', {'comment'} + def __init__( self, *, diff --git a/box_sdk_gen/schemas/conflict_error.py b/box_sdk_gen/schemas/conflict_error.py index 4ac1341c5..a35298c4f 100644 --- a/box_sdk_gen/schemas/conflict_error.py +++ b/box_sdk_gen/schemas/conflict_error.py @@ -28,6 +28,8 @@ def __init__(self, *, conflicts: Optional[List[FileConflict]] = None, **kwargs): class ConflictError(ClientError): + _discriminator = 'type', {'error'} + def __init__( self, *, diff --git a/box_sdk_gen/schemas/file.py b/box_sdk_gen/schemas/file.py index 2e9344e88..5eaacca29 100644 --- a/box_sdk_gen/schemas/file.py +++ b/box_sdk_gen/schemas/file.py @@ -175,6 +175,8 @@ class FileItemStatusField(str, Enum): class File(FileMini): + _discriminator = 'type', {'file'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/file_conflict.py b/box_sdk_gen/schemas/file_conflict.py index 5e88917fe..32c99b407 100644 --- a/box_sdk_gen/schemas/file_conflict.py +++ b/box_sdk_gen/schemas/file_conflict.py @@ -12,6 +12,8 @@ class FileConflict(FileMini): + _discriminator = 'type', {'file'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/file_full.py b/box_sdk_gen/schemas/file_full.py index 3396821d6..c9535978f 100644 --- a/box_sdk_gen/schemas/file_full.py +++ b/box_sdk_gen/schemas/file_full.py @@ -414,6 +414,8 @@ class FileFullSharedLinkPermissionOptionsField(str, Enum): class FileFull(File): + _discriminator = 'type', {'file'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/file_mini.py b/box_sdk_gen/schemas/file_mini.py index c8b72f4e7..39f093652 100644 --- a/box_sdk_gen/schemas/file_mini.py +++ b/box_sdk_gen/schemas/file_mini.py @@ -20,6 +20,7 @@ class FileMini(FileBase): 'sha1': 'sha_1', **FileBase._json_to_fields_mapping, } + _discriminator = 'type', {'file'} def __init__( self, diff --git a/box_sdk_gen/schemas/file_version.py b/box_sdk_gen/schemas/file_version.py index 3b213fb07..cb0c31fff 100644 --- a/box_sdk_gen/schemas/file_version.py +++ b/box_sdk_gen/schemas/file_version.py @@ -14,6 +14,8 @@ class FileVersion(FileVersionMini): + _discriminator = 'type', {'file_version'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/file_version_full.py b/box_sdk_gen/schemas/file_version_full.py index 7d778bd3a..da5cc4479 100644 --- a/box_sdk_gen/schemas/file_version_full.py +++ b/box_sdk_gen/schemas/file_version_full.py @@ -16,6 +16,8 @@ class FileVersionFull(FileVersion): + _discriminator = 'type', {'file_version'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/file_version_mini.py b/box_sdk_gen/schemas/file_version_mini.py index d4c03e4c9..6a8fed8ee 100644 --- a/box_sdk_gen/schemas/file_version_mini.py +++ b/box_sdk_gen/schemas/file_version_mini.py @@ -18,6 +18,7 @@ class FileVersionMini(FileVersionBase): 'sha1': 'sha_1', **FileVersionBase._json_to_fields_mapping, } + _discriminator = 'type', {'file_version'} def __init__( self, diff --git a/box_sdk_gen/schemas/folder.py b/box_sdk_gen/schemas/folder.py index a0330bec5..87118100d 100644 --- a/box_sdk_gen/schemas/folder.py +++ b/box_sdk_gen/schemas/folder.py @@ -209,6 +209,8 @@ class FolderItemStatusField(str, Enum): class Folder(FolderMini): + _discriminator = 'type', {'folder'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/folder_full.py b/box_sdk_gen/schemas/folder_full.py index b2594ff12..734d8ef94 100644 --- a/box_sdk_gen/schemas/folder_full.py +++ b/box_sdk_gen/schemas/folder_full.py @@ -157,6 +157,8 @@ def __init__( class FolderFull(Folder): + _discriminator = 'type', {'folder'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/folder_mini.py b/box_sdk_gen/schemas/folder_mini.py index 1e7d71a14..7339ceab3 100644 --- a/box_sdk_gen/schemas/folder_mini.py +++ b/box_sdk_gen/schemas/folder_mini.py @@ -8,6 +8,8 @@ class FolderMini(FolderBase): + _discriminator = 'type', {'folder'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/group.py b/box_sdk_gen/schemas/group.py index fbb2fdc88..f2ff7299e 100644 --- a/box_sdk_gen/schemas/group.py +++ b/box_sdk_gen/schemas/group.py @@ -14,6 +14,8 @@ class Group(GroupMini): + _discriminator = 'type', {'group'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/group_full.py b/box_sdk_gen/schemas/group_full.py index c0503abfa..0dfcf75ea 100644 --- a/box_sdk_gen/schemas/group_full.py +++ b/box_sdk_gen/schemas/group_full.py @@ -42,6 +42,8 @@ def __init__(self, *, can_invite_as_collaborator: Optional[bool] = None, **kwarg class GroupFull(Group): + _discriminator = 'type', {'group'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/group_mini.py b/box_sdk_gen/schemas/group_mini.py index a05571868..80b14b320 100644 --- a/box_sdk_gen/schemas/group_mini.py +++ b/box_sdk_gen/schemas/group_mini.py @@ -15,6 +15,8 @@ class GroupMiniGroupTypeField(str, Enum): class GroupMini(GroupBase): + _discriminator = 'type', {'group'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/integration_mapping.py b/box_sdk_gen/schemas/integration_mapping.py index be4efb56a..8e38ffce9 100644 --- a/box_sdk_gen/schemas/integration_mapping.py +++ b/box_sdk_gen/schemas/integration_mapping.py @@ -28,6 +28,8 @@ class IntegrationMappingIntegrationTypeField(str, Enum): class IntegrationMapping(IntegrationMappingBase): + _discriminator = 'type', {'integration_mapping'} + def __init__( self, partner_item: IntegrationMappingPartnerItemSlack, diff --git a/box_sdk_gen/schemas/integration_mapping_teams.py b/box_sdk_gen/schemas/integration_mapping_teams.py index d4df1fafc..3c1dfc4f3 100644 --- a/box_sdk_gen/schemas/integration_mapping_teams.py +++ b/box_sdk_gen/schemas/integration_mapping_teams.py @@ -22,6 +22,8 @@ class IntegrationMappingTeamsIntegrationTypeField(str, Enum): class IntegrationMappingTeams(IntegrationMappingBase): + _discriminator = 'type', {'integration_mapping'} + def __init__( self, partner_item: IntegrationMappingPartnerItemTeams, diff --git a/box_sdk_gen/schemas/legal_hold_policy.py b/box_sdk_gen/schemas/legal_hold_policy.py index f3e85921e..84763fb31 100644 --- a/box_sdk_gen/schemas/legal_hold_policy.py +++ b/box_sdk_gen/schemas/legal_hold_policy.py @@ -58,6 +58,8 @@ def __init__( class LegalHoldPolicy(LegalHoldPolicyMini): + _discriminator = 'type', {'legal_hold_policy'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/legal_hold_policy_assignment.py b/box_sdk_gen/schemas/legal_hold_policy_assignment.py index 55892ffff..b8221d9ce 100644 --- a/box_sdk_gen/schemas/legal_hold_policy_assignment.py +++ b/box_sdk_gen/schemas/legal_hold_policy_assignment.py @@ -22,6 +22,8 @@ class LegalHoldPolicyAssignment(LegalHoldPolicyAssignmentBase): + _discriminator = 'type', {'legal_hold_policy_assignment'} + def __init__( self, *, diff --git a/box_sdk_gen/schemas/retention_policy.py b/box_sdk_gen/schemas/retention_policy.py index 91f884090..0b7b929ab 100644 --- a/box_sdk_gen/schemas/retention_policy.py +++ b/box_sdk_gen/schemas/retention_policy.py @@ -62,6 +62,8 @@ def __init__( class RetentionPolicy(RetentionPolicyMini): + _discriminator = 'type', {'retention_policy'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/retention_policy_mini.py b/box_sdk_gen/schemas/retention_policy_mini.py index ff8ad0b20..1980630e4 100644 --- a/box_sdk_gen/schemas/retention_policy_mini.py +++ b/box_sdk_gen/schemas/retention_policy_mini.py @@ -15,6 +15,8 @@ class RetentionPolicyMiniDispositionActionField(str, Enum): class RetentionPolicyMini(RetentionPolicyBase): + _discriminator = 'type', {'retention_policy'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/shield_information_barrier_report.py b/box_sdk_gen/schemas/shield_information_barrier_report.py index 4701838e4..b45403a8d 100644 --- a/box_sdk_gen/schemas/shield_information_barrier_report.py +++ b/box_sdk_gen/schemas/shield_information_barrier_report.py @@ -33,6 +33,8 @@ class ShieldInformationBarrierReportStatusField(str, Enum): class ShieldInformationBarrierReport(ShieldInformationBarrierReportBase): + _discriminator = 'type', {'shield_information_barrier_report'} + def __init__( self, *, diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_member.py b/box_sdk_gen/schemas/shield_information_barrier_segment_member.py index 0a037bc8c..74fa40d30 100644 --- a/box_sdk_gen/schemas/shield_information_barrier_segment_member.py +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_member.py @@ -60,6 +60,8 @@ def __init__( class ShieldInformationBarrierSegmentMember(ShieldInformationBarrierSegmentMemberMini): + _discriminator = 'type', {'shield_information_barrier_segment_member'} + def __init__( self, *, diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_member_mini.py b/box_sdk_gen/schemas/shield_information_barrier_segment_member_mini.py index ef416af8a..6ea3facc3 100644 --- a/box_sdk_gen/schemas/shield_information_barrier_segment_member_mini.py +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_member_mini.py @@ -16,6 +16,8 @@ class ShieldInformationBarrierSegmentMemberMini( ShieldInformationBarrierSegmentMemberBase ): + _discriminator = 'type', {'shield_information_barrier_segment_member'} + def __init__( self, *, diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_restriction.py b/box_sdk_gen/schemas/shield_information_barrier_segment_restriction.py index 2715bcb33..62b91b62c 100644 --- a/box_sdk_gen/schemas/shield_information_barrier_segment_restriction.py +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_restriction.py @@ -34,6 +34,8 @@ class ShieldInformationBarrierSegmentRestriction( ShieldInformationBarrierSegmentRestrictionMini ): + _discriminator = 'type', {'shield_information_barrier_segment_restriction'} + def __init__( self, shield_information_barrier_segment: ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentField, diff --git a/box_sdk_gen/schemas/shield_information_barrier_segment_restriction_mini.py b/box_sdk_gen/schemas/shield_information_barrier_segment_restriction_mini.py index a84864fe2..659d5ff6d 100644 --- a/box_sdk_gen/schemas/shield_information_barrier_segment_restriction_mini.py +++ b/box_sdk_gen/schemas/shield_information_barrier_segment_restriction_mini.py @@ -80,6 +80,8 @@ def __init__( class ShieldInformationBarrierSegmentRestrictionMini( ShieldInformationBarrierSegmentRestrictionBase ): + _discriminator = 'type', {'shield_information_barrier_segment_restriction'} + def __init__( self, shield_information_barrier_segment: ShieldInformationBarrierSegmentRestrictionMiniShieldInformationBarrierSegmentField, diff --git a/box_sdk_gen/schemas/sign_request.py b/box_sdk_gen/schemas/sign_request.py index d07c8ec41..d8e052fa1 100644 --- a/box_sdk_gen/schemas/sign_request.py +++ b/box_sdk_gen/schemas/sign_request.py @@ -64,6 +64,8 @@ def __init__( class SignRequest(SignRequestBase): + _discriminator = 'type', {'sign-request'} + def __init__( self, *, diff --git a/box_sdk_gen/schemas/sign_request_signer_input.py b/box_sdk_gen/schemas/sign_request_signer_input.py index 8218fbca2..ffb7b1a18 100644 --- a/box_sdk_gen/schemas/sign_request_signer_input.py +++ b/box_sdk_gen/schemas/sign_request_signer_input.py @@ -85,6 +85,15 @@ class SignRequestSignerInputContentTypeField(str, Enum): class SignRequestSignerInput(SignRequestPrefillTag): + _discriminator = 'type', { + 'signature', + 'date', + 'text', + 'checkbox', + 'radio', + 'dropdown', + } + def __init__( self, page_index: int, diff --git a/box_sdk_gen/schemas/storage_policy.py b/box_sdk_gen/schemas/storage_policy.py index eb5e1cf38..541fb36ad 100644 --- a/box_sdk_gen/schemas/storage_policy.py +++ b/box_sdk_gen/schemas/storage_policy.py @@ -8,6 +8,8 @@ class StoragePolicy(StoragePolicyMini): + _discriminator = 'type', {'storage_policy'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/template_signer_input.py b/box_sdk_gen/schemas/template_signer_input.py index 845999bc8..fafc085e8 100644 --- a/box_sdk_gen/schemas/template_signer_input.py +++ b/box_sdk_gen/schemas/template_signer_input.py @@ -120,6 +120,16 @@ def __init__( class TemplateSignerInput(SignRequestPrefillTag): + _discriminator = 'type', { + 'signature', + 'date', + 'text', + 'checkbox', + 'attachment', + 'radio', + 'dropdown', + } + def __init__( self, page_index: int, diff --git a/box_sdk_gen/schemas/terms_of_service.py b/box_sdk_gen/schemas/terms_of_service.py index f36ac992c..55e47b01a 100644 --- a/box_sdk_gen/schemas/terms_of_service.py +++ b/box_sdk_gen/schemas/terms_of_service.py @@ -53,6 +53,8 @@ class TermsOfServiceTosTypeField(str, Enum): class TermsOfService(TermsOfServiceBase): + _discriminator = 'type', {'terms_of_service'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/user.py b/box_sdk_gen/schemas/user.py index 8e98fec97..d21d62bbc 100644 --- a/box_sdk_gen/schemas/user.py +++ b/box_sdk_gen/schemas/user.py @@ -42,6 +42,8 @@ def __init__( class User(UserMini): + _discriminator = 'type', {'user'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/user_collaborations.py b/box_sdk_gen/schemas/user_collaborations.py index 848ce5974..056d1d021 100644 --- a/box_sdk_gen/schemas/user_collaborations.py +++ b/box_sdk_gen/schemas/user_collaborations.py @@ -8,6 +8,8 @@ class UserCollaborations(UserBase): + _discriminator = 'type', {'user'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/user_full.py b/box_sdk_gen/schemas/user_full.py index 6ae220de1..e67367c46 100644 --- a/box_sdk_gen/schemas/user_full.py +++ b/box_sdk_gen/schemas/user_full.py @@ -61,6 +61,8 @@ def __init__( class UserFull(User): + _discriminator = 'type', {'user'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/user_integration_mappings.py b/box_sdk_gen/schemas/user_integration_mappings.py index 8e14d80f5..326b43fe5 100644 --- a/box_sdk_gen/schemas/user_integration_mappings.py +++ b/box_sdk_gen/schemas/user_integration_mappings.py @@ -8,6 +8,8 @@ class UserIntegrationMappings(UserBase): + _discriminator = 'type', {'user'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/user_mini.py b/box_sdk_gen/schemas/user_mini.py index e53d185b7..738781fb0 100644 --- a/box_sdk_gen/schemas/user_mini.py +++ b/box_sdk_gen/schemas/user_mini.py @@ -8,6 +8,8 @@ class UserMini(UserBase): + _discriminator = 'type', {'user'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py index 003e192b2..596476236 100644 --- a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py @@ -32,6 +32,8 @@ class DocGenJobFullV2025R0(DocGenJobV2025R0): + _discriminator = 'type', {'docgen_job'} + def __init__( self, created_by: UserBaseV2025R0, diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py index dd1134d17..f1fbdadcd 100644 --- a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py @@ -30,6 +30,8 @@ class DocGenJobV2025R0StatusField(str, Enum): class DocGenJobV2025R0(DocGenJobBaseV2025R0): + _discriminator = 'type', {'docgen_job'} + def __init__( self, batch: DocGenBatchBaseV2025R0, diff --git a/box_sdk_gen/schemas/v2025_r0/group_mini_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/group_mini_v2025_r0.py index 965e5e513..3e0bf1107 100644 --- a/box_sdk_gen/schemas/v2025_r0/group_mini_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/group_mini_v2025_r0.py @@ -15,6 +15,8 @@ class GroupMiniV2025R0GroupTypeField(str, Enum): class GroupMiniV2025R0(GroupBaseV2025R0): + _discriminator = 'type', {'group'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_user_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_user_v2025_r0.py index cff4046ce..f6563c889 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_collaboration_user_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_collaboration_user_v2025_r0.py @@ -8,6 +8,8 @@ class HubCollaborationUserV2025R0(UserBaseV2025R0): + _discriminator = 'type', {'user'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py index e2559bba4..3c77dc73f 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py @@ -12,6 +12,8 @@ class HubV2025R0(HubBaseV2025R0): + _discriminator = 'type', {'hubs'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/v2025_r0/user_mini_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/user_mini_v2025_r0.py index f9ef21883..aa2a2b493 100644 --- a/box_sdk_gen/schemas/v2025_r0/user_mini_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/user_mini_v2025_r0.py @@ -8,6 +8,8 @@ class UserMiniV2025R0(UserBaseV2025R0): + _discriminator = 'type', {'user'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/web_link.py b/box_sdk_gen/schemas/web_link.py index c85512f47..744df7c58 100644 --- a/box_sdk_gen/schemas/web_link.py +++ b/box_sdk_gen/schemas/web_link.py @@ -173,6 +173,8 @@ class WebLinkItemStatusField(str, Enum): class WebLink(WebLinkMini): + _discriminator = 'type', {'web_link'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/web_link_mini.py b/box_sdk_gen/schemas/web_link_mini.py index 8beb65852..fd887b107 100644 --- a/box_sdk_gen/schemas/web_link_mini.py +++ b/box_sdk_gen/schemas/web_link_mini.py @@ -8,6 +8,8 @@ class WebLinkMini(WebLinkBase): + _discriminator = 'type', {'web_link'} + def __init__( self, id: str, diff --git a/box_sdk_gen/schemas/webhook.py b/box_sdk_gen/schemas/webhook.py index 8163c7ee9..a2fddb15f 100644 --- a/box_sdk_gen/schemas/webhook.py +++ b/box_sdk_gen/schemas/webhook.py @@ -64,6 +64,8 @@ class WebhookTriggersField(str, Enum): class Webhook(WebhookMini): + _discriminator = 'type', {'webhook'} + def __init__( self, *, diff --git a/box_sdk_gen/schemas/workflow.py b/box_sdk_gen/schemas/workflow.py index 2d5ecc394..71606c426 100644 --- a/box_sdk_gen/schemas/workflow.py +++ b/box_sdk_gen/schemas/workflow.py @@ -259,6 +259,8 @@ def __init__( class Workflow(WorkflowMini): + _discriminator = 'type', {'workflow'} + def __init__( self, *, From 39fbc7cd88a3e0a6a002cd2aa202d10730c6d91a Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:48:12 +0200 Subject: [PATCH 097/139] fix!: Correct search content type 'tags' to match API (box/box-openapi#591) (#1369) --- .codegen.json | 2 +- box_sdk_gen/managers/search.py | 2 +- .../schemas/v2025_r0/hub_item_operation_result_v2025_r0.py | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 6521d1b3d..4cb098394 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "02fdae4", "specHash": "74a1e4a", "version": "10.5.0" } +{ "engineHash": "02fdae4", "specHash": "c8e3a85", "version": "10.5.0" } diff --git a/box_sdk_gen/managers/search.py b/box_sdk_gen/managers/search.py index dfb1a16df..cdfc76d84 100644 --- a/box_sdk_gen/managers/search.py +++ b/box_sdk_gen/managers/search.py @@ -93,7 +93,7 @@ class SearchForContentContentTypes(str, Enum): DESCRIPTION = 'description' FILE_CONTENT = 'file_content' COMMENTS = 'comments' - TAG = 'tag' + TAGS = 'tags' class SearchForContentType(str, Enum): diff --git a/box_sdk_gen/schemas/v2025_r0/hub_item_operation_result_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_result_v2025_r0.py index f1423d2c2..7df545084 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_item_operation_result_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_item_operation_result_v2025_r0.py @@ -25,6 +25,7 @@ def __init__( *, action: Optional[str] = None, item: Optional[HubItemReferenceV2025R0] = None, + parent_id: Optional[str] = None, status: Optional[int] = None, error: Optional[str] = None, **kwargs @@ -32,6 +33,8 @@ def __init__( """ :param action: The action performed on the item., defaults to None :type action: Optional[str], optional + :param parent_id: The ID of the parent block the item was added to., defaults to None + :type parent_id: Optional[str], optional :param status: The HTTP status code of the operation., defaults to None :type status: Optional[int], optional :param error: Error message if the operation failed., defaults to None @@ -40,5 +43,6 @@ def __init__( super().__init__(**kwargs) self.action = action self.item = item + self.parent_id = parent_id self.status = status self.error = error From 720b94835b1b5308d8c1b8b3a7bc176766d98a04 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:33:22 +0200 Subject: [PATCH 098/139] feat: Support Hub Document API (box/box-codegen#930) (#1371) --- .codegen.json | 2 +- box_sdk_gen/client.py | 5 + box_sdk_gen/managers/__init__.py | 2 + box_sdk_gen/managers/hub_document.py | 187 ++++++++++++++++++ box_sdk_gen/schemas/v2025_r0/__init__.py | 20 ++ .../hub_callout_box_text_block_v2025_r0.py | 40 ++++ .../v2025_r0/hub_divider_block_v2025_r0.py | 36 ++++ .../hub_document_block_entry_v2025_r0.py | 31 +++ .../v2025_r0/hub_document_block_v2025_r0.py | 18 ++ .../v2025_r0/hub_document_blocks_v2025_r0.py | 68 +++++++ .../v2025_r0/hub_document_page_v2025_r0.py | 32 +++ .../v2025_r0/hub_document_pages_v2025_r0.py | 48 +++++ .../v2025_r0/hub_item_list_block_v2025_r0.py | 38 ++++ .../hub_paragraph_text_block_v2025_r0.py | 40 ++++ .../hub_section_title_text_block_v2025_r0.py | 40 ++++ docs/README.md | 1 + docs/hub_document.md | 79 ++++++++ test/hub_document.py | 71 +++++++ 18 files changed, 757 insertions(+), 1 deletion(-) create mode 100644 box_sdk_gen/managers/hub_document.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_callout_box_text_block_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_divider_block_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_document_block_entry_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_document_block_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_document_blocks_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_document_page_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_document_pages_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_item_list_block_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_paragraph_text_block_v2025_r0.py create mode 100644 box_sdk_gen/schemas/v2025_r0/hub_section_title_text_block_v2025_r0.py create mode 100644 docs/hub_document.md create mode 100644 test/hub_document.py diff --git a/.codegen.json b/.codegen.json index 4cb098394..168441c6b 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "02fdae4", "specHash": "c8e3a85", "version": "10.5.0" } +{ "engineHash": "e77f966", "specHash": "c8e3a85", "version": "10.5.0" } diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py index f141a1306..060a6249b 100644 --- a/box_sdk_gen/client.py +++ b/box_sdk_gen/client.py @@ -184,6 +184,8 @@ from box_sdk_gen.managers.hub_items import HubItemsManager +from box_sdk_gen.managers.hub_document import HubDocumentManager + from box_sdk_gen.managers.shield_lists import ShieldListsManager from box_sdk_gen.managers.archives import ArchivesManager @@ -450,6 +452,9 @@ def __init__(self, auth: Authentication, *, network_session: NetworkSession = No self.hub_items = HubItemsManager( auth=self.auth, network_session=self.network_session ) + self.hub_document = HubDocumentManager( + auth=self.auth, network_session=self.network_session + ) self.shield_lists = ShieldListsManager( auth=self.auth, network_session=self.network_session ) diff --git a/box_sdk_gen/managers/__init__.py b/box_sdk_gen/managers/__init__.py index 9ab97dc30..fa6fecc26 100644 --- a/box_sdk_gen/managers/__init__.py +++ b/box_sdk_gen/managers/__init__.py @@ -156,6 +156,8 @@ from box_sdk_gen.managers.hub_items import * +from box_sdk_gen.managers.hub_document import * + from box_sdk_gen.managers.shield_lists import * from box_sdk_gen.managers.archives import * diff --git a/box_sdk_gen/managers/hub_document.py b/box_sdk_gen/managers/hub_document.py new file mode 100644 index 000000000..e5b80191b --- /dev/null +++ b/box_sdk_gen/managers/hub_document.py @@ -0,0 +1,187 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2025_r0.hub_document_pages_v2025_r0 import ( + HubDocumentPagesV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.client_error_v2025_r0 import ClientErrorV2025R0 + +from box_sdk_gen.parameters.v2025_r0.box_version_header_v2025_r0 import ( + BoxVersionHeaderV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_document_blocks_v2025_r0 import ( + HubDocumentBlocksV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class HubDocumentManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_hub_document_pages_v2025_r0( + self, + hub_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubDocumentPagesV2025R0: + """ + Retrieves a list of Hub Document Pages for the specified hub. + + Includes both root-level pages and sub pages. + + :param hub_id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting this hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + :type hub_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'hub_id': to_string(hub_id), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/hub_document_pages'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubDocumentPagesV2025R0) + + def get_hub_document_blocks_v2025_r0( + self, + hub_id: str, + page_id: str, + *, + marker: Optional[str] = None, + limit: Optional[int] = None, + box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> HubDocumentBlocksV2025R0: + """ + Retrieves a sorted list of all Hub Document Blocks on a specified page in the hub document, excluding items. + + Blocks are hierarchically organized by their `parent_id`. + + + Blocks are sorted in order based on user specification in the user interface. + + + The response will only include content blocks that belong to the specified page. This will not include sub pages or sub page content blocks. + + :param hub_id: The unique identifier that represent a hub. + + The ID for any hub can be determined + by visiting this hub in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/hubs/123` + the `hub_id` is `123`. + :type hub_id: str + :param page_id: The unique identifier of a page within the Box Hub. + :type page_id: str + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination., defaults to None + :type marker: Optional[str], optional + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 + :type box_version: BoxVersionHeaderV2025R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'hub_id': to_string(hub_id), + 'page_id': to_string(page_id), + 'marker': to_string(marker), + 'limit': to_string(limit), + } + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/hub_document_blocks', + ] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, HubDocumentBlocksV2025R0) diff --git a/box_sdk_gen/schemas/v2025_r0/__init__.py b/box_sdk_gen/schemas/v2025_r0/__init__.py index 64d7b89b1..0a3bee194 100644 --- a/box_sdk_gen/schemas/v2025_r0/__init__.py +++ b/box_sdk_gen/schemas/v2025_r0/__init__.py @@ -70,6 +70,26 @@ from box_sdk_gen.schemas.v2025_r0.hub_create_request_v2025_r0 import * +from box_sdk_gen.schemas.v2025_r0.hub_document_block_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_section_title_text_block_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_paragraph_text_block_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_item_list_block_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_divider_block_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_callout_box_text_block_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_document_block_entry_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_document_blocks_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_document_page_v2025_r0 import * + +from box_sdk_gen.schemas.v2025_r0.hub_document_pages_v2025_r0 import * + from box_sdk_gen.schemas.v2025_r0.hub_item_v2025_r0 import * from box_sdk_gen.schemas.v2025_r0.hub_items_v2025_r0 import * diff --git a/box_sdk_gen/schemas/v2025_r0/hub_callout_box_text_block_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_callout_box_text_block_v2025_r0.py new file mode 100644 index 000000000..7eb5d872f --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_callout_box_text_block_v2025_r0.py @@ -0,0 +1,40 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.hub_document_block_v2025_r0 import ( + HubDocumentBlockV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubCalloutBoxTextBlockV2025R0TypeField(str, Enum): + CALLOUT_BOX = 'callout_box' + + +class HubCalloutBoxTextBlockV2025R0(HubDocumentBlockV2025R0): + _discriminator = 'type', {'callout_box'} + + def __init__( + self, + fragment: str, + id: str, + *, + type: HubCalloutBoxTextBlockV2025R0TypeField = HubCalloutBoxTextBlockV2025R0TypeField.CALLOUT_BOX, + parent_id: Optional[str] = None, + **kwargs + ): + """ + :param fragment: Text content of the block. Includes rich text formatting. + :type fragment: str + :param id: The unique identifier for this block. + :type id: str + :param type: The type of this block. The value is always `callout_box`., defaults to HubCalloutBoxTextBlockV2025R0TypeField.CALLOUT_BOX + :type type: HubCalloutBoxTextBlockV2025R0TypeField, optional + :param parent_id: The unique identifier of the parent block. Null for direct children of the page., defaults to None + :type parent_id: Optional[str], optional + """ + super().__init__(id=id, parent_id=parent_id, **kwargs) + self.fragment = fragment + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/hub_divider_block_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_divider_block_v2025_r0.py new file mode 100644 index 000000000..e4dad32a3 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_divider_block_v2025_r0.py @@ -0,0 +1,36 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.hub_document_block_v2025_r0 import ( + HubDocumentBlockV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubDividerBlockV2025R0TypeField(str, Enum): + DIVIDER = 'divider' + + +class HubDividerBlockV2025R0(HubDocumentBlockV2025R0): + _discriminator = 'type', {'divider'} + + def __init__( + self, + id: str, + *, + type: HubDividerBlockV2025R0TypeField = HubDividerBlockV2025R0TypeField.DIVIDER, + parent_id: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this block. + :type id: str + :param type: The type of this block. The value is always `divider`., defaults to HubDividerBlockV2025R0TypeField.DIVIDER + :type type: HubDividerBlockV2025R0TypeField, optional + :param parent_id: The unique identifier of the parent block. Null for direct children of the page., defaults to None + :type parent_id: Optional[str], optional + """ + super().__init__(id=id, parent_id=parent_id, **kwargs) + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/hub_document_block_entry_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_document_block_entry_v2025_r0.py new file mode 100644 index 000000000..e7ef7e628 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_document_block_entry_v2025_r0.py @@ -0,0 +1,31 @@ +from typing import Union + +from box_sdk_gen.schemas.v2025_r0.hub_paragraph_text_block_v2025_r0 import ( + HubParagraphTextBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_section_title_text_block_v2025_r0 import ( + HubSectionTitleTextBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_callout_box_text_block_v2025_r0 import ( + HubCalloutBoxTextBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_item_list_block_v2025_r0 import ( + HubItemListBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_divider_block_v2025_r0 import ( + HubDividerBlockV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +HubDocumentBlockEntryV2025R0 = Union[ + HubParagraphTextBlockV2025R0, + HubSectionTitleTextBlockV2025R0, + HubCalloutBoxTextBlockV2025R0, + HubItemListBlockV2025R0, + HubDividerBlockV2025R0, +] diff --git a/box_sdk_gen/schemas/v2025_r0/hub_document_block_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_document_block_v2025_r0.py new file mode 100644 index 000000000..f9f829a7a --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_document_block_v2025_r0.py @@ -0,0 +1,18 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubDocumentBlockV2025R0(BaseObject): + def __init__(self, id: str, *, parent_id: Optional[str] = None, **kwargs): + """ + :param id: The unique identifier for this block. + :type id: str + :param parent_id: The unique identifier of the parent block. Null for direct children of the page., defaults to None + :type parent_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.parent_id = parent_id diff --git a/box_sdk_gen/schemas/v2025_r0/hub_document_blocks_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_document_blocks_v2025_r0.py new file mode 100644 index 000000000..58843acfb --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_document_blocks_v2025_r0.py @@ -0,0 +1,68 @@ +from enum import Enum + +from typing import List + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.hub_paragraph_text_block_v2025_r0 import ( + HubParagraphTextBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_section_title_text_block_v2025_r0 import ( + HubSectionTitleTextBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_callout_box_text_block_v2025_r0 import ( + HubCalloutBoxTextBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_item_list_block_v2025_r0 import ( + HubItemListBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_divider_block_v2025_r0 import ( + HubDividerBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_document_block_entry_v2025_r0 import ( + HubDocumentBlockEntryV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubDocumentBlocksV2025R0TypeField(str, Enum): + DOCUMENT_BLOCKS = 'document_blocks' + + +class HubDocumentBlocksV2025R0(BaseObject): + _discriminator = 'type', {'document_blocks'} + + def __init__( + self, + entries: List[HubDocumentBlockEntryV2025R0], + *, + type: HubDocumentBlocksV2025R0TypeField = HubDocumentBlocksV2025R0TypeField.DOCUMENT_BLOCKS, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: Ordered list of blocks. + :type entries: List[HubDocumentBlockEntryV2025R0] + :param type: The value will always be `document_blocks`., defaults to HubDocumentBlocksV2025R0TypeField.DOCUMENT_BLOCKS + :type type: HubDocumentBlocksV2025R0TypeField, optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.type = type + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/v2025_r0/hub_document_page_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_document_page_v2025_r0.py new file mode 100644 index 000000000..00129ce5e --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_document_page_v2025_r0.py @@ -0,0 +1,32 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubDocumentPageV2025R0(BaseObject): + def __init__( + self, + id: str, + type: str, + title_fragment: str, + *, + parent_id: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this page. + :type id: str + :param type: The type of this resource. The value is always `page`. + :type type: str + :param title_fragment: The title text of the page. Includes rich text formatting. + :type title_fragment: str + :param parent_id: The unique identifier of the parent page. Null for root-level pages., defaults to None + :type parent_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.title_fragment = title_fragment + self.parent_id = parent_id diff --git a/box_sdk_gen/schemas/v2025_r0/hub_document_pages_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_document_pages_v2025_r0.py new file mode 100644 index 000000000..210fdfa1e --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_document_pages_v2025_r0.py @@ -0,0 +1,48 @@ +from enum import Enum + +from typing import List + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2025_r0.hub_document_page_v2025_r0 import ( + HubDocumentPageV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubDocumentPagesV2025R0TypeField(str, Enum): + DOCUMENT_PAGES = 'document_pages' + + +class HubDocumentPagesV2025R0(BaseObject): + _discriminator = 'type', {'document_pages'} + + def __init__( + self, + entries: List[HubDocumentPageV2025R0], + *, + type: HubDocumentPagesV2025R0TypeField = HubDocumentPagesV2025R0TypeField.DOCUMENT_PAGES, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: Ordered list of pages. + :type entries: List[HubDocumentPageV2025R0] + :param type: The value will always be `document_pages`., defaults to HubDocumentPagesV2025R0TypeField.DOCUMENT_PAGES + :type type: HubDocumentPagesV2025R0TypeField, optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.type = type + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/v2025_r0/hub_item_list_block_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_item_list_block_v2025_r0.py new file mode 100644 index 000000000..b01579885 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_item_list_block_v2025_r0.py @@ -0,0 +1,38 @@ +from enum import Enum + +from typing import Optional + +from typing import List + +from box_sdk_gen.schemas.v2025_r0.hub_document_block_v2025_r0 import ( + HubDocumentBlockV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubItemListBlockV2025R0TypeField(str, Enum): + ITEM_LIST = 'item_list' + + +class HubItemListBlockV2025R0(HubDocumentBlockV2025R0): + _discriminator = 'type', {'item_list'} + + def __init__( + self, + id: str, + *, + type: HubItemListBlockV2025R0TypeField = HubItemListBlockV2025R0TypeField.ITEM_LIST, + parent_id: Optional[str] = None, + **kwargs + ): + """ + :param id: The unique identifier for this block. + :type id: str + :param type: The type of this block. The value is always `item_list`., defaults to HubItemListBlockV2025R0TypeField.ITEM_LIST + :type type: HubItemListBlockV2025R0TypeField, optional + :param parent_id: The unique identifier of the parent block. Null for direct children of the page., defaults to None + :type parent_id: Optional[str], optional + """ + super().__init__(id=id, parent_id=parent_id, **kwargs) + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/hub_paragraph_text_block_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_paragraph_text_block_v2025_r0.py new file mode 100644 index 000000000..215f37d31 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_paragraph_text_block_v2025_r0.py @@ -0,0 +1,40 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.hub_document_block_v2025_r0 import ( + HubDocumentBlockV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubParagraphTextBlockV2025R0TypeField(str, Enum): + PARAGRAPH = 'paragraph' + + +class HubParagraphTextBlockV2025R0(HubDocumentBlockV2025R0): + _discriminator = 'type', {'paragraph'} + + def __init__( + self, + fragment: str, + id: str, + *, + type: HubParagraphTextBlockV2025R0TypeField = HubParagraphTextBlockV2025R0TypeField.PARAGRAPH, + parent_id: Optional[str] = None, + **kwargs + ): + """ + :param fragment: Text content of the block. Includes rich text formatting. + :type fragment: str + :param id: The unique identifier for this block. + :type id: str + :param type: The type of this block. The value is always `paragraph`., defaults to HubParagraphTextBlockV2025R0TypeField.PARAGRAPH + :type type: HubParagraphTextBlockV2025R0TypeField, optional + :param parent_id: The unique identifier of the parent block. Null for direct children of the page., defaults to None + :type parent_id: Optional[str], optional + """ + super().__init__(id=id, parent_id=parent_id, **kwargs) + self.fragment = fragment + self.type = type diff --git a/box_sdk_gen/schemas/v2025_r0/hub_section_title_text_block_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_section_title_text_block_v2025_r0.py new file mode 100644 index 000000000..6f6b95ef5 --- /dev/null +++ b/box_sdk_gen/schemas/v2025_r0/hub_section_title_text_block_v2025_r0.py @@ -0,0 +1,40 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.schemas.v2025_r0.hub_document_block_v2025_r0 import ( + HubDocumentBlockV2025R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class HubSectionTitleTextBlockV2025R0TypeField(str, Enum): + SECTION_TITLE = 'section_title' + + +class HubSectionTitleTextBlockV2025R0(HubDocumentBlockV2025R0): + _discriminator = 'type', {'section_title'} + + def __init__( + self, + fragment: str, + id: str, + *, + type: HubSectionTitleTextBlockV2025R0TypeField = HubSectionTitleTextBlockV2025R0TypeField.SECTION_TITLE, + parent_id: Optional[str] = None, + **kwargs + ): + """ + :param fragment: Text content of the block. Includes rich text formatting. + :type fragment: str + :param id: The unique identifier for this block. + :type id: str + :param type: The type of this block. The value is always `section_title`., defaults to HubSectionTitleTextBlockV2025R0TypeField.SECTION_TITLE + :type type: HubSectionTitleTextBlockV2025R0TypeField, optional + :param parent_id: The unique identifier of the parent block. Null for direct children of the page., defaults to None + :type parent_id: Optional[str], optional + """ + super().__init__(id=id, parent_id=parent_id, **kwargs) + self.fragment = fragment + self.type = type diff --git a/docs/README.md b/docs/README.md index c626b6118..a97a2ce83 100644 --- a/docs/README.md +++ b/docs/README.md @@ -48,6 +48,7 @@ the SDK are available by topic: - [Folders](folders.md) - [Groups](groups.md) - [Hub collaborations](hub_collaborations.md) +- [Hub document](hub_document.md) - [Hub items](hub_items.md) - [Hubs](hubs.md) - [Integration mappings](integration_mappings.md) diff --git a/docs/hub_document.md b/docs/hub_document.md new file mode 100644 index 000000000..9909ec2b8 --- /dev/null +++ b/docs/hub_document.md @@ -0,0 +1,79 @@ +# HubDocumentManager + +- [List Hub Document Pages](#list-hub-document-pages) +- [List Hub Document blocks for page](#list-hub-document-blocks-for-page) + +## List Hub Document Pages + +Retrieves a list of Hub Document Pages for the specified hub. +Includes both root-level pages and sub pages. + +This operation is performed by calling function `get_hub_document_pages_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-hub-document-pages/). + + + +```python +client.hub_document.get_hub_document_pages_v2025_r0(hub_id) +``` + +### Arguments + +- hub_id `str` + - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubDocumentPagesV2025R0`. + +Returns a Hub Document Pages response whose `entries` array contains root-level pages and sub pages. Includes pagination when more results are available. + +## List Hub Document blocks for page + +Retrieves a sorted list of all Hub Document Blocks on a specified page in the hub document, excluding items. +Blocks are hierarchically organized by their `parent_id`. +Blocks are sorted in order based on user specification in the user interface. +The response will only include content blocks that belong to the specified page. This will not include sub pages or sub page content blocks. + +This operation is performed by calling function `get_hub_document_blocks_v2025_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2025.0/get-hub-document-blocks/). + + + +```python +client.hub_document.get_hub_document_blocks_v2025_r0(hub_id, page_id) +``` + +### Arguments + +- hub_id `str` + - The unique identifier that represent a hub. The ID for any hub can be determined by visiting this hub in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/hubs/123` the `hub_id` is `123`. +- page_id `str` + - The unique identifier of a page within the Box Hub. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. +- limit `Optional[int]` + - The maximum number of items to return per page. +- box_version `BoxVersionHeaderV2025R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `HubDocumentBlocksV2025R0`. + +Returns a Hub Document Blocks response whose `entries` array contains all content blocks of the specified page, except for items. +To retrieve items, use the `GET /hub_items` endpoint. diff --git a/test/hub_document.py b/test/hub_document.py new file mode 100644 index 000000000..e12947653 --- /dev/null +++ b/test/hub_document.py @@ -0,0 +1,71 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.v2025_r0.hub_v2025_r0 import HubV2025R0 + +from box_sdk_gen.schemas.v2025_r0.hub_document_pages_v2025_r0 import ( + HubDocumentPagesV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_document_page_v2025_r0 import ( + HubDocumentPageV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_document_blocks_v2025_r0 import ( + HubDocumentBlocksV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_paragraph_text_block_v2025_r0 import ( + HubParagraphTextBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_section_title_text_block_v2025_r0 import ( + HubSectionTitleTextBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_callout_box_text_block_v2025_r0 import ( + HubCalloutBoxTextBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_item_list_block_v2025_r0 import ( + HubItemListBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_divider_block_v2025_r0 import ( + HubDividerBlockV2025R0, +) + +from box_sdk_gen.schemas.v2025_r0.hub_document_block_entry_v2025_r0 import ( + HubDocumentBlockEntryV2025R0, +) + +from test.commons import get_default_client_with_user_subject + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import get_uuid + +client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + + +def testGetHubDocumentPagesAndBlocks(): + hub_title: str = get_uuid() + created_hub: HubV2025R0 = client.hubs.create_hub_v2025_r0(hub_title) + hub_id: str = created_hub.id + pages: HubDocumentPagesV2025R0 = ( + client.hub_document.get_hub_document_pages_v2025_r0(hub_id) + ) + assert len(pages.entries) > 0 + assert to_string(pages.type) == 'document_pages' + first_page: HubDocumentPageV2025R0 = pages.entries[0] + assert to_string(first_page.type) == 'page' + page_id: str = first_page.id + blocks: HubDocumentBlocksV2025R0 = ( + client.hub_document.get_hub_document_blocks_v2025_r0(hub_id, page_id) + ) + assert to_string(blocks.type) == 'document_blocks' + assert len(blocks.entries) > 0 + first_block: HubDocumentBlockEntryV2025R0 = blocks.entries[0] + assert to_string(first_block.type) == 'item_list' + client.hubs.delete_hub_by_id_v2025_r0(hub_id) From ed3ed0469dabb7ef7c34e7bd76b6d67332ad1028 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 1 Apr 2026 14:27:34 +0200 Subject: [PATCH 099/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1378) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 168441c6b..ccf30bdd2 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "e77f966", "specHash": "c8e3a85", "version": "10.5.0" } +{ "engineHash": "8a22201", "specHash": "f8e0d99", "version": "10.5.0" } From 29edee40523129bcfdcf74a158fe8adc01ddb367 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:20:03 +0200 Subject: [PATCH 100/139] chore: release version 10.6.0 (#1380) --- .codegen.json | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index ccf30bdd2..a5bd8423a 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "8a22201", "specHash": "f8e0d99", "version": "10.5.0" } +{ "engineHash": "8a22201", "specHash": "f8e0d99", "version": "10.6.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c9cd4265..5a695ba02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.6.0](https://github.com/box/box-python-sdk/compare/v10.5.0...v10.6.0) (2026-04-01) + + +### ⚠ BREAKING CHANGES + +* Correct search content type 'tags' to match API (box/box-openapi#591) (#1369) + +### Bug Fixes + +* Add missing discriminators for schema types (box/box-codegen[#931](https://github.com/box/box-python-sdk/issues/931)) ([#1368](https://github.com/box/box-python-sdk/issues/1368)) ([fe0a3b8](https://github.com/box/box-python-sdk/commit/fe0a3b86b292d7304c244e4b6933612bbce5f332)) +* Align `LegalHoldPolicyAssignedItem` with API (box/box-openapi[#1554](https://github.com/box/box-python-sdk/issues/1554)) (box/box-openapi[#590](https://github.com/box/box-python-sdk/issues/590)) ([#1365](https://github.com/box/box-python-sdk/issues/1365)) ([feb2010](https://github.com/box/box-python-sdk/commit/feb201068f851701ecd6f2dfea522182a33a6846)) +* Correct search content type 'tags' to match API (box/box-openapi[#591](https://github.com/box/box-python-sdk/issues/591)) ([#1369](https://github.com/box/box-python-sdk/issues/1369)) ([39fbc7c](https://github.com/box/box-python-sdk/commit/39fbc7cd88a3e0a6a002cd2aa202d10730c6d91a)) + + +### New Features and Enhancements + +* Add `include_reference` to `AI` and `parent_id` to `HubsItems` (box/box-openapi[#588](https://github.com/box/box-python-sdk/issues/588)) ([#1360](https://github.com/box/box-python-sdk/issues/1360)) ([59c1d3c](https://github.com/box/box-python-sdk/commit/59c1d3c156cc079aa1ad66f7bb2476d48c67a57c)) +* Support Hub Document API (box/box-codegen[#930](https://github.com/box/box-python-sdk/issues/930)) ([#1371](https://github.com/box/box-python-sdk/issues/1371)) ([720b948](https://github.com/box/box-python-sdk/commit/720b94835b1b5308d8c1b8b3a7bc176766d98a04)) + ## [10.5.0](https://github.com/box/box-python-sdk/compare/v10.4.0...v10.5.0) (2026-03-18) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 98d96db57..c8c9ced2c 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.5.0' +__version__ = '10.6.0' From e8f93ce4da1d2c68d809e4af38741ebf91bc6fe2 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:37:02 +0200 Subject: [PATCH 101/139] fix: Correct `retention_length` type hint to accept both integer and string (box/box-codegen#932) (#1382) --- .codegen.json | 2 +- box_sdk_gen/managers/retention_policies.py | 12 ++++++------ docs/retention_policies.md | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.codegen.json b/.codegen.json index a5bd8423a..b083491e5 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "8a22201", "specHash": "f8e0d99", "version": "10.6.0" } +{ "engineHash": "37c0986", "specHash": "f8e0d99", "version": "10.6.0" } diff --git a/box_sdk_gen/managers/retention_policies.py b/box_sdk_gen/managers/retention_policies.py index 9f5f5f723..bfd717c1e 100644 --- a/box_sdk_gen/managers/retention_policies.py +++ b/box_sdk_gen/managers/retention_policies.py @@ -10,12 +10,12 @@ from box_sdk_gen.serialization.json import deserialize +from typing import Union + from box_sdk_gen.serialization.json import serialize from box_sdk_gen.internal.null_value import NullValue -from typing import Union - from box_sdk_gen.networking.fetch_options import ResponseFormat from box_sdk_gen.schemas.retention_policies import RetentionPolicies @@ -154,7 +154,7 @@ def create_retention_policy( disposition_action: CreateRetentionPolicyDispositionAction, *, description: Optional[str] = None, - retention_length: Optional[str] = None, + retention_length: Optional[Union[str, int]] = None, retention_type: Optional[CreateRetentionPolicyRetentionType] = None, can_owner_extend_retention: Optional[bool] = None, are_owners_notified: Optional[bool] = None, @@ -186,7 +186,7 @@ def create_retention_policy( content. If the policy has a `policy_type` of `indefinite`, the `retention_length` will also be `indefinite`., defaults to None - :type retention_length: Optional[str], optional + :type retention_length: Optional[Union[str, int]], optional :param retention_type: Specifies the retention type: * `modifiable`: You can modify the retention policy. For example, @@ -301,7 +301,7 @@ def update_retention_policy_by_id( description: Union[Optional[str], NullValue] = None, disposition_action: Optional[str] = None, retention_type: Union[Optional[str], NullValue] = None, - retention_length: Optional[str] = None, + retention_length: Optional[Union[str, int]] = None, status: Union[Optional[str], NullValue] = None, can_owner_extend_retention: Union[Optional[bool], NullValue] = None, are_owners_notified: Union[Optional[bool], NullValue] = None, @@ -354,7 +354,7 @@ def update_retention_policy_by_id( content. If the policy has a `policy_type` of `indefinite`, the `retention_length` will also be `indefinite`., defaults to None - :type retention_length: Optional[str], optional + :type retention_length: Optional[Union[str, int]], optional :param status: Used to retire a retention policy. If not retiring a policy, do not include this parameter diff --git a/docs/retention_policies.md b/docs/retention_policies.md index d92ba85bb..aa34cefac 100644 --- a/docs/retention_policies.md +++ b/docs/retention_policies.md @@ -78,7 +78,7 @@ client.retention_policies.create_retention_policy( - The type of the retention policy. A retention policy type can either be `finite`, where a specific amount of time to retain the content is known upfront, or `indefinite`, where the amount of time to retain the content is still unknown. - disposition_action `CreateRetentionPolicyDispositionAction` - The disposition action of the retention policy. `permanently_delete` deletes the content retained by the policy permanently. `remove_retention` lifts retention policy from the content, allowing it to be deleted by users once the retention policy has expired. -- retention_length `Optional[str]` +- retention_length `Optional[Union[str, int]]` - The length of the retention policy. This value specifies the duration in days that the retention policy will be active for after being assigned to content. If the policy has a `policy_type` of `indefinite`, the `retention_length` will also be `indefinite`. - retention_type `Optional[CreateRetentionPolicyRetentionType]` - Specifies the retention type: _ `modifiable`: You can modify the retention policy. For example, you can add or remove folders, shorten or lengthen the policy duration, or delete the assignment. Use this type if your retention policy is not related to any regulatory purposes. _ `non_modifiable`: You can modify the retention policy only in a limited way: add a folder, lengthen the duration, retire the policy, change the disposition action or notification settings. You cannot perform other actions, such as deleting the assignment or shortening the policy duration. Use this type to ensure compliance with regulatory retention policies. @@ -156,7 +156,7 @@ client.retention_policies.update_retention_policy_by_id( - The disposition action of the retention policy. This action can be `permanently_delete`, which will cause the content retained by the policy to be permanently deleted, or `remove_retention`, which will lift the retention policy from the content, allowing it to be deleted by users, once the retention policy has expired. You can use `null` if you don't want to change `disposition_action`. - retention_type `Optional[str]` - Specifies the retention type: _ `modifiable`: You can modify the retention policy. For example, you can add or remove folders, shorten or lengthen the policy duration, or delete the assignment. Use this type if your retention policy is not related to any regulatory purposes. _ `non-modifiable`: You can modify the retention policy only in a limited way: add a folder, lengthen the duration, retire the policy, change the disposition action or notification settings. You cannot perform other actions, such as deleting the assignment or shortening the policy duration. Use this type to ensure compliance with regulatory retention policies. When updating a retention policy, you can use `non-modifiable` type only. You can convert a `modifiable` policy to `non-modifiable`, but not the other way around. -- retention_length `Optional[str]` +- retention_length `Optional[Union[str, int]]` - The length of the retention policy. This value specifies the duration in days that the retention policy will be active for after being assigned to content. If the policy has a `policy_type` of `indefinite`, the `retention_length` will also be `indefinite`. - status `Optional[str]` - Used to retire a retention policy. If not retiring a policy, do not include this parameter or set it to `null`. From 049f3aa8c2fc17cbbe67d012f8d98dadf6c41823 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:47:37 +0200 Subject: [PATCH 102/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1392) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index b083491e5..795ea28e3 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "37c0986", "specHash": "f8e0d99", "version": "10.6.0" } +{ "engineHash": "450fb53", "specHash": "f8e0d99", "version": "10.6.0" } From acbb3c629be44d0fd6d507f9b164f87439d6083d Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:25:45 +0200 Subject: [PATCH 103/139] feat: Add `max_extension_length` field to Retention Policies (box/box-openapi#593) (#1394) --- .codegen.json | 2 +- box_sdk_gen/managers/ai.py | 10 +++++----- box_sdk_gen/managers/retention_policies.py | 12 ++++++++++++ box_sdk_gen/managers/users.py | 5 ++--- box_sdk_gen/schemas/__init__.py | 4 ++++ box_sdk_gen/schemas/ai_extract_structured.py | 10 +++++----- box_sdk_gen/schemas/retention_policy.py | 12 ++++++++++++ ...etention_policy_max_extension_length_request.py | 14 ++++++++++++++ ...tention_policy_max_extension_length_response.py | 14 ++++++++++++++ box_sdk_gen/schemas/retention_policy_mini.py | 12 ++++++++++++ docs/ai.md | 4 ++-- docs/retention_policies.md | 2 ++ docs/users.md | 4 ++-- test/ai.py | 2 +- 14 files changed, 88 insertions(+), 19 deletions(-) create mode 100644 box_sdk_gen/schemas/retention_policy_max_extension_length_request.py create mode 100644 box_sdk_gen/schemas/retention_policy_max_extension_length_response.py diff --git a/.codegen.json b/.codegen.json index 795ea28e3..6002d4940 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "450fb53", "specHash": "f8e0d99", "version": "10.6.0" } +{ "engineHash": "450fb53", "specHash": "d0976fc", "version": "10.6.0" } diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index b50eb57c0..6a684a478 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -159,7 +159,7 @@ def __init__( class CreateAiExtractStructuredFieldsOptionsField(BaseObject): def __init__(self, key: str, **kwargs): """ - :param key: A unique identifier for the field. + :param key: A unique identifier for the option. :type key: str """ super().__init__(**kwargs) @@ -196,9 +196,9 @@ def __init__( :type display_name: Optional[str], optional :param prompt: The context about the key that may include how to find and format it., defaults to None :type prompt: Optional[str], optional - :param type: The type of the field. It include but is not limited to string, float, date, enum, and multiSelect., defaults to None + :param type: The type of the field. It can include but is not limited to `string`, `float`, `date`, `enum`, and `multiSelect`., defaults to None :type type: Optional[str], optional - :param options: A list of options for this field. This is most often used in combination with the enum and multiSelect field types., defaults to None + :param options: A list of options for this field. This is most often used in combination with the `enum` and `multiSelect` field types., defaults to None :type options: Optional[List[CreateAiExtractStructuredFieldsOptionsField]], optional """ super().__init__(**kwargs) @@ -425,9 +425,9 @@ def create_ai_extract_structured( *, metadata_template: Optional[CreateAiExtractStructuredMetadataTemplate] = None, fields: Optional[List[CreateAiExtractStructuredFields]] = None, + ai_agent: Optional[AiExtractStructuredAgent] = None, include_confidence_score: Optional[bool] = None, include_reference: Optional[bool] = None, - ai_agent: Optional[AiExtractStructuredAgent] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> AiExtractStructuredResponse: """ @@ -465,9 +465,9 @@ def create_ai_extract_structured( 'items': items, 'metadata_template': metadata_template, 'fields': fields, + 'ai_agent': ai_agent, 'include_confidence_score': include_confidence_score, 'include_reference': include_reference, - 'ai_agent': ai_agent, } headers_map: Dict[str, str] = prepare_params({**extra_headers}) response: FetchResponse = self.network_session.network_client.fetch( diff --git a/box_sdk_gen/managers/retention_policies.py b/box_sdk_gen/managers/retention_policies.py index bfd717c1e..5b05c1c94 100644 --- a/box_sdk_gen/managers/retention_policies.py +++ b/box_sdk_gen/managers/retention_policies.py @@ -18,12 +18,20 @@ from box_sdk_gen.networking.fetch_options import ResponseFormat +from box_sdk_gen.schemas.retention_policy_max_extension_length_request import ( + RetentionPolicyMaxExtensionLengthRequestEnum, +) + from box_sdk_gen.schemas.retention_policies import RetentionPolicies from box_sdk_gen.schemas.client_error import ClientError from box_sdk_gen.schemas.retention_policy import RetentionPolicy +from box_sdk_gen.schemas.retention_policy_max_extension_length_request import ( + RetentionPolicyMaxExtensionLengthRequest, +) + from box_sdk_gen.schemas.user_mini import UserMini from box_sdk_gen.schemas.user_base import UserBase @@ -157,6 +165,7 @@ def create_retention_policy( retention_length: Optional[Union[str, int]] = None, retention_type: Optional[CreateRetentionPolicyRetentionType] = None, can_owner_extend_retention: Optional[bool] = None, + max_extension_length: Optional[RetentionPolicyMaxExtensionLengthRequest] = None, are_owners_notified: Optional[bool] = None, custom_notification_recipients: Optional[List[UserMini]] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None @@ -225,6 +234,7 @@ def create_retention_policy( 'retention_length': retention_length, 'retention_type': retention_type, 'can_owner_extend_retention': can_owner_extend_retention, + 'max_extension_length': max_extension_length, 'are_owners_notified': are_owners_notified, 'custom_notification_recipients': custom_notification_recipients, } @@ -304,6 +314,7 @@ def update_retention_policy_by_id( retention_length: Optional[Union[str, int]] = None, status: Union[Optional[str], NullValue] = None, can_owner_extend_retention: Union[Optional[bool], NullValue] = None, + max_extension_length: Optional[RetentionPolicyMaxExtensionLengthRequest] = None, are_owners_notified: Union[Optional[bool], NullValue] = None, custom_notification_recipients: Union[ Optional[List[UserBase]], NullValue @@ -383,6 +394,7 @@ def update_retention_policy_by_id( 'retention_length': retention_length, 'status': status, 'can_owner_extend_retention': can_owner_extend_retention, + 'max_extension_length': max_extension_length, 'are_owners_notified': are_owners_notified, 'custom_notification_recipients': custom_notification_recipients, } diff --git a/box_sdk_gen/managers/users.py b/box_sdk_gen/managers/users.py index 69549f8cc..aa878aab1 100644 --- a/box_sdk_gen/managers/users.py +++ b/box_sdk_gen/managers/users.py @@ -655,7 +655,7 @@ def delete_user_by_id( """ Deletes a user. By default, this operation fails if the user - still owns any content, was recently active, or recently joined the enterprise from a free account. + still owns any content. To proceed, move their owned content first, or use the `force` parameter to delete @@ -669,8 +669,7 @@ def delete_user_by_id( :param notify: Whether the user will receive email notification of the deletion., defaults to None :type notify: Optional[bool], optional - :param force: Specifies whether to delete the user even if they still own files, - were recently active, or recently joined the enterprise from a free account., defaults to None + :param force: Specifies whether to delete the user even if they still own files., defaults to None :type force: Optional[bool], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index d43309f4d..e5dfb6bb8 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -250,6 +250,10 @@ from box_sdk_gen.schemas.retention_policy_base import * +from box_sdk_gen.schemas.retention_policy_max_extension_length_request import * + +from box_sdk_gen.schemas.retention_policy_max_extension_length_response import * + from box_sdk_gen.schemas.retention_policy_mini import * from box_sdk_gen.schemas.file_version_retention import * diff --git a/box_sdk_gen/schemas/ai_extract_structured.py b/box_sdk_gen/schemas/ai_extract_structured.py index a6bafd0ce..49f208fce 100644 --- a/box_sdk_gen/schemas/ai_extract_structured.py +++ b/box_sdk_gen/schemas/ai_extract_structured.py @@ -56,7 +56,7 @@ def __init__( class AiExtractStructuredFieldsOptionsField(BaseObject): def __init__(self, key: str, **kwargs): """ - :param key: A unique identifier for the field. + :param key: A unique identifier for the option. :type key: str """ super().__init__(**kwargs) @@ -93,9 +93,9 @@ def __init__( :type display_name: Optional[str], optional :param prompt: The context about the key that may include how to find and format it., defaults to None :type prompt: Optional[str], optional - :param type: The type of the field. It include but is not limited to string, float, date, enum, and multiSelect., defaults to None + :param type: The type of the field. It can include but is not limited to `string`, `float`, `date`, `enum`, and `multiSelect`., defaults to None :type type: Optional[str], optional - :param options: A list of options for this field. This is most often used in combination with the enum and multiSelect field types., defaults to None + :param options: A list of options for this field. This is most often used in combination with the `enum` and `multiSelect` field types., defaults to None :type options: Optional[List[AiExtractStructuredFieldsOptionsField]], optional """ super().__init__(**kwargs) @@ -114,9 +114,9 @@ def __init__( *, metadata_template: Optional[AiExtractStructuredMetadataTemplateField] = None, fields: Optional[List[AiExtractStructuredFieldsField]] = None, + ai_agent: Optional[AiExtractStructuredAgent] = None, include_confidence_score: Optional[bool] = None, include_reference: Optional[bool] = None, - ai_agent: Optional[AiExtractStructuredAgent] = None, **kwargs ): """ @@ -137,6 +137,6 @@ def __init__( self.items = items self.metadata_template = metadata_template self.fields = fields + self.ai_agent = ai_agent self.include_confidence_score = include_confidence_score self.include_reference = include_reference - self.ai_agent = ai_agent diff --git a/box_sdk_gen/schemas/retention_policy.py b/box_sdk_gen/schemas/retention_policy.py index 0b7b929ab..9cfba9bd8 100644 --- a/box_sdk_gen/schemas/retention_policy.py +++ b/box_sdk_gen/schemas/retention_policy.py @@ -14,6 +14,14 @@ RetentionPolicyMiniDispositionActionField, ) +from box_sdk_gen.schemas.retention_policy_max_extension_length_response import ( + RetentionPolicyMaxExtensionLengthResponseEnum, +) + +from box_sdk_gen.schemas.retention_policy_max_extension_length_response import ( + RetentionPolicyMaxExtensionLengthResponse, +) + from box_sdk_gen.schemas.retention_policy_mini import RetentionPolicyMini from box_sdk_gen.schemas.user_mini import UserMini @@ -82,6 +90,9 @@ def __init__( policy_name: Optional[str] = None, retention_length: Optional[str] = None, disposition_action: Optional[RetentionPolicyMiniDispositionActionField] = None, + max_extension_length: Optional[ + RetentionPolicyMaxExtensionLengthResponse + ] = None, type: RetentionPolicyBaseTypeField = RetentionPolicyBaseTypeField.RETENTION_POLICY, **kwargs ): @@ -159,6 +170,7 @@ def __init__( policy_name=policy_name, retention_length=retention_length, disposition_action=disposition_action, + max_extension_length=max_extension_length, type=type, **kwargs ) diff --git a/box_sdk_gen/schemas/retention_policy_max_extension_length_request.py b/box_sdk_gen/schemas/retention_policy_max_extension_length_request.py new file mode 100644 index 000000000..8684de41a --- /dev/null +++ b/box_sdk_gen/schemas/retention_policy_max_extension_length_request.py @@ -0,0 +1,14 @@ +from enum import Enum + +from typing import Union + +from box_sdk_gen.box.errors import BoxSDKError + + +class RetentionPolicyMaxExtensionLengthRequestEnum(str, Enum): + NONE = 'none' + + +RetentionPolicyMaxExtensionLengthRequest = Union[ + RetentionPolicyMaxExtensionLengthRequestEnum, str, int +] diff --git a/box_sdk_gen/schemas/retention_policy_max_extension_length_response.py b/box_sdk_gen/schemas/retention_policy_max_extension_length_response.py new file mode 100644 index 000000000..988d466a7 --- /dev/null +++ b/box_sdk_gen/schemas/retention_policy_max_extension_length_response.py @@ -0,0 +1,14 @@ +from enum import Enum + +from typing import Union + +from box_sdk_gen.box.errors import BoxSDKError + + +class RetentionPolicyMaxExtensionLengthResponseEnum(str, Enum): + NONE = 'none' + + +RetentionPolicyMaxExtensionLengthResponse = Union[ + RetentionPolicyMaxExtensionLengthResponseEnum, str +] diff --git a/box_sdk_gen/schemas/retention_policy_mini.py b/box_sdk_gen/schemas/retention_policy_mini.py index 1980630e4..8a0914fef 100644 --- a/box_sdk_gen/schemas/retention_policy_mini.py +++ b/box_sdk_gen/schemas/retention_policy_mini.py @@ -4,8 +4,16 @@ from box_sdk_gen.schemas.retention_policy_base import RetentionPolicyBaseTypeField +from box_sdk_gen.schemas.retention_policy_max_extension_length_response import ( + RetentionPolicyMaxExtensionLengthResponseEnum, +) + from box_sdk_gen.schemas.retention_policy_base import RetentionPolicyBase +from box_sdk_gen.schemas.retention_policy_max_extension_length_response import ( + RetentionPolicyMaxExtensionLengthResponse, +) + from box_sdk_gen.box.errors import BoxSDKError @@ -24,6 +32,9 @@ def __init__( policy_name: Optional[str] = None, retention_length: Optional[str] = None, disposition_action: Optional[RetentionPolicyMiniDispositionActionField] = None, + max_extension_length: Optional[ + RetentionPolicyMaxExtensionLengthResponse + ] = None, type: RetentionPolicyBaseTypeField = RetentionPolicyBaseTypeField.RETENTION_POLICY, **kwargs ): @@ -54,3 +65,4 @@ def __init__( self.policy_name = policy_name self.retention_length = retention_length self.disposition_action = disposition_action + self.max_extension_length = max_extension_length diff --git a/docs/ai.md b/docs/ai.md index 7e0e7b005..aabdf4acf 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -246,8 +246,8 @@ client.ai.create_ai_extract_structured( ], ), ], - include_confidence_score=True, ai_agent=ai_extract_structured_agent_basic_text_config, + include_confidence_score=True, ) ``` @@ -259,11 +259,11 @@ client.ai.create_ai_extract_structured( - The metadata template containing the fields to extract. For your request to work, you must provide either `metadata_template` or `fields`, but not both. - fields `Optional[List[CreateAiExtractStructuredFields]]` - The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both. +- ai_agent `Optional[AiExtractStructuredAgent]` - include_confidence_score `Optional[bool]` - A flag to indicate whether confidence scores for every extracted field should be returned. - include_reference `Optional[bool]` - A flag to indicate whether references for every extracted field should be returned. -- ai_agent `Optional[AiExtractStructuredAgent]` - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/retention_policies.md b/docs/retention_policies.md index aa34cefac..1211508d1 100644 --- a/docs/retention_policies.md +++ b/docs/retention_policies.md @@ -84,6 +84,7 @@ client.retention_policies.create_retention_policy( - Specifies the retention type: _ `modifiable`: You can modify the retention policy. For example, you can add or remove folders, shorten or lengthen the policy duration, or delete the assignment. Use this type if your retention policy is not related to any regulatory purposes. _ `non_modifiable`: You can modify the retention policy only in a limited way: add a folder, lengthen the duration, retire the policy, change the disposition action or notification settings. You cannot perform other actions, such as deleting the assignment or shortening the policy duration. Use this type to ensure compliance with regulatory retention policies. - can_owner_extend_retention `Optional[bool]` - Whether the owner of a file will be allowed to extend the retention. +- max_extension_length `Optional[RetentionPolicyMaxExtensionLengthRequest]` - are_owners_notified `Optional[bool]` - Whether owner and co-owners of a file are notified when the policy nears expiration. - custom_notification_recipients `Optional[List[UserMini]]` @@ -162,6 +163,7 @@ client.retention_policies.update_retention_policy_by_id( - Used to retire a retention policy. If not retiring a policy, do not include this parameter or set it to `null`. - can_owner_extend_retention `Optional[bool]` - Determines if the owner of items under the policy can extend the retention when the original retention duration is about to end. +- max_extension_length `Optional[RetentionPolicyMaxExtensionLengthRequest]` - are_owners_notified `Optional[bool]` - Determines if owners and co-owners of items under the policy are notified when the retention duration is about to end. - custom_notification_recipients `Optional[List[UserBase]]` diff --git a/docs/users.md b/docs/users.md index 725d9d912..980e5d876 100644 --- a/docs/users.md +++ b/docs/users.md @@ -279,7 +279,7 @@ Returns the updated user object. ## Delete user Deletes a user. By default, this operation fails if the user -still owns any content, was recently active, or recently joined the enterprise from a free account. +still owns any content. To proceed, move their owned content first, or use the `force` parameter to delete the user and their files. @@ -301,7 +301,7 @@ client.users.delete_user_by_id(user.id) - notify `Optional[bool]` - Whether the user will receive email notification of the deletion. - force `Optional[bool]` - - Specifies whether to delete the user even if they still own files, were recently active, or recently joined the enterprise from a free account. + - Specifies whether to delete the user even if they still own files. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/test/ai.py b/test/ai.py index 00e1100ca..43fc08459 100644 --- a/test/ai.py +++ b/test/ai.py @@ -305,8 +305,8 @@ def testAIExtractStructuredWithFields(): ], ), ], - include_confidence_score=True, ai_agent=ai_extract_structured_agent_basic_text_config, + include_confidence_score=True, ) assert not response.confidence_score == None assert to_string(response.answer.get('hobby')) == to_string(['guitar']) From 8987a698d8f6c7a8234a19ed018795193aec5028 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:33:54 +0200 Subject: [PATCH 104/139] chore: release version 10.7.0 (#1396) --- .codegen.json | 2 +- CHANGELOG.md | 12 ++++++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 6002d4940..f83b5da42 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "450fb53", "specHash": "d0976fc", "version": "10.6.0" } +{ "engineHash": "450fb53", "specHash": "d0976fc", "version": "10.7.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a695ba02..88fa10b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.7.0](https://github.com/box/box-python-sdk/compare/v10.6.0...v10.7.0) (2026-04-22) + + +### Bug Fixes + +* Correct `retention_length` type hint to accept both integer and string (box/box-codegen[#932](https://github.com/box/box-python-sdk/issues/932)) ([#1382](https://github.com/box/box-python-sdk/issues/1382)) ([e8f93ce](https://github.com/box/box-python-sdk/commit/e8f93ce4da1d2c68d809e4af38741ebf91bc6fe2)) + + +### New Features and Enhancements + +* Add `max_extension_length` field to Retention Policies (box/box-openapi[#593](https://github.com/box/box-python-sdk/issues/593)) ([#1394](https://github.com/box/box-python-sdk/issues/1394)) ([acbb3c6](https://github.com/box/box-python-sdk/commit/acbb3c629be44d0fd6d507f9b164f87439d6083d)) + ## [10.6.0](https://github.com/box/box-python-sdk/compare/v10.5.0...v10.6.0) (2026-04-01) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index c8c9ced2c..bdaac5d60 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.6.0' +__version__ = '10.7.0' From 5020a01dce0de7399c3f4d7ae9765f2fcea86cfe Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:36:14 +0200 Subject: [PATCH 105/139] docs: add documentation about default timeout (box/box-codegen#944) (#1402) --- .codegen.json | 2 +- docs/configuration.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index f83b5da42..fe226eafa 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "450fb53", "specHash": "d0976fc", "version": "10.7.0" } +{ "engineHash": "18868e7", "specHash": "d0976fc", "version": "10.7.0" } diff --git a/docs/configuration.md b/docs/configuration.md index 6e9500871..9a0ae0b9e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -211,7 +211,7 @@ client = BoxClient(auth=auth, network_session=network_session) How timeout handling works: - Timeout values are configured in milliseconds and converted to seconds internally for HTTP requests. -- The SDK uses default timeouts when timeout config is not provided: `connection_timeout_ms=5000` and `read_timeout_ms=60000`. +- If timeout config is not provided, the SDK uses default timeouts: `connection_timeout_ms=5000` (5 seconds) and `read_timeout_ms=60000` (60 seconds). - To disable all SDK timeouts, pass `TimeoutConfig(connection_timeout_ms=None, read_timeout_ms=None)` explicitly to `NetworkSession`. - You can also disable only one timeout by setting one value to `None` (for example, `connection_timeout_ms=None` or `read_timeout_ms=None`). If you provide only the other value (for example, `read_timeout_ms=30000`) and leave one unspecified, the unspecified field remains `None` and that timeout stays disabled. - Timeout failures are treated as network exceptions, and retry behavior is controlled by the configured retry strategy. From 3cfb35c6a86062658588afd779410bb426472427 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:54:59 +0200 Subject: [PATCH 106/139] feat: Automate Public API (box/box-openapi#594) (#1409) --- .codegen.json | 2 +- box_sdk_gen/client.py | 5 + box_sdk_gen/managers/__init__.py | 2 + box_sdk_gen/managers/automate_workflows.py | 175 ++++++++++++++++++ box_sdk_gen/parameters/__init__.py | 2 + box_sdk_gen/parameters/v2026_r0/__init__.py | 1 + .../v2026_r0/box_version_header_v2026_r0.py | 7 + box_sdk_gen/schemas/__init__.py | 2 + box_sdk_gen/schemas/v2026_r0/__init__.py | 13 ++ .../automate_workflow_action_v2026_r0.py | 66 +++++++ .../automate_workflow_reference_v2026_r0.py | 36 ++++ ...utomate_workflow_start_request_v2026_r0.py | 18 ++ .../v2026_r0/automate_workflows_v2026_r0.py | 36 ++++ .../schemas/v2026_r0/client_error_v2026_r0.py | 77 ++++++++ .../schemas/v2026_r0/user_base_v2026_r0.py | 30 +++ .../schemas/v2026_r0/user_mini_v2026_r0.py | 34 ++++ docs/README.md | 1 + docs/automate_workflows.md | 65 +++++++ 18 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 box_sdk_gen/managers/automate_workflows.py create mode 100644 box_sdk_gen/parameters/v2026_r0/__init__.py create mode 100644 box_sdk_gen/parameters/v2026_r0/box_version_header_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/__init__.py create mode 100644 box_sdk_gen/schemas/v2026_r0/automate_workflow_action_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/automate_workflow_reference_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/automate_workflow_start_request_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/automate_workflows_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/client_error_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/user_base_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/user_mini_v2026_r0.py create mode 100644 docs/automate_workflows.md diff --git a/.codegen.json b/.codegen.json index fe226eafa..a013a6923 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "18868e7", "specHash": "d0976fc", "version": "10.7.0" } +{ "engineHash": "18868e7", "specHash": "f899bf6", "version": "10.7.0" } diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py index 060a6249b..7e807a9d7 100644 --- a/box_sdk_gen/client.py +++ b/box_sdk_gen/client.py @@ -192,6 +192,8 @@ from box_sdk_gen.managers.external_users import ExternalUsersManager +from box_sdk_gen.managers.automate_workflows import AutomateWorkflowsManager + from box_sdk_gen.networking.auth import Authentication from box_sdk_gen.networking.network import NetworkSession @@ -464,6 +466,9 @@ def __init__(self, auth: Authentication, *, network_session: NetworkSession = No self.external_users = ExternalUsersManager( auth=self.auth, network_session=self.network_session ) + self.automate_workflows = AutomateWorkflowsManager( + auth=self.auth, network_session=self.network_session + ) def make_request(self, fetch_options: FetchOptions) -> FetchResponse: """ diff --git a/box_sdk_gen/managers/__init__.py b/box_sdk_gen/managers/__init__.py index fa6fecc26..84b4a75ef 100644 --- a/box_sdk_gen/managers/__init__.py +++ b/box_sdk_gen/managers/__init__.py @@ -163,3 +163,5 @@ from box_sdk_gen.managers.archives import * from box_sdk_gen.managers.external_users import * + +from box_sdk_gen.managers.automate_workflows import * diff --git a/box_sdk_gen/managers/automate_workflows.py b/box_sdk_gen/managers/automate_workflows.py new file mode 100644 index 000000000..4ce3592d6 --- /dev/null +++ b/box_sdk_gen/managers/automate_workflows.py @@ -0,0 +1,175 @@ +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import deserialize + +from typing import List + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2026_r0.automate_workflows_v2026_r0 import ( + AutomateWorkflowsV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.client_error_v2026_r0 import ClientErrorV2026R0 + +from box_sdk_gen.parameters.v2026_r0.box_version_header_v2026_r0 import ( + BoxVersionHeaderV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.automate_workflow_start_request_v2026_r0 import ( + AutomateWorkflowStartRequestV2026R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class AutomateWorkflowsManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def get_automate_workflows_v2026_r0( + self, + folder_id: str, + *, + limit: Optional[int] = None, + marker: Optional[str] = None, + box_version: BoxVersionHeaderV2026R0 = BoxVersionHeaderV2026R0._2026_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> AutomateWorkflowsV2026R0: + """ + Returns workflow actions from Automate for a folder, using the + + `WORKFLOW` action category. + + :param folder_id: The unique identifier that represent a folder. + + The ID for any folder can be determined + by visiting this folder in the web application + and copying the ID from the URL. For example, + for the URL `https://*.app.box.com/folder/123` + the `folder_id` is `123`. + + The root folder of a Box account is + always represented by the ID `0`. + :type folder_id: str + :param limit: The maximum number of items to return per page., defaults to None + :type limit: Optional[int], optional + :param marker: Defines the position marker at which to begin returning results. This is + used when paginating using marker-based pagination., defaults to None + :type marker: Optional[str], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2026R0._2026_0 + :type box_version: BoxVersionHeaderV2026R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + query_params_map: Dict[str, str] = prepare_params( + { + 'folder_id': to_string(folder_id), + 'limit': to_string(limit), + 'marker': to_string(marker), + } + ) + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/automate_workflows'] + ), + method='GET', + params=query_params_map, + headers=headers_map, + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, AutomateWorkflowsV2026R0) + + def create_automate_workflow_start_v2026_r0( + self, + workflow_id: str, + workflow_action_id: str, + file_ids: List[str], + *, + box_version: BoxVersionHeaderV2026R0 = BoxVersionHeaderV2026R0._2026_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> None: + """ + Starts an Automate workflow manually by using a workflow action ID and file IDs. + :param workflow_id: The ID of the workflow. + Example: "12345" + :type workflow_id: str + :param workflow_action_id: The callable action ID used to trigger the selected workflow. + :type workflow_action_id: str + :param file_ids: The files to process with the selected workflow. + :type file_ids: List[str] + :param box_version: Version header., defaults to BoxVersionHeaderV2026R0._2026_0 + :type box_version: BoxVersionHeaderV2026R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'workflow_action_id': workflow_action_id, + 'file_ids': file_ids, + } + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [ + self.network_session.base_urls.base_url, + '/2.0/automate_workflows/', + to_string(workflow_id), + '/start', + ] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.NO_CONTENT, + auth=self.auth, + network_session=self.network_session, + ) + ) + return None diff --git a/box_sdk_gen/parameters/__init__.py b/box_sdk_gen/parameters/__init__.py index 6ee9acb7e..1493eab0a 100644 --- a/box_sdk_gen/parameters/__init__.py +++ b/box_sdk_gen/parameters/__init__.py @@ -1 +1,3 @@ from box_sdk_gen.parameters.v2025_r0 import * + +from box_sdk_gen.parameters.v2026_r0 import * diff --git a/box_sdk_gen/parameters/v2026_r0/__init__.py b/box_sdk_gen/parameters/v2026_r0/__init__.py new file mode 100644 index 000000000..068a08d4b --- /dev/null +++ b/box_sdk_gen/parameters/v2026_r0/__init__.py @@ -0,0 +1 @@ +from box_sdk_gen.parameters.v2026_r0.box_version_header_v2026_r0 import * diff --git a/box_sdk_gen/parameters/v2026_r0/box_version_header_v2026_r0.py b/box_sdk_gen/parameters/v2026_r0/box_version_header_v2026_r0.py new file mode 100644 index 000000000..956895ee3 --- /dev/null +++ b/box_sdk_gen/parameters/v2026_r0/box_version_header_v2026_r0.py @@ -0,0 +1,7 @@ +from enum import Enum + +from box_sdk_gen.box.errors import BoxSDKError + + +class BoxVersionHeaderV2026R0(str, Enum): + _2026_0 = '2026.0' diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index e5dfb6bb8..668de48cf 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -591,3 +591,5 @@ from box_sdk_gen.schemas.zip_download_status import * from box_sdk_gen.schemas.v2025_r0 import * + +from box_sdk_gen.schemas.v2026_r0 import * diff --git a/box_sdk_gen/schemas/v2026_r0/__init__.py b/box_sdk_gen/schemas/v2026_r0/__init__.py new file mode 100644 index 000000000..6b48bc6d9 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/__init__.py @@ -0,0 +1,13 @@ +from box_sdk_gen.schemas.v2026_r0.automate_workflow_reference_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.automate_workflow_start_request_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.client_error_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.user_base_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.user_mini_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.automate_workflow_action_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.automate_workflows_v2026_r0 import * diff --git a/box_sdk_gen/schemas/v2026_r0/automate_workflow_action_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/automate_workflow_action_v2026_r0.py new file mode 100644 index 000000000..25cfb5da1 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/automate_workflow_action_v2026_r0.py @@ -0,0 +1,66 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2026_r0.user_mini_v2026_r0 import UserMiniV2026R0 + +from box_sdk_gen.schemas.v2026_r0.automate_workflow_reference_v2026_r0 import ( + AutomateWorkflowReferenceV2026R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.internal.utils import DateTime + + +class AutomateWorkflowActionV2026R0TypeField(str, Enum): + WORKFLOW_ACTION = 'workflow_action' + + +class AutomateWorkflowActionV2026R0ActionTypeField(str, Enum): + RUN_WORKFLOW = 'run_workflow' + + +class AutomateWorkflowActionV2026R0(BaseObject): + _discriminator = 'type', {'workflow_action'} + + def __init__( + self, + id: str, + workflow: AutomateWorkflowReferenceV2026R0, + *, + type: AutomateWorkflowActionV2026R0TypeField = AutomateWorkflowActionV2026R0TypeField.WORKFLOW_ACTION, + action_type: AutomateWorkflowActionV2026R0ActionTypeField = AutomateWorkflowActionV2026R0ActionTypeField.RUN_WORKFLOW, + description: Optional[str] = None, + created_at: Optional[DateTime] = None, + updated_at: Optional[DateTime] = None, + created_by: Optional[UserMiniV2026R0] = None, + updated_by: Optional[UserMiniV2026R0] = None, + **kwargs + ): + """ + :param id: The identifier for the Automate action. + :type id: str + :param type: The object type for this workflow action wrapper., defaults to AutomateWorkflowActionV2026R0TypeField.WORKFLOW_ACTION + :type type: AutomateWorkflowActionV2026R0TypeField, optional + :param action_type: The type that defines the behavior of this action., defaults to AutomateWorkflowActionV2026R0ActionTypeField.RUN_WORKFLOW + :type action_type: AutomateWorkflowActionV2026R0ActionTypeField, optional + :param description: A human-readable description of the workflow action., defaults to None + :type description: Optional[str], optional + :param created_at: The date and time when the action was created., defaults to None + :type created_at: Optional[DateTime], optional + :param updated_at: The date and time when the action was last updated., defaults to None + :type updated_at: Optional[DateTime], optional + """ + super().__init__(**kwargs) + self.id = id + self.workflow = workflow + self.type = type + self.action_type = action_type + self.description = description + self.created_at = created_at + self.updated_at = updated_at + self.created_by = created_by + self.updated_by = updated_by diff --git a/box_sdk_gen/schemas/v2026_r0/automate_workflow_reference_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/automate_workflow_reference_v2026_r0.py new file mode 100644 index 000000000..c943591ba --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/automate_workflow_reference_v2026_r0.py @@ -0,0 +1,36 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AutomateWorkflowReferenceV2026R0TypeField(str, Enum): + WORKFLOW = 'workflow' + + +class AutomateWorkflowReferenceV2026R0(BaseObject): + _discriminator = 'type', {'workflow'} + + def __init__( + self, + id: str, + *, + type: AutomateWorkflowReferenceV2026R0TypeField = AutomateWorkflowReferenceV2026R0TypeField.WORKFLOW, + name: Optional[str] = None, + **kwargs + ): + """ + :param id: The identifier for the Automate workflow instance. + :type id: str + :param type: The object type., defaults to AutomateWorkflowReferenceV2026R0TypeField.WORKFLOW + :type type: AutomateWorkflowReferenceV2026R0TypeField, optional + :param name: The display name for the Automate workflow., defaults to None + :type name: Optional[str], optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.name = name diff --git a/box_sdk_gen/schemas/v2026_r0/automate_workflow_start_request_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/automate_workflow_start_request_v2026_r0.py new file mode 100644 index 000000000..6e26f2db6 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/automate_workflow_start_request_v2026_r0.py @@ -0,0 +1,18 @@ +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AutomateWorkflowStartRequestV2026R0(BaseObject): + def __init__(self, workflow_action_id: str, file_ids: List[str], **kwargs): + """ + :param workflow_action_id: The callable action ID used to trigger the selected workflow. + :type workflow_action_id: str + :param file_ids: The files to process with the selected workflow. + :type file_ids: List[str] + """ + super().__init__(**kwargs) + self.workflow_action_id = workflow_action_id + self.file_ids = file_ids diff --git a/box_sdk_gen/schemas/v2026_r0/automate_workflows_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/automate_workflows_v2026_r0.py new file mode 100644 index 000000000..5e67551a7 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/automate_workflows_v2026_r0.py @@ -0,0 +1,36 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2026_r0.automate_workflow_action_v2026_r0 import ( + AutomateWorkflowActionV2026R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class AutomateWorkflowsV2026R0(BaseObject): + def __init__( + self, + *, + entries: Optional[List[AutomateWorkflowActionV2026R0]] = None, + limit: Optional[int] = None, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: Workflow actions available for manual start., defaults to None + :type entries: Optional[List[AutomateWorkflowActionV2026R0]], optional + :param limit: The limit that was used for these entries. This will be the same as the + `limit` query parameter unless that value exceeded the maximum value + allowed. The maximum value varies by API., defaults to None + :type limit: Optional[int], optional + :param next_marker: The marker for the start of the next page of results., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/v2026_r0/client_error_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/client_error_v2026_r0.py new file mode 100644 index 000000000..7d905776a --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/client_error_v2026_r0.py @@ -0,0 +1,77 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class ClientErrorV2026R0TypeField(str, Enum): + ERROR = 'error' + + +class ClientErrorV2026R0CodeField(str, Enum): + CREATED = 'created' + ACCEPTED = 'accepted' + NO_CONTENT = 'no_content' + REDIRECT = 'redirect' + NOT_MODIFIED = 'not_modified' + BAD_REQUEST = 'bad_request' + UNAUTHORIZED = 'unauthorized' + FORBIDDEN = 'forbidden' + NOT_FOUND = 'not_found' + METHOD_NOT_ALLOWED = 'method_not_allowed' + CONFLICT = 'conflict' + PRECONDITION_FAILED = 'precondition_failed' + TOO_MANY_REQUESTS = 'too_many_requests' + INTERNAL_SERVER_ERROR = 'internal_server_error' + UNAVAILABLE = 'unavailable' + ITEM_NAME_INVALID = 'item_name_invalid' + INSUFFICIENT_SCOPE = 'insufficient_scope' + + +class ClientErrorV2026R0(BaseObject): + _discriminator = 'type', {'error'} + + def __init__( + self, + *, + type: Optional[ClientErrorV2026R0TypeField] = None, + status: Optional[int] = None, + code: Optional[ClientErrorV2026R0CodeField] = None, + message: Optional[str] = None, + context_info: Optional[Dict] = None, + help_url: Optional[str] = None, + request_id: Optional[str] = None, + **kwargs + ): + """ + :param type: The value will always be `error`., defaults to None + :type type: Optional[ClientErrorV2026R0TypeField], optional + :param status: The HTTP status of the response., defaults to None + :type status: Optional[int], optional + :param code: A Box-specific error code., defaults to None + :type code: Optional[ClientErrorV2026R0CodeField], optional + :param message: A short message describing the error., defaults to None + :type message: Optional[str], optional + :param context_info: A free-form object that contains additional context + about the error. The possible fields are defined on + a per-endpoint basis. `message` is only one example., defaults to None + :type context_info: Optional[Dict], optional + :param help_url: A URL that links to more information about why this error occurred., defaults to None + :type help_url: Optional[str], optional + :param request_id: A unique identifier for this response, which can be used + when contacting Box support., defaults to None + :type request_id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.status = status + self.code = code + self.message = message + self.context_info = context_info + self.help_url = help_url + self.request_id = request_id diff --git a/box_sdk_gen/schemas/v2026_r0/user_base_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/user_base_v2026_r0.py new file mode 100644 index 000000000..33248225f --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/user_base_v2026_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserBaseV2026R0TypeField(str, Enum): + USER = 'user' + + +class UserBaseV2026R0(BaseObject): + _discriminator = 'type', {'user'} + + def __init__( + self, + id: str, + *, + type: UserBaseV2026R0TypeField = UserBaseV2026R0TypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param type: The value will always be `user`., defaults to UserBaseV2026R0TypeField.USER + :type type: UserBaseV2026R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2026_r0/user_mini_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/user_mini_v2026_r0.py new file mode 100644 index 000000000..d6d68d65f --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/user_mini_v2026_r0.py @@ -0,0 +1,34 @@ +from typing import Optional + +from box_sdk_gen.schemas.v2026_r0.user_base_v2026_r0 import UserBaseV2026R0TypeField + +from box_sdk_gen.schemas.v2026_r0.user_base_v2026_r0 import UserBaseV2026R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class UserMiniV2026R0(UserBaseV2026R0): + _discriminator = 'type', {'user'} + + def __init__( + self, + id: str, + *, + name: Optional[str] = None, + login: Optional[str] = None, + type: UserBaseV2026R0TypeField = UserBaseV2026R0TypeField.USER, + **kwargs + ): + """ + :param id: The unique identifier for this user. + :type id: str + :param name: The display name of this user., defaults to None + :type name: Optional[str], optional + :param login: The primary email address of this user., defaults to None + :type login: Optional[str], optional + :param type: The value will always be `user`., defaults to UserBaseV2026R0TypeField.USER + :type type: UserBaseV2026R0TypeField, optional + """ + super().__init__(id=id, type=type, **kwargs) + self.name = name + self.login = login diff --git a/docs/README.md b/docs/README.md index a97a2ce83..16e8f70b2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,7 @@ the SDK are available by topic: - [App item associations](app_item_associations.md) - [Archives](archives.md) - [Authorization](authorization.md) +- [Automate workflows](automate_workflows.md) - [Avatars](avatars.md) - [Chunked uploads](chunked_uploads.md) - [Classifications](classifications.md) diff --git a/docs/automate_workflows.md b/docs/automate_workflows.md new file mode 100644 index 000000000..2931823d1 --- /dev/null +++ b/docs/automate_workflows.md @@ -0,0 +1,65 @@ +# AutomateWorkflowsManager + +- [List Automate workflows defined as callable actions](#list-automate-workflows-defined-as-callable-actions) +- [Start Automate workflow](#start-automate-workflow) + +## List Automate workflows defined as callable actions + +Returns workflow actions from Automate for a folder, using the +`WORKFLOW` action category. + +This operation is performed by calling function `get_automate_workflows_v2026_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2026.0/get-automate-workflows/). + +_Currently we don't have an example for calling `get_automate_workflows_v2026_r0` in integration tests_ + +### Arguments + +- folder_id `str` + - The unique identifier that represent a folder. The ID for any folder can be determined by visiting this folder in the web application and copying the ID from the URL. For example, for the URL `https://*.app.box.com/folder/123` the `folder_id` is `123`. The root folder of a Box account is always represented by the ID `0`. +- limit `Optional[int]` + - The maximum number of items to return per page. +- marker `Optional[str]` + - Defines the position marker at which to begin returning results. This is used when paginating using marker-based pagination. +- box_version `BoxVersionHeaderV2026R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `AutomateWorkflowsV2026R0`. + +Returns workflow actions that can be manually started. + +## Start Automate workflow + +Starts an Automate workflow manually by using a workflow action ID and file IDs. + +This operation is performed by calling function `create_automate_workflow_start_v2026_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2026.0/post-automate-workflows-id-start/). + +_Currently we don't have an example for calling `create_automate_workflow_start_v2026_r0` in integration tests_ + +### Arguments + +- workflow_id `str` + - The ID of the workflow. Example: "12345" +- workflow_action_id `str` + - The callable action ID used to trigger the selected workflow. +- file_ids `List[str]` + - The files to process with the selected workflow. +- box_version `BoxVersionHeaderV2026R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `None`. + +Starts the workflow. From d21cc8cd4e9313b6920f195dc0805da4a2c35b90 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:58:41 +0200 Subject: [PATCH 107/139] test: Add tests for automate api (box/box-codegen#943) (#1411) --- .codegen.json | 2 +- .github/workflows/build-and-test-daily.yml | 2 + .github/workflows/build.yml | 1 + docs/automate_workflows.md | 14 ++++- test/automate_workflows.py | 60 ++++++++++++++++++++++ tox.ini | 4 +- 6 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 test/automate_workflows.py diff --git a/.codegen.json b/.codegen.json index a013a6923..c4b22db09 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "18868e7", "specHash": "f899bf6", "version": "10.7.0" } +{ "engineHash": "c571efa", "specHash": "f899bf6", "version": "10.7.0" } diff --git a/.github/workflows/build-and-test-daily.yml b/.github/workflows/build-and-test-daily.yml index 7ed858860..c8dbdec2c 100644 --- a/.github/workflows/build-and-test-daily.yml +++ b/.github/workflows/build-and-test-daily.yml @@ -49,6 +49,7 @@ jobs: SLACK_AUTOMATION_USER_ID: ${{ secrets.SLACK_AUTOMATION_USER_ID }} SLACK_ORG_ID: ${{ secrets.SLACK_ORG_ID }} SLACK_PARTNER_ITEM_ID: ${{ secrets.SLACK_PARTNER_ITEM_ID }} + AUTOMATE_WORKFLOW_FOLDER_ID: ${{ secrets.AUTOMATE_WORKFLOW_FOLDER_ID }} run: | tox coverage: @@ -90,6 +91,7 @@ jobs: SLACK_AUTOMATION_USER_ID: ${{ secrets.SLACK_AUTOMATION_USER_ID }} SLACK_ORG_ID: ${{ secrets.SLACK_ORG_ID }} SLACK_PARTNER_ITEM_ID: ${{ secrets.SLACK_PARTNER_ITEM_ID }} + AUTOMATE_WORKFLOW_FOLDER_ID: ${{ secrets.AUTOMATE_WORKFLOW_FOLDER_ID }} notify: name: Send Slack Notification runs-on: ubuntu-latest diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 68c075193..edc7f12e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,6 +48,7 @@ jobs: SLACK_AUTOMATION_USER_ID: ${{ secrets.SLACK_AUTOMATION_USER_ID }} SLACK_ORG_ID: ${{ secrets.SLACK_ORG_ID }} SLACK_PARTNER_ITEM_ID: ${{ secrets.SLACK_PARTNER_ITEM_ID }} + AUTOMATE_WORKFLOW_FOLDER_ID: ${{ secrets.AUTOMATE_WORKFLOW_FOLDER_ID }} run: | tox - name: Smoke Tests diff --git a/docs/automate_workflows.md b/docs/automate_workflows.md index 2931823d1..c857811e3 100644 --- a/docs/automate_workflows.md +++ b/docs/automate_workflows.md @@ -13,7 +13,11 @@ This operation is performed by calling function `get_automate_workflows_v2026_r0 See the endpoint docs at [API Reference](https://developer.box.com/reference/v2026.0/get-automate-workflows/). -_Currently we don't have an example for calling `get_automate_workflows_v2026_r0` in integration tests_ + + +```python +admin_client.automate_workflows.get_automate_workflows_v2026_r0(workflow_folder_id) +``` ### Arguments @@ -43,7 +47,13 @@ This operation is performed by calling function `create_automate_workflow_start_ See the endpoint docs at [API Reference](https://developer.box.com/reference/v2026.0/post-automate-workflows-id-start/). -_Currently we don't have an example for calling `create_automate_workflow_start_v2026_r0` in integration tests_ + + +```python +admin_client.automate_workflows.create_automate_workflow_start_v2026_r0( + workflow_action.workflow.id, workflow_action.id, [workflow_file_id] +) +``` ### Arguments diff --git a/test/automate_workflows.py b/test/automate_workflows.py new file mode 100644 index 000000000..93c1c6e18 --- /dev/null +++ b/test/automate_workflows.py @@ -0,0 +1,60 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.files import Files + +from box_sdk_gen.managers.uploads import UploadFileAttributes + +from box_sdk_gen.managers.uploads import UploadFileAttributesParentField + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.v2026_r0.automate_workflows_v2026_r0 import ( + AutomateWorkflowsV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.automate_workflow_action_v2026_r0 import ( + AutomateWorkflowActionV2026R0, +) + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.internal.utils import generate_byte_stream + +from test.commons import get_default_client + +from test.commons import get_default_client_with_user_subject + +client: BoxClient = get_default_client() + + +def testAutomateWorkflows(): + admin_client: BoxClient = get_default_client_with_user_subject( + get_env_var('USER_ID') + ) + workflow_folder_id: str = get_env_var('AUTOMATE_WORKFLOW_FOLDER_ID') + uploaded_files: Files = admin_client.uploads.upload_file( + UploadFileAttributes( + name=get_uuid(), + parent=UploadFileAttributesParentField(id=workflow_folder_id), + ), + generate_byte_stream(1024 * 1024), + ) + file: FileFull = uploaded_files.entries[0] + workflow_file_id: str = file.id + automate_workflows: AutomateWorkflowsV2026R0 = ( + admin_client.automate_workflows.get_automate_workflows_v2026_r0( + workflow_folder_id + ) + ) + assert len(automate_workflows.entries) == 1 + workflow_action: AutomateWorkflowActionV2026R0 = automate_workflows.entries[0] + assert to_string(workflow_action.type) == 'workflow_action' + assert to_string(workflow_action.action_type) == 'run_workflow' + assert to_string(workflow_action.workflow.type) == 'workflow' + admin_client.automate_workflows.create_automate_workflow_start_v2026_r0( + workflow_action.workflow.id, workflow_action.id, [workflow_file_id] + ) diff --git a/tox.ini b/tox.ini index 6e05d509f..c4b5612d7 100644 --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,7 @@ commands = pytest -n 5 {posargs} --disable-pytest-warnings --reruns 2 deps = -rrequirements-test.txt allowlist_externals = pytest -passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,BOX_EXTERNAL_USER_ID,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID +passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,BOX_EXTERNAL_USER_ID,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID,AUTOMATE_WORKFLOW_FOLDER_ID [testenv:pycodestyle] commands = @@ -47,7 +47,7 @@ commands = deps = coverage -rrequirements-test.txt -passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,BOX_EXTERNAL_USER_ID,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID +passenv = JWT_CONFIG_BASE_64,ADMIN_USER_ID,CLIENT_ID,CLIENT_SECRET,USER_ID,ENTERPRISE_ID,BOX_FILE_REQUEST_ID,BOX_EXTERNAL_USER_EMAIL,BOX_EXTERNAL_USER_ID,WORKFLOW_FOLDER_ID,APP_ITEM_ASSOCIATION_FILE_ID,APP_ITEM_ASSOCIATION_FOLDER_ID,APP_ITEM_SHARED_LINK,SLACK_AUTOMATION_USER_ID,SLACK_ORG_ID,SLACK_PARTNER_ITEM_ID,AUTOMATE_WORKFLOW_FOLDER_ID [testenv:py311-build] description = Build the source and binary wheel packages for distribution. From 14ecb7b90cbd3e066e78b53914e5b756ef21613c Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:23:18 +0200 Subject: [PATCH 108/139] chore: release version 10.8.0 (#1415) --- .codegen.json | 2 +- CHANGELOG.md | 7 +++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index c4b22db09..3b548676f 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "c571efa", "specHash": "f899bf6", "version": "10.7.0" } +{ "engineHash": "c571efa", "specHash": "f899bf6", "version": "10.8.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 88fa10b6c..b5fa752b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.8.0](https://github.com/box/box-python-sdk/compare/v10.7.0...v10.8.0) (2026-04-30) + + +### New Features and Enhancements + +* Automate Public API (box/box-openapi[#594](https://github.com/box/box-python-sdk/issues/594)) ([#1409](https://github.com/box/box-python-sdk/issues/1409)) ([3cfb35c](https://github.com/box/box-python-sdk/commit/3cfb35c6a86062658588afd779410bb426472427)) + ## [10.7.0](https://github.com/box/box-python-sdk/compare/v10.6.0...v10.7.0) (2026-04-22) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index bdaac5d60..d2fd1cdd8 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.7.0' +__version__ = '10.8.0' From d31bca44f67decd8e2a65eab8d95f45971bba591 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 5 May 2026 18:29:15 +0200 Subject: [PATCH 109/139] fix!: Mark `id` field of `AIAgentReference` required (box/box-openapi#1629) (box/box-openapi#595) (#1422) --- .codegen.json | 2 +- box_sdk_gen/schemas/ai_agent_reference.py | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.codegen.json b/.codegen.json index 3b548676f..120a38d26 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "c571efa", "specHash": "f899bf6", "version": "10.8.0" } +{ "engineHash": "c571efa", "specHash": "65c9c57", "version": "10.8.0" } diff --git a/box_sdk_gen/schemas/ai_agent_reference.py b/box_sdk_gen/schemas/ai_agent_reference.py index 9f7dd1dc9..5b95d3b7b 100644 --- a/box_sdk_gen/schemas/ai_agent_reference.py +++ b/box_sdk_gen/schemas/ai_agent_reference.py @@ -1,7 +1,5 @@ from enum import Enum -from typing import Optional - from box_sdk_gen.internal.base_object import BaseObject from box_sdk_gen.box.errors import BoxSDKError @@ -16,19 +14,19 @@ class AiAgentReference(BaseObject): def __init__( self, + id: str, *, type: AiAgentReferenceTypeField = AiAgentReferenceTypeField.AI_AGENT_ID, - id: Optional[str] = None, **kwargs ): """ - :param type: The type of AI agent used to handle queries., defaults to AiAgentReferenceTypeField.AI_AGENT_ID - :type type: AiAgentReferenceTypeField, optional :param id: The ID of an Agent. This can be a numeric ID for custom agents (for example, `14031`) or a unique identifier for pre-built agents (for example, `enhanced_extract_agent` - for the [Enhanced Extract Agent](https://developer.box.com/guides/box-ai/ai-tutorials/extract-metadata-structured#enhanced-extract-agent))., defaults to None - :type id: Optional[str], optional + for the [Enhanced Extract Agent](https://developer.box.com/guides/box-ai/ai-tutorials/extract-metadata-structured#enhanced-extract-agent)). + :type id: str + :param type: The type of AI agent used to handle queries., defaults to AiAgentReferenceTypeField.AI_AGENT_ID + :type type: AiAgentReferenceTypeField, optional """ super().__init__(**kwargs) - self.type = type self.id = id + self.type = type From 3ef0ffe6288161d0f0216ddb0b07024307094fe9 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 6 May 2026 09:39:57 +0200 Subject: [PATCH 110/139] fix: Fix metadata template schema (box/box-openapi#596) (#1423) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/managers/metadata_templates.py | 9 ++- box_sdk_gen/schemas/metadata_template.py | 66 +++++++++++++++++++++- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/.codegen.json b/.codegen.json index 120a38d26..d4f1eb8cd 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "c571efa", "specHash": "65c9c57", "version": "10.8.0" } +{ "engineHash": "c571efa", "specHash": "fa39a3f", "version": "10.8.0" } diff --git a/box_sdk_gen/managers/metadata_templates.py b/box_sdk_gen/managers/metadata_templates.py index 3c6e12b83..aa93dedeb 100644 --- a/box_sdk_gen/managers/metadata_templates.py +++ b/box_sdk_gen/managers/metadata_templates.py @@ -199,12 +199,14 @@ class CreateMetadataTemplateFields(BaseObject): _fields_to_json_mapping: Dict[str, str] = { 'display_name': 'displayName', 'taxonomy_key': 'taxonomyKey', + 'taxonomy_id': 'taxonomyId', 'options_rules': 'optionsRules', **BaseObject._fields_to_json_mapping, } _json_to_fields_mapping: Dict[str, str] = { 'displayName': 'display_name', 'taxonomyKey': 'taxonomy_key', + 'taxonomyId': 'taxonomy_id', 'optionsRules': 'options_rules', **BaseObject._json_to_fields_mapping, } @@ -227,6 +229,7 @@ def __init__( hidden: Optional[bool] = None, options: Optional[List[CreateMetadataTemplateFieldsOptionsField]] = None, taxonomy_key: Optional[str] = None, + taxonomy_id: Optional[str] = None, namespace: Optional[str] = None, options_rules: Optional[CreateMetadataTemplateFieldsOptionsRulesField] = None, **kwargs @@ -237,7 +240,7 @@ def __init__( date-time picker. Additionally, metadata templates support an `enum` field for a basic list - of items, and ` multiSelect` field for a similar list of items where the + of items, and `multiSelect` field for a similar list of items where the user can select more than one value. Metadata taxonomies are also supported as a `taxonomy` field type @@ -260,6 +263,9 @@ def __init__( :param taxonomy_key: The unique key of the metadata taxonomy to use for this taxonomy field. This property is required when the field `type` is set to `taxonomy`., defaults to None :type taxonomy_key: Optional[str], optional + :param taxonomy_id: The unique ID of the metadata taxonomy to use for this taxonomy field. + This property is required when the field `type` is set to `taxonomy`., defaults to None + :type taxonomy_id: Optional[str], optional :param namespace: The namespace of the metadata taxonomy to use for this taxonomy field. This property is required when the field `type` is set to `taxonomy`., defaults to None :type namespace: Optional[str], optional @@ -275,6 +281,7 @@ def __init__( self.hidden = hidden self.options = options self.taxonomy_key = taxonomy_key + self.taxonomy_id = taxonomy_id self.namespace = namespace self.options_rules = options_rules diff --git a/box_sdk_gen/schemas/metadata_template.py b/box_sdk_gen/schemas/metadata_template.py index d844ab9ab..b79658273 100644 --- a/box_sdk_gen/schemas/metadata_template.py +++ b/box_sdk_gen/schemas/metadata_template.py @@ -21,8 +21,8 @@ class MetadataTemplateFieldsTypeField(str, Enum): DATE = 'date' ENUM = 'enum' MULTISELECT = 'multiSelect' - INTEGER = 'integer' TAXONOMY = 'taxonomy' + INTEGER = 'integer' class MetadataTemplateFieldsOptionsField(BaseObject): @@ -39,13 +39,50 @@ def __init__(self, key: str, *, id: Optional[str] = None, **kwargs): self.id = id +class MetadataTemplateFieldsOptionsRulesField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'multi_select': 'multiSelect', + 'selectable_levels': 'selectableLevels', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'multiSelect': 'multi_select', + 'selectableLevels': 'selectable_levels', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + *, + multi_select: Optional[bool] = None, + selectable_levels: Optional[List[int]] = None, + **kwargs + ): + """ + :param multi_select: Whether to allow users to select multiple values., defaults to None + :type multi_select: Optional[bool], optional + :param selectable_levels: An array of integers defining which levels of the taxonomy are + selectable by users., defaults to None + :type selectable_levels: Optional[List[int]], optional + """ + super().__init__(**kwargs) + self.multi_select = multi_select + self.selectable_levels = selectable_levels + + class MetadataTemplateFieldsField(BaseObject): _fields_to_json_mapping: Dict[str, str] = { 'display_name': 'displayName', + 'taxonomy_key': 'taxonomyKey', + 'taxonomy_id': 'taxonomyId', + 'options_rules': 'optionsRules', **BaseObject._fields_to_json_mapping, } _json_to_fields_mapping: Dict[str, str] = { 'displayName': 'display_name', + 'taxonomyKey': 'taxonomy_key', + 'taxonomyId': 'taxonomy_id', + 'optionsRules': 'options_rules', **BaseObject._json_to_fields_mapping, } _discriminator = 'type', { @@ -54,8 +91,8 @@ class MetadataTemplateFieldsField(BaseObject): 'date', 'enum', 'multiSelect', - 'integer', 'taxonomy', + 'integer', } def __init__( @@ -67,6 +104,10 @@ def __init__( description: Optional[str] = None, hidden: Optional[bool] = None, options: Optional[List[MetadataTemplateFieldsOptionsField]] = None, + taxonomy_key: Optional[str] = None, + taxonomy_id: Optional[str] = None, + namespace: Optional[str] = None, + options_rules: Optional[MetadataTemplateFieldsOptionsRulesField] = None, id: Optional[str] = None, **kwargs ): @@ -76,9 +117,12 @@ def __init__( date-time picker. Additionally, metadata templates support an `enum` field for a basic list - of items, and ` multiSelect` field for a similar list of items where the + of items, and `multiSelect` field for a similar list of items where the user can select more than one value. + Metadata taxonomies are also supported as a `taxonomy` field type + with a specific set of additional properties, which describe its structure. + **Note**: The `integer` value is deprecated. It is still present in the response, but cannot be used in the POST request. @@ -97,6 +141,18 @@ def __init__( :param options: A list of options for this field. This is used in combination with the `enum` and `multiSelect` field types., defaults to None :type options: Optional[List[MetadataTemplateFieldsOptionsField]], optional + :param taxonomy_key: The unique key of the metadata taxonomy to use for this taxonomy field. + This property is required when the field `type` is set to `taxonomy`., defaults to None + :type taxonomy_key: Optional[str], optional + :param taxonomy_id: The unique ID of the metadata taxonomy to use for this taxonomy field. + This property is required when the field `type` is set to `taxonomy`., defaults to None + :type taxonomy_id: Optional[str], optional + :param namespace: The namespace of the metadata taxonomy to use for this taxonomy field. + This property is required when the field `type` is set to `taxonomy`., defaults to None + :type namespace: Optional[str], optional + :param options_rules: An object defining additional rules for the options of the taxonomy field. + This property is required when the field `type` is set to `taxonomy`., defaults to None + :type options_rules: Optional[MetadataTemplateFieldsOptionsRulesField], optional :param id: The unique ID of the metadata template field., defaults to None :type id: Optional[str], optional """ @@ -107,6 +163,10 @@ def __init__( self.description = description self.hidden = hidden self.options = options + self.taxonomy_key = taxonomy_key + self.taxonomy_id = taxonomy_id + self.namespace = namespace + self.options_rules = options_rules self.id = id From 6260fd0b636276279a8df1d93a113b9fc66f68c9 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 6 May 2026 10:03:21 +0200 Subject: [PATCH 111/139] test: Test missing metadata taxonomy fields (box/box-codegen#940) (#1426) --- .codegen.json | 2 +- docs/metadata_taxonomies.md | 2 +- test/metadata_taxonomies.py | 17 ++++++++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.codegen.json b/.codegen.json index d4f1eb8cd..d2e72385d 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "c571efa", "specHash": "fa39a3f", "version": "10.8.0" } +{ "engineHash": "2492c3e", "specHash": "fa39a3f", "version": "10.8.0" } diff --git a/docs/metadata_taxonomies.md b/docs/metadata_taxonomies.md index c84fd2910..776db4eb1 100644 --- a/docs/metadata_taxonomies.md +++ b/docs/metadata_taxonomies.md @@ -533,7 +533,7 @@ See the endpoint docs at ```python client.metadata_taxonomies.get_metadata_template_field_options( - namespace, metadata_template_key, "taxonomy" + namespace, metadata_template_key, field_key ) ``` diff --git a/test/metadata_taxonomies.py b/test/metadata_taxonomies.py index eda959118..742779e46 100644 --- a/test/metadata_taxonomies.py +++ b/test/metadata_taxonomies.py @@ -171,6 +171,8 @@ def testMetadataTaxonomiesNodes(): assert get_country_node.display_name == 'Poland UPDATED' assert get_country_node.id == country_node.id metadata_template_key: str = ''.join(['templateKey', get_uuid()]) + field_display_name: str = 'testTaxonomy123' + field_key: str = 'testTaxonomy321' metadata_template: MetadataTemplate = ( client.metadata_templates.create_metadata_template( 'enterprise', @@ -179,8 +181,8 @@ def testMetadataTaxonomiesNodes(): fields=[ CreateMetadataTemplateFields( type=CreateMetadataTemplateFieldsTypeField.TAXONOMY, - key='taxonomy', - display_name='taxonomy', + key=field_key, + display_name=field_display_name, taxonomy_key=taxonomy_key, namespace=namespace, options_rules=CreateMetadataTemplateFieldsOptionsRulesField( @@ -193,10 +195,19 @@ def testMetadataTaxonomiesNodes(): assert metadata_template.template_key == metadata_template_key assert metadata_template.display_name == metadata_template_key assert len(metadata_template.fields) == 1 + assert metadata_template.fields[0].display_name == field_display_name + assert metadata_template.fields[0].hidden == False + assert not metadata_template.fields[0].id == '' + assert metadata_template.fields[0].key == field_key + assert metadata_template.fields[0].namespace == namespace + assert metadata_template.fields[0].options_rules.multi_select == True + assert metadata_template.fields[0].options_rules.selectable_levels[0] == 1 + assert not metadata_template.fields[0].taxonomy_id == '' + assert metadata_template.fields[0].taxonomy_key == taxonomy_key assert to_string(metadata_template.fields[0].type) == 'taxonomy' options: MetadataTaxonomyNodes = ( client.metadata_taxonomies.get_metadata_template_field_options( - namespace, metadata_template_key, 'taxonomy' + namespace, metadata_template_key, field_key ) ) assert len(options.entries) == 1 From 1fd133b0240a8c49b26948c0865b5dc96ae871ea Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 6 May 2026 16:00:25 +0200 Subject: [PATCH 112/139] chore: release version 10.9.0 (#1429) Co-authored-by: Artur Jankowski --- .codegen.json | 2 +- CHANGELOG.md | 12 ++++++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index d2e72385d..9f11760a1 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "2492c3e", "specHash": "fa39a3f", "version": "10.8.0" } +{ "engineHash": "2492c3e", "specHash": "fa39a3f", "version": "10.9.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b5fa752b4..dae346c18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.9.0](https://github.com/box/box-python-sdk/compare/v10.8.0...v10.9.0) (2026-05-06) + + +### ⚠ BREAKING CHANGES + +* Mark `id` field of `AIAgentReference` required (box/box-openapi[#1629](https://github.com/box/box-python-sdk/issues/1629)) (box/box-openapi[#595](https://github.com/box/box-python-sdk/issues/595)) ([#1422](https://github.com/box/box-python-sdk/issues/1422)) ([d31bca4](https://github.com/box/box-python-sdk/commit/d31bca44f67decd8e2a65eab8d95f45971bba591)) + +### Bug Fixes + +* Fix metadata template schema (box/box-openapi[#596](https://github.com/box/box-python-sdk/issues/596)) ([#1423](https://github.com/box/box-python-sdk/issues/1423)) ([3ef0ffe](https://github.com/box/box-python-sdk/commit/3ef0ffe6288161d0f0216ddb0b07024307094fe9)) +* Mark `id` field of `AIAgentReference` required (box/box-openapi[#1629](https://github.com/box/box-python-sdk/issues/1629)) (box/box-openapi[#595](https://github.com/box/box-python-sdk/issues/595)) ([#1422](https://github.com/box/box-python-sdk/issues/1422)) ([d31bca4](https://github.com/box/box-python-sdk/commit/d31bca44f67decd8e2a65eab8d95f45971bba591)) + ## [10.8.0](https://github.com/box/box-python-sdk/compare/v10.7.0...v10.8.0) (2026-04-30) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index d2fd1cdd8..0a4534f17 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.8.0' +__version__ = '10.9.0' From 22450ee1982796649dfb1abb2ef2570c2e719fba Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 15 May 2026 12:14:59 +0200 Subject: [PATCH 113/139] feat: Add new event types (box/box-openapi#597) (#1436) --- .codegen.json | 2 +- box_sdk_gen/schemas/event.py | 63 +++++++++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/.codegen.json b/.codegen.json index 9f11760a1..50991ec96 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "2492c3e", "specHash": "fa39a3f", "version": "10.9.0" } +{ "engineHash": "0f85d1e", "specHash": "576cd17", "version": "10.9.0" } diff --git a/box_sdk_gen/schemas/event.py b/box_sdk_gen/schemas/event.py index 56ac2b68e..ed7b09b69 100644 --- a/box_sdk_gen/schemas/event.py +++ b/box_sdk_gen/schemas/event.py @@ -31,9 +31,15 @@ class EventEventTypeField(str, Enum): ADD_DEVICE_ASSOCIATION = 'ADD_DEVICE_ASSOCIATION' ADD_LOGIN_ACTIVITY_DEVICE = 'ADD_LOGIN_ACTIVITY_DEVICE' ADMIN_LOGIN = 'ADMIN_LOGIN' + AI_SECURITY_DETECTION = 'AI_SECURITY_DETECTION' + ANNOTATIONV2_CREATE = 'ANNOTATIONV2_CREATE' + ANNOTATIONV2_DELETE = 'ANNOTATIONV2_DELETE' + ANNOTATIONV2_EDIT = 'ANNOTATIONV2_EDIT' APPLICATION_CREATED = 'APPLICATION_CREATED' APPLICATION_PUBLIC_KEY_ADDED = 'APPLICATION_PUBLIC_KEY_ADDED' APPLICATION_PUBLIC_KEY_DELETED = 'APPLICATION_PUBLIC_KEY_DELETED' + BOX_AI_USER_FAILED_REQUEST = 'BOX_AI_USER_FAILED_REQUEST' + BOX_AI_USER_REQUEST = 'BOX_AI_USER_REQUEST' CHANGE_ADMIN_ROLE = 'CHANGE_ADMIN_ROLE' CHANGE_FOLDER_PERMISSION = 'CHANGE_FOLDER_PERMISSION' COLLABORATION_ACCEPT = 'COLLABORATION_ACCEPT' @@ -45,9 +51,18 @@ class EventEventTypeField(str, Enum): COLLAB_INVITE_COLLABORATOR = 'COLLAB_INVITE_COLLABORATOR' COLLAB_REMOVE_COLLABORATOR = 'COLLAB_REMOVE_COLLABORATOR' COLLAB_ROLE_CHANGE = 'COLLAB_ROLE_CHANGE' + COLLECTION_CREATE = 'COLLECTION_CREATE' + COLLECTION_DELETE = 'COLLECTION_DELETE' + COLLECTION_ITEM_CREATE = 'COLLECTION_ITEM_CREATE' + COLLECTION_ITEM_DELETE = 'COLLECTION_ITEM_DELETE' + COLLECTION_ITEM_UPDATE = 'COLLECTION_ITEM_UPDATE' + COLLECTION_UPDATE = 'COLLECTION_UPDATE' COMMENT_CREATE = 'COMMENT_CREATE' COMMENT_DELETE = 'COMMENT_DELETE' CONTENT_ACCESS = 'CONTENT_ACCESS' + CONTENT_RECOVERY_REPORT_CREATE = 'CONTENT_RECOVERY_REPORT_CREATE' + CONTENT_RECOVERY_REPORT_DELETE = 'CONTENT_RECOVERY_REPORT_DELETE' + CONTENT_RECOVERY_REPORT_INITIATE = 'CONTENT_RECOVERY_REPORT_INITIATE' CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY = ( 'CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY' ) @@ -66,31 +81,40 @@ class EventEventTypeField(str, Enum): DELETE = 'DELETE' DELETE_USER = 'DELETE_USER' DEVICE_TRUST_CHECK_FAILED = 'DEVICE_TRUST_CHECK_FAILED' + DISABLE_MULTI_FACTOR_AUTH = 'DISABLE_MULTI_FACTOR_AUTH' DOWNLOAD = 'DOWNLOAD' EDIT = 'EDIT' EDIT_USER = 'EDIT_USER' - EDR_CROWDSTRIKE_DEVICE_DETECTED = 'EDR_CROWDSTRIKE_DEVICE_DETECTED' - EDR_CROWDSTRIKE_NO_BOX_TOOLS = 'EDR_CROWDSTRIKE_NO_BOX_TOOLS' - EDR_CROWDSTRIKE_BOX_TOOLS_OUTDATED = 'EDR_CROWDSTRIKE_BOX_TOOLS_OUTDATED' - EDR_CROWDSTRIKE_DRIVE_OUTDATED = 'EDR_CROWDSTRIKE_DRIVE_OUTDATED' EDR_CROWDSTRIKE_ACCESS_ALLOWED_NO_CROWDSTRIKE_DEVICE = ( 'EDR_CROWDSTRIKE_ACCESS_ALLOWED_NO_CROWDSTRIKE_DEVICE' ) EDR_CROWDSTRIKE_ACCESS_REVOKED = 'EDR_CROWDSTRIKE_ACCESS_REVOKED' + EDR_CROWDSTRIKE_BOX_TOOLS_OUTDATED = 'EDR_CROWDSTRIKE_BOX_TOOLS_OUTDATED' + EDR_CROWDSTRIKE_DEVICE_DETECTED = 'EDR_CROWDSTRIKE_DEVICE_DETECTED' + EDR_CROWDSTRIKE_DRIVE_OUTDATED = 'EDR_CROWDSTRIKE_DRIVE_OUTDATED' + EDR_CROWDSTRIKE_NO_BOX_TOOLS = 'EDR_CROWDSTRIKE_NO_BOX_TOOLS' EMAIL_ALIAS_CONFIRM = 'EMAIL_ALIAS_CONFIRM' EMAIL_ALIAS_REMOVE = 'EMAIL_ALIAS_REMOVE' + ENABLE_MULTI_FACTOR_AUTH = 'ENABLE_MULTI_FACTOR_AUTH' ENABLE_TWO_FACTOR_AUTH = 'ENABLE_TWO_FACTOR_AUTH' ENTERPRISE_APP_AUTHORIZATION_UPDATE = 'ENTERPRISE_APP_AUTHORIZATION_UPDATE' + EXTERNAL_COLLAB_SECURITY_SETTINGS = 'EXTERNAL_COLLAB_SECURITY_SETTINGS' FAILED_LOGIN = 'FAILED_LOGIN' FILE_MARKED_MALICIOUS = 'FILE_MARKED_MALICIOUS' FILE_WATERMARKED_DOWNLOAD = 'FILE_WATERMARKED_DOWNLOAD' GROUP_ADD_ITEM = 'GROUP_ADD_ITEM' GROUP_ADD_USER = 'GROUP_ADD_USER' + GROUP_ADMIN_CREATED = 'GROUP_ADMIN_CREATED' + GROUP_ADMIN_DELETED = 'GROUP_ADMIN_DELETED' + GROUP_ADMIN_PERMISSIONS_UPDATED = 'GROUP_ADMIN_PERMISSIONS_UPDATED' GROUP_CREATION = 'GROUP_CREATION' GROUP_DELETION = 'GROUP_DELETION' GROUP_EDITED = 'GROUP_EDITED' GROUP_REMOVE_ITEM = 'GROUP_REMOVE_ITEM' GROUP_REMOVE_USER = 'GROUP_REMOVE_USER' + ITEM_ASSOCIATION_CREATED = 'ITEM_ASSOCIATION_CREATED' + ITEM_ASSOCIATION_DELETED = 'ITEM_ASSOCIATION_DELETED' + ITEM_ASSOCIATION_UPDATED = 'ITEM_ASSOCIATION_UPDATED' ITEM_COPY = 'ITEM_COPY' ITEM_CREATE = 'ITEM_CREATE' ITEM_DOWNLOAD = 'ITEM_DOWNLOAD' @@ -129,15 +153,23 @@ class EventEventTypeField(str, Enum): METADATA_TEMPLATE_UPDATE = 'METADATA_TEMPLATE_UPDATE' MOVE = 'MOVE' NEW_USER = 'NEW_USER' + OAUTH2_ACCESS_TOKEN_REVOKE = 'OAUTH2_ACCESS_TOKEN_REVOKE' PREVIEW = 'PREVIEW' REMOVE_DEVICE_ASSOCIATION = 'REMOVE_DEVICE_ASSOCIATION' REMOVE_LOGIN_ACTIVITY_DEVICE = 'REMOVE_LOGIN_ACTIVITY_DEVICE' RENAME = 'RENAME' RETENTION_POLICY_ASSIGNMENT_ADD = 'RETENTION_POLICY_ASSIGNMENT_ADD' SHARE = 'SHARE' + SHARED_LINK_REDIRECT_OUT_OF_SHARED_CONTEXT = ( + 'SHARED_LINK_REDIRECT_OUT_OF_SHARED_CONTEXT' + ) SHARED_LINK_SEND = 'SHARED_LINK_SEND' SHARE_EXPIRATION = 'SHARE_EXPIRATION' + SHIELD_ACCESS_POLICY_CREATED = 'SHIELD_ACCESS_POLICY_CREATED' + SHIELD_ACCESS_POLICY_DELETED = 'SHIELD_ACCESS_POLICY_DELETED' + SHIELD_ACCESS_POLICY_UPDATED = 'SHIELD_ACCESS_POLICY_UPDATED' SHIELD_ALERT = 'SHIELD_ALERT' + SHIELD_DOWNLOAD_BLOCKED = 'SHIELD_DOWNLOAD_BLOCKED' SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED = 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED' SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION = ( 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION' @@ -146,6 +178,28 @@ class EventEventTypeField(str, Enum): SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED_MISSING_JUSTIFICATION = ( 'SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED_MISSING_JUSTIFICATION' ) + SHIELD_EXTERNAL_COLLAB_INVITE_JUSTIFIED = 'SHIELD_EXTERNAL_COLLAB_INVITE_JUSTIFIED' + SHIELD_INFORMATION_BARRIER_COLLAB_BLOCKED = ( + 'SHIELD_INFORMATION_BARRIER_COLLAB_BLOCKED' + ) + SHIELD_INFORMATION_BARRIER_DISABLED = 'SHIELD_INFORMATION_BARRIER_DISABLED' + SHIELD_INFORMATION_BARRIER_ENABLED = 'SHIELD_INFORMATION_BARRIER_ENABLED' + SHIELD_INFORMATION_BARRIER_GROUP_ADD_USER_BLOCKED = ( + 'SHIELD_INFORMATION_BARRIER_GROUP_ADD_USER_BLOCKED' + ) + SHIELD_INFORMATION_BARRIER_ITEM_COPY_BLOCKED = ( + 'SHIELD_INFORMATION_BARRIER_ITEM_COPY_BLOCKED' + ) + SHIELD_INFORMATION_BARRIER_ITEM_MOVE_BLOCKED = ( + 'SHIELD_INFORMATION_BARRIER_ITEM_MOVE_BLOCKED' + ) + SHIELD_INFORMATION_BARRIER_ITEM_OWNER_TRANSFER_BLOCKED = ( + 'SHIELD_INFORMATION_BARRIER_ITEM_OWNER_TRANSFER_BLOCKED' + ) + SHIELD_INFORMATION_BARRIER_PENDING = 'SHIELD_INFORMATION_BARRIER_PENDING' + SHIELD_INFORMATION_BARRIER_SHARED_ITEM_ACCESS_BLOCKED = ( + 'SHIELD_INFORMATION_BARRIER_SHARED_ITEM_ACCESS_BLOCKED' + ) SHIELD_JUSTIFICATION_APPROVAL = 'SHIELD_JUSTIFICATION_APPROVAL' SHIELD_SHARED_LINK_ACCESS_BLOCKED = 'SHIELD_SHARED_LINK_ACCESS_BLOCKED' SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE = ( @@ -163,6 +217,7 @@ class EventEventTypeField(str, Enum): SIGN_DOCUMENT_EXPIRED = 'SIGN_DOCUMENT_EXPIRED' SIGN_DOCUMENT_SIGNED = 'SIGN_DOCUMENT_SIGNED' SIGN_DOCUMENT_VIEWED_BY_SIGNED = 'SIGN_DOCUMENT_VIEWED_BY_SIGNED' + SIGN_DOCUMENT_VIEWED_BY_SIGNER = 'SIGN_DOCUMENT_VIEWED_BY_SIGNER' SIGNER_DOWNLOADED = 'SIGNER_DOWNLOADED' SIGNER_FORWARDED = 'SIGNER_FORWARDED' STORAGE_EXPIRATION = 'STORAGE_EXPIRATION' From 0f046db10d7bc9f2c82c10e5277190e6b7d526c2 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 15 May 2026 12:52:34 +0200 Subject: [PATCH 114/139] feat: Sanitize request body in `BoxAPIError` (box/box-codegen#948) (#1440) --- .codegen.json | 2 +- box_sdk_gen/box/errors.py | 15 ++++++++--- box_sdk_gen/internal/logging.py | 26 ++++++++++++++++++++ box_sdk_gen/networking/box_network_client.py | 3 +++ box_sdk_gen/serialization/json.py | 26 ++++++++++++++++++++ test/box_network_client.py | 1 + 6 files changed, 69 insertions(+), 4 deletions(-) diff --git a/.codegen.json b/.codegen.json index 50991ec96..36119e89b 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "0f85d1e", "specHash": "576cd17", "version": "10.9.0" } +{ "engineHash": "78a8dc0", "specHash": "576cd17", "version": "10.9.0" } diff --git a/box_sdk_gen/box/errors.py b/box_sdk_gen/box/errors.py index b1baf3d72..cc9b36a9f 100644 --- a/box_sdk_gen/box/errors.py +++ b/box_sdk_gen/box/errors.py @@ -37,15 +37,24 @@ def __init__( url: str, query_params: Dict[str, str], headers: Dict[str, str], - body: Optional[str] = None, + body: Optional[Any] = None, + content_type: Optional[str] = None, ): self.method = method self.url = url self.query_params = query_params self.headers = headers self.body = body + self.content_type = content_type def print(self, data_sanitizer: DataSanitizer): + sanitized_body = ( + data_sanitizer.sanitize_string_body( + self.body, content_type=self.content_type + ) + if isinstance(self.body, str) + else self.body + ) return ''.join( ( f'\n\tMethod: {self.method}', @@ -55,8 +64,8 @@ def print(self, data_sanitizer: DataSanitizer): ''.join( [ '\n\tBody: ', - '\n' if self.body else '', - pprint.pformat(self.body, indent=8), + '\n' if sanitized_body else '', + pprint.pformat(sanitized_body, indent=8), ] ), ) diff --git a/box_sdk_gen/internal/logging.py b/box_sdk_gen/internal/logging.py index 41371aa47..83180f5dc 100644 --- a/box_sdk_gen/internal/logging.py +++ b/box_sdk_gen/internal/logging.py @@ -1,11 +1,19 @@ from typing import Dict +from typing import Optional + from box_sdk_gen.serialization.json import SerializedData from box_sdk_gen.internal.utils import sanitize_map from box_sdk_gen.serialization.json import sanitize_serialized_data +from box_sdk_gen.serialization.json import sanitize_form_encoded_body_from_string + +from box_sdk_gen.serialization.json import json_to_serialized_data + +from box_sdk_gen.serialization.json import sd_to_json + class DataSanitizer: def __init__(self): @@ -29,3 +37,21 @@ def sanitize_headers(self, headers: Dict[str, str]) -> Dict[str, str]: def sanitize_body(self, body: SerializedData) -> SerializedData: return sanitize_serialized_data(body, self._keys_to_sanitize) + + def sanitize_form_encoded_body(self, body: str) -> str: + return sanitize_form_encoded_body_from_string(body, self._keys_to_sanitize) + + def sanitize_string_body( + self, body: str, *, content_type: Optional[str] = None + ) -> str: + if ( + content_type == 'application/json' + or content_type == 'application/json-patch+json' + ): + try: + return sd_to_json(self.sanitize_body(json_to_serialized_data(body))) + except Exception: + return body + if content_type == 'application/x-www-form-urlencoded': + return self.sanitize_form_encoded_body(body) + return body diff --git a/box_sdk_gen/networking/box_network_client.py b/box_sdk_gen/networking/box_network_client.py index 3edeef995..ddb8af4a3 100644 --- a/box_sdk_gen/networking/box_network_client.py +++ b/box_sdk_gen/networking/box_network_client.py @@ -40,6 +40,7 @@ class APIRequest: headers: Dict[str, str] params: Dict[str, str] data: Optional[Union[str, ByteStream, MultipartEncoder]] + content_type: Optional[str] = None allow_redirects: bool = True timeout: Optional[Tuple[Optional[float], Optional[float]]] = None @@ -180,6 +181,7 @@ def _prepare_request( headers=headers, params=params, data=data, + content_type=options.content_type, allow_redirects=allow_redirects, timeout=timeout, ) @@ -304,6 +306,7 @@ def _raise_on_unsuccessful_request( query_params=request.params, headers=request.headers, body=request.data, + content_type=request.content_type, ), response_info=ResponseInfo( status_code=network_response.status_code, diff --git a/box_sdk_gen/serialization/json.py b/box_sdk_gen/serialization/json.py index 7d7b580c0..4c0500c48 100644 --- a/box_sdk_gen/serialization/json.py +++ b/box_sdk_gen/serialization/json.py @@ -48,6 +48,32 @@ def sanitized_value() -> str: return '---[redacted]---' +def sanitize_form_encoded_body_from_string( + body: str, keys_to_sanitize: Dict[str, str] +) -> str: + return '&'.join( + [ + _sanitize_form_encoded_parameter(parameter, keys_to_sanitize) + for parameter in body.split('&') + ] + ) + + +def _sanitize_form_encoded_parameter( + parameter: str, keys_to_sanitize: Dict[str, str] +) -> str: + separator_index = parameter.find('=') + if separator_index < 0: + return parameter + + key = parameter[:separator_index] + value = parameter[separator_index + 1 :] + sanitized_parameter_value = ( + sanitized_value() if key.lower() in keys_to_sanitize else value + ) + return f'{key}={sanitized_parameter_value}' + + def sanitize_serialized_data( sd: SerializedData, keys_to_sanitize: Dict[str, str] ) -> SerializedData: diff --git a/test/box_network_client.py b/test/box_network_client.py index bfd003846..ecda14ae2 100644 --- a/test/box_network_client.py +++ b/test/box_network_client.py @@ -338,6 +338,7 @@ def test_prepare_json_request(network_client, network_session_mock): }, params={"param": "value"}, data='{"key": "value"}', + content_type="application/json", timeout=(5, 60), ) From 29169071c482a3a3ed5ebfefc1d2377f7f049da9 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 20 May 2026 11:25:22 +0200 Subject: [PATCH 115/139] feat: add failure related fields to `DocGenJob` (box/box-openapi#598) (#1445) --- .codegen.json | 2 +- .../v2025_r0/doc_gen_job_full_v2025_r0.py | 8 +++++++ .../schemas/v2025_r0/doc_gen_job_v2025_r0.py | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 36119e89b..1d5a5126e 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "78a8dc0", "specHash": "576cd17", "version": "10.9.0" } +{ "engineHash": "4de40e1", "specHash": "e0ffc4a", "version": "10.9.0" } diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py index 596476236..2e4eec564 100644 --- a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_full_v2025_r0.py @@ -20,6 +20,10 @@ DocGenJobV2025R0StatusField, ) +from box_sdk_gen.schemas.v2025_r0.doc_gen_job_v2025_r0 import ( + DocGenJobV2025R0FailuresField, +) + from box_sdk_gen.schemas.v2025_r0.doc_gen_job_v2025_r0 import DocGenJobV2025R0 from box_sdk_gen.schemas.v2025_r0.user_base_v2025_r0 import UserBaseV2025R0 @@ -49,6 +53,7 @@ def __init__( created_at: Optional[str] = None, output_file: Optional[FileReferenceV2025R0] = None, output_file_version: Optional[FileVersionBaseV2025R0] = None, + failures: Optional[DocGenJobV2025R0FailuresField] = None, type: DocGenJobBaseV2025R0TypeField = DocGenJobBaseV2025R0TypeField.DOCGEN_JOB, **kwargs ): @@ -63,6 +68,8 @@ def __init__( :type id: str :param created_at: Time of job creation., defaults to None :type created_at: Optional[str], optional + :param failures: Errors and warnings that occurred during document generation., defaults to None + :type failures: Optional[DocGenJobV2025R0FailuresField], optional :param type: The value will always be `docgen_job`., defaults to DocGenJobBaseV2025R0TypeField.DOCGEN_JOB :type type: DocGenJobBaseV2025R0TypeField, optional """ @@ -75,6 +82,7 @@ def __init__( id=id, output_file=output_file, output_file_version=output_file_version, + failures=failures, type=type, **kwargs ) diff --git a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py index f1fbdadcd..0ea76f6f8 100644 --- a/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/doc_gen_job_v2025_r0.py @@ -1,5 +1,9 @@ from enum import Enum +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + from typing import Optional from box_sdk_gen.schemas.v2025_r0.doc_gen_job_base_v2025_r0 import ( @@ -29,6 +33,19 @@ class DocGenJobV2025R0StatusField(str, Enum): PENDING = 'pending' +class DocGenJobV2025R0FailuresField(BaseObject): + def __init__(self, errors: List[str], warnings: List[str], **kwargs): + """ + :param errors: A list of errors that occurred during document generation. + :type errors: List[str] + :param warnings: A list of warnings that occurred during document generation. + :type warnings: List[str] + """ + super().__init__(**kwargs) + self.errors = errors + self.warnings = warnings + + class DocGenJobV2025R0(DocGenJobBaseV2025R0): _discriminator = 'type', {'docgen_job'} @@ -43,6 +60,7 @@ def __init__( *, output_file: Optional[FileReferenceV2025R0] = None, output_file_version: Optional[FileVersionBaseV2025R0] = None, + failures: Optional[DocGenJobV2025R0FailuresField] = None, type: DocGenJobBaseV2025R0TypeField = DocGenJobBaseV2025R0TypeField.DOCGEN_JOB, **kwargs ): @@ -53,6 +71,8 @@ def __init__( :type output_type: str :param id: The unique identifier that represent a Box Doc Gen job. :type id: str + :param failures: Errors and warnings that occurred during document generation., defaults to None + :type failures: Optional[DocGenJobV2025R0FailuresField], optional :param type: The value will always be `docgen_job`., defaults to DocGenJobBaseV2025R0TypeField.DOCGEN_JOB :type type: DocGenJobBaseV2025R0TypeField, optional """ @@ -64,3 +84,4 @@ def __init__( self.output_type = output_type self.output_file = output_file self.output_file_version = output_file_version + self.failures = failures From 11c9f1fefbc7f5cc80aec6e6d2e2a75879479c36 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 20 May 2026 11:57:11 +0200 Subject: [PATCH 116/139] chore: release version 10.10.0 (#1448) --- .codegen.json | 2 +- CHANGELOG.md | 9 +++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 1d5a5126e..e41b91288 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "4de40e1", "specHash": "e0ffc4a", "version": "10.9.0" } +{ "engineHash": "4de40e1", "specHash": "e0ffc4a", "version": "10.10.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index dae346c18..f9128601c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.10.0](https://github.com/box/box-python-sdk/compare/v10.9.0...v10.10.0) (2026-05-20) + + +### New Features and Enhancements + +* add failure related fields to `DocGenJob` (box/box-openapi[#598](https://github.com/box/box-python-sdk/issues/598)) ([#1445](https://github.com/box/box-python-sdk/issues/1445)) ([2916907](https://github.com/box/box-python-sdk/commit/29169071c482a3a3ed5ebfefc1d2377f7f049da9)) +* Add new event types (box/box-openapi[#597](https://github.com/box/box-python-sdk/issues/597)) ([#1436](https://github.com/box/box-python-sdk/issues/1436)) ([22450ee](https://github.com/box/box-python-sdk/commit/22450ee1982796649dfb1abb2ef2570c2e719fba)) +* Sanitize request body in `BoxAPIError` (box/box-codegen[#948](https://github.com/box/box-python-sdk/issues/948)) ([#1440](https://github.com/box/box-python-sdk/issues/1440)) ([0f046db](https://github.com/box/box-python-sdk/commit/0f046db10d7bc9f2c82c10e5277190e6b7d526c2)) + ## [10.9.0](https://github.com/box/box-python-sdk/compare/v10.8.0...v10.9.0) (2026-05-06) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 0a4534f17..196cf7e8d 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.9.0' +__version__ = '10.10.0' From 1215d52b80e4ab833c6d51615cfa2e60114f9326 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 22 May 2026 16:54:34 +0200 Subject: [PATCH 117/139] feat: Add Convert Note API (box/box-codegen#955) (#1458) --- .codegen.json | 2 +- box_sdk_gen/client.py | 3 + box_sdk_gen/managers/__init__.py | 2 + box_sdk_gen/managers/notes.py | 118 ++++++++++++++++++ box_sdk_gen/schemas/sign_request.py | 1 + box_sdk_gen/schemas/v2026_r0/__init__.py | 6 + .../v2026_r0/folder_reference_v2026_r0.py | 30 +++++ .../notes_convert_request_body_v2026_r0.py | 38 ++++++ .../notes_convert_response_v2026_r0.py | 30 +++++ docs/README.md | 1 + docs/notes.md | 43 +++++++ test/notes.py | 52 ++++++++ 12 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 box_sdk_gen/managers/notes.py create mode 100644 box_sdk_gen/schemas/v2026_r0/folder_reference_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/notes_convert_request_body_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/notes_convert_response_v2026_r0.py create mode 100644 docs/notes.md create mode 100644 test/notes.py diff --git a/.codegen.json b/.codegen.json index e41b91288..8ea504285 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "4de40e1", "specHash": "e0ffc4a", "version": "10.10.0" } +{ "engineHash": "5a7add6", "specHash": "d028758", "version": "10.10.0" } diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py index 7e807a9d7..25518b944 100644 --- a/box_sdk_gen/client.py +++ b/box_sdk_gen/client.py @@ -194,6 +194,8 @@ from box_sdk_gen.managers.automate_workflows import AutomateWorkflowsManager +from box_sdk_gen.managers.notes import NotesManager + from box_sdk_gen.networking.auth import Authentication from box_sdk_gen.networking.network import NetworkSession @@ -469,6 +471,7 @@ def __init__(self, auth: Authentication, *, network_session: NetworkSession = No self.automate_workflows = AutomateWorkflowsManager( auth=self.auth, network_session=self.network_session ) + self.notes = NotesManager(auth=self.auth, network_session=self.network_session) def make_request(self, fetch_options: FetchOptions) -> FetchResponse: """ diff --git a/box_sdk_gen/managers/__init__.py b/box_sdk_gen/managers/__init__.py index 84b4a75ef..2a1464fc0 100644 --- a/box_sdk_gen/managers/__init__.py +++ b/box_sdk_gen/managers/__init__.py @@ -165,3 +165,5 @@ from box_sdk_gen.managers.external_users import * from box_sdk_gen.managers.automate_workflows import * + +from box_sdk_gen.managers.notes import * diff --git a/box_sdk_gen/managers/notes.py b/box_sdk_gen/managers/notes.py new file mode 100644 index 000000000..5d76d7bb3 --- /dev/null +++ b/box_sdk_gen/managers/notes.py @@ -0,0 +1,118 @@ +from enum import Enum + +from typing import Optional + +from typing import Dict + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.schemas.v2026_r0.folder_reference_v2026_r0 import ( + FolderReferenceV2026R0, +) + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2026_r0.notes_convert_response_v2026_r0 import ( + NotesConvertResponseV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.client_error_v2026_r0 import ClientErrorV2026R0 + +from box_sdk_gen.parameters.v2026_r0.box_version_header_v2026_r0 import ( + BoxVersionHeaderV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.notes_convert_request_body_v2026_r0 import ( + NotesConvertRequestBodyV2026R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateNoteConvertV2026R0ContentFormat(str, Enum): + MARKDOWN = 'markdown' + + +class NotesManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_note_convert_v2026_r0( + self, + content: str, + parent: FolderReferenceV2026R0, + name: str, + *, + content_format: CreateNoteConvertV2026R0ContentFormat = CreateNoteConvertV2026R0ContentFormat.MARKDOWN, + box_version: BoxVersionHeaderV2026R0 = BoxVersionHeaderV2026R0._2026_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> NotesConvertResponseV2026R0: + """ + Creates a Box Note (`.boxnote` file) from supported source content. See the `content_format` field for supported formats. + :param content: The content to convert to a note. See the `content_format` field for supported formats. + :type content: str + :param name: The name for the created note. The `.boxnote` extension is appended automatically. + :type name: str + :param content_format: Format of the content to convert., defaults to CreateNoteConvertV2026R0ContentFormat.MARKDOWN + :type content_format: CreateNoteConvertV2026R0ContentFormat, optional + :param box_version: Version header., defaults to BoxVersionHeaderV2026R0._2026_0 + :type box_version: BoxVersionHeaderV2026R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'content': content, + 'content_format': content_format, + 'parent': parent, + 'name': name, + } + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/notes/convert'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, NotesConvertResponseV2026R0) diff --git a/box_sdk_gen/schemas/sign_request.py b/box_sdk_gen/schemas/sign_request.py index d8e052fa1..9535cdb92 100644 --- a/box_sdk_gen/schemas/sign_request.py +++ b/box_sdk_gen/schemas/sign_request.py @@ -35,6 +35,7 @@ class SignRequestStatusField(str, Enum): SIGNED = 'signed' CANCELLED = 'cancelled' DECLINED = 'declined' + ERROR = 'error' ERROR_CONVERTING = 'error_converting' ERROR_SENDING = 'error_sending' EXPIRED = 'expired' diff --git a/box_sdk_gen/schemas/v2026_r0/__init__.py b/box_sdk_gen/schemas/v2026_r0/__init__.py index 6b48bc6d9..5735f86fd 100644 --- a/box_sdk_gen/schemas/v2026_r0/__init__.py +++ b/box_sdk_gen/schemas/v2026_r0/__init__.py @@ -4,6 +4,12 @@ from box_sdk_gen.schemas.v2026_r0.client_error_v2026_r0 import * +from box_sdk_gen.schemas.v2026_r0.folder_reference_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.notes_convert_request_body_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.notes_convert_response_v2026_r0 import * + from box_sdk_gen.schemas.v2026_r0.user_base_v2026_r0 import * from box_sdk_gen.schemas.v2026_r0.user_mini_v2026_r0 import * diff --git a/box_sdk_gen/schemas/v2026_r0/folder_reference_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/folder_reference_v2026_r0.py new file mode 100644 index 000000000..4646de78a --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/folder_reference_v2026_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class FolderReferenceV2026R0TypeField(str, Enum): + FOLDER = 'folder' + + +class FolderReferenceV2026R0(BaseObject): + _discriminator = 'type', {'folder'} + + def __init__( + self, + id: str, + *, + type: FolderReferenceV2026R0TypeField = FolderReferenceV2026R0TypeField.FOLDER, + **kwargs + ): + """ + :param id: ID of the folder. + :type id: str + :param type: The value will always be `folder`., defaults to FolderReferenceV2026R0TypeField.FOLDER + :type type: FolderReferenceV2026R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2026_r0/notes_convert_request_body_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/notes_convert_request_body_v2026_r0.py new file mode 100644 index 000000000..f5eaf82f2 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/notes_convert_request_body_v2026_r0.py @@ -0,0 +1,38 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2026_r0.folder_reference_v2026_r0 import ( + FolderReferenceV2026R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class NotesConvertRequestBodyV2026R0ContentFormatField(str, Enum): + MARKDOWN = 'markdown' + + +class NotesConvertRequestBodyV2026R0(BaseObject): + def __init__( + self, + content: str, + parent: FolderReferenceV2026R0, + name: str, + *, + content_format: NotesConvertRequestBodyV2026R0ContentFormatField = NotesConvertRequestBodyV2026R0ContentFormatField.MARKDOWN, + **kwargs + ): + """ + :param content: The content to convert to a note. See the `content_format` field for supported formats. + :type content: str + :param name: The name for the created note. The `.boxnote` extension is appended automatically. + :type name: str + :param content_format: Format of the content to convert., defaults to NotesConvertRequestBodyV2026R0ContentFormatField.MARKDOWN + :type content_format: NotesConvertRequestBodyV2026R0ContentFormatField, optional + """ + super().__init__(**kwargs) + self.content = content + self.parent = parent + self.name = name + self.content_format = content_format diff --git a/box_sdk_gen/schemas/v2026_r0/notes_convert_response_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/notes_convert_response_v2026_r0.py new file mode 100644 index 000000000..76ccea3f4 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/notes_convert_response_v2026_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class NotesConvertResponseV2026R0TypeField(str, Enum): + FILE = 'file' + + +class NotesConvertResponseV2026R0(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + id: str, + *, + type: NotesConvertResponseV2026R0TypeField = NotesConvertResponseV2026R0TypeField.FILE, + **kwargs + ): + """ + :param id: Box file ID of the created `.boxnote` file. + :type id: str + :param type: The Box resource type; always `file` for a Box file., defaults to NotesConvertResponseV2026R0TypeField.FILE + :type type: NotesConvertResponseV2026R0TypeField, optional + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/docs/README.md b/docs/README.md index 16e8f70b2..e9d057eef 100644 --- a/docs/README.md +++ b/docs/README.md @@ -61,6 +61,7 @@ the SDK are available by topic: - [Metadata cascade policies](metadata_cascade_policies.md) - [Metadata taxonomies](metadata_taxonomies.md) - [Metadata templates](metadata_templates.md) +- [Notes](notes.md) - [Recent items](recent_items.md) - [Retention policies](retention_policies.md) - [Retention policy assignments](retention_policy_assignments.md) diff --git a/docs/notes.md b/docs/notes.md new file mode 100644 index 000000000..e8127a282 --- /dev/null +++ b/docs/notes.md @@ -0,0 +1,43 @@ +# NotesManager + +- [Convert content to Box Note](#convert-content-to-box-note) + +## Convert content to Box Note + +Creates a Box Note (`.boxnote` file) from supported source content. See the `content_format` field for supported formats. + +This operation is performed by calling function `create_note_convert_v2026_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2026.0/post-notes-convert/). + + + +```python +downscoped_client.notes.create_note_convert_v2026_r0( + markdown_content, + FolderReferenceV2026R0(id="0"), + note_name, + content_format=CreateNoteConvertV2026R0ContentFormat.MARKDOWN, +) +``` + +### Arguments + +- content `str` + - The content to convert to a note. See the `content_format` field for supported formats. +- content_format `CreateNoteConvertV2026R0ContentFormat` + - Format of the content to convert. +- parent `FolderReferenceV2026R0` +- name `str` + - The name for the created note. The `.boxnote` extension is appended automatically. +- box_version `BoxVersionHeaderV2026R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `NotesConvertResponseV2026R0`. + +The note was created successfully. diff --git a/test/notes.py b/test/notes.py new file mode 100644 index 000000000..6b0139436 --- /dev/null +++ b/test/notes.py @@ -0,0 +1,52 @@ +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.schemas.access_token import AccessToken + +from box_sdk_gen.schemas.v2026_r0.notes_convert_response_v2026_r0 import ( + NotesConvertResponseV2026R0, +) + +from box_sdk_gen.managers.notes import CreateNoteConvertV2026R0ContentFormat + +from box_sdk_gen.schemas.v2026_r0.folder_reference_v2026_r0 import ( + FolderReferenceV2026R0, +) + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import get_env_var + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.box.developer_token_auth import BoxDeveloperTokenAuth + +from test.commons import get_default_client_with_user_subject + +client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) + + +def testConvertMarkdownToBoxNote(): + note_name: str = get_uuid() + markdown_content: str = r"""# Heading + +Some text""" + downscoped_token: AccessToken = client.auth.downscope_token(['item_upload']) + downscoped_client: BoxClient = BoxClient( + auth=BoxDeveloperTokenAuth(token=downscoped_token.access_token) + ) + response: NotesConvertResponseV2026R0 = ( + downscoped_client.notes.create_note_convert_v2026_r0( + markdown_content, + FolderReferenceV2026R0(id='0'), + note_name, + content_format=CreateNoteConvertV2026R0ContentFormat.MARKDOWN, + ) + ) + assert not response.id == '' + assert to_string(response.type) == 'file' + file: FileFull = client.files.get_file_by_id(response.id) + assert file.name == ''.join([note_name, '.boxnote']) + assert file.parent.id == '0' + client.files.delete_file_by_id(response.id) From be8ce9a3250c2f7e98693f2e0ce14ff5f7a6b5e7 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 27 May 2026 11:44:54 +0200 Subject: [PATCH 118/139] test: remove token downscope from notes test (box/box-codegen#957) (#1460) --- .codegen.json | 2 +- docs/notes.md | 2 +- test/notes.py | 20 +++++--------------- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/.codegen.json b/.codegen.json index 8ea504285..ecc667a99 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "5a7add6", "specHash": "d028758", "version": "10.10.0" } +{ "engineHash": "2e559a0", "specHash": "d028758", "version": "10.10.0" } diff --git a/docs/notes.md b/docs/notes.md index e8127a282..c4f482b3b 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -14,7 +14,7 @@ See the endpoint docs at ```python -downscoped_client.notes.create_note_convert_v2026_r0( +client.notes.create_note_convert_v2026_r0( markdown_content, FolderReferenceV2026R0(id="0"), note_name, diff --git a/test/notes.py b/test/notes.py index 6b0139436..f5fd8a803 100644 --- a/test/notes.py +++ b/test/notes.py @@ -1,7 +1,5 @@ from box_sdk_gen.internal.utils import to_string -from box_sdk_gen.schemas.access_token import AccessToken - from box_sdk_gen.schemas.v2026_r0.notes_convert_response_v2026_r0 import ( NotesConvertResponseV2026R0, ) @@ -20,8 +18,6 @@ from box_sdk_gen.client import BoxClient -from box_sdk_gen.box.developer_token_auth import BoxDeveloperTokenAuth - from test.commons import get_default_client_with_user_subject client: BoxClient = get_default_client_with_user_subject(get_env_var('USER_ID')) @@ -32,17 +28,11 @@ def testConvertMarkdownToBoxNote(): markdown_content: str = r"""# Heading Some text""" - downscoped_token: AccessToken = client.auth.downscope_token(['item_upload']) - downscoped_client: BoxClient = BoxClient( - auth=BoxDeveloperTokenAuth(token=downscoped_token.access_token) - ) - response: NotesConvertResponseV2026R0 = ( - downscoped_client.notes.create_note_convert_v2026_r0( - markdown_content, - FolderReferenceV2026R0(id='0'), - note_name, - content_format=CreateNoteConvertV2026R0ContentFormat.MARKDOWN, - ) + response: NotesConvertResponseV2026R0 = client.notes.create_note_convert_v2026_r0( + markdown_content, + FolderReferenceV2026R0(id='0'), + note_name, + content_format=CreateNoteConvertV2026R0ContentFormat.MARKDOWN, ) assert not response.id == '' assert to_string(response.type) == 'file' From 2d8c1b35bfd60e6005cf7ce58c943d4e09b554f7 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 3 Jun 2026 10:24:20 +0200 Subject: [PATCH 119/139] chore: release version 10.11.0 (#1468) --- .codegen.json | 2 +- CHANGELOG.md | 7 +++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index ecc667a99..0c9014d92 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "2e559a0", "specHash": "d028758", "version": "10.10.0" } +{ "engineHash": "2e559a0", "specHash": "d028758", "version": "10.11.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f9128601c..81c7ddf45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.11.0](https://github.com/box/box-python-sdk/compare/v10.10.0...v10.11.0) (2026-06-03) + + +### New Features and Enhancements + +* Add Convert Note API (box/box-codegen[#955](https://github.com/box/box-python-sdk/issues/955)) ([#1458](https://github.com/box/box-python-sdk/issues/1458)) ([1215d52](https://github.com/box/box-python-sdk/commit/1215d52b80e4ab833c6d51615cfa2e60114f9326)) + ## [10.10.0](https://github.com/box/box-python-sdk/compare/v10.9.0...v10.10.0) (2026-05-20) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 196cf7e8d..3e1f94e44 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.10.0' +__version__ = '10.11.0' From 3319b4a96259d8727a590831e445739fe0d0310f Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:33:06 +0200 Subject: [PATCH 120/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1474) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 0c9014d92..88f25b8da 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "2e559a0", "specHash": "d028758", "version": "10.11.0" } +{ "engineHash": "b8db85c", "specHash": "d028758", "version": "10.11.0" } From d2ff760a62fd1e4df31312d12af891e9e4862728 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:00:28 +0200 Subject: [PATCH 121/139] feat: Expose `errorCode` in sign request schemas (box/box-openapi#601) (#1484) --- .codegen.json | 2 +- box_sdk_gen/schemas/sign_request.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 88f25b8da..d4c6916aa 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "b8db85c", "specHash": "d028758", "version": "10.11.0" } +{ "engineHash": "a87c352", "specHash": "ded99bf", "version": "10.11.0" } diff --git a/box_sdk_gen/schemas/sign_request.py b/box_sdk_gen/schemas/sign_request.py index 9535cdb92..be46f1267 100644 --- a/box_sdk_gen/schemas/sign_request.py +++ b/box_sdk_gen/schemas/sign_request.py @@ -85,6 +85,7 @@ def __init__( short_id: Optional[str] = None, created_at: Optional[DateTime] = None, finished_at: Optional[DateTime] = None, + error_code: Optional[str] = None, sender_email: Optional[str] = None, sender_id: Optional[int] = None, is_document_preparation_needed: Optional[bool] = None, @@ -136,6 +137,9 @@ def __init__( :type created_at: Optional[DateTime], optional :param finished_at: Timestamp indicating when all signing actions completed., defaults to None :type finished_at: Optional[DateTime], optional + :param error_code: When the sign request is in an error state, identifies the specific + reason. Null when no error code applies., defaults to None + :type error_code: Optional[str], optional :param sender_email: The email address of the sender of the sign request., defaults to None :type sender_email: Optional[str], optional :param sender_id: The user ID of the sender of the sign request., defaults to None @@ -198,5 +202,6 @@ def __init__( self.short_id = short_id self.created_at = created_at self.finished_at = finished_at + self.error_code = error_code self.sender_email = sender_email self.sender_id = sender_id From ba4f5ab5f0b9ad9d5a72fdd9f069fe4c35807986 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:03:26 +0200 Subject: [PATCH 122/139] feat: Add new parameters to ai extract structured method (box/box-openapi#604) (#1485) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/managers/ai.py | 31 ++++++++++- box_sdk_gen/managers/user_collaborations.py | 12 +--- box_sdk_gen/schemas/__init__.py | 16 +++++- .../schemas/ai_extract_field_option.py | 13 +++++ box_sdk_gen/schemas/ai_extract_structured.py | 31 ++++++++++- box_sdk_gen/schemas/ai_extract_sub_field.py | 55 +++++++++++++++++++ box_sdk_gen/schemas/ai_options_rules.py | 28 ++++++++++ .../schemas/ai_taxonomy_file_reference.py | 36 ++++++++++++ box_sdk_gen/schemas/ai_taxonomy_reference.py | 36 ++++++++++++ box_sdk_gen/schemas/ai_taxonomy_source.py | 9 +++ box_sdk_gen/schemas/collaboration.py | 2 +- box_sdk_gen/schemas/user_collaborations.py | 2 +- docs/ai.md | 2 + docs/user_collaborations.md | 8 +-- 15 files changed, 262 insertions(+), 21 deletions(-) create mode 100644 box_sdk_gen/schemas/ai_extract_field_option.py create mode 100644 box_sdk_gen/schemas/ai_extract_sub_field.py create mode 100644 box_sdk_gen/schemas/ai_options_rules.py create mode 100644 box_sdk_gen/schemas/ai_taxonomy_file_reference.py create mode 100644 box_sdk_gen/schemas/ai_taxonomy_reference.py create mode 100644 box_sdk_gen/schemas/ai_taxonomy_source.py diff --git a/.codegen.json b/.codegen.json index d4c6916aa..886dff6c1 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "a87c352", "specHash": "ded99bf", "version": "10.11.0" } +{ "engineHash": "a87c352", "specHash": "0ac3d31", "version": "10.11.0" } diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index 6a684a478..675ae5df2 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -14,6 +14,10 @@ from box_sdk_gen.serialization.json import deserialize +from box_sdk_gen.schemas.ai_extract_sub_field import AiExtractSubField + +from box_sdk_gen.schemas.ai_options_rules import AiOptionsRules + from box_sdk_gen.schemas.ai_item_ask import AiItemAsk from box_sdk_gen.schemas.ai_dialogue_history import AiDialogueHistory @@ -40,6 +44,12 @@ from box_sdk_gen.schemas.ai_extract_structured_agent import AiExtractStructuredAgent +from box_sdk_gen.schemas.ai_taxonomy_reference import AiTaxonomyReference + +from box_sdk_gen.schemas.ai_taxonomy_file_reference import AiTaxonomyFileReference + +from box_sdk_gen.schemas.ai_taxonomy_source import AiTaxonomySource + from box_sdk_gen.schemas.ai_response_full import AiResponseFull from box_sdk_gen.schemas.client_error import ClientError @@ -185,6 +195,10 @@ def __init__( prompt: Optional[str] = None, type: Optional[str] = None, options: Optional[List[CreateAiExtractStructuredFieldsOptionsField]] = None, + fields: Optional[List[AiExtractSubField]] = None, + taxonomy_key: Optional[str] = None, + namespace: Optional[str] = None, + options_rules: Optional[AiOptionsRules] = None, **kwargs ): """ @@ -196,10 +210,16 @@ def __init__( :type display_name: Optional[str], optional :param prompt: The context about the key that may include how to find and format it., defaults to None :type prompt: Optional[str], optional - :param type: The type of the field. It can include but is not limited to `string`, `float`, `date`, `enum`, and `multiSelect`., defaults to None + :param type: The type of the field. It can include but is not limited to `string`, `float`, `date`, `enum`, `multiSelect`,`taxonomy`, `struct`, and `table`., defaults to None :type type: Optional[str], optional :param options: A list of options for this field. This is most often used in combination with the `enum` and `multiSelect` field types., defaults to None :type options: Optional[List[CreateAiExtractStructuredFieldsOptionsField]], optional + :param fields: The nested fields for this field. Used with `struct` and `table` field types to define the nested structure., defaults to None + :type fields: Optional[List[AiExtractSubField]], optional + :param taxonomy_key: The identifier for a taxonomy, which corresponds to the `key` of the taxonomy source. Required if using `taxonomy` type field., defaults to None + :type taxonomy_key: Optional[str], optional + :param namespace: The namespace of the taxonomy source. Required if using `taxonomy` type field from an existing taxonomy., defaults to None + :type namespace: Optional[str], optional """ super().__init__(**kwargs) self.key = key @@ -208,6 +228,10 @@ def __init__( self.prompt = prompt self.type = type self.options = options + self.fields = fields + self.taxonomy_key = taxonomy_key + self.namespace = namespace + self.options_rules = options_rules class AiManager: @@ -428,6 +452,7 @@ def create_ai_extract_structured( ai_agent: Optional[AiExtractStructuredAgent] = None, include_confidence_score: Optional[bool] = None, include_reference: Optional[bool] = None, + taxonomy_sources: Optional[List[AiTaxonomySource]] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> AiExtractStructuredResponse: """ @@ -456,6 +481,9 @@ def create_ai_extract_structured( :type include_confidence_score: Optional[bool], optional :param include_reference: A flag to indicate whether references for every extracted field should be returned., defaults to None :type include_reference: Optional[bool], optional + :param taxonomy_sources: The taxonomy sources to be used for the structured extraction. They can either be an existing file or a taxonomy. + For your request to work, `fields` must also be provided. `taxonomy_sources` is not supported with `metadata_template`., defaults to None + :type taxonomy_sources: Optional[List[AiTaxonomySource]], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ @@ -468,6 +496,7 @@ def create_ai_extract_structured( 'ai_agent': ai_agent, 'include_confidence_score': include_confidence_score, 'include_reference': include_reference, + 'taxonomy_sources': taxonomy_sources, } headers_map: Dict[str, str] = prepare_params({**extra_headers}) response: FetchResponse = self.network_session.network_client.fetch( diff --git a/box_sdk_gen/managers/user_collaborations.py b/box_sdk_gen/managers/user_collaborations.py index 4c2bae562..3728c07aa 100644 --- a/box_sdk_gen/managers/user_collaborations.py +++ b/box_sdk_gen/managers/user_collaborations.py @@ -350,19 +350,13 @@ def create_collaboration( this endpoint is dependent on the group's ability to be invited. - If collaboration is in `pending` status, the following fields + If collaboration is in `pending` status, field `name` is redacted when: - are redacted: + - a collaboration was created using `user_id`, - - `login` and `name` are hidden if a collaboration was created - - - using `user_id`, - - - - `name` is hidden if a collaboration was created using `login`. + - a collaboration was created using `login`. :param item: The item to attach the comment to. :type item: CreateCollaborationItem diff --git a/box_sdk_gen/schemas/__init__.py b/box_sdk_gen/schemas/__init__.py index 668de48cf..4641e7c34 100644 --- a/box_sdk_gen/schemas/__init__.py +++ b/box_sdk_gen/schemas/__init__.py @@ -10,6 +10,10 @@ from box_sdk_gen.schemas.ai_dialogue_history import * +from box_sdk_gen.schemas.ai_extract_field_option import * + +from box_sdk_gen.schemas.ai_extract_sub_field import * + from box_sdk_gen.schemas.ai_extract_structured_response import * from box_sdk_gen.schemas.ai_item_base import * @@ -78,8 +82,6 @@ from box_sdk_gen.schemas.ai_extract_structured_agent import * -from box_sdk_gen.schemas.ai_extract_structured import * - from box_sdk_gen.schemas.ai_agent_extract import * from box_sdk_gen.schemas.ai_extract_agent import * @@ -94,6 +96,16 @@ from box_sdk_gen.schemas.ai_agent import * +from box_sdk_gen.schemas.ai_options_rules import * + +from box_sdk_gen.schemas.ai_taxonomy_file_reference import * + +from box_sdk_gen.schemas.ai_taxonomy_reference import * + +from box_sdk_gen.schemas.ai_taxonomy_source import * + +from box_sdk_gen.schemas.ai_extract_structured import * + from box_sdk_gen.schemas.app_item import * from box_sdk_gen.schemas.classification import * diff --git a/box_sdk_gen/schemas/ai_extract_field_option.py b/box_sdk_gen/schemas/ai_extract_field_option.py new file mode 100644 index 000000000..661b9ec99 --- /dev/null +++ b/box_sdk_gen/schemas/ai_extract_field_option.py @@ -0,0 +1,13 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiExtractFieldOption(BaseObject): + def __init__(self, key: str, **kwargs): + """ + :param key: A unique identifier for the option. + :type key: str + """ + super().__init__(**kwargs) + self.key = key diff --git a/box_sdk_gen/schemas/ai_extract_structured.py b/box_sdk_gen/schemas/ai_extract_structured.py index 49f208fce..e87d8e0d9 100644 --- a/box_sdk_gen/schemas/ai_extract_structured.py +++ b/box_sdk_gen/schemas/ai_extract_structured.py @@ -12,10 +12,20 @@ from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured +from box_sdk_gen.schemas.ai_taxonomy_reference import AiTaxonomyReference + +from box_sdk_gen.schemas.ai_taxonomy_file_reference import AiTaxonomyFileReference + from box_sdk_gen.schemas.ai_item_base import AiItemBase +from box_sdk_gen.schemas.ai_extract_sub_field import AiExtractSubField + +from box_sdk_gen.schemas.ai_options_rules import AiOptionsRules + from box_sdk_gen.schemas.ai_extract_structured_agent import AiExtractStructuredAgent +from box_sdk_gen.schemas.ai_taxonomy_source import AiTaxonomySource + from box_sdk_gen.box.errors import BoxSDKError @@ -82,6 +92,10 @@ def __init__( prompt: Optional[str] = None, type: Optional[str] = None, options: Optional[List[AiExtractStructuredFieldsOptionsField]] = None, + fields: Optional[List[AiExtractSubField]] = None, + taxonomy_key: Optional[str] = None, + namespace: Optional[str] = None, + options_rules: Optional[AiOptionsRules] = None, **kwargs ): """ @@ -93,10 +107,16 @@ def __init__( :type display_name: Optional[str], optional :param prompt: The context about the key that may include how to find and format it., defaults to None :type prompt: Optional[str], optional - :param type: The type of the field. It can include but is not limited to `string`, `float`, `date`, `enum`, and `multiSelect`., defaults to None + :param type: The type of the field. It can include but is not limited to `string`, `float`, `date`, `enum`, `multiSelect`,`taxonomy`, `struct`, and `table`., defaults to None :type type: Optional[str], optional :param options: A list of options for this field. This is most often used in combination with the `enum` and `multiSelect` field types., defaults to None :type options: Optional[List[AiExtractStructuredFieldsOptionsField]], optional + :param fields: The nested fields for this field. Used with `struct` and `table` field types to define the nested structure., defaults to None + :type fields: Optional[List[AiExtractSubField]], optional + :param taxonomy_key: The identifier for a taxonomy, which corresponds to the `key` of the taxonomy source. Required if using `taxonomy` type field., defaults to None + :type taxonomy_key: Optional[str], optional + :param namespace: The namespace of the taxonomy source. Required if using `taxonomy` type field from an existing taxonomy., defaults to None + :type namespace: Optional[str], optional """ super().__init__(**kwargs) self.key = key @@ -105,6 +125,10 @@ def __init__( self.prompt = prompt self.type = type self.options = options + self.fields = fields + self.taxonomy_key = taxonomy_key + self.namespace = namespace + self.options_rules = options_rules class AiExtractStructured(BaseObject): @@ -117,6 +141,7 @@ def __init__( ai_agent: Optional[AiExtractStructuredAgent] = None, include_confidence_score: Optional[bool] = None, include_reference: Optional[bool] = None, + taxonomy_sources: Optional[List[AiTaxonomySource]] = None, **kwargs ): """ @@ -132,6 +157,9 @@ def __init__( :type include_confidence_score: Optional[bool], optional :param include_reference: A flag to indicate whether references for every extracted field should be returned., defaults to None :type include_reference: Optional[bool], optional + :param taxonomy_sources: The taxonomy sources to be used for the structured extraction. They can either be an existing file or a taxonomy. + For your request to work, `fields` must also be provided. `taxonomy_sources` is not supported with `metadata_template`., defaults to None + :type taxonomy_sources: Optional[List[AiTaxonomySource]], optional """ super().__init__(**kwargs) self.items = items @@ -140,3 +168,4 @@ def __init__( self.ai_agent = ai_agent self.include_confidence_score = include_confidence_score self.include_reference = include_reference + self.taxonomy_sources = taxonomy_sources diff --git a/box_sdk_gen/schemas/ai_extract_sub_field.py b/box_sdk_gen/schemas/ai_extract_sub_field.py new file mode 100644 index 000000000..2a6c7a948 --- /dev/null +++ b/box_sdk_gen/schemas/ai_extract_sub_field.py @@ -0,0 +1,55 @@ +from typing import Optional + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.ai_extract_field_option import AiExtractFieldOption + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiExtractSubField(BaseObject): + _fields_to_json_mapping: Dict[str, str] = { + 'display_name': 'displayName', + **BaseObject._fields_to_json_mapping, + } + _json_to_fields_mapping: Dict[str, str] = { + 'displayName': 'display_name', + **BaseObject._json_to_fields_mapping, + } + + def __init__( + self, + key: str, + *, + description: Optional[str] = None, + display_name: Optional[str] = None, + prompt: Optional[str] = None, + type: Optional[str] = None, + options: Optional[List[AiExtractFieldOption]] = None, + **kwargs + ): + """ + :param key: A unique identifier for the nested field. + :type key: str + :param description: A description of the nested field., defaults to None + :type description: Optional[str], optional + :param display_name: The display name of the nested field., defaults to None + :type display_name: Optional[str], optional + :param prompt: Context about the nested field that may include how to find and how to format it., defaults to None + :type prompt: Optional[str], optional + :param type: The type of the nested field. Allowed types include `string`, `float`, `date`, `number`, `text`, `boolean`, `enum` and `multiSelect`., defaults to None + :type type: Optional[str], optional + :param options: A list of options for this nested field. Used with `enum` and `multiSelect` types., defaults to None + :type options: Optional[List[AiExtractFieldOption]], optional + """ + super().__init__(**kwargs) + self.key = key + self.description = description + self.display_name = display_name + self.prompt = prompt + self.type = type + self.options = options diff --git a/box_sdk_gen/schemas/ai_options_rules.py b/box_sdk_gen/schemas/ai_options_rules.py new file mode 100644 index 000000000..85b3cd7df --- /dev/null +++ b/box_sdk_gen/schemas/ai_options_rules.py @@ -0,0 +1,28 @@ +from typing import Optional + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiOptionsRules(BaseObject): + def __init__( + self, + *, + multi_select: Optional[bool] = None, + selectable_levels: Optional[List[int]] = None, + **kwargs + ): + """ + :param multi_select: Indicates whether the field is a multi-select field. + If true, the field can have multiple values., defaults to None + :type multi_select: Optional[bool], optional + :param selectable_levels: The selectable levels for the field. + This is used to limit the levels of the taxonomy that can be selected., defaults to None + :type selectable_levels: Optional[List[int]], optional + """ + super().__init__(**kwargs) + self.multi_select = multi_select + self.selectable_levels = selectable_levels diff --git a/box_sdk_gen/schemas/ai_taxonomy_file_reference.py b/box_sdk_gen/schemas/ai_taxonomy_file_reference.py new file mode 100644 index 000000000..62c314852 --- /dev/null +++ b/box_sdk_gen/schemas/ai_taxonomy_file_reference.py @@ -0,0 +1,36 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiTaxonomyFileReferenceTypeField(str, Enum): + FILE = 'file' + + +class AiTaxonomyFileReference(BaseObject): + _discriminator = 'type', {'file'} + + def __init__( + self, + *, + type: Optional[AiTaxonomyFileReferenceTypeField] = None, + taxonomy_key: Optional[str] = None, + id: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of the taxonomy source., defaults to None + :type type: Optional[AiTaxonomyFileReferenceTypeField], optional + :param taxonomy_key: The identifier for a taxonomy, which corresponds to the `taxonomy_key` of the taxonomy source., defaults to None + :type taxonomy_key: Optional[str], optional + :param id: The ID of the taxonomy source. Required if the type is `file` and unsupported if the type is `taxonomy`., defaults to None + :type id: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.taxonomy_key = taxonomy_key + self.id = id diff --git a/box_sdk_gen/schemas/ai_taxonomy_reference.py b/box_sdk_gen/schemas/ai_taxonomy_reference.py new file mode 100644 index 000000000..b1ba008e1 --- /dev/null +++ b/box_sdk_gen/schemas/ai_taxonomy_reference.py @@ -0,0 +1,36 @@ +from enum import Enum + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class AiTaxonomyReferenceTypeField(str, Enum): + TAXONOMY = 'taxonomy' + + +class AiTaxonomyReference(BaseObject): + _discriminator = 'type', {'taxonomy'} + + def __init__( + self, + *, + type: Optional[AiTaxonomyReferenceTypeField] = None, + taxonomy_key: Optional[str] = None, + namespace: Optional[str] = None, + **kwargs + ): + """ + :param type: The type of the taxonomy source., defaults to None + :type type: Optional[AiTaxonomyReferenceTypeField], optional + :param taxonomy_key: The identifier for a taxonomy, which corresponds to the `taxonomy_key` of the taxonomy source., defaults to None + :type taxonomy_key: Optional[str], optional + :param namespace: The namespace of the taxonomy source., defaults to None + :type namespace: Optional[str], optional + """ + super().__init__(**kwargs) + self.type = type + self.taxonomy_key = taxonomy_key + self.namespace = namespace diff --git a/box_sdk_gen/schemas/ai_taxonomy_source.py b/box_sdk_gen/schemas/ai_taxonomy_source.py new file mode 100644 index 000000000..a9e357b0d --- /dev/null +++ b/box_sdk_gen/schemas/ai_taxonomy_source.py @@ -0,0 +1,9 @@ +from typing import Union + +from box_sdk_gen.schemas.ai_taxonomy_reference import AiTaxonomyReference + +from box_sdk_gen.schemas.ai_taxonomy_file_reference import AiTaxonomyFileReference + +from box_sdk_gen.box.errors import BoxSDKError + +AiTaxonomySource = Union[AiTaxonomyReference, AiTaxonomyFileReference] diff --git a/box_sdk_gen/schemas/collaboration.py b/box_sdk_gen/schemas/collaboration.py index ef7ef0bca..cec5073d0 100644 --- a/box_sdk_gen/schemas/collaboration.py +++ b/box_sdk_gen/schemas/collaboration.py @@ -194,7 +194,7 @@ def __init__( shared item., defaults to None :type is_access_only: Optional[bool], optional :param status: The status of the collaboration invitation. If the status - is `pending`, `login` and `name` return an empty string., defaults to None + is `pending`, `name` returns an empty string., defaults to None :type status: Optional[CollaborationStatusField], optional :param acknowledged_at: When the `status` of the collaboration object changed to `accepted` or `rejected`., defaults to None diff --git a/box_sdk_gen/schemas/user_collaborations.py b/box_sdk_gen/schemas/user_collaborations.py index 056d1d021..4bcc677c3 100644 --- a/box_sdk_gen/schemas/user_collaborations.py +++ b/box_sdk_gen/schemas/user_collaborations.py @@ -25,7 +25,7 @@ def __init__( :type id: str :param name: The display name of this user. If the collaboration status is `pending`, an empty string is returned., defaults to None :type name: Optional[str], optional - :param login: The primary email address of this user. If the collaboration status is `pending`, an empty string is returned., defaults to None + :param login: The primary email address of this user. If the collaboration status is `pending`, a login value is returned., defaults to None :type login: Optional[str], optional :param is_active: If set to `false`, the user is either deactivated or deleted., defaults to None :type is_active: Optional[bool], optional diff --git a/docs/ai.md b/docs/ai.md index aabdf4acf..b8424f126 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -264,6 +264,8 @@ client.ai.create_ai_extract_structured( - A flag to indicate whether confidence scores for every extracted field should be returned. - include_reference `Optional[bool]` - A flag to indicate whether references for every extracted field should be returned. +- taxonomy_sources `Optional[List[AiTaxonomySource]]` + - The taxonomy sources to be used for the structured extraction. They can either be an existing file or a taxonomy. For your request to work, `fields` must also be provided. `taxonomy_sources` is not supported with `metadata_template`. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/user_collaborations.md b/docs/user_collaborations.md index 2884021a8..3408b9c7a 100644 --- a/docs/user_collaborations.md +++ b/docs/user_collaborations.md @@ -117,12 +117,10 @@ group IDs. If a collaboration is being created with a group, access to this endpoint is dependent on the group's ability to be invited. -If collaboration is in `pending` status, the following fields -are redacted: +If collaboration is in `pending` status, field `name` is redacted when: -- `login` and `name` are hidden if a collaboration was created - using `user_id`, -- `name` is hidden if a collaboration was created using `login`. +- a collaboration was created using `user_id`, +- a collaboration was created using `login`. This operation is performed by calling function `create_collaboration`. From fa0bef9ba2ecd43a6490f9f176c45888e1ee03a5 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:37:45 +0200 Subject: [PATCH 123/139] feat: Add new event types (box/box-openapi#1703) (box/box-openapi#605) (#1486) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/managers/ai.py | 8 +++---- box_sdk_gen/managers/events.py | 38 ++++++++++++++++++++++++++++++ box_sdk_gen/schemas/ai_ask.py | 4 ++-- box_sdk_gen/schemas/ai_text_gen.py | 4 ++-- box_sdk_gen/schemas/event.py | 19 +++++++++++++++ docs/ai.md | 4 ++-- 7 files changed, 68 insertions(+), 11 deletions(-) diff --git a/.codegen.json b/.codegen.json index 886dff6c1..726325351 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "a87c352", "specHash": "0ac3d31", "version": "10.11.0" } +{ "engineHash": "a87c352", "specHash": "dd7f7a9", "version": "10.11.0" } diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index 675ae5df2..aafc29b3c 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -259,8 +259,8 @@ def create_ai_ask( ) -> Optional[AiResponseFull]: """ Sends an AI request to supported LLMs and returns an answer specifically focused on the user's question given the provided context. - :param mode: Box AI handles text documents with text representations up to 1MB in size, or a maximum of 25 files, - whichever comes first. If the text file size exceeds 1MB, the first 1MB of text representation will be processed. + :param mode: Box AI handles text documents with text representations up to 2MB in size, or a maximum of 25 files, + whichever comes first. If the text file size exceeds 2MB, the first 2MB of text representation will be processed. Box AI handles image documents with a resolution of 1024 x 1024 pixels, with a maximum of 5 images or 5 pages for multi-page images. If the number of image or image pages exceeds 5, the first 5 images or pages will be processed. If you set mode parameter to `single_item_qa`, the items array can have one element only. @@ -322,8 +322,8 @@ def create_ai_text_gen( :param items: The items to be processed by the LLM, often files. The array can include **exactly one** element. - **Note**: Box AI handles documents with text representations up to 1MB in size. - If the file size exceeds 1MB, the first 1MB of text representation will be processed. + **Note**: Box AI handles documents with text representations up to 2MB in size. + If the file size exceeds 2MB, the first 2MB of text representation will be processed. :type items: List[CreateAiTextGenItems] :param dialogue_history: The history of prompts and answers previously passed to the LLM. This parameter provides the additional context to the LLM when generating the response., defaults to None :type dialogue_history: Optional[List[AiDialogueHistory]], optional diff --git a/box_sdk_gen/managers/events.py b/box_sdk_gen/managers/events.py index e929f8d83..9d3243373 100644 --- a/box_sdk_gen/managers/events.py +++ b/box_sdk_gen/managers/events.py @@ -57,6 +57,7 @@ class GetEventsEventType(str, Enum): ADD_DEVICE_ASSOCIATION = 'ADD_DEVICE_ASSOCIATION' ADD_LOGIN_ACTIVITY_DEVICE = 'ADD_LOGIN_ACTIVITY_DEVICE' ADMIN_LOGIN = 'ADMIN_LOGIN' + ADVANCED_FOLDER_SETTINGS_UPDATE = 'ADVANCED_FOLDER_SETTINGS_UPDATE' APPLICATION_CREATED = 'APPLICATION_CREATED' APPLICATION_PUBLIC_KEY_ADDED = 'APPLICATION_PUBLIC_KEY_ADDED' APPLICATION_PUBLIC_KEY_DELETED = 'APPLICATION_PUBLIC_KEY_DELETED' @@ -69,6 +70,7 @@ class GetEventsEventType(str, Enum): COLLABORATION_ROLE_CHANGE = 'COLLABORATION_ROLE_CHANGE' COMMENT_CREATE = 'COMMENT_CREATE' COMMENT_DELETE = 'COMMENT_DELETE' + COMMENT_EDIT = 'COMMENT_EDIT' CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY = ( 'CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY' ) @@ -91,11 +93,19 @@ class GetEventsEventType(str, Enum): EDIT = 'EDIT' EDIT_USER = 'EDIT_USER' EMAIL_ALIAS_CONFIRM = 'EMAIL_ALIAS_CONFIRM' + EMAIL_ALIAS_PRIMARY = 'EMAIL_ALIAS_PRIMARY' EMAIL_ALIAS_REMOVE = 'EMAIL_ALIAS_REMOVE' + EMAIL_UPLOAD_DISABLED = 'EMAIL_UPLOAD_DISABLED' + EMAIL_UPLOAD_ENABLED = 'EMAIL_UPLOAD_ENABLED' ENTERPRISE_APP_AUTHORIZATION_UPDATE = 'ENTERPRISE_APP_AUTHORIZATION_UPDATE' EXTERNAL_COLLAB_SECURITY_SETTINGS = 'EXTERNAL_COLLAB_SECURITY_SETTINGS' FAILED_LOGIN = 'FAILED_LOGIN' + FAVORITE = 'FAVORITE' FILE_MARKED_MALICIOUS = 'FILE_MARKED_MALICIOUS' + FILE_REQUEST_CREATE = 'FILE_REQUEST_CREATE' + FILE_REQUEST_DELETE = 'FILE_REQUEST_DELETE' + FILE_REQUEST_UPDATE = 'FILE_REQUEST_UPDATE' + FILE_VERSION_RESTORE = 'FILE_VERSION_RESTORE' FILE_WATERMARKED_DOWNLOAD = 'FILE_WATERMARKED_DOWNLOAD' GROUP_ADD_ITEM = 'GROUP_ADD_ITEM' GROUP_ADD_USER = 'GROUP_ADD_USER' @@ -104,6 +114,7 @@ class GetEventsEventType(str, Enum): GROUP_EDITED = 'GROUP_EDITED' GROUP_REMOVE_ITEM = 'GROUP_REMOVE_ITEM' GROUP_REMOVE_USER = 'GROUP_REMOVE_USER' + ILLEGAL_ITEM_OWNERSHIP_TRANSFER_BY_USER = 'ILLEGAL_ITEM_OWNERSHIP_TRANSFER_BY_USER' ITEM_EMAIL_SEND = 'ITEM_EMAIL_SEND' ITEM_MODIFY = 'ITEM_MODIFY' ITEM_OPEN = 'ITEM_OPEN' @@ -117,6 +128,9 @@ class GetEventsEventType(str, Enum): LEGAL_HOLD_POLICY_UPDATE = 'LEGAL_HOLD_POLICY_UPDATE' LOCK = 'LOCK' LOGIN = 'LOGIN' + METADATA_CASCADE_POLICY_APPLY = 'METADATA_CASCADE_POLICY_APPLY' + METADATA_CASCADE_POLICY_CREATE = 'METADATA_CASCADE_POLICY_CREATE' + METADATA_INSTANCE_COPY = 'METADATA_INSTANCE_COPY' METADATA_INSTANCE_CREATE = 'METADATA_INSTANCE_CREATE' METADATA_INSTANCE_DELETE = 'METADATA_INSTANCE_DELETE' METADATA_INSTANCE_UPDATE = 'METADATA_INSTANCE_UPDATE' @@ -126,6 +140,7 @@ class GetEventsEventType(str, Enum): MOVE = 'MOVE' NEW_USER = 'NEW_USER' OAUTH2_ACCESS_TOKEN_REVOKE = 'OAUTH2_ACCESS_TOKEN_REVOKE' + OAUTH2_REFRESH_TOKEN_REVOKE = 'OAUTH2_REFRESH_TOKEN_REVOKE' PREVIEW = 'PREVIEW' REMOVE_DEVICE_ASSOCIATION = 'REMOVE_DEVICE_ASSOCIATION' REMOVE_LOGIN_ACTIVITY_DEVICE = 'REMOVE_LOGIN_ACTIVITY_DEVICE' @@ -171,6 +186,7 @@ class GetEventsEventType(str, Enum): TERMS_OF_SERVICE_ACCEPT = 'TERMS_OF_SERVICE_ACCEPT' TERMS_OF_SERVICE_REJECT = 'TERMS_OF_SERVICE_REJECT' UNDELETE = 'UNDELETE' + UNFAVORITE = 'UNFAVORITE' UNLOCK = 'UNLOCK' UNSHARE = 'UNSHARE' UPDATE_COLLABORATION_EXPIRATION = 'UPDATE_COLLABORATION_EXPIRATION' @@ -181,6 +197,9 @@ class GetEventsEventType(str, Enum): ) WATERMARK_LABEL_CREATE = 'WATERMARK_LABEL_CREATE' WATERMARK_LABEL_DELETE = 'WATERMARK_LABEL_DELETE' + WORKFLOW_AUTOMATION_CREATE = 'WORKFLOW_AUTOMATION_CREATE' + WORKFLOW_AUTOMATION_DELETE = 'WORKFLOW_AUTOMATION_DELETE' + WORKFLOW_AUTOMATION_UPDATE = 'WORKFLOW_AUTOMATION_UPDATE' class GetEventStreamQueryParamsStreamTypeField(str, Enum): @@ -197,6 +216,7 @@ class GetEventStreamQueryParamsEventTypeField(str, Enum): ADD_DEVICE_ASSOCIATION = 'ADD_DEVICE_ASSOCIATION' ADD_LOGIN_ACTIVITY_DEVICE = 'ADD_LOGIN_ACTIVITY_DEVICE' ADMIN_LOGIN = 'ADMIN_LOGIN' + ADVANCED_FOLDER_SETTINGS_UPDATE = 'ADVANCED_FOLDER_SETTINGS_UPDATE' APPLICATION_CREATED = 'APPLICATION_CREATED' APPLICATION_PUBLIC_KEY_ADDED = 'APPLICATION_PUBLIC_KEY_ADDED' APPLICATION_PUBLIC_KEY_DELETED = 'APPLICATION_PUBLIC_KEY_DELETED' @@ -209,6 +229,7 @@ class GetEventStreamQueryParamsEventTypeField(str, Enum): COLLABORATION_ROLE_CHANGE = 'COLLABORATION_ROLE_CHANGE' COMMENT_CREATE = 'COMMENT_CREATE' COMMENT_DELETE = 'COMMENT_DELETE' + COMMENT_EDIT = 'COMMENT_EDIT' CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY = ( 'CONTENT_WORKFLOW_ABNORMAL_DOWNLOAD_ACTIVITY' ) @@ -231,11 +252,19 @@ class GetEventStreamQueryParamsEventTypeField(str, Enum): EDIT = 'EDIT' EDIT_USER = 'EDIT_USER' EMAIL_ALIAS_CONFIRM = 'EMAIL_ALIAS_CONFIRM' + EMAIL_ALIAS_PRIMARY = 'EMAIL_ALIAS_PRIMARY' EMAIL_ALIAS_REMOVE = 'EMAIL_ALIAS_REMOVE' + EMAIL_UPLOAD_DISABLED = 'EMAIL_UPLOAD_DISABLED' + EMAIL_UPLOAD_ENABLED = 'EMAIL_UPLOAD_ENABLED' ENTERPRISE_APP_AUTHORIZATION_UPDATE = 'ENTERPRISE_APP_AUTHORIZATION_UPDATE' EXTERNAL_COLLAB_SECURITY_SETTINGS = 'EXTERNAL_COLLAB_SECURITY_SETTINGS' FAILED_LOGIN = 'FAILED_LOGIN' + FAVORITE = 'FAVORITE' FILE_MARKED_MALICIOUS = 'FILE_MARKED_MALICIOUS' + FILE_REQUEST_CREATE = 'FILE_REQUEST_CREATE' + FILE_REQUEST_DELETE = 'FILE_REQUEST_DELETE' + FILE_REQUEST_UPDATE = 'FILE_REQUEST_UPDATE' + FILE_VERSION_RESTORE = 'FILE_VERSION_RESTORE' FILE_WATERMARKED_DOWNLOAD = 'FILE_WATERMARKED_DOWNLOAD' GROUP_ADD_ITEM = 'GROUP_ADD_ITEM' GROUP_ADD_USER = 'GROUP_ADD_USER' @@ -244,6 +273,7 @@ class GetEventStreamQueryParamsEventTypeField(str, Enum): GROUP_EDITED = 'GROUP_EDITED' GROUP_REMOVE_ITEM = 'GROUP_REMOVE_ITEM' GROUP_REMOVE_USER = 'GROUP_REMOVE_USER' + ILLEGAL_ITEM_OWNERSHIP_TRANSFER_BY_USER = 'ILLEGAL_ITEM_OWNERSHIP_TRANSFER_BY_USER' ITEM_EMAIL_SEND = 'ITEM_EMAIL_SEND' ITEM_MODIFY = 'ITEM_MODIFY' ITEM_OPEN = 'ITEM_OPEN' @@ -257,6 +287,9 @@ class GetEventStreamQueryParamsEventTypeField(str, Enum): LEGAL_HOLD_POLICY_UPDATE = 'LEGAL_HOLD_POLICY_UPDATE' LOCK = 'LOCK' LOGIN = 'LOGIN' + METADATA_CASCADE_POLICY_APPLY = 'METADATA_CASCADE_POLICY_APPLY' + METADATA_CASCADE_POLICY_CREATE = 'METADATA_CASCADE_POLICY_CREATE' + METADATA_INSTANCE_COPY = 'METADATA_INSTANCE_COPY' METADATA_INSTANCE_CREATE = 'METADATA_INSTANCE_CREATE' METADATA_INSTANCE_DELETE = 'METADATA_INSTANCE_DELETE' METADATA_INSTANCE_UPDATE = 'METADATA_INSTANCE_UPDATE' @@ -266,6 +299,7 @@ class GetEventStreamQueryParamsEventTypeField(str, Enum): MOVE = 'MOVE' NEW_USER = 'NEW_USER' OAUTH2_ACCESS_TOKEN_REVOKE = 'OAUTH2_ACCESS_TOKEN_REVOKE' + OAUTH2_REFRESH_TOKEN_REVOKE = 'OAUTH2_REFRESH_TOKEN_REVOKE' PREVIEW = 'PREVIEW' REMOVE_DEVICE_ASSOCIATION = 'REMOVE_DEVICE_ASSOCIATION' REMOVE_LOGIN_ACTIVITY_DEVICE = 'REMOVE_LOGIN_ACTIVITY_DEVICE' @@ -311,6 +345,7 @@ class GetEventStreamQueryParamsEventTypeField(str, Enum): TERMS_OF_SERVICE_ACCEPT = 'TERMS_OF_SERVICE_ACCEPT' TERMS_OF_SERVICE_REJECT = 'TERMS_OF_SERVICE_REJECT' UNDELETE = 'UNDELETE' + UNFAVORITE = 'UNFAVORITE' UNLOCK = 'UNLOCK' UNSHARE = 'UNSHARE' UPDATE_COLLABORATION_EXPIRATION = 'UPDATE_COLLABORATION_EXPIRATION' @@ -321,6 +356,9 @@ class GetEventStreamQueryParamsEventTypeField(str, Enum): ) WATERMARK_LABEL_CREATE = 'WATERMARK_LABEL_CREATE' WATERMARK_LABEL_DELETE = 'WATERMARK_LABEL_DELETE' + WORKFLOW_AUTOMATION_CREATE = 'WORKFLOW_AUTOMATION_CREATE' + WORKFLOW_AUTOMATION_DELETE = 'WORKFLOW_AUTOMATION_DELETE' + WORKFLOW_AUTOMATION_UPDATE = 'WORKFLOW_AUTOMATION_UPDATE' class GetEventStreamQueryParams: diff --git a/box_sdk_gen/schemas/ai_ask.py b/box_sdk_gen/schemas/ai_ask.py index d4a347357..74c9f7b19 100644 --- a/box_sdk_gen/schemas/ai_ask.py +++ b/box_sdk_gen/schemas/ai_ask.py @@ -37,8 +37,8 @@ def __init__( **kwargs ): """ - :param mode: Box AI handles text documents with text representations up to 1MB in size, or a maximum of 25 files, - whichever comes first. If the text file size exceeds 1MB, the first 1MB of text representation will be processed. + :param mode: Box AI handles text documents with text representations up to 2MB in size, or a maximum of 25 files, + whichever comes first. If the text file size exceeds 2MB, the first 2MB of text representation will be processed. Box AI handles image documents with a resolution of 1024 x 1024 pixels, with a maximum of 5 images or 5 pages for multi-page images. If the number of image or image pages exceeds 5, the first 5 images or pages will be processed. If you set mode parameter to `single_item_qa`, the items array can have one element only. diff --git a/box_sdk_gen/schemas/ai_text_gen.py b/box_sdk_gen/schemas/ai_text_gen.py index a994b793a..5a9d7f6f6 100644 --- a/box_sdk_gen/schemas/ai_text_gen.py +++ b/box_sdk_gen/schemas/ai_text_gen.py @@ -62,8 +62,8 @@ def __init__( :param items: The items to be processed by the LLM, often files. The array can include **exactly one** element. - **Note**: Box AI handles documents with text representations up to 1MB in size. - If the file size exceeds 1MB, the first 1MB of text representation will be processed. + **Note**: Box AI handles documents with text representations up to 2MB in size. + If the file size exceeds 2MB, the first 2MB of text representation will be processed. :type items: List[AiTextGenItemsField] :param dialogue_history: The history of prompts and answers previously passed to the LLM. This parameter provides the additional context to the LLM when generating the response., defaults to None :type dialogue_history: Optional[List[AiDialogueHistory]], optional diff --git a/box_sdk_gen/schemas/event.py b/box_sdk_gen/schemas/event.py index ed7b09b69..1e71c0d56 100644 --- a/box_sdk_gen/schemas/event.py +++ b/box_sdk_gen/schemas/event.py @@ -31,6 +31,7 @@ class EventEventTypeField(str, Enum): ADD_DEVICE_ASSOCIATION = 'ADD_DEVICE_ASSOCIATION' ADD_LOGIN_ACTIVITY_DEVICE = 'ADD_LOGIN_ACTIVITY_DEVICE' ADMIN_LOGIN = 'ADMIN_LOGIN' + ADVANCED_FOLDER_SETTINGS_UPDATE = 'ADVANCED_FOLDER_SETTINGS_UPDATE' AI_SECURITY_DETECTION = 'AI_SECURITY_DETECTION' ANNOTATIONV2_CREATE = 'ANNOTATIONV2_CREATE' ANNOTATIONV2_DELETE = 'ANNOTATIONV2_DELETE' @@ -59,6 +60,7 @@ class EventEventTypeField(str, Enum): COLLECTION_UPDATE = 'COLLECTION_UPDATE' COMMENT_CREATE = 'COMMENT_CREATE' COMMENT_DELETE = 'COMMENT_DELETE' + COMMENT_EDIT = 'COMMENT_EDIT' CONTENT_ACCESS = 'CONTENT_ACCESS' CONTENT_RECOVERY_REPORT_CREATE = 'CONTENT_RECOVERY_REPORT_CREATE' CONTENT_RECOVERY_REPORT_DELETE = 'CONTENT_RECOVERY_REPORT_DELETE' @@ -94,13 +96,21 @@ class EventEventTypeField(str, Enum): EDR_CROWDSTRIKE_DRIVE_OUTDATED = 'EDR_CROWDSTRIKE_DRIVE_OUTDATED' EDR_CROWDSTRIKE_NO_BOX_TOOLS = 'EDR_CROWDSTRIKE_NO_BOX_TOOLS' EMAIL_ALIAS_CONFIRM = 'EMAIL_ALIAS_CONFIRM' + EMAIL_ALIAS_PRIMARY = 'EMAIL_ALIAS_PRIMARY' EMAIL_ALIAS_REMOVE = 'EMAIL_ALIAS_REMOVE' + EMAIL_UPLOAD_DISABLED = 'EMAIL_UPLOAD_DISABLED' + EMAIL_UPLOAD_ENABLED = 'EMAIL_UPLOAD_ENABLED' ENABLE_MULTI_FACTOR_AUTH = 'ENABLE_MULTI_FACTOR_AUTH' ENABLE_TWO_FACTOR_AUTH = 'ENABLE_TWO_FACTOR_AUTH' ENTERPRISE_APP_AUTHORIZATION_UPDATE = 'ENTERPRISE_APP_AUTHORIZATION_UPDATE' EXTERNAL_COLLAB_SECURITY_SETTINGS = 'EXTERNAL_COLLAB_SECURITY_SETTINGS' FAILED_LOGIN = 'FAILED_LOGIN' + FAVORITE = 'FAVORITE' FILE_MARKED_MALICIOUS = 'FILE_MARKED_MALICIOUS' + FILE_REQUEST_CREATE = 'FILE_REQUEST_CREATE' + FILE_REQUEST_DELETE = 'FILE_REQUEST_DELETE' + FILE_REQUEST_UPDATE = 'FILE_REQUEST_UPDATE' + FILE_VERSION_RESTORE = 'FILE_VERSION_RESTORE' FILE_WATERMARKED_DOWNLOAD = 'FILE_WATERMARKED_DOWNLOAD' GROUP_ADD_ITEM = 'GROUP_ADD_ITEM' GROUP_ADD_USER = 'GROUP_ADD_USER' @@ -112,6 +122,7 @@ class EventEventTypeField(str, Enum): GROUP_EDITED = 'GROUP_EDITED' GROUP_REMOVE_ITEM = 'GROUP_REMOVE_ITEM' GROUP_REMOVE_USER = 'GROUP_REMOVE_USER' + ILLEGAL_ITEM_OWNERSHIP_TRANSFER_BY_USER = 'ILLEGAL_ITEM_OWNERSHIP_TRANSFER_BY_USER' ITEM_ASSOCIATION_CREATED = 'ITEM_ASSOCIATION_CREATED' ITEM_ASSOCIATION_DELETED = 'ITEM_ASSOCIATION_DELETED' ITEM_ASSOCIATION_UPDATED = 'ITEM_ASSOCIATION_UPDATED' @@ -145,6 +156,9 @@ class EventEventTypeField(str, Enum): LOGIN = 'LOGIN' MASTER_INVITE_ACCEPT = 'MASTER_INVITE_ACCEPT' MASTER_INVITE_REJECT = 'MASTER_INVITE_REJECT' + METADATA_CASCADE_POLICY_APPLY = 'METADATA_CASCADE_POLICY_APPLY' + METADATA_CASCADE_POLICY_CREATE = 'METADATA_CASCADE_POLICY_CREATE' + METADATA_INSTANCE_COPY = 'METADATA_INSTANCE_COPY' METADATA_INSTANCE_CREATE = 'METADATA_INSTANCE_CREATE' METADATA_INSTANCE_DELETE = 'METADATA_INSTANCE_DELETE' METADATA_INSTANCE_UPDATE = 'METADATA_INSTANCE_UPDATE' @@ -154,6 +168,7 @@ class EventEventTypeField(str, Enum): MOVE = 'MOVE' NEW_USER = 'NEW_USER' OAUTH2_ACCESS_TOKEN_REVOKE = 'OAUTH2_ACCESS_TOKEN_REVOKE' + OAUTH2_REFRESH_TOKEN_REVOKE = 'OAUTH2_REFRESH_TOKEN_REVOKE' PREVIEW = 'PREVIEW' REMOVE_DEVICE_ASSOCIATION = 'REMOVE_DEVICE_ASSOCIATION' REMOVE_LOGIN_ACTIVITY_DEVICE = 'REMOVE_LOGIN_ACTIVITY_DEVICE' @@ -230,6 +245,7 @@ class EventEventTypeField(str, Enum): TERMS_OF_SERVICE_ACCEPT = 'TERMS_OF_SERVICE_ACCEPT' TERMS_OF_SERVICE_REJECT = 'TERMS_OF_SERVICE_REJECT' UNDELETE = 'UNDELETE' + UNFAVORITE = 'UNFAVORITE' UNLOCK = 'UNLOCK' UNSHARE = 'UNSHARE' UPDATE_COLLABORATION_EXPIRATION = 'UPDATE_COLLABORATION_EXPIRATION' @@ -240,6 +256,9 @@ class EventEventTypeField(str, Enum): ) WATERMARK_LABEL_CREATE = 'WATERMARK_LABEL_CREATE' WATERMARK_LABEL_DELETE = 'WATERMARK_LABEL_DELETE' + WORKFLOW_AUTOMATION_CREATE = 'WORKFLOW_AUTOMATION_CREATE' + WORKFLOW_AUTOMATION_DELETE = 'WORKFLOW_AUTOMATION_DELETE' + WORKFLOW_AUTOMATION_UPDATE = 'WORKFLOW_AUTOMATION_UPDATE' class Event(BaseObject): diff --git a/docs/ai.md b/docs/ai.md index b8424f126..e41053adc 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -35,7 +35,7 @@ client.ai.create_ai_ask( ### Arguments - mode `CreateAiAskMode` - - Box AI handles text documents with text representations up to 1MB in size, or a maximum of 25 files, whichever comes first. If the text file size exceeds 1MB, the first 1MB of text representation will be processed. Box AI handles image documents with a resolution of 1024 x 1024 pixels, with a maximum of 5 images or 5 pages for multi-page images. If the number of image or image pages exceeds 5, the first 5 images or pages will be processed. If you set mode parameter to `single_item_qa`, the items array can have one element only. Currently Box AI does not support multi-modal requests. If both images and text are sent Box AI will only process the text. + - Box AI handles text documents with text representations up to 2MB in size, or a maximum of 25 files, whichever comes first. If the text file size exceeds 2MB, the first 2MB of text representation will be processed. Box AI handles image documents with a resolution of 1024 x 1024 pixels, with a maximum of 5 images or 5 pages for multi-page images. If the number of image or image pages exceeds 5, the first 5 images or pages will be processed. If you set mode parameter to `single_item_qa`, the items array can have one element only. Currently Box AI does not support multi-modal requests. If both images and text are sent Box AI will only process the text. - prompt `str` - The prompt provided by the client to be answered by the LLM. The prompt's length is limited to 10000 characters. - items `List[AiItemAsk]` @@ -95,7 +95,7 @@ client.ai.create_ai_text_gen( - prompt `str` - The prompt provided by the client to be answered by the LLM. The prompt's length is limited to 10000 characters. - items `List[CreateAiTextGenItems]` - - The items to be processed by the LLM, often files. The array can include **exactly one** element. **Note**: Box AI handles documents with text representations up to 1MB in size. If the file size exceeds 1MB, the first 1MB of text representation will be processed. + - The items to be processed by the LLM, often files. The array can include **exactly one** element. **Note**: Box AI handles documents with text representations up to 2MB in size. If the file size exceeds 2MB, the first 2MB of text representation will be processed. - dialogue_history `Optional[List[AiDialogueHistory]]` - The history of prompts and answers previously passed to the LLM. This parameter provides the additional context to the LLM when generating the response. - ai_agent `Optional[AiTextGenAgent]` From 8b2efa5dd139cf1cb2f274a815f4651d3b15e482 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 11 Jun 2026 10:16:58 +0200 Subject: [PATCH 124/139] test: Expand tests for ai extract structured with new fields (box/box-codegen#952) (#1488) --- .codegen.json | 2 +- docs/ai.md | 34 ++++++++++++++++++++++++++++++++++ test/ai.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 726325351..4d41c88f6 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "a87c352", "specHash": "dd7f7a9", "version": "10.11.0" } +{ "engineHash": "e392e8c", "specHash": "dd7f7a9", "version": "10.11.0" } diff --git a/docs/ai.md b/docs/ai.md index e41053adc..9861dd015 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -245,9 +245,43 @@ client.ai.create_ai_extract_structured( CreateAiExtractStructuredFieldsOptionsField(key="books"), ], ), + CreateAiExtractStructuredFields( + key="address", + display_name="Address", + description="Person address", + type="struct", + prompt="Extract the full mailing address.", + fields=[ + AiExtractSubField(key="street", display_name="Street", type="string"), + AiExtractSubField(key="city", display_name="City", type="string"), + AiExtractSubField(key="state", display_name="State", type="string"), + AiExtractSubField(key="zip", display_name="Zip", type="string"), + AiExtractSubField(key="country", display_name="Country", type="string"), + ], + ), + CreateAiExtractStructuredFields( + key="work_history", + display_name="Work history", + description="Person work history", + type="table", + prompt="Extract each job as a row.", + fields=[ + AiExtractSubField( + key="job_title", display_name="Job title", type="string" + ), + AiExtractSubField(key="company", display_name="Company", type="string"), + AiExtractSubField( + key="start_year", display_name="Start year", type="string" + ), + AiExtractSubField( + key="end_year", display_name="End year", type="string" + ), + ], + ), ], ai_agent=ai_extract_structured_agent_basic_text_config, include_confidence_score=True, + include_reference=True, ) ``` diff --git a/test/ai.py b/test/ai.py index 43fc08459..9ee311dfd 100644 --- a/test/ai.py +++ b/test/ai.py @@ -78,6 +78,8 @@ from box_sdk_gen.schemas.ai_agent_extract_structured import AiAgentExtractStructured +from box_sdk_gen.schemas.ai_extract_sub_field import AiExtractSubField + client: BoxClient = get_default_client() @@ -254,7 +256,7 @@ def testAIExtractStructuredWithFields(): string_to_byte_stream( ''.join( [ - 'My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar. My UUID is ', + 'My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar. I live at 900 Jefferson Ave, Redwood City, CA 94063, US. My work history: Software Engineer at Box from 2020 to 2024. My UUID is ', get_uuid(), ] ) @@ -304,16 +306,62 @@ def testAIExtractStructuredWithFields(): CreateAiExtractStructuredFieldsOptionsField(key='books'), ], ), + CreateAiExtractStructuredFields( + key='address', + display_name='Address', + description='Person address', + type='struct', + prompt='Extract the full mailing address.', + fields=[ + AiExtractSubField( + key='street', display_name='Street', type='string' + ), + AiExtractSubField(key='city', display_name='City', type='string'), + AiExtractSubField(key='state', display_name='State', type='string'), + AiExtractSubField(key='zip', display_name='Zip', type='string'), + AiExtractSubField( + key='country', display_name='Country', type='string' + ), + ], + ), + CreateAiExtractStructuredFields( + key='work_history', + display_name='Work history', + description='Person work history', + type='table', + prompt='Extract each job as a row.', + fields=[ + AiExtractSubField( + key='job_title', display_name='Job title', type='string' + ), + AiExtractSubField( + key='company', display_name='Company', type='string' + ), + AiExtractSubField( + key='start_year', display_name='Start year', type='string' + ), + AiExtractSubField( + key='end_year', display_name='End year', type='string' + ), + ], + ), ], ai_agent=ai_extract_structured_agent_basic_text_config, include_confidence_score=True, + include_reference=True, ) assert not response.confidence_score == None + assert not response.reference == None + assert not response.ai_agent_info == None assert to_string(response.answer.get('hobby')) == to_string(['guitar']) assert to_string(response.answer.get('firstName')) == 'John' assert to_string(response.answer.get('lastName')) == 'Doe' assert to_string(response.answer.get('dateOfBirth')) == '1990-07-04' assert to_string(response.answer.get('age')) == '34' + assert 'Redwood City' in to_string(response.answer.get('address')) + assert 'CA' in to_string(response.answer.get('address')) + assert '94063' in to_string(response.answer.get('address')) + assert 'Box' in to_string(response.answer.get('work_history')) assert response.completion_reason == 'done' client.files.delete_file_by_id(file.id) From d75d61d00b61464307820eddbb4b4064de9a4abc Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:26:19 +0200 Subject: [PATCH 125/139] chore: release version 10.12.0 (#1490) --- .codegen.json | 2 +- CHANGELOG.md | 9 +++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 4d41c88f6..7756a89bb 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "e392e8c", "specHash": "dd7f7a9", "version": "10.11.0" } +{ "engineHash": "e392e8c", "specHash": "dd7f7a9", "version": "10.12.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 81c7ddf45..6a0417cbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.12.0](https://github.com/box/box-python-sdk/compare/v10.11.0...v10.12.0) (2026-06-11) + + +### New Features and Enhancements + +* Add new event types (box/box-openapi[#1703](https://github.com/box/box-python-sdk/issues/1703)) (box/box-openapi[#605](https://github.com/box/box-python-sdk/issues/605)) ([#1486](https://github.com/box/box-python-sdk/issues/1486)) ([fa0bef9](https://github.com/box/box-python-sdk/commit/fa0bef9ba2ecd43a6490f9f176c45888e1ee03a5)) +* Add new parameters to ai extract structured method (box/box-openapi[#604](https://github.com/box/box-python-sdk/issues/604)) ([#1485](https://github.com/box/box-python-sdk/issues/1485)) ([ba4f5ab](https://github.com/box/box-python-sdk/commit/ba4f5ab5f0b9ad9d5a72fdd9f069fe4c35807986)) +* Expose `errorCode` in sign request schemas (box/box-openapi[#601](https://github.com/box/box-python-sdk/issues/601)) ([#1484](https://github.com/box/box-python-sdk/issues/1484)) ([d2ff760](https://github.com/box/box-python-sdk/commit/d2ff760a62fd1e4df31312d12af891e9e4862728)) + ## [10.11.0](https://github.com/box/box-python-sdk/compare/v10.10.0...v10.11.0) (2026-06-03) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 3e1f94e44..3f60f2411 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.11.0' +__version__ = '10.12.0' From 1247aba74b3430fc22e8b709c23ab353ec771bc5 Mon Sep 17 00:00:00 2001 From: Minh Nguyen Cong Date: Wed, 17 Jun 2026 10:30:55 +0200 Subject: [PATCH 126/139] ci: add FOSSA license and dependency scan (#1493) --- .github/workflows/fossa.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/fossa.yml diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml new file mode 100644 index 000000000..3da49d58c --- /dev/null +++ b/.github/workflows/fossa.yml @@ -0,0 +1,22 @@ +name: FOSSA Scan + +on: + push: + branches: [main, combined-sdk] + pull_request: + branches: [main, combined-sdk] + +permissions: + contents: read + +jobs: + fossa: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: fossas/fossa-action@main + with: + api-key: ${{ secrets.FOSSA_API_KEY }} + branch: ${{ github.head_ref || github.ref_name }} From 159c5ac0395d61e5b18fdebf6306eb0b7a121b57 Mon Sep 17 00:00:00 2001 From: Minh Nguyen Cong Date: Tue, 23 Jun 2026 15:21:05 +0200 Subject: [PATCH 127/139] ci: full setup python for fossa scan (#1495) --- .github/workflows/fossa.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 3da49d58c..01efe1db9 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -14,8 +14,16 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e .[dev] - uses: fossas/fossa-action@main with: api-key: ${{ secrets.FOSSA_API_KEY }} From c083ad0af00b962a7224795bbf56ab04d3deffc5 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:48:34 +0200 Subject: [PATCH 128/139] feat: Add new fields to Hubs (box/box-openapi#606) (#1499) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- .github/workflows/fossa.yml | 30 ---------- box_sdk_gen/managers/ai.py | 9 +++ box_sdk_gen/managers/hubs.py | 24 +++++++- box_sdk_gen/managers/sign_requests.py | 5 ++ box_sdk_gen/schemas/ai_ask.py | 3 + box_sdk_gen/schemas/ai_item_ask.py | 14 +++-- box_sdk_gen/schemas/sign_request.py | 5 ++ box_sdk_gen/schemas/sign_request_base.py | 57 ++++++++++--------- .../schemas/sign_request_create_request.py | 5 ++ .../schemas/sign_request_signer_input.py | 11 ++++ box_sdk_gen/schemas/sign_template.py | 4 ++ .../v2025_r0/hub_copy_request_v2025_r0.py | 13 +++-- .../v2025_r0/hub_update_request_v2025_r0.py | 44 +++++++++----- box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py | 16 ++++++ docs/ai.md | 10 +++- docs/ai_studio.md | 6 ++ docs/avatars.md | 2 + docs/chunked_uploads.md | 4 +- docs/docgen.md | 4 ++ docs/docgen_template.md | 1 + docs/events.md | 4 +- docs/files.md | 2 + docs/folders.md | 8 ++- docs/groups.md | 4 +- docs/hubs.md | 4 ++ docs/integration_mappings.md | 8 +++ docs/metadata_cascade_policies.md | 2 +- docs/notes.md | 1 + docs/retention_policies.md | 6 +- docs/search.md | 14 ++--- docs/shield_information_barrier_reports.md | 1 + ...eld_information_barrier_segment_members.md | 1 + ...nformation_barrier_segment_restrictions.md | 1 + docs/shield_information_barrier_segments.md | 1 + docs/shield_lists.md | 2 + docs/sign_requests.md | 3 + docs/task_assignments.md | 2 +- docs/tasks.md | 8 +-- docs/trashed_files.md | 1 + docs/trashed_folders.md | 1 + docs/trashed_web_links.md | 1 + docs/uploads.md | 8 +++ docs/users.md | 2 +- docs/web_links.md | 1 + 45 files changed, 248 insertions(+), 107 deletions(-) delete mode 100644 .github/workflows/fossa.yml diff --git a/.codegen.json b/.codegen.json index 7756a89bb..1947d1b98 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "e392e8c", "specHash": "dd7f7a9", "version": "10.12.0" } +{ "engineHash": "6f9492d", "specHash": "131c54a", "version": "10.12.0" } diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml deleted file mode 100644 index 01efe1db9..000000000 --- a/.github/workflows/fossa.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: FOSSA Scan - -on: - push: - branches: [main, combined-sdk] - pull_request: - branches: [main, combined-sdk] - -permissions: - contents: read - -jobs: - fossa: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e .[dev] - - uses: fossas/fossa-action@main - with: - api-key: ${{ secrets.FOSSA_API_KEY }} - branch: ${{ github.head_ref || github.ref_name }} diff --git a/box_sdk_gen/managers/ai.py b/box_sdk_gen/managers/ai.py index aafc29b3c..0e3926a08 100644 --- a/box_sdk_gen/managers/ai.py +++ b/box_sdk_gen/managers/ai.py @@ -259,6 +259,12 @@ def create_ai_ask( ) -> Optional[AiResponseFull]: """ Sends an AI request to supported LLMs and returns an answer specifically focused on the user's question given the provided context. + + You can ask a question about a single file, several files, or the entire contents of a Box Hub. To search across and ask questions about everything in a Box Hub, send a single item with `type` set to `hubs` and the Hub's ID as the `id`. Box AI answers the question using the indexed content of all files in that Hub. + + + Asking questions about a Box Hub requires Box AI for Hubs to be enabled in the Admin Console before the Hub is created, so that its content is indexed. + :param mode: Box AI handles text documents with text representations up to 2MB in size, or a maximum of 25 files, whichever comes first. If the text file size exceeds 2MB, the first 2MB of text representation will be processed. Box AI handles image documents with a resolution of 1024 x 1024 pixels, with a maximum of 5 images or 5 pages @@ -271,6 +277,9 @@ def create_ai_ask( The prompt's length is limited to 10000 characters. :type prompt: str :param items: The items to be processed by the LLM, often files. + To search across and ask questions about the contents of a Box Hub, + pass a single item with `type` set to `hubs`. See the item `type` + property for details. :type items: List[AiItemAsk] :param dialogue_history: The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response., defaults to None :type dialogue_history: Optional[List[AiDialogueHistory]], optional diff --git a/box_sdk_gen/managers/hubs.py b/box_sdk_gen/managers/hubs.py index 26e34bca1..a919678a1 100644 --- a/box_sdk_gen/managers/hubs.py +++ b/box_sdk_gen/managers/hubs.py @@ -63,6 +63,12 @@ class GetEnterpriseHubsV2025R0Direction(str, Enum): DESC = 'DESC' +class UpdateHubByIdV2025R0CopyHubAccess(str, Enum): + ALL = 'all' + COMPANY = 'company' + NONE = 'none' + + class HubsManager: def __init__( self, @@ -304,6 +310,7 @@ def update_hub_by_id_v2025_r0( can_non_owners_invite: Optional[bool] = None, can_shared_link_be_created: Optional[bool] = None, can_public_shared_link_be_created: Optional[bool] = None, + copy_hub_access: Optional[UpdateHubByIdV2025R0CopyHubAccess] = None, box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubV2025R0: @@ -332,6 +339,12 @@ def update_hub_by_id_v2025_r0( :type can_shared_link_be_created: Optional[bool], optional :param can_public_shared_link_be_created: Indicates if a public shared link can be created for the Box Hub., defaults to None :type can_public_shared_link_be_created: Optional[bool], optional + :param copy_hub_access: Specifies who is allowed to copy the Box Hub. + + * `all` - Any user with access to the Hub can copy it. + * `company` - Only users within the same enterprise as the Hub can copy it. + * `none` - No one can copy the Hub., defaults to None + :type copy_hub_access: Optional[UpdateHubByIdV2025R0CopyHubAccess], optional :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 :type box_version: BoxVersionHeaderV2025R0, optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None @@ -349,6 +362,7 @@ def update_hub_by_id_v2025_r0( 'can_non_owners_invite': can_non_owners_invite, 'can_shared_link_be_created': can_shared_link_be_created, 'can_public_shared_link_be_created': can_public_shared_link_be_created, + 'copy_hub_access': copy_hub_access, } headers_map: Dict[str, str] = prepare_params( {'box-version': to_string(box_version), **extra_headers} @@ -425,6 +439,7 @@ def copy_hub_v2025_r0( *, title: Optional[str] = None, description: Optional[str] = None, + include_items: Optional[bool] = None, box_version: BoxVersionHeaderV2025R0 = BoxVersionHeaderV2025R0._2025_0, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> HubV2025R0: @@ -446,6 +461,9 @@ def copy_hub_v2025_r0( :type title: Optional[str], optional :param description: Description of the Box Hub., defaults to None :type description: Optional[str], optional + :param include_items: If true, the items which the user has Editor or Owner access to in the original Box Hub will be copied to the new Box Hub. + Defaults to false., defaults to None + :type include_items: Optional[bool], optional :param box_version: Version header., defaults to BoxVersionHeaderV2025R0._2025_0 :type box_version: BoxVersionHeaderV2025R0, optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None @@ -453,7 +471,11 @@ def copy_hub_v2025_r0( """ if extra_headers is None: extra_headers = {} - request_body: Dict = {'title': title, 'description': description} + request_body: Dict = { + 'title': title, + 'description': description, + 'include_items': include_items, + } headers_map: Dict[str, str] = prepare_params( {'box-version': to_string(box_version), **extra_headers} ) diff --git a/box_sdk_gen/managers/sign_requests.py b/box_sdk_gen/managers/sign_requests.py index 09e00bf54..cc1dffd75 100644 --- a/box_sdk_gen/managers/sign_requests.py +++ b/box_sdk_gen/managers/sign_requests.py @@ -268,6 +268,7 @@ def create_sign_request( external_id: Union[Optional[str], NullValue] = None, template_id: Union[Optional[str], NullValue] = None, external_system_name: Union[Optional[str], NullValue] = None, + request_flow: Union[Optional[str], NullValue] = None, extra_headers: Optional[Dict[str, Optional[str]]] = None ) -> SignRequest: """ @@ -314,6 +315,9 @@ def create_sign_request( :type template_id: Union[Optional[str], NullValue], optional :param external_system_name: Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`., defaults to None :type external_system_name: Union[Optional[str], NullValue], optional + :param request_flow: The flow type of the sign request. Values can include `standard` or `cfr11`. + When not specified during creation, a default is chosen based on admin settings., defaults to None + :type request_flow: Union[Optional[str], NullValue], optional :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None :type extra_headers: Optional[Dict[str, Optional[str]]], optional """ @@ -337,6 +341,7 @@ def create_sign_request( 'external_id': external_id, 'template_id': template_id, 'external_system_name': external_system_name, + 'request_flow': request_flow, } headers_map: Dict[str, str] = prepare_params({**extra_headers}) response: FetchResponse = self.network_session.network_client.fetch( diff --git a/box_sdk_gen/schemas/ai_ask.py b/box_sdk_gen/schemas/ai_ask.py index 74c9f7b19..9ab0ee957 100644 --- a/box_sdk_gen/schemas/ai_ask.py +++ b/box_sdk_gen/schemas/ai_ask.py @@ -49,6 +49,9 @@ def __init__( The prompt's length is limited to 10000 characters. :type prompt: str :param items: The items to be processed by the LLM, often files. + To search across and ask questions about the contents of a Box Hub, + pass a single item with `type` set to `hubs`. See the item `type` + property for details. :type items: List[AiItemAsk] :param dialogue_history: The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response., defaults to None :type dialogue_history: Optional[List[AiDialogueHistory]], optional diff --git a/box_sdk_gen/schemas/ai_item_ask.py b/box_sdk_gen/schemas/ai_item_ask.py index d2702b88c..b0c3f8bdf 100644 --- a/box_sdk_gen/schemas/ai_item_ask.py +++ b/box_sdk_gen/schemas/ai_item_ask.py @@ -24,12 +24,14 @@ def __init__( **kwargs ): """ - :param id: The ID of the file. - :type id: str - :param type: The type of the item. A `hubs` item must be used as a single item. - :type type: AiItemAskTypeField - :param content: The content of the item, often the text representation., defaults to None - :type content: Optional[str], optional + :param id: The ID of the file, or the ID of the Box Hub when `type` is `hubs`. + :type id: str + :param type: The type of the item. Use `file` to ask a question about a file, or `hubs` to + search across and ask a question about the entire contents of a Box Hub. + A `hubs` item must be the only item in the request. + :type type: AiItemAskTypeField + :param content: The content of the item, often the text representation., defaults to None + :type content: Optional[str], optional """ super().__init__(**kwargs) self.id = id diff --git a/box_sdk_gen/schemas/sign_request.py b/box_sdk_gen/schemas/sign_request.py index be46f1267..1cdf35034 100644 --- a/box_sdk_gen/schemas/sign_request.py +++ b/box_sdk_gen/schemas/sign_request.py @@ -101,6 +101,7 @@ def __init__( external_id: Optional[str] = None, template_id: Optional[str] = None, external_system_name: Optional[str] = None, + request_flow: Optional[str] = None, **kwargs ): """ @@ -170,6 +171,9 @@ def __init__( :type template_id: Optional[str], optional :param external_system_name: Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`., defaults to None :type external_system_name: Optional[str], optional + :param request_flow: The flow type of the sign request. Values can include `standard` or `cfr11`. + When not specified during creation, a default is chosen based on admin settings., defaults to None + :type request_flow: Optional[str], optional """ super().__init__( is_document_preparation_needed=is_document_preparation_needed, @@ -185,6 +189,7 @@ def __init__( external_id=external_id, template_id=template_id, external_system_name=external_system_name, + request_flow=request_flow, **kwargs ) self.type = type diff --git a/box_sdk_gen/schemas/sign_request_base.py b/box_sdk_gen/schemas/sign_request_base.py index 181c97249..abad716d3 100644 --- a/box_sdk_gen/schemas/sign_request_base.py +++ b/box_sdk_gen/schemas/sign_request_base.py @@ -26,35 +26,39 @@ def __init__( external_id: Optional[str] = None, template_id: Optional[str] = None, external_system_name: Optional[str] = None, + request_flow: Optional[str] = None, **kwargs ): """ - :param is_document_preparation_needed: Indicates if the sender should receive a `prepare_url` in the response to complete document preparation using the UI., defaults to None - :type is_document_preparation_needed: Optional[bool], optional - :param redirect_url: When specified, the signature request will be redirected to this url when a document is signed., defaults to None - :type redirect_url: Optional[str], optional - :param declined_redirect_url: The uri that a signer will be redirected to after declining to sign a document., defaults to None - :type declined_redirect_url: Optional[str], optional - :param are_text_signatures_enabled: Disables the usage of signatures generated by typing (text)., defaults to None - :type are_text_signatures_enabled: Optional[bool], optional - :param email_subject: Subject of sign request email. This is cleaned by sign request. If this field is not passed, a default subject will be used., defaults to None - :type email_subject: Optional[str], optional - :param email_message: Message to include in sign request email. The field is cleaned through sanitization of specific characters. However, some html tags are allowed. Links included in the message are also converted to hyperlinks in the email. The message may contain the following html tags including `a`, `abbr`, `acronym`, `b`, `blockquote`, `code`, `em`, `i`, `ul`, `li`, `ol`, and `strong`. Be aware that when the text to html ratio is too high, the email may end up in spam filters. Custom styles on these tags are not allowed. If this field is not passed, a default message will be used., defaults to None - :type email_message: Optional[str], optional - :param are_reminders_enabled: Reminds signers to sign a document on day 3, 8, 13 and 18. Reminders are only sent to outstanding signers., defaults to None - :type are_reminders_enabled: Optional[bool], optional - :param name: Name of the signature request., defaults to None - :type name: Optional[str], optional - :param prefill_tags: When a document contains sign-related tags in the content, you can prefill them using this `prefill_tags` by referencing the 'id' of the tag as the `external_id` field of the prefill tag., defaults to None - :type prefill_tags: Optional[List[SignRequestPrefillTag]], optional - :param days_valid: Set the number of days after which the created signature request will automatically expire if not completed. By default, we do not apply any expiration date on signature requests, and the signature request does not expire., defaults to None - :type days_valid: Optional[int], optional - :param external_id: This can be used to reference an ID in an external system that the sign request is related to., defaults to None - :type external_id: Optional[str], optional - :param template_id: When a signature request is created from a template this field will indicate the id of that template., defaults to None - :type template_id: Optional[str], optional - :param external_system_name: Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`., defaults to None - :type external_system_name: Optional[str], optional + :param is_document_preparation_needed: Indicates if the sender should receive a `prepare_url` in the response to complete document preparation using the UI., defaults to None + :type is_document_preparation_needed: Optional[bool], optional + :param redirect_url: When specified, the signature request will be redirected to this url when a document is signed., defaults to None + :type redirect_url: Optional[str], optional + :param declined_redirect_url: The uri that a signer will be redirected to after declining to sign a document., defaults to None + :type declined_redirect_url: Optional[str], optional + :param are_text_signatures_enabled: Disables the usage of signatures generated by typing (text)., defaults to None + :type are_text_signatures_enabled: Optional[bool], optional + :param email_subject: Subject of sign request email. This is cleaned by sign request. If this field is not passed, a default subject will be used., defaults to None + :type email_subject: Optional[str], optional + :param email_message: Message to include in sign request email. The field is cleaned through sanitization of specific characters. However, some html tags are allowed. Links included in the message are also converted to hyperlinks in the email. The message may contain the following html tags including `a`, `abbr`, `acronym`, `b`, `blockquote`, `code`, `em`, `i`, `ul`, `li`, `ol`, and `strong`. Be aware that when the text to html ratio is too high, the email may end up in spam filters. Custom styles on these tags are not allowed. If this field is not passed, a default message will be used., defaults to None + :type email_message: Optional[str], optional + :param are_reminders_enabled: Reminds signers to sign a document on day 3, 8, 13 and 18. Reminders are only sent to outstanding signers., defaults to None + :type are_reminders_enabled: Optional[bool], optional + :param name: Name of the signature request., defaults to None + :type name: Optional[str], optional + :param prefill_tags: When a document contains sign-related tags in the content, you can prefill them using this `prefill_tags` by referencing the 'id' of the tag as the `external_id` field of the prefill tag., defaults to None + :type prefill_tags: Optional[List[SignRequestPrefillTag]], optional + :param days_valid: Set the number of days after which the created signature request will automatically expire if not completed. By default, we do not apply any expiration date on signature requests, and the signature request does not expire., defaults to None + :type days_valid: Optional[int], optional + :param external_id: This can be used to reference an ID in an external system that the sign request is related to., defaults to None + :type external_id: Optional[str], optional + :param template_id: When a signature request is created from a template this field will indicate the id of that template., defaults to None + :type template_id: Optional[str], optional + :param external_system_name: Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`., defaults to None + :type external_system_name: Optional[str], optional + :param request_flow: The flow type of the sign request. Values can include `standard` or `cfr11`. + When not specified during creation, a default is chosen based on admin settings., defaults to None + :type request_flow: Optional[str], optional """ super().__init__(**kwargs) self.is_document_preparation_needed = is_document_preparation_needed @@ -70,3 +74,4 @@ def __init__( self.external_id = external_id self.template_id = template_id self.external_system_name = external_system_name + self.request_flow = request_flow diff --git a/box_sdk_gen/schemas/sign_request_create_request.py b/box_sdk_gen/schemas/sign_request_create_request.py index 7222098ae..a1d53a5c9 100644 --- a/box_sdk_gen/schemas/sign_request_create_request.py +++ b/box_sdk_gen/schemas/sign_request_create_request.py @@ -44,6 +44,7 @@ def __init__( external_id: Optional[str] = None, template_id: Optional[str] = None, external_system_name: Optional[str] = None, + request_flow: Optional[str] = None, **kwargs ): """ @@ -86,6 +87,9 @@ def __init__( :type template_id: Optional[str], optional :param external_system_name: Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`., defaults to None :type external_system_name: Optional[str], optional + :param request_flow: The flow type of the sign request. Values can include `standard` or `cfr11`. + When not specified during creation, a default is chosen based on admin settings., defaults to None + :type request_flow: Optional[str], optional """ super().__init__( is_document_preparation_needed=is_document_preparation_needed, @@ -101,6 +105,7 @@ def __init__( external_id=external_id, template_id=template_id, external_system_name=external_system_name, + request_flow=request_flow, **kwargs ) self.signers = signers diff --git a/box_sdk_gen/schemas/sign_request_signer_input.py b/box_sdk_gen/schemas/sign_request_signer_input.py index ffb7b1a18..ca2b8d66f 100644 --- a/box_sdk_gen/schemas/sign_request_signer_input.py +++ b/box_sdk_gen/schemas/sign_request_signer_input.py @@ -102,6 +102,8 @@ def __init__( content_type: Optional[SignRequestSignerInputContentTypeField] = None, read_only: Optional[bool] = None, validation: Optional[SignRequestSignerInputValidation] = None, + reason: Optional[str] = None, + is_validated: Optional[bool] = None, document_tag_id: Optional[str] = None, text_value: Optional[str] = None, checkbox_value: Optional[bool] = None, @@ -120,6 +122,13 @@ def __init__( :param validation: Specifies the formatting rules that signers must follow for text field inputs. If set, this validation is mandatory., defaults to None :type validation: Optional[SignRequestSignerInputValidation], optional + :param reason: The reason for the signer's input, applicable to signature or initial content types + in a `cfr11` request flow. The value is `null` when not applicable., defaults to None + :type reason: Optional[str], optional + :param is_validated: Indicates whether the signer's input has been validated through re-authentication. + Applicable only for signature or initial content types in a `cfr11` request flow. + The value is `null` for standard request flows or non-applicable input types., defaults to None + :type is_validated: Optional[bool], optional :param document_tag_id: This references the ID of a specific tag contained in a file of the signature request., defaults to None :type document_tag_id: Optional[str], optional :param text_value: Text prefill value., defaults to None @@ -141,3 +150,5 @@ def __init__( self.content_type = content_type self.read_only = read_only self.validation = validation + self.reason = reason + self.is_validated = is_validated diff --git a/box_sdk_gen/schemas/sign_template.py b/box_sdk_gen/schemas/sign_template.py index f96b30cad..55d60fa57 100644 --- a/box_sdk_gen/schemas/sign_template.py +++ b/box_sdk_gen/schemas/sign_template.py @@ -158,6 +158,7 @@ def __init__( additional_info: Optional[SignTemplateAdditionalInfoField] = None, ready_sign_link: Optional[SignTemplateReadySignLinkField] = None, custom_branding: Optional[SignTemplateCustomBrandingField] = None, + request_flow: Optional[str] = None, **kwargs ): """ @@ -230,6 +231,8 @@ def __init__( :param custom_branding: Custom branding applied to notifications and signature requests., defaults to None :type custom_branding: Optional[SignTemplateCustomBrandingField], optional + :param request_flow: The sign flow of sign requests created from the template. Values can include `standard` or `cfr11`., defaults to None + :type request_flow: Optional[str], optional """ super().__init__(**kwargs) self.type = type @@ -249,3 +252,4 @@ def __init__( self.additional_info = additional_info self.ready_sign_link = ready_sign_link self.custom_branding = custom_branding + self.request_flow = request_flow diff --git a/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py index a3926a9a2..0788c13e1 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_copy_request_v2025_r0.py @@ -11,14 +11,19 @@ def __init__( *, title: Optional[str] = None, description: Optional[str] = None, + include_items: Optional[bool] = None, **kwargs ): """ - :param title: Title of the Box Hub. It cannot be empty and should be less than 50 characters., defaults to None - :type title: Optional[str], optional - :param description: Description of the Box Hub., defaults to None - :type description: Optional[str], optional + :param title: Title of the Box Hub. It cannot be empty and should be less than 50 characters., defaults to None + :type title: Optional[str], optional + :param description: Description of the Box Hub., defaults to None + :type description: Optional[str], optional + :param include_items: If true, the items which the user has Editor or Owner access to in the original Box Hub will be copied to the new Box Hub. + Defaults to false., defaults to None + :type include_items: Optional[bool], optional """ super().__init__(**kwargs) self.title = title self.description = description + self.include_items = include_items diff --git a/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py index cf120b7cd..edaf88895 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_update_request_v2025_r0.py @@ -1,3 +1,5 @@ +from enum import Enum + from typing import Optional from box_sdk_gen.internal.base_object import BaseObject @@ -5,6 +7,12 @@ from box_sdk_gen.box.errors import BoxSDKError +class HubUpdateRequestV2025R0CopyHubAccessField(str, Enum): + ALL = 'all' + COMPANY = 'company' + NONE = 'none' + + class HubUpdateRequestV2025R0(BaseObject): def __init__( self, @@ -16,23 +24,30 @@ def __init__( can_non_owners_invite: Optional[bool] = None, can_shared_link_be_created: Optional[bool] = None, can_public_shared_link_be_created: Optional[bool] = None, + copy_hub_access: Optional[HubUpdateRequestV2025R0CopyHubAccessField] = None, **kwargs ): """ - :param title: Title of the Box Hub. It cannot be empty and should be less than 50 characters., defaults to None - :type title: Optional[str], optional - :param description: Description of the Box Hub., defaults to None - :type description: Optional[str], optional - :param is_ai_enabled: Indicates if AI features are enabled for the Box Hub., defaults to None - :type is_ai_enabled: Optional[bool], optional - :param is_collaboration_restricted_to_enterprise: Indicates if collaboration is restricted to the enterprise., defaults to None - :type is_collaboration_restricted_to_enterprise: Optional[bool], optional - :param can_non_owners_invite: Indicates if non-owners can invite others to the Box Hub., defaults to None - :type can_non_owners_invite: Optional[bool], optional - :param can_shared_link_be_created: Indicates if a shared link can be created for the Box Hub., defaults to None - :type can_shared_link_be_created: Optional[bool], optional - :param can_public_shared_link_be_created: Indicates if a public shared link can be created for the Box Hub., defaults to None - :type can_public_shared_link_be_created: Optional[bool], optional + :param title: Title of the Box Hub. It cannot be empty and should be less than 50 characters., defaults to None + :type title: Optional[str], optional + :param description: Description of the Box Hub., defaults to None + :type description: Optional[str], optional + :param is_ai_enabled: Indicates if AI features are enabled for the Box Hub., defaults to None + :type is_ai_enabled: Optional[bool], optional + :param is_collaboration_restricted_to_enterprise: Indicates if collaboration is restricted to the enterprise., defaults to None + :type is_collaboration_restricted_to_enterprise: Optional[bool], optional + :param can_non_owners_invite: Indicates if non-owners can invite others to the Box Hub., defaults to None + :type can_non_owners_invite: Optional[bool], optional + :param can_shared_link_be_created: Indicates if a shared link can be created for the Box Hub., defaults to None + :type can_shared_link_be_created: Optional[bool], optional + :param can_public_shared_link_be_created: Indicates if a public shared link can be created for the Box Hub., defaults to None + :type can_public_shared_link_be_created: Optional[bool], optional + :param copy_hub_access: Specifies who is allowed to copy the Box Hub. + + * `all` - Any user with access to the Hub can copy it. + * `company` - Only users within the same enterprise as the Hub can copy it. + * `none` - No one can copy the Hub., defaults to None + :type copy_hub_access: Optional[HubUpdateRequestV2025R0CopyHubAccessField], optional """ super().__init__(**kwargs) self.title = title @@ -44,3 +59,4 @@ def __init__( self.can_non_owners_invite = can_non_owners_invite self.can_shared_link_be_created = can_shared_link_be_created self.can_public_shared_link_be_created = can_public_shared_link_be_created + self.copy_hub_access = copy_hub_access diff --git a/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py b/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py index 3c77dc73f..0dee52269 100644 --- a/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py +++ b/box_sdk_gen/schemas/v2025_r0/hub_v2025_r0.py @@ -1,3 +1,5 @@ +from enum import Enum + from typing import Optional from box_sdk_gen.schemas.v2025_r0.hub_base_v2025_r0 import HubBaseV2025R0TypeField @@ -11,6 +13,12 @@ from box_sdk_gen.internal.utils import DateTime +class HubV2025R0CopyHubAccessField(str, Enum): + ALL = 'all' + COMPANY = 'company' + NONE = 'none' + + class HubV2025R0(HubBaseV2025R0): _discriminator = 'type', {'hubs'} @@ -30,6 +38,7 @@ def __init__( can_non_owners_invite: Optional[bool] = None, can_shared_link_be_created: Optional[bool] = None, can_public_shared_link_be_created: Optional[bool] = None, + copy_hub_access: Optional[HubV2025R0CopyHubAccessField] = None, type: HubBaseV2025R0TypeField = HubBaseV2025R0TypeField.HUBS, **kwargs ): @@ -64,6 +73,12 @@ def __init__( :type can_shared_link_be_created: Optional[bool], optional :param can_public_shared_link_be_created: Indicates if a public shared link can be created for the Box Hub., defaults to None :type can_public_shared_link_be_created: Optional[bool], optional + :param copy_hub_access: Specifies who is allowed to copy the Box Hub. + + * `all` - Any user with access to the Hub can copy it. + * `company` - Only users within the same enterprise as the Hub can copy it. + * `none` - No one can copy the Hub., defaults to None + :type copy_hub_access: Optional[HubV2025R0CopyHubAccessField], optional :param type: The value will always be `hubs`., defaults to HubBaseV2025R0TypeField.HUBS :type type: HubBaseV2025R0TypeField, optional """ @@ -82,3 +97,4 @@ def __init__( self.can_non_owners_invite = can_non_owners_invite self.can_shared_link_be_created = can_shared_link_be_created self.can_public_shared_link_be_created = can_public_shared_link_be_created + self.copy_hub_access = copy_hub_access diff --git a/docs/ai.md b/docs/ai.md index 9861dd015..6c2ef21cf 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -10,6 +10,10 @@ Sends an AI request to supported LLMs and returns an answer specifically focused on the user's question given the provided context. +You can ask a question about a single file, several files, or the entire contents of a Box Hub. To search across and ask questions about everything in a Box Hub, send a single item with `type` set to `hubs` and the Hub's ID as the `id`. Box AI answers the question using the indexed content of all files in that Hub. + +Asking questions about a Box Hub requires Box AI for Hubs to be enabled in the Admin Console before the Hub is created, so that its content is indexed. + This operation is performed by calling function `create_ai_ask`. See the endpoint docs at @@ -39,12 +43,13 @@ client.ai.create_ai_ask( - prompt `str` - The prompt provided by the client to be answered by the LLM. The prompt's length is limited to 10000 characters. - items `List[AiItemAsk]` - - The items to be processed by the LLM, often files. + - The items to be processed by the LLM, often files. To search across and ask questions about the contents of a Box Hub, pass a single item with `type` set to `hubs`. See the item `type` property for details. - dialogue_history `Optional[List[AiDialogueHistory]]` - The history of prompts and answers previously passed to the LLM. This provides additional context to the LLM in generating the response. - include_citations `Optional[bool]` - A flag to indicate whether citations should be returned. - ai_agent `Optional[AiAskAgent]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -99,6 +104,7 @@ client.ai.create_ai_text_gen( - dialogue_history `Optional[List[AiDialogueHistory]]` - The history of prompts and answers previously passed to the LLM. This parameter provides the additional context to the LLM when generating the response. - ai_agent `Optional[AiTextGenAgent]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -175,6 +181,7 @@ client.ai.create_ai_extract( - items `List[AiItemBase]` - The items that LLM will process. Currently, you can use files only. - ai_agent `Optional[AiExtractAgent]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -294,6 +301,7 @@ client.ai.create_ai_extract_structured( - fields `Optional[List[CreateAiExtractStructuredFields]]` - The fields to be extracted from the provided items. For your request to work, you must provide either `metadata_template` or `fields`, but not both. - ai_agent `Optional[AiExtractStructuredAgent]` + - include_confidence_score `Optional[bool]` - A flag to indicate whether confidence scores for every extracted field should be returned. - include_reference `Optional[bool]` diff --git a/docs/ai_studio.md b/docs/ai_studio.md index 92e42046e..908048b8d 100644 --- a/docs/ai_studio.md +++ b/docs/ai_studio.md @@ -76,8 +76,11 @@ client.ai_studio.create_ai_agent( - allowed_entities `Optional[List[AiAgentAllowedEntity]]` - List of allowed users or groups. - ask `Optional[AiStudioAgentAsk]` + - text_gen `Optional[AiStudioAgentTextGen]` + - extract `Optional[AiStudioAgentExtract]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -122,8 +125,11 @@ client.ai_studio.update_ai_agent_by_id( - allowed_entities `Optional[List[AiAgentAllowedEntity]]` - List of allowed users or groups. - ask `Optional[AiStudioAgentAsk]` + - text_gen `Optional[AiStudioAgentTextGen]` + - extract `Optional[AiStudioAgentExtract]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/avatars.md b/docs/avatars.md index 702da5049..85fd9e68b 100644 --- a/docs/avatars.md +++ b/docs/avatars.md @@ -63,7 +63,9 @@ client.avatars.create_user_avatar( - pic `ByteStream` - The image file to be uploaded to Box. Accepted file extensions are `.jpg` or `.png`. The maximum file size is 1MB. - pic_file_name `Optional[str]` + - pic_content_type `Optional[str]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/chunked_uploads.md b/docs/chunked_uploads.md index d5d5d75a9..64cbcb290 100644 --- a/docs/chunked_uploads.md +++ b/docs/chunked_uploads.md @@ -170,7 +170,7 @@ client.chunked_uploads.upload_file_part_by_url( - digest `str` - The [RFC3230][1] message digest of the chunk uploaded. Only SHA1 is supported. The SHA1 digest must be base64 encoded. The format of this header is as `sha=BASE64_ENCODED_DIGEST`. To get the value for the `SHA` digest, use the openSSL command to encode the file part: `openssl sha1 -binary | base64`. [1]: https://tools.ietf.org/html/rfc3230 - content_range `str` - - The byte range of the chunk. Must not overlap with the range of a part already uploaded this session. Each part’s size must be exactly equal in size to the part size specified in the upload session that you created. One exception is the last part of the file, as this can be smaller. When providing the value for `content-range`, remember that: _ The lower bound of each part's byte range must be a multiple of the part size. _ The higher bound must be a multiple of the part size - 1. + - The byte range of the chunk. Must not overlap with the range of a part already uploaded this session. Each part’s size must be exactly equal in size to the part size specified in the upload session that you created. One exception is the last part of the file, as this can be smaller. When providing the value for `content-range`, remember that: * The lower bound of each part's byte range must be a multiple of the part size. * The higher bound must be a multiple of the part size - 1. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -212,7 +212,7 @@ client.chunked_uploads.upload_file_part( - digest `str` - The [RFC3230][1] message digest of the chunk uploaded. Only SHA1 is supported. The SHA1 digest must be base64 encoded. The format of this header is as `sha=BASE64_ENCODED_DIGEST`. To get the value for the `SHA` digest, use the openSSL command to encode the file part: `openssl sha1 -binary | base64`. [1]: https://tools.ietf.org/html/rfc3230 - content_range `str` - - The byte range of the chunk. Must not overlap with the range of a part already uploaded this session. Each part’s size must be exactly equal in size to the part size specified in the upload session that you created. One exception is the last part of the file, as this can be smaller. When providing the value for `content-range`, remember that: _ The lower bound of each part's byte range must be a multiple of the part size. _ The higher bound must be a multiple of the part size - 1. + - The byte range of the chunk. Must not overlap with the range of a part already uploaded this session. Each part’s size must be exactly equal in size to the part size specified in the upload session that you created. One exception is the last part of the file, as this can be smaller. When providing the value for `content-range`, remember that: * The lower bound of each part's byte range must be a multiple of the part size. * The higher bound must be a multiple of the part size - 1. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/docgen.md b/docs/docgen.md index d0994c7ec..44eb2f948 100644 --- a/docs/docgen.md +++ b/docs/docgen.md @@ -129,13 +129,17 @@ client.docgen.create_docgen_batch_v2025_r0( ### Arguments - file `FileReferenceV2025R0` + - file_version `Optional[FileVersionBaseV2025R0]` + - input_source `str` - Source of input. The value has to be `api` for all the API-based document generation requests. - destination_folder `CreateDocgenBatchV2025R0DestinationFolder` + - output_type `str` - Type of the output file. - document_generation_data `List[DocGenDocumentGenerationDataV2025R0]` + - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` diff --git a/docs/docgen_template.md b/docs/docgen_template.md index e9e979a55..1b2238fb0 100644 --- a/docs/docgen_template.md +++ b/docs/docgen_template.md @@ -25,6 +25,7 @@ client.docgen_template.create_docgen_template_v2025_r0(FileReferenceV2025R0(id=f ### Arguments - file `FileReferenceV2025R0` + - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` diff --git a/docs/events.md b/docs/events.md index 80a656e42..c7e16dbad 100644 --- a/docs/events.md +++ b/docs/events.md @@ -89,9 +89,9 @@ client.events.get_events() ### Arguments - stream_type `Optional[GetEventsStreamType]` - - Defines the type of events that are returned _ `all` returns everything for a user and is the default _ `changes` returns events that may cause file tree changes such as file updates or collaborations. _ `sync` is similar to `changes` but only applies to synced folders _ `admin_logs` returns all events for an entire enterprise and requires the user making the API call to have admin permissions. This stream type is for programmatically pulling from a 1 year history of events across all users within the enterprise and within a `created_after` and `created_before` time frame. The complete history of events will be returned in chronological order based on the event time, but latency will be much higher than `admin_logs_streaming`. \* `admin_logs_streaming` returns all events for an entire enterprise and requires the user making the API call to have admin permissions. This stream type is for polling for recent events across all users within the enterprise. Latency will be much lower than `admin_logs`, but events will not be returned in chronological order and may contain duplicates. + - Defines the type of events that are returned * `all` returns everything for a user and is the default * `changes` returns events that may cause file tree changes such as file updates or collaborations. * `sync` is similar to `changes` but only applies to synced folders * `admin_logs` returns all events for an entire enterprise and requires the user making the API call to have admin permissions. This stream type is for programmatically pulling from a 1 year history of events across all users within the enterprise and within a `created_after` and `created_before` time frame. The complete history of events will be returned in chronological order based on the event time, but latency will be much higher than `admin_logs_streaming`. * `admin_logs_streaming` returns all events for an entire enterprise and requires the user making the API call to have admin permissions. This stream type is for polling for recent events across all users within the enterprise. Latency will be much lower than `admin_logs`, but events will not be returned in chronological order and may contain duplicates. - stream_position `Optional[str]` - - The location in the event stream to start receiving events from. _ `now` will return an empty list events and the latest stream position for initialization. _ `0` or `null` will return all events. + - The location in the event stream to start receiving events from. * `now` will return an empty list events and the latest stream position for initialization. * `0` or `null` will return all events. - limit `Optional[int]` - Limits the number of events returned. Note: Sometimes, the events less than the limit requested can be returned even when there may be more events remaining. This is primarily done in the case where a number of events have already been retrieved and these retrieved events are returned rather than delaying for an unknown amount of time to see if there are any more results. - event_type `Optional[List[GetEventsEventType]]` diff --git a/docs/files.md b/docs/files.md index 5698c052d..201636b72 100644 --- a/docs/files.md +++ b/docs/files.md @@ -76,7 +76,9 @@ client.files.update_file_by_id( - description `Optional[str]` - The description for a file. This can be seen in the right-hand sidebar panel when viewing a file in the Box web app. Additionally, this index is used in the search index of the file, allowing users to find the file by the content in the description. - parent `Optional[UpdateFileByIdParent]` + - shared_link `Optional[UpdateFileByIdSharedLink]` + - lock `Optional[UpdateFileByIdLock]` - Defines a lock on an item. This prevents the item from being moved, renamed, or otherwise changed by anyone other than the user who created the lock. Set this to `null` to remove the lock. - disposition_at `Optional[DateTime]` diff --git a/docs/folders.md b/docs/folders.md index 62db04d5b..8e255ee6d 100644 --- a/docs/folders.md +++ b/docs/folders.md @@ -38,7 +38,7 @@ client.folders.get_folder_by_id("0") - fields `Optional[List[str]]` - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. Additionally this field can be used to query any metadata applied to the file by specifying the `metadata` field as well as the scope and key of the template to retrieve, for example `?fields=metadata.enterprise_12345.contractTemplate`. - sort `Optional[GetFolderByIdSort]` - - Defines the **second** attribute by which items are sorted. The folder type affects the way the items are sorted: _ **Standard folder**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. _ **Root folder**: This parameter is not supported for marker-based pagination on the root folder (the folder with an `id` of `0`). \* **Shared folder with parent path to the associated folder visible to the collaborator**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. + - Defines the **second** attribute by which items are sorted. The folder type affects the way the items are sorted: * **Standard folder**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. * **Root folder**: This parameter is not supported for marker-based pagination on the root folder (the folder with an `id` of `0`). * **Shared folder with parent path to the associated folder visible to the collaborator**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. - direction `Optional[GetFolderByIdDirection]` - The direction to sort results in. This can be either in alphabetical ascending (`ASC`) or descending (`DESC`) order. - offset `Optional[int]` @@ -99,8 +99,11 @@ client.folders.update_folder_by_id( - can_non_owners_invite `Optional[bool]` - Specifies if users who are not the owner of the folder can invite new collaborators to the folder. - parent `Optional[UpdateFolderByIdParent]` + - shared_link `Optional[UpdateFolderByIdSharedLink]` + - folder_upload_email `Optional[UpdateFolderByIdFolderUploadEmail]` + - tags `Optional[List[str]]` - The tags for this item. These tags are shown in the Box web app and mobile apps next to an item. To add or remove a tag, retrieve the item's current tags, modify them, and then update this field. There is a limit of 100 tags per item, and 10,000 unique tags per enterprise. - is_collaboration_restricted_to_enterprise `Optional[bool]` @@ -199,7 +202,7 @@ client.folders.get_folder_items(folder_origin.id) - limit `Optional[int]` - The maximum number of items to return per page. - sort `Optional[GetFolderItemsSort]` - - Defines the **second** attribute by which items are sorted. The folder type affects the way the items are sorted: _ **Standard folder**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. _ **Root folder**: This parameter is not supported for marker-based pagination on the root folder (the folder with an `id` of `0`). \* **Shared folder with parent path to the associated folder visible to the collaborator**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. + - Defines the **second** attribute by which items are sorted. The folder type affects the way the items are sorted: * **Standard folder**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. * **Root folder**: This parameter is not supported for marker-based pagination on the root folder (the folder with an `id` of `0`). * **Shared folder with parent path to the associated folder visible to the collaborator**: Items are always sorted by their `type` first, with folders listed before files, and files listed before web links. - direction `Optional[GetFolderItemsDirection]` - The direction to sort results in. This can be either in alphabetical ascending (`ASC`) or descending (`DESC`) order. - boxapi `Optional[str]` @@ -235,6 +238,7 @@ client.folders.create_folder(new_folder_name, CreateFolderParent(id="0")) - parent `CreateFolderParent` - The parent folder to create the new folder within. - folder_upload_email `Optional[CreateFolderFolderUploadEmail]` + - sync_state `Optional[CreateFolderSyncState]` - Specifies whether a folder should be synced to a user's device or not. This is used by Box Sync (discontinued) and is not used by Box Drive. - fields `Optional[List[str]]` diff --git a/docs/groups.md b/docs/groups.md index 6f1143c33..501f4164d 100644 --- a/docs/groups.md +++ b/docs/groups.md @@ -71,7 +71,7 @@ client.groups.create_group(group_name, description=group_description) - invitability_level `Optional[CreateGroupInvitabilityLevel]` - Specifies who can invite the group to collaborate on folders. When set to `admins_only` the enterprise admin, co-admins, and the group's admin can invite the group. When set to `admins_and_members` all the admins listed above and group members can invite the group. When set to `all_managed_users` all managed users in the enterprise can invite the group. - member_viewability_level `Optional[CreateGroupMemberViewabilityLevel]` - - Specifies who can see the members of the group. _ `admins_only` - the enterprise admin, co-admins, group's group admin. _ `admins_and_members` - all admins and group members. \* `all_managed_users` - all managed users in the enterprise. + - Specifies who can see the members of the group. * `admins_only` - the enterprise admin, co-admins, group's group admin. * `admins_and_members` - all admins and group members. * `all_managed_users` - all managed users in the enterprise. - fields `Optional[List[str]]` - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. - extra_headers `Optional[Dict[str, Optional[str]]]` @@ -149,7 +149,7 @@ client.groups.update_group_by_id(group.id, name=updated_group_name) - invitability_level `Optional[UpdateGroupByIdInvitabilityLevel]` - Specifies who can invite the group to collaborate on folders. When set to `admins_only` the enterprise admin, co-admins, and the group's admin can invite the group. When set to `admins_and_members` all the admins listed above and group members can invite the group. When set to `all_managed_users` all managed users in the enterprise can invite the group. - member_viewability_level `Optional[UpdateGroupByIdMemberViewabilityLevel]` - - Specifies who can see the members of the group. _ `admins_only` - the enterprise admin, co-admins, group's group admin. _ `admins_and_members` - all admins and group members. \* `all_managed_users` - all managed users in the enterprise. + - Specifies who can see the members of the group. * `admins_only` - the enterprise admin, co-admins, group's group admin. * `admins_and_members` - all admins and group members. * `all_managed_users` - all managed users in the enterprise. - fields `Optional[List[str]]` - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. - extra_headers `Optional[Dict[str, Optional[str]]]` diff --git a/docs/hubs.md b/docs/hubs.md index 54e0de223..7ef44e9f6 100644 --- a/docs/hubs.md +++ b/docs/hubs.md @@ -190,6 +190,8 @@ client.hubs.update_hub_by_id_v2025_r0( - Indicates if a shared link can be created for the Box Hub. - can_public_shared_link_be_created `Optional[bool]` - Indicates if a public shared link can be created for the Box Hub. +- copy_hub_access `Optional[UpdateHubByIdV2025R0CopyHubAccess]` + - Specifies who is allowed to copy the Box Hub. * `all` - Any user with access to the Hub can copy it. * `company` - Only users within the same enterprise as the Hub can copy it. * `none` - No one can copy the Hub. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` @@ -259,6 +261,8 @@ client.hubs.copy_hub_v2025_r0( - Title of the Box Hub. It cannot be empty and should be less than 50 characters. - description `Optional[str]` - Description of the Box Hub. +- include_items `Optional[bool]` + - If true, the items which the user has Editor or Owner access to in the original Box Hub will be copied to the new Box Hub. Defaults to false. - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` diff --git a/docs/integration_mappings.md b/docs/integration_mappings.md index 1b48ead7e..6da6a2514 100644 --- a/docs/integration_mappings.md +++ b/docs/integration_mappings.md @@ -79,8 +79,11 @@ user_client.integration_mappings.create_slack_integration_mapping( ### Arguments - partner_item `IntegrationMappingPartnerItemSlack` + - box_item `IntegrationMappingBoxItemSlack` + - options `Optional[IntegrationMappingSlackOptions]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -116,7 +119,9 @@ user_client.integration_mappings.update_slack_integration_mapping_by_id( - integration_mapping_id `str` - An ID of an integration mapping. Example: "11235432" - box_item `Optional[IntegrationMappingBoxItemSlack]` + - options `Optional[IntegrationMappingSlackOptions]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -224,7 +229,9 @@ user_client.integration_mappings.create_teams_integration_mapping( ### Arguments - partner_item `IntegrationMappingPartnerItemTeamsCreateRequest` + - box_item `FolderReference` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -259,6 +266,7 @@ user_client.integration_mappings.update_teams_integration_mapping_by_id( - integration_mapping_id `str` - An ID of an integration mapping. Example: "11235432" - box_item `Optional[FolderReference]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/metadata_cascade_policies.md b/docs/metadata_cascade_policies.md index b1d7b0bb7..c161a2e0c 100644 --- a/docs/metadata_cascade_policies.md +++ b/docs/metadata_cascade_policies.md @@ -163,7 +163,7 @@ client.metadata_cascade_policies.apply_metadata_cascade_policy( - metadata_cascade_policy_id `str` - The ID of the cascade policy to force-apply. Example: "6fd4ff89-8fc1-42cf-8b29-1890dedd26d7" - conflict_resolution `ApplyMetadataCascadePolicyConflictResolution` - - Describes the desired behavior when dealing with the conflict where a metadata template already has an instance applied to a child. _ `none` will preserve the existing value on the file _ `overwrite` will force-apply the templates values over any existing values. + - Describes the desired behavior when dealing with the conflict where a metadata template already has an instance applied to a child. * `none` will preserve the existing value on the file * `overwrite` will force-apply the templates values over any existing values. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/notes.md b/docs/notes.md index c4f482b3b..28b9aab9c 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -29,6 +29,7 @@ client.notes.create_note_convert_v2026_r0( - content_format `CreateNoteConvertV2026R0ContentFormat` - Format of the content to convert. - parent `FolderReferenceV2026R0` + - name `str` - The name for the created note. The `.boxnote` extension is appended automatically. - box_version `BoxVersionHeaderV2026R0` diff --git a/docs/retention_policies.md b/docs/retention_policies.md index 1211508d1..f342ff723 100644 --- a/docs/retention_policies.md +++ b/docs/retention_policies.md @@ -81,10 +81,11 @@ client.retention_policies.create_retention_policy( - retention_length `Optional[Union[str, int]]` - The length of the retention policy. This value specifies the duration in days that the retention policy will be active for after being assigned to content. If the policy has a `policy_type` of `indefinite`, the `retention_length` will also be `indefinite`. - retention_type `Optional[CreateRetentionPolicyRetentionType]` - - Specifies the retention type: _ `modifiable`: You can modify the retention policy. For example, you can add or remove folders, shorten or lengthen the policy duration, or delete the assignment. Use this type if your retention policy is not related to any regulatory purposes. _ `non_modifiable`: You can modify the retention policy only in a limited way: add a folder, lengthen the duration, retire the policy, change the disposition action or notification settings. You cannot perform other actions, such as deleting the assignment or shortening the policy duration. Use this type to ensure compliance with regulatory retention policies. + - Specifies the retention type: * `modifiable`: You can modify the retention policy. For example, you can add or remove folders, shorten or lengthen the policy duration, or delete the assignment. Use this type if your retention policy is not related to any regulatory purposes. * `non_modifiable`: You can modify the retention policy only in a limited way: add a folder, lengthen the duration, retire the policy, change the disposition action or notification settings. You cannot perform other actions, such as deleting the assignment or shortening the policy duration. Use this type to ensure compliance with regulatory retention policies. - can_owner_extend_retention `Optional[bool]` - Whether the owner of a file will be allowed to extend the retention. - max_extension_length `Optional[RetentionPolicyMaxExtensionLengthRequest]` + - are_owners_notified `Optional[bool]` - Whether owner and co-owners of a file are notified when the policy nears expiration. - custom_notification_recipients `Optional[List[UserMini]]` @@ -156,7 +157,7 @@ client.retention_policies.update_retention_policy_by_id( - disposition_action `Optional[str]` - The disposition action of the retention policy. This action can be `permanently_delete`, which will cause the content retained by the policy to be permanently deleted, or `remove_retention`, which will lift the retention policy from the content, allowing it to be deleted by users, once the retention policy has expired. You can use `null` if you don't want to change `disposition_action`. - retention_type `Optional[str]` - - Specifies the retention type: _ `modifiable`: You can modify the retention policy. For example, you can add or remove folders, shorten or lengthen the policy duration, or delete the assignment. Use this type if your retention policy is not related to any regulatory purposes. _ `non-modifiable`: You can modify the retention policy only in a limited way: add a folder, lengthen the duration, retire the policy, change the disposition action or notification settings. You cannot perform other actions, such as deleting the assignment or shortening the policy duration. Use this type to ensure compliance with regulatory retention policies. When updating a retention policy, you can use `non-modifiable` type only. You can convert a `modifiable` policy to `non-modifiable`, but not the other way around. + - Specifies the retention type: * `modifiable`: You can modify the retention policy. For example, you can add or remove folders, shorten or lengthen the policy duration, or delete the assignment. Use this type if your retention policy is not related to any regulatory purposes. * `non-modifiable`: You can modify the retention policy only in a limited way: add a folder, lengthen the duration, retire the policy, change the disposition action or notification settings. You cannot perform other actions, such as deleting the assignment or shortening the policy duration. Use this type to ensure compliance with regulatory retention policies. When updating a retention policy, you can use `non-modifiable` type only. You can convert a `modifiable` policy to `non-modifiable`, but not the other way around. - retention_length `Optional[Union[str, int]]` - The length of the retention policy. This value specifies the duration in days that the retention policy will be active for after being assigned to content. If the policy has a `policy_type` of `indefinite`, the `retention_length` will also be `indefinite`. - status `Optional[str]` @@ -164,6 +165,7 @@ client.retention_policies.update_retention_policy_by_id( - can_owner_extend_retention `Optional[bool]` - Determines if the owner of items under the policy can extend the retention when the original retention duration is about to end. - max_extension_length `Optional[RetentionPolicyMaxExtensionLengthRequest]` + - are_owners_notified `Optional[bool]` - Determines if owners and co-owners of items under the policy are notified when the retention duration is about to end. - custom_notification_recipients `Optional[List[UserBase]]` diff --git a/docs/search.md b/docs/search.md index c2c2e27de..9e0012a4d 100644 --- a/docs/search.md +++ b/docs/search.md @@ -36,7 +36,7 @@ client.search.search_by_metadata_query( ### Arguments -- from\_ `str` +- from_ `str` - Specifies the template used in the query. Must be in the form `scope.templateKey`. Not all templates can be used in this field, most notably the built-in, Box-provided classification templates can not be used in a query. - query `Optional[str]` - The query to perform. A query is a logical expression that is very similar to a SQL `SELECT` statement. Values in the search query can be turned into parameters specified in the `query_param` arguments list to prevent having to manually insert search values into the query string. For example, a value of `:amount` would represent the `amount` value in `query_params` object. @@ -51,7 +51,7 @@ client.search.search_by_metadata_query( - marker `Optional[str]` - Marker to use for requesting the next page. - fields `Optional[List[str]]` - - By default, this endpoint returns only the most basic info about the items for which the query matches. This attribute can be used to specify a list of additional attributes to return for any item, including its metadata. This attribute takes a list of item fields, metadata template identifiers, or metadata template field identifiers. For example: _ `created_by` will add the details of the user who created the item to the response. _ `metadata..` will return the mini-representation of the metadata instance identified by the `scope` and `templateKey`. \* `metadata...` will return all the mini-representation of the metadata instance identified by the `scope` and `templateKey` plus the field specified by the `field` name. Multiple fields for the same `scope` and `templateKey` can be defined. + - By default, this endpoint returns only the most basic info about the items for which the query matches. This attribute can be used to specify a list of additional attributes to return for any item, including its metadata. This attribute takes a list of item fields, metadata template identifiers, or metadata template field identifiers. For example: * `created_by` will add the details of the user who created the item to the response. * `metadata..` will return the mini-representation of the metadata instance identified by the `scope` and `templateKey`. * `metadata...` will return all the mini-representation of the metadata instance identified by the `scope` and `templateKey` plus the field specified by the `field` name. Multiple fields for the same `scope` and `templateKey` can be defined. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -89,7 +89,7 @@ client.search.search_for_content( ### Arguments - query `Optional[str]` - - The string to search for. This query is matched against item names, descriptions, text content of files, and various other fields of the different item types. This parameter supports a variety of operators to further refine the results returns. _ `""` - by wrapping a query in double quotes only exact matches are returned by the API. Exact searches do not return search matches based on specific character sequences. Instead, they return matches based on phrases, that is, word sequences. For example: A search for `"Blue-Box"` may return search results including the sequence `"blue.box"`, `"Blue Box"`, and `"Blue-Box"`; any item containing the words `Blue` and `Box` consecutively, in the order specified. _ `AND` - returns items that contain both the search terms. For example, a search for `marketing AND BoxWorks` returns items that have both `marketing` and `BoxWorks` within its text in any order. It does not return a result that only has `BoxWorks` in its text. _ `OR` - returns items that contain either of the search terms. For example, a search for `marketing OR BoxWorks` returns a result that has either `marketing` or `BoxWorks` within its text. Using this operator is not necessary as we implicitly interpret multi-word queries as `OR` unless another supported boolean term is used. _ `NOT` - returns items that do not contain the search term provided. For example, a search for `marketing AND NOT BoxWorks` returns a result that has only `marketing` within its text. Results containing `BoxWorks` are omitted. We do not support lower case (that is, `and`, `or`, and `not`) or mixed case (that is, `And`, `Or`, and `Not`) operators. This field is required unless the `mdfilters` parameter is defined. + - The string to search for. This query is matched against item names, descriptions, text content of files, and various other fields of the different item types. This parameter supports a variety of operators to further refine the results returns. * `""` - by wrapping a query in double quotes only exact matches are returned by the API. Exact searches do not return search matches based on specific character sequences. Instead, they return matches based on phrases, that is, word sequences. For example: A search for `"Blue-Box"` may return search results including the sequence `"blue.box"`, `"Blue Box"`, and `"Blue-Box"`; any item containing the words `Blue` and `Box` consecutively, in the order specified. * `AND` - returns items that contain both the search terms. For example, a search for `marketing AND BoxWorks` returns items that have both `marketing` and `BoxWorks` within its text in any order. It does not return a result that only has `BoxWorks` in its text. * `OR` - returns items that contain either of the search terms. For example, a search for `marketing OR BoxWorks` returns a result that has either `marketing` or `BoxWorks` within its text. Using this operator is not necessary as we implicitly interpret multi-word queries as `OR` unless another supported boolean term is used. * `NOT` - returns items that do not contain the search term provided. For example, a search for `marketing AND NOT BoxWorks` returns a result that has only `marketing` within its text. Results containing `BoxWorks` are omitted. We do not support lower case (that is, `and`, `or`, and `not`) or mixed case (that is, `And`, `Or`, and `Not`) operators. This field is required unless the `mdfilters` parameter is defined. - scope `Optional[SearchForContentScope]` - Limits the search results to either the files that the user has access to, or to files available to the entire enterprise. The scope defaults to `user_content`, which limits the search results to content that is available to the currently authenticated user. The `enterprise_content` can be requested by an admin through our support channels. Once this scope has been enabled for a user, it will allow that use to query for content across the entire enterprise and not only the content that they have access to. - file_extensions `Optional[List[str]]` @@ -107,15 +107,15 @@ client.search.search_for_content( - ancestor_folder_ids `Optional[List[str]]` - Limits the search results to items within the given list of folders, defined as a comma separated lists of folder IDs. Search results will also include items within any subfolders of those ancestor folders. The folders still need to be owned or shared with the currently authenticated user. If the folder is not accessible by this user, or it does not exist, a `HTTP 404` error code will be returned instead. To search across an entire enterprise, we recommend using the `enterprise_content` scope parameter which can be requested with our support team. - content_types `Optional[List[SearchForContentContentTypes]]` - - Limits the search results to any items that match the search query for a specific part of the file, for example the file description. Content types are defined as a comma separated lists of Box recognized content types. The allowed content types are as follows. _ `name` - The name of the item, as defined by its `name` field. _ `description` - The description of the item, as defined by its `description` field. _ `file_content` - The actual content of the file. _ `comments` - The content of any of the comments on a file or folder. \* `tags` - Any tags that are applied to an item, as defined by its `tags` field. + - Limits the search results to any items that match the search query for a specific part of the file, for example the file description. Content types are defined as a comma separated lists of Box recognized content types. The allowed content types are as follows. * `name` - The name of the item, as defined by its `name` field. * `description` - The description of the item, as defined by its `description` field. * `file_content` - The actual content of the file. * `comments` - The content of any of the comments on a file or folder. * `tags` - Any tags that are applied to an item, as defined by its `tags` field. - type `Optional[SearchForContentType]` - - Limits the search results to any items of this type. This parameter only takes one value. By default the API returns items that match any of these types. _ `file` - Limits the search results to files, _ `folder` - Limits the search results to folders, \* `web_link` - Limits the search results to web links, also known as bookmarks. + - Limits the search results to any items of this type. This parameter only takes one value. By default the API returns items that match any of these types. * `file` - Limits the search results to files, * `folder` - Limits the search results to folders, * `web_link` - Limits the search results to web links, also known as bookmarks. - 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. + - 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 is a list that specifies exactly **one** metadata template used to filter the search results. The parameter 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. + - 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]` - Defines the direction in which search results are ordered. This API defaults to returning items in descending (`DESC`) order unless this parameter is explicitly specified. When results are sorted by `relevance` the ordering is locked to returning items in descending order of relevance, and this parameter is ignored. - limit `Optional[int]` diff --git a/docs/shield_information_barrier_reports.md b/docs/shield_information_barrier_reports.md index f9b7990ed..1bc17210e 100644 --- a/docs/shield_information_barrier_reports.md +++ b/docs/shield_information_barrier_reports.md @@ -61,6 +61,7 @@ client.shield_information_barrier_reports.create_shield_information_barrier_repo ### Arguments - shield_information_barrier `Optional[ShieldInformationBarrierBase]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/shield_information_barrier_segment_members.md b/docs/shield_information_barrier_segment_members.md index 13fa7510b..a81521be5 100644 --- a/docs/shield_information_barrier_segment_members.md +++ b/docs/shield_information_barrier_segment_members.md @@ -130,6 +130,7 @@ client.shield_information_barrier_segment_members.create_shield_information_barr - type `Optional[CreateShieldInformationBarrierSegmentMemberType]` - A type of the shield barrier segment member. - shield_information_barrier `Optional[ShieldInformationBarrierBase]` + - shield_information_barrier_segment `CreateShieldInformationBarrierSegmentMemberShieldInformationBarrierSegment` - The `type` and `id` of the requested shield information barrier segment. - user `UserBase` diff --git a/docs/shield_information_barrier_segment_restrictions.md b/docs/shield_information_barrier_segment_restrictions.md index d18dbbe5b..47fdbe5a8 100644 --- a/docs/shield_information_barrier_segment_restrictions.md +++ b/docs/shield_information_barrier_segment_restrictions.md @@ -135,6 +135,7 @@ client.shield_information_barrier_segment_restrictions.create_shield_information - type `CreateShieldInformationBarrierSegmentRestrictionType` - The type of the shield barrier segment restriction for this member. - shield_information_barrier `Optional[ShieldInformationBarrierBase]` + - shield_information_barrier_segment `CreateShieldInformationBarrierSegmentRestrictionShieldInformationBarrierSegment` - The `type` and `id` of the requested shield information barrier segment. - restricted_segment `CreateShieldInformationBarrierSegmentRestrictionRestrictedSegment` diff --git a/docs/shield_information_barrier_segments.md b/docs/shield_information_barrier_segments.md index c208101ee..20a595059 100644 --- a/docs/shield_information_barrier_segments.md +++ b/docs/shield_information_barrier_segments.md @@ -161,6 +161,7 @@ client.shield_information_barrier_segments.create_shield_information_barrier_seg ### Arguments - shield_information_barrier `ShieldInformationBarrierBase` + - name `str` - Name of the shield information barrier segment. - description `Optional[str]` diff --git a/docs/shield_lists.md b/docs/shield_lists.md index 39505cada..a92bf9429 100644 --- a/docs/shield_lists.md +++ b/docs/shield_lists.md @@ -63,6 +63,7 @@ client.shield_lists.create_shield_list_v2025_r0( - description `Optional[str]` - Optional description of Shield List. - content `ShieldListContentRequestV2025R0` + - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` @@ -165,6 +166,7 @@ client.shield_lists.update_shield_list_by_id_v2025_r0( - description `Optional[str]` - Optional description of Shield List. - content `ShieldListContentRequestV2025R0` + - box_version `BoxVersionHeaderV2025R0` - Version header. - extra_headers `Optional[Dict[str, Optional[str]]]` diff --git a/docs/sign_requests.md b/docs/sign_requests.md index 1abe8c888..cd2481160 100644 --- a/docs/sign_requests.md +++ b/docs/sign_requests.md @@ -180,6 +180,7 @@ client.sign_requests.create_sign_request( - signers `List[SignRequestCreateSigner]` - Array of signers for the signature request. 35 is the max number of signers permitted. **Note**: It may happen that some signers belong to conflicting [segments](https://developer.box.com/reference/resources/shield-information-barrier-segment-member) (user groups). This means that due to the security policies, users are assigned to segments to prevent exchanges or communication that could lead to ethical conflicts. In such a case, an attempt to send the sign request will result in an error. Read more about [segments and ethical walls](https://support.box.com/hc/en-us/articles/9920431507603-Understanding-Information-Barriers#h_01GFVJEHQA06N7XEZ4GCZ9GFAQ). - parent_folder `Optional[FolderMini]` + - is_document_preparation_needed `Optional[bool]` - Indicates if the sender should receive a `prepare_url` in the response to complete document preparation using the UI. - redirect_url `Optional[str]` @@ -206,6 +207,8 @@ client.sign_requests.create_sign_request( - When a signature request is created from a template this field will indicate the id of that template. - external_system_name `Optional[str]` - Used as an optional system name to appear in the signature log next to the signers who have been assigned the `embed_url_external_id`. +- request_flow `Optional[str]` + - The flow type of the sign request. Values can include `standard` or `cfr11`. When not specified during creation, a default is chosen based on admin settings. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/task_assignments.md b/docs/task_assignments.md index c0fcbefbd..13def3f2c 100644 --- a/docs/task_assignments.md +++ b/docs/task_assignments.md @@ -127,7 +127,7 @@ client.task_assignments.update_task_assignment_by_id( - message `Optional[str]` - An optional message by the assignee that can be added to the task. - resolution_state `Optional[UpdateTaskAssignmentByIdResolutionState]` - - The state of the task assigned to the user. _ For a task with an `action` value of `complete` this can be `incomplete` or `completed`. _ For a task with an `action` of `review` this can be `incomplete`, `approved`, or `rejected`. + - The state of the task assigned to the user. * For a task with an `action` value of `complete` this can be `incomplete` or `completed`. * For a task with an `action` of `review` this can be `incomplete`, `approved`, or `rejected`. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/tasks.md b/docs/tasks.md index ae6dc3b58..86681e7ba 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -65,13 +65,13 @@ client.tasks.create_task( - item `CreateTaskItem` - The file to attach the task to. - action `Optional[CreateTaskAction]` - - The action the task assignee will be prompted to do. Must be _ `review` defines an approval task that can be approved or, rejected _ `complete` defines a general task which can be completed. + - The action the task assignee will be prompted to do. Must be * `review` defines an approval task that can be approved or, rejected * `complete` defines a general task which can be completed. - message `Optional[str]` - An optional message to include with the task. - due_at `Optional[DateTime]` - Defines when the task is due. Defaults to `null` if not provided. - completion_rule `Optional[CreateTaskCompletionRule]` - - Defines which assignees need to complete this task before the task is considered completed. _ `all_assignees` (default) requires all assignees to review or approve the task in order for it to be considered completed. _ `any_assignee` accepts any one assignee to review or approve the task in order for it to be considered completed. + - Defines which assignees need to complete this task before the task is considered completed. * `all_assignees` (default) requires all assignees to review or approve the task in order for it to be considered completed. * `any_assignee` accepts any one assignee to review or approve the task in order for it to be considered completed. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -130,13 +130,13 @@ client.tasks.update_task_by_id(task.id, message="updated message") - task_id `str` - The ID of the task. Example: "12345" - action `Optional[UpdateTaskByIdAction]` - - The action the task assignee will be prompted to do. Must be _ `review` defines an approval task that can be approved or rejected, _ `complete` defines a general task which can be completed. + - The action the task assignee will be prompted to do. Must be * `review` defines an approval task that can be approved or rejected, * `complete` defines a general task which can be completed. - message `Optional[str]` - The message included with the task. - due_at `Optional[DateTime]` - When the task is due at. - completion_rule `Optional[UpdateTaskByIdCompletionRule]` - - Defines which assignees need to complete this task before the task is considered completed. _ `all_assignees` (default) requires all assignees to review or approve the task in order for it to be considered completed. _ `any_assignee` accepts any one assignee to review or approve the task in order for it to be considered completed. + - Defines which assignees need to complete this task before the task is considered completed. * `all_assignees` (default) requires all assignees to review or approve the task in order for it to be considered completed. * `any_assignee` accepts any one assignee to review or approve the task in order for it to be considered completed. - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. diff --git a/docs/trashed_files.md b/docs/trashed_files.md index 0afadfa88..32f08cc18 100644 --- a/docs/trashed_files.md +++ b/docs/trashed_files.md @@ -29,6 +29,7 @@ client.trashed_files.restore_file_from_trash(file.id) - name `Optional[str]` - An optional new name for the file. - parent `Optional[RestoreFileFromTrashParent]` + - fields `Optional[List[str]]` - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. - extra_headers `Optional[Dict[str, Optional[str]]]` diff --git a/docs/trashed_folders.md b/docs/trashed_folders.md index 8be203502..67c8b7da3 100644 --- a/docs/trashed_folders.md +++ b/docs/trashed_folders.md @@ -36,6 +36,7 @@ client.trashed_folders.restore_folder_from_trash(folder.id) - name `Optional[str]` - An optional new name for the folder. - parent `Optional[RestoreFolderFromTrashParent]` + - fields `Optional[List[str]]` - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. - extra_headers `Optional[Dict[str, Optional[str]]]` diff --git a/docs/trashed_web_links.md b/docs/trashed_web_links.md index b1d31d080..97d52542c 100644 --- a/docs/trashed_web_links.md +++ b/docs/trashed_web_links.md @@ -29,6 +29,7 @@ client.trashed_web_links.restore_weblink_from_trash(weblink.id) - name `Optional[str]` - An optional new name for the web link. - parent `Optional[RestoreWeblinkFromTrashParent]` + - fields `Optional[List[str]]` - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. - extra_headers `Optional[Dict[str, Optional[str]]]` diff --git a/docs/uploads.md b/docs/uploads.md index 8d4095301..a5ff6ce51 100644 --- a/docs/uploads.md +++ b/docs/uploads.md @@ -39,7 +39,9 @@ client.uploads.upload_file_version( - file `ByteStream` - The content of the file to upload to Box. The `attributes` part of the body must come **before** the `file` part. Requests that do not follow this format when uploading the file will receive a HTTP `400` error with a `metadata_after_file_contents` error code. - file_file_name `Optional[str]` + - file_content_type `Optional[str]` + - fields `Optional[List[str]]` - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. - if_match `Optional[str]` @@ -80,6 +82,7 @@ client.uploads.preflight_file_upload_check( - size `Optional[int]` - The size of the file in bytes. - parent `Optional[PreflightFileUploadCheckParent]` + - extra_headers `Optional[Dict[str, Optional[str]]]` - Extra headers that will be included in the HTTP request. @@ -123,7 +126,9 @@ client.uploads.upload_file( - file `ByteStream` - The content of the file to upload to Box. The `attributes` part of the body must come **before** the `file` part. Requests that do not follow this format when uploading the file will receive a HTTP `400` error with a `metadata_after_file_contents` error code. - file_file_name `Optional[str]` + - file_content_type `Optional[str]` + - fields `Optional[List[str]]` - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. - content_md_5 `Optional[str]` @@ -157,10 +162,13 @@ client.uploads.upload_with_preflight_check( ### Arguments - attributes `UploadWithPreflightCheckAttributes` + - file `ByteStream` - The content of the file to upload to Box. The `attributes` part of the body must come **before** the `file` part. Requests that do not follow this format when uploading the file will receive a HTTP `400` error with a `metadata_after_file_contents` error code. - file_file_name `Optional[str]` + - file_content_type `Optional[str]` + - fields `Optional[List[str]]` - A comma-separated list of attributes to include in the response. This can be used to request fields that are not normally returned in a standard response. Be aware that specifying this parameter will have the effect that none of the standard fields are returned in the response unless explicitly specified, instead only fields for the mini representation are returned, additional to the fields requested. - content_md_5 `Optional[str]` diff --git a/docs/users.md b/docs/users.md index 980e5d876..178dd4dc4 100644 --- a/docs/users.md +++ b/docs/users.md @@ -32,7 +32,7 @@ client.users.get_users() - filter_term `Optional[str]` - Limits the results to only users who's `name` or `login` start with the search term. For externally managed users, the search term needs to completely match the in order to find the user, and it will only return one user at a time. - user_type `Optional[GetUsersUserType]` - - Limits the results to the kind of user specified. _ `all` returns every kind of user for whom the `login` or `name` partially matches the `filter_term`. It will only return an external user if the login matches the `filter_term` completely, and in that case it will only return that user. _ `managed` returns all managed and app users for whom the `login` or `name` partially matches the `filter_term`. \* `external` returns all external users for whom the `login` matches the `filter_term` exactly. + - Limits the results to the kind of user specified. * `all` returns every kind of user for whom the `login` or `name` partially matches the `filter_term`. It will only return an external user if the login matches the `filter_term` completely, and in that case it will only return that user. * `managed` returns all managed and app users for whom the `login` or `name` partially matches the `filter_term`. * `external` returns all external users for whom the `login` matches the `filter_term` exactly. - external_app_user_id `Optional[str]` - Limits the results to app users with the given `external_app_user_id` value. When creating an app user, an `external_app_user_id` value can be set. This value can then be used in this endpoint to find any users that match that `external_app_user_id` value. - fields `Optional[List[str]]` diff --git a/docs/web_links.md b/docs/web_links.md index 1700761fc..66ff7f69d 100644 --- a/docs/web_links.md +++ b/docs/web_links.md @@ -102,6 +102,7 @@ client.web_links.update_web_link_by_id( - url `Optional[str]` - The new URL that the web link links to. Must start with `"http://"` or `"https://"`. - parent `Optional[UpdateWebLinkByIdParent]` + - name `Optional[str]` - A new name for the web link. Defaults to the URL if not set. - description `Optional[str]` From b502193e66b56796ba9ea9967ad13c2f6f9edfca Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:44:18 +0200 Subject: [PATCH 129/139] feat: Setup common default timeout (box/box-codegen#965) (#1507) --- .codegen.json | 2 +- box_sdk_gen/networking/box_network_client.py | 30 +++++++++----------- box_sdk_gen/networking/network.py | 2 +- docs/client.md | 7 ++++- docs/configuration.md | 14 ++++++--- test/box_network_client.py | 14 ++++----- 6 files changed, 39 insertions(+), 30 deletions(-) diff --git a/.codegen.json b/.codegen.json index 1947d1b98..ba584f4c0 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "6f9492d", "specHash": "131c54a", "version": "10.12.0" } +{ "engineHash": "ed5236c", "specHash": "131c54a", "version": "10.12.0" } diff --git a/box_sdk_gen/networking/box_network_client.py b/box_sdk_gen/networking/box_network_client.py index ddb8af4a3..5bd7d5959 100644 --- a/box_sdk_gen/networking/box_network_client.py +++ b/box_sdk_gen/networking/box_network_client.py @@ -42,7 +42,7 @@ class APIRequest: data: Optional[Union[str, ByteStream, MultipartEncoder]] content_type: Optional[str] = None allow_redirects: bool = True - timeout: Optional[Tuple[Optional[float], Optional[float]]] = None + timeout: Optional[Union[float, Tuple[Optional[float], Optional[float]]]] = None @dataclass @@ -189,36 +189,34 @@ def _prepare_request( @staticmethod def _get_request_timeout( options: 'FetchOptions', - ) -> Optional[Tuple[Optional[float], Optional[float]]]: + ) -> Optional[Union[float, Tuple[Optional[float], Optional[float]]]]: """ - Derive requests timeout tuple (connect, read) in seconds. + Derive requests timeout in seconds. Uses `options.network_session.timeout_config` when present. The timeout config values are expected to be in milliseconds. + + Returns a tuple (connect, read) when both timeouts are specified, + a single float when only one is set, or None to use no timeout. """ network_session = options.network_session timeout_config = network_session.timeout_config if network_session else None if timeout_config is None: return None - connection_timeout_ms, read_timeout_ms = ( - timeout_config.connection_timeout_ms, - timeout_config.read_timeout_ms, - ) + connection_timeout_ms = timeout_config.connection_timeout_ms + read_timeout_ms = timeout_config.read_timeout_ms if connection_timeout_ms is None and read_timeout_ms is None: return None - connection_timeout_sec = ( - connection_timeout_ms / 1000.0 - if connection_timeout_ms is not None - else None - ) - read_timeout_sec = ( - read_timeout_ms / 1000.0 if read_timeout_ms is not None else None - ) + if connection_timeout_ms is not None and read_timeout_ms is not None: + return (connection_timeout_ms / 1000.0, read_timeout_ms / 1000.0) + + if connection_timeout_ms is not None: + return connection_timeout_ms / 1000.0 - return (connection_timeout_sec, read_timeout_sec) + return read_timeout_ms / 1000.0 @staticmethod def _prepare_headers( diff --git a/box_sdk_gen/networking/network.py b/box_sdk_gen/networking/network.py index a3ed2fb23..7431d9918 100644 --- a/box_sdk_gen/networking/network.py +++ b/box_sdk_gen/networking/network.py @@ -42,7 +42,7 @@ def __init__( data_sanitizer = DataSanitizer() if timeout_config is None: timeout_config = TimeoutConfig( - connection_timeout_ms=5000, + connection_timeout_ms=10000, read_timeout_ms=60000, ) self.additional_headers = additional_headers diff --git a/docs/client.md b/docs/client.md index 7fa552e2d..9f9c148e8 100644 --- a/docs/client.md +++ b/docs/client.md @@ -158,8 +158,13 @@ new_client = client.with_custom_base_urls( In order to configure timeout for API calls, calling the `client.with_timeouts(config)` method creates a new client with timeout settings, leaving the original client unmodified. +All timeout values are in milliseconds. + ```python -timeout_config = TimeoutConfig(connection_timeout_ms=10000, read_timeout_ms=30000) +timeout_config = TimeoutConfig( + connection_timeout_ms=5000, + read_timeout_ms=30000, +) new_client = client.with_timeouts(timeout_config) ``` diff --git a/docs/configuration.md b/docs/configuration.md index 9a0ae0b9e..f19cdaecc 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -194,14 +194,19 @@ client = BoxClient(auth=auth, network_session=network_session) ## Timeouts You can configure network timeouts with `TimeoutConfig` on `NetworkSession`. -Python SDK supports separate connection and read timeout values in milliseconds. +The SDK supports two timeout values, both in milliseconds: + +| Parameter | Description | +| ----------------------- | ------------------------------------------------------------------ | +| `connection_timeout_ms` | Maximum time to wait for the TCP connection to be established. | +| `read_timeout_ms` | Maximum idle time between data packets while reading the response. | ```python from box_sdk_gen import BoxClient, BoxDeveloperTokenAuth, NetworkSession, TimeoutConfig auth = BoxDeveloperTokenAuth(token="DEVELOPER_TOKEN_GOES_HERE") timeout_config = TimeoutConfig( - connection_timeout_ms=10000, + connection_timeout_ms=5000, read_timeout_ms=30000, ) network_session = NetworkSession(timeout_config=timeout_config) @@ -211,9 +216,10 @@ client = BoxClient(auth=auth, network_session=network_session) How timeout handling works: - Timeout values are configured in milliseconds and converted to seconds internally for HTTP requests. -- If timeout config is not provided, the SDK uses default timeouts: `connection_timeout_ms=5000` (5 seconds) and `read_timeout_ms=60000` (60 seconds). +- If timeout config is not provided, the SDK uses default timeouts: `connection_timeout_ms=10000` (10 seconds) and `read_timeout_ms=60000` (60 seconds). +- When both `connection_timeout_ms` and `read_timeout_ms` are set, the SDK passes them as a `(connect, read)` tuple to the underlying `requests` library. +- When only one timeout is set, it applies as a single timeout value for the request. - To disable all SDK timeouts, pass `TimeoutConfig(connection_timeout_ms=None, read_timeout_ms=None)` explicitly to `NetworkSession`. -- You can also disable only one timeout by setting one value to `None` (for example, `connection_timeout_ms=None` or `read_timeout_ms=None`). If you provide only the other value (for example, `read_timeout_ms=30000`) and leave one unspecified, the unspecified field remains `None` and that timeout stays disabled. - Timeout failures are treated as network exceptions, and retry behavior is controlled by the configured retry strategy. - Timeout applies to a single HTTP request attempt to the Box API (not the total time across all retries). - If retries are exhausted, the SDK raises `BoxSDKError` with the underlying request exception. diff --git a/test/box_network_client.py b/test/box_network_client.py index ecda14ae2..5b295566f 100644 --- a/test/box_network_client.py +++ b/test/box_network_client.py @@ -178,7 +178,7 @@ def network_session_mock(): def test_network_session_uses_default_timeout_config_values(): network_session = NetworkSession() - assert network_session.timeout_config.connection_timeout_ms == 5000 + assert network_session.timeout_config.connection_timeout_ms == 10000 assert network_session.timeout_config.read_timeout_ms == 60000 @@ -191,7 +191,7 @@ def test_prepare_request_uses_default_network_session_timeouts(network_client): api_request = network_client._prepare_request(options=options) - assert api_request.timeout == (5, 60) + assert api_request.timeout == (10, 60) @pytest.fixture @@ -339,7 +339,7 @@ def test_prepare_json_request(network_client, network_session_mock): params={"param": "value"}, data='{"key": "value"}', content_type="application/json", - timeout=(5, 60), + timeout=(10, 60), ) @@ -727,7 +727,7 @@ def test_retrying_401_response_with_new_token_and_auth_provided( data=None, stream=True, allow_redirects=True, - timeout=(5, 60), + timeout=(10, 60), ), mock.call( method="GET", @@ -742,7 +742,7 @@ def test_retrying_401_response_with_new_token_and_auth_provided( data=None, stream=True, allow_redirects=True, - timeout=(5, 60), + timeout=(10, 60), ), ], ) @@ -782,7 +782,7 @@ def test_not_retrying_401_when_auth_not_provided( data=None, stream=True, allow_redirects=True, - timeout=(5, 60), + timeout=(10, 60), ) @@ -1244,5 +1244,5 @@ def test_disable_follow_redirects( data=None, stream=True, allow_redirects=False, - timeout=(5, 60), + timeout=(10, 60), ) From 7352ce921211dc9c68f7af4344067361ecb0ebf0 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:14:44 +0200 Subject: [PATCH 130/139] fix: Fix dictionary values deserialization (box/box-codegen#966) (#1511) --- .codegen.json | 2 +- box_sdk_gen/internal/base_object.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index ba584f4c0..ee7efba2b 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "ed5236c", "specHash": "131c54a", "version": "10.12.0" } +{ "engineHash": "5560f9a", "specHash": "131c54a", "version": "10.12.0" } diff --git a/box_sdk_gen/internal/base_object.py b/box_sdk_gen/internal/base_object.py index 47c407323..f9ce7718e 100644 --- a/box_sdk_gen/internal/base_object.py +++ b/box_sdk_gen/internal/base_object.py @@ -81,6 +81,8 @@ def _deserialize(cls, key, value, annotation=None): if get_origin(annotation) == list: return cls._deserialize_list(key, value, annotation) + elif get_origin(annotation) == dict: + return cls._deserialize_dict(key, value, annotation) elif get_origin(annotation) == Union: return cls._deserialize_union(key, value, annotation) elif isinstance(annotation, EnumMeta): @@ -104,6 +106,17 @@ def _deserialize_list(cls, key, value, annotation: list): except Exception: return value + @classmethod + def _deserialize_dict(cls, key, value, annotation): + try: + args = get_args(annotation) + if len(args) < 2: + return value + value_type = args[1] + return {k: cls._deserialize(key, v, value_type) for k, v in value.items()} + except Exception: + return value + @classmethod def _deserialize_union(cls, key, value, annotation): try: From 4aaf9fffc2cf245dee30c3a2fcfc3b850e6a65a1 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Thu, 9 Jul 2026 18:58:04 +0200 Subject: [PATCH 131/139] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] (#1515) --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index ee7efba2b..02485783d 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "5560f9a", "specHash": "131c54a", "version": "10.12.0" } +{ "engineHash": "7fa5d4a", "specHash": "131c54a", "version": "10.12.0" } From 656e9bbb24de3384c766aa77d8182a5114b19d0d Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:19:05 +0200 Subject: [PATCH 132/139] chore: release version 10.13.0 (#1517) --- .codegen.json | 2 +- CHANGELOG.md | 13 +++++++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 02485783d..f12674df6 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "7fa5d4a", "specHash": "131c54a", "version": "10.12.0" } +{ "engineHash": "7fa5d4a", "specHash": "131c54a", "version": "10.13.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a0417cbe..ea9d3f15b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.13.0](https://github.com/box/box-python-sdk/compare/v10.12.0...v10.13.0) (2026-07-10) + + +### Bug Fixes + +* Fix dictionary values deserialization (box/box-codegen[#966](https://github.com/box/box-python-sdk/issues/966)) ([#1511](https://github.com/box/box-python-sdk/issues/1511)) ([7352ce9](https://github.com/box/box-python-sdk/commit/7352ce921211dc9c68f7af4344067361ecb0ebf0)) + + +### New Features and Enhancements + +* Add new fields to Hubs (box/box-openapi[#606](https://github.com/box/box-python-sdk/issues/606)) ([#1499](https://github.com/box/box-python-sdk/issues/1499)) ([c083ad0](https://github.com/box/box-python-sdk/commit/c083ad0af00b962a7224795bbf56ab04d3deffc5)) +* Setup common default timeout (box/box-codegen[#965](https://github.com/box/box-python-sdk/issues/965)) ([#1507](https://github.com/box/box-python-sdk/issues/1507)) ([b502193](https://github.com/box/box-python-sdk/commit/b502193e66b56796ba9ea9967ad13c2f6f9edfca)) + ## [10.12.0](https://github.com/box/box-python-sdk/compare/v10.11.0...v10.12.0) (2026-06-11) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 3f60f2411..4a6c005a6 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.12.0' +__version__ = '10.13.0' From 1f18e931e8fcc5b13429acb9a2669e7bd9610c10 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 31 Jul 2026 13:28:52 +0200 Subject: [PATCH 133/139] feat: Support query APIs (box/box-openapi#610) (#1529) --- .codegen.json | 2 +- box_sdk_gen/client.py | 3 + box_sdk_gen/managers/__init__.py | 2 + box_sdk_gen/managers/query.py | 269 ++++++++++++++++++ box_sdk_gen/schemas/file_full.py | 50 ++++ box_sdk_gen/schemas/folder_full.py | 18 +- box_sdk_gen/schemas/v2026_r0/__init__.py | 22 ++ .../query_ancestor_reference_v2026_r0.py | 16 ++ .../v2026_r0/query_insight_entry_v2026_r0.py | 47 +++ .../query_insight_metric_result_v2026_r0.py | 19 ++ .../query_insights_group_by_v2026_r0.py | 19 ++ ...ery_insights_metric_definition_v2026_r0.py | 30 ++ .../query_insights_request_body_v2026_r0.py | 78 +++++ .../v2026_r0/query_insights_v2026_r0.py | 20 ++ .../v2026_r0/query_order_by_v2026_r0.py | 25 ++ .../v2026_r0/query_request_body_v2026_r0.py | 86 ++++++ .../v2026_r0/query_result_entry_v2026_r0.py | 17 ++ .../v2026_r0/query_results_v2026_r0.py | 36 +++ box_sdk_gen/schemas/web_link.py | 26 ++ docs/README.md | 1 + docs/query.md | 71 +++++ 21 files changed, 852 insertions(+), 5 deletions(-) create mode 100644 box_sdk_gen/managers/query.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_ancestor_reference_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_insight_entry_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_insight_metric_result_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_insights_group_by_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_insights_metric_definition_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_insights_request_body_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_insights_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_order_by_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_request_body_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_result_entry_v2026_r0.py create mode 100644 box_sdk_gen/schemas/v2026_r0/query_results_v2026_r0.py create mode 100644 docs/query.md diff --git a/.codegen.json b/.codegen.json index f12674df6..d018b3409 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "7fa5d4a", "specHash": "131c54a", "version": "10.13.0" } +{ "engineHash": "2cee311", "specHash": "cc375ad", "version": "10.13.0" } diff --git a/box_sdk_gen/client.py b/box_sdk_gen/client.py index 25518b944..1be4933e1 100644 --- a/box_sdk_gen/client.py +++ b/box_sdk_gen/client.py @@ -196,6 +196,8 @@ from box_sdk_gen.managers.notes import NotesManager +from box_sdk_gen.managers.query import QueryManager + from box_sdk_gen.networking.auth import Authentication from box_sdk_gen.networking.network import NetworkSession @@ -472,6 +474,7 @@ def __init__(self, auth: Authentication, *, network_session: NetworkSession = No auth=self.auth, network_session=self.network_session ) self.notes = NotesManager(auth=self.auth, network_session=self.network_session) + self.query = QueryManager(auth=self.auth, network_session=self.network_session) def make_request(self, fetch_options: FetchOptions) -> FetchResponse: """ diff --git a/box_sdk_gen/managers/__init__.py b/box_sdk_gen/managers/__init__.py index 2a1464fc0..7ce34c33f 100644 --- a/box_sdk_gen/managers/__init__.py +++ b/box_sdk_gen/managers/__init__.py @@ -167,3 +167,5 @@ from box_sdk_gen.managers.automate_workflows import * from box_sdk_gen.managers.notes import * + +from box_sdk_gen.managers.query import * diff --git a/box_sdk_gen/managers/query.py b/box_sdk_gen/managers/query.py new file mode 100644 index 000000000..82001e0ac --- /dev/null +++ b/box_sdk_gen/managers/query.py @@ -0,0 +1,269 @@ +from typing import Optional + +from typing import Dict + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.serialization.json import serialize + +from box_sdk_gen.serialization.json import deserialize + +from box_sdk_gen.schemas.v2026_r0.query_ancestor_reference_v2026_r0 import ( + QueryAncestorReferenceV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.query_insights_group_by_v2026_r0 import ( + QueryInsightsGroupByV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.query_order_by_v2026_r0 import QueryOrderByV2026R0 + +from box_sdk_gen.networking.fetch_options import ResponseFormat + +from box_sdk_gen.schemas.v2026_r0.query_insights_metric_definition_v2026_r0 import ( + QueryInsightsMetricDefinitionV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.query_results_v2026_r0 import QueryResultsV2026R0 + +from box_sdk_gen.schemas.v2026_r0.client_error_v2026_r0 import ClientErrorV2026R0 + +from box_sdk_gen.parameters.v2026_r0.box_version_header_v2026_r0 import ( + BoxVersionHeaderV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.query_request_body_v2026_r0 import ( + QueryRequestBodyV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.query_insights_v2026_r0 import QueryInsightsV2026R0 + +from box_sdk_gen.schemas.v2026_r0.query_insights_request_body_v2026_r0 import ( + QueryInsightsRequestBodyV2026R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + +from box_sdk_gen.networking.auth import Authentication + +from box_sdk_gen.networking.network import NetworkSession + +from box_sdk_gen.networking.fetch_options import FetchOptions + +from box_sdk_gen.networking.fetch_response import FetchResponse + +from box_sdk_gen.internal.utils import prepare_params + +from box_sdk_gen.internal.utils import to_string + +from box_sdk_gen.internal.utils import ByteStream + +from box_sdk_gen.serialization.json import sd_to_json + +from box_sdk_gen.serialization.json import SerializedData + + +class CreateQueryV2026R0Query(BaseObject): + def __init__( + self, + predicate: str, + *, + params: Optional[Dict] = None, + ancestors: Optional[List[QueryAncestorReferenceV2026R0]] = None, + **kwargs + ): + """ + :param predicate: A logical expression used to filter the dataset, similar to an SQL + `WHERE` clause. May include named parameters referenced as + `:placeholder`. + :type predicate: str + :param params: A map of placeholder names (without the `:` prefix) to their values. + Required only when the predicate contains parameter placeholders. The + type of each value must match the type of the field it is compared to., defaults to None + :type params: Optional[Dict], optional + :param ancestors: Restricts results to the specified ancestor entities and their + recursive descendants. The user must have read access to every listed + ancestor., defaults to None + :type ancestors: Optional[List[QueryAncestorReferenceV2026R0]], optional + """ + super().__init__(**kwargs) + self.predicate = predicate + self.params = params + self.ancestors = ancestors + + +class CreateQueryInsightV2026R0Query(BaseObject): + def __init__( + self, + predicate: str, + *, + params: Optional[Dict] = None, + ancestors: Optional[List[QueryAncestorReferenceV2026R0]] = None, + group_by: Optional[List[QueryInsightsGroupByV2026R0]] = None, + **kwargs + ): + """ + :param predicate: A logical expression used to filter the dataset prior to metric + computation, similar to an SQL `WHERE` clause. May include + named parameters referenced as `:placeholder`. + :type predicate: str + :param params: A map of placeholder names (without the `:` prefix) to their values. + Required only when the predicate contains parameter placeholders. The + type of each value must match the type of the field it is compared to., defaults to None + :type params: Optional[Dict], optional + :param ancestors: Restricts results to items contained within any of the specified + ancestors. The user must have access to every listed ancestor. When + omitted, insights are computed across all accessible items., defaults to None + :type ancestors: Optional[List[QueryAncestorReferenceV2026R0]], optional + :param group_by: Defines how data is grouped for insights computation. Currently only a + single grouping field is supported., defaults to None + :type group_by: Optional[List[QueryInsightsGroupByV2026R0]], optional + """ + super().__init__(**kwargs) + self.predicate = predicate + self.params = params + self.ancestors = ancestors + self.group_by = group_by + + +class QueryManager: + def __init__( + self, + *, + auth: Optional[Authentication] = None, + network_session: NetworkSession = None + ): + if network_session is None: + network_session = NetworkSession() + self.auth = auth + self.network_session = network_session + + def create_query_v2026_r0( + self, + query: CreateQueryV2026R0Query, + *, + order_by: Optional[List[QueryOrderByV2026R0]] = None, + limit: Optional[int] = None, + fields: Optional[List[str]] = None, + marker: Optional[str] = None, + box_version: BoxVersionHeaderV2026R0 = BoxVersionHeaderV2026R0._2026_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> QueryResultsV2026R0: + """ + Runs a query to discover Box items using a logical predicate that can filter + + across item fields and metadata templates. Results can be sorted, paginated, + + + and shaped to include additional item or metadata fields. + + :param query: The query definition, including the filtering predicate and its optional + parameters and ancestor restrictions. + :type query: CreateQueryV2026R0Query + :param order_by: The sorting criteria for the result set. Entries are applied sequentially + to define multi-level sorting., defaults to None + :type order_by: Optional[List[QueryOrderByV2026R0]], optional + :param limit: The maximum number of results to return. Defaults to `50` when not + provided., defaults to None + :type limit: Optional[int], optional + :param fields: Controls which additional fields are included in each result entry. Each + value must be one of: a fully qualified item field key (for example + `box:item:name`), a metadata template key to hydrate the full template (for + example `enterprise_12345678:project`), or a specific metadata template + field key to hydrate a single field from the template (for example + `enterprise_12345678:project:name`). When omitted, entries include only the + item type and identifier., defaults to None + :type fields: Optional[List[str]], optional + :param marker: An opaque token returned from a previous response, used to continue + retrieval. When provided, all other request parameters must exactly match + those of the original request., defaults to None + :type marker: Optional[str], optional + :param box_version: Version header., defaults to BoxVersionHeaderV2026R0._2026_0 + :type box_version: BoxVersionHeaderV2026R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = { + 'query': query, + 'order_by': order_by, + 'limit': limit, + 'fields': fields, + 'marker': marker, + } + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join([self.network_session.base_urls.base_url, '/2.0/query']), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, QueryResultsV2026R0) + + def create_query_insight_v2026_r0( + self, + query: CreateQueryInsightV2026R0Query, + metrics: Dict[str, QueryInsightsMetricDefinitionV2026R0], + *, + box_version: BoxVersionHeaderV2026R0 = BoxVersionHeaderV2026R0._2026_0, + extra_headers: Optional[Dict[str, Optional[str]]] = None + ) -> QueryInsightsV2026R0: + """ + Computes aggregated metrics over Box items matching a query predicate. + + Filters are applied first, followed by optional grouping, after which the + + + requested metrics (such as `sum`, `avg`, `min`, `max`, and `count`) are + + + computed for each resulting group or over the entire filtered dataset. + + :param query: The filtering and grouping definition. Filters are applied first, followed + by grouping, before metrics are computed. + :type query: CreateQueryInsightV2026R0Query + :param metrics: A map of user-defined metric aliases to their definitions. A maximum of 10 + metrics may be defined. Each alias must be a unique, non-empty string of up + to 256 characters, containing only letters, digits, `_`, `-`, or `.`, and + must not start with a digit, `_`, `-`, or `.`. May be empty to request + only a total count. + :type metrics: Dict[str, QueryInsightsMetricDefinitionV2026R0] + :param box_version: Version header., defaults to BoxVersionHeaderV2026R0._2026_0 + :type box_version: BoxVersionHeaderV2026R0, optional + :param extra_headers: Extra headers that will be included in the HTTP request., defaults to None + :type extra_headers: Optional[Dict[str, Optional[str]]], optional + """ + if extra_headers is None: + extra_headers = {} + request_body: Dict = {'query': query, 'metrics': metrics} + headers_map: Dict[str, str] = prepare_params( + {'box-version': to_string(box_version), **extra_headers} + ) + response: FetchResponse = self.network_session.network_client.fetch( + FetchOptions( + url=''.join( + [self.network_session.base_urls.base_url, '/2.0/query_insights'] + ), + method='POST', + headers=headers_map, + data=serialize(request_body), + content_type='application/json', + response_format=ResponseFormat.JSON, + auth=self.auth, + network_session=self.network_session, + ) + ) + return deserialize(response.data, QueryInsightsV2026R0) diff --git a/box_sdk_gen/schemas/file_full.py b/box_sdk_gen/schemas/file_full.py index c9535978f..682a68515 100644 --- a/box_sdk_gen/schemas/file_full.py +++ b/box_sdk_gen/schemas/file_full.py @@ -30,6 +30,8 @@ from box_sdk_gen.schemas.metadata_full import MetadataFull +from box_sdk_gen.schemas.collection import Collection + from box_sdk_gen.box.errors import BoxSDKError from box_sdk_gen.internal.utils import DateTime @@ -413,6 +415,12 @@ class FileFullSharedLinkPermissionOptionsField(str, Enum): CAN_EDIT = 'can_edit' +class FileFullAllowedSharedLinkAccessLevelsField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + class FileFull(File): _discriminator = 'type', {'file'} @@ -443,6 +451,13 @@ def __init__( List[FileFullSharedLinkPermissionOptionsField] ] = None, is_associated_with_app_item: Optional[bool] = None, + collections: Optional[List[Collection]] = None, + is_download_available: Optional[bool] = None, + download_url: Optional[str] = None, + authenticated_download_url: Optional[str] = None, + allowed_shared_link_access_levels: Optional[ + List[FileFullAllowedSharedLinkAccessLevelsField] + ] = None, description: Optional[str] = None, size: Optional[int] = None, path_collection: Optional[FilePathCollectionField] = None, @@ -509,6 +524,36 @@ def __init__( true even if the context user does not have access to the app item(s) associated with the file., defaults to None :type is_associated_with_app_item: Optional[bool], optional + :param collections: The collections that this file belongs to. + + For more information, see the + [collections guide](https://developer.box.com/guides/collections)., defaults to None + :type collections: Optional[List[Collection]], optional + :param is_download_available: Whether the file's binary content is eligible to be downloaded. + + This is a content-level flag and does not reflect whether the + current user is authorized to download the file. Use + `permissions.can_download`, when available, for that., defaults to None + :type is_download_available: Optional[bool], optional + :param download_url: A pre-authorized, expiring URL for directly downloading the file's + content. Requires authentication and is valid only for the current + session. + + This field is only returned for files, not folders or web links., defaults to None + :type download_url: Optional[str], optional + :param authenticated_download_url: A stable API URL for the file content endpoint, + `/2.0/files/{id}/content`. Unlike `download_url`, authorization is + evaluated when the URL is requested with a valid access token. + + This field is only returned for files, not folders or web links., defaults to None + :type authenticated_download_url: Optional[str], optional + :param allowed_shared_link_access_levels: The shared link access levels the authenticated user is allowed to + use when creating or updating a shared link for this file. + + The list depends on item policy and user authorization, so it may be + narrower than the levels available to the owner. An empty array means + no access level is available to this user., defaults to None + :type allowed_shared_link_access_levels: Optional[List[FileFullAllowedSharedLinkAccessLevelsField]], optional :param description: The optional description of this file. If the description exceeds 255 characters, the first 255 characters are set as a file description and the rest of it is ignored., defaults to None @@ -595,3 +640,8 @@ def __init__( self.disposition_at = disposition_at self.shared_link_permission_options = shared_link_permission_options self.is_associated_with_app_item = is_associated_with_app_item + self.collections = collections + self.is_download_available = is_download_available + self.download_url = download_url + self.authenticated_download_url = authenticated_download_url + self.allowed_shared_link_access_levels = allowed_shared_link_access_levels diff --git a/box_sdk_gen/schemas/folder_full.py b/box_sdk_gen/schemas/folder_full.py index 734d8ef94..2d58bcced 100644 --- a/box_sdk_gen/schemas/folder_full.py +++ b/box_sdk_gen/schemas/folder_full.py @@ -30,6 +30,8 @@ from box_sdk_gen.schemas.metadata_full import MetadataFull +from box_sdk_gen.schemas.collection import Collection + from box_sdk_gen.box.errors import BoxSDKError @@ -182,6 +184,7 @@ def __init__( can_non_owners_view_collaborators: Optional[bool] = None, classification: Optional[FolderFullClassificationField] = None, is_associated_with_app_item: Optional[bool] = None, + collections: Optional[List[Collection]] = None, created_at: Optional[DateTime] = None, modified_at: Optional[DateTime] = None, description: Optional[str] = None, @@ -219,11 +222,12 @@ def __init__( :param is_externally_owned: Specifies if this folder is owned by a user outside of the authenticated enterprise., defaults to None :type is_externally_owned: Optional[bool], optional - :param allowed_shared_link_access_levels: A list of access levels that are available - for this folder. + :param allowed_shared_link_access_levels: The shared link access levels the authenticated user is allowed to + use when creating or updating a shared link for this folder. - For some folders, like the root folder, this will always - be an empty list as sharing is not allowed at that level., defaults to None + The list depends on item policy and user authorization. For some + folders, like the root folder, this is always empty as sharing is + not allowed at that level., defaults to None :type allowed_shared_link_access_levels: Optional[List[FolderFullAllowedSharedLinkAccessLevelsField]], optional :param allowed_invitee_roles: A list of the types of roles that user can be invited at when sharing this folder., defaults to None @@ -244,6 +248,11 @@ def __init__( return true even if the context user does not have access to the app item(s) associated with the folder., defaults to None :type is_associated_with_app_item: Optional[bool], optional + :param collections: The collections that this folder belongs to. + + For more information, see the + [collections guide](https://developer.box.com/guides/collections)., defaults to None + :type collections: Optional[List[Collection]], optional :param created_at: The date and time when the folder was created. This value may be `null` for some folders such as the root folder or the trash folder., defaults to None @@ -332,3 +341,4 @@ def __init__( self.can_non_owners_view_collaborators = can_non_owners_view_collaborators self.classification = classification self.is_associated_with_app_item = is_associated_with_app_item + self.collections = collections diff --git a/box_sdk_gen/schemas/v2026_r0/__init__.py b/box_sdk_gen/schemas/v2026_r0/__init__.py index 5735f86fd..95e2bce5d 100644 --- a/box_sdk_gen/schemas/v2026_r0/__init__.py +++ b/box_sdk_gen/schemas/v2026_r0/__init__.py @@ -10,6 +10,28 @@ from box_sdk_gen.schemas.v2026_r0.notes_convert_response_v2026_r0 import * +from box_sdk_gen.schemas.v2026_r0.query_ancestor_reference_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.query_insight_metric_result_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.query_insight_entry_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.query_insights_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.query_insights_group_by_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.query_insights_metric_definition_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.query_insights_request_body_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.query_order_by_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.query_request_body_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.query_result_entry_v2026_r0 import * + +from box_sdk_gen.schemas.v2026_r0.query_results_v2026_r0 import * + from box_sdk_gen.schemas.v2026_r0.user_base_v2026_r0 import * from box_sdk_gen.schemas.v2026_r0.user_mini_v2026_r0 import * diff --git a/box_sdk_gen/schemas/v2026_r0/query_ancestor_reference_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_ancestor_reference_v2026_r0.py new file mode 100644 index 000000000..b8515b8bd --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_ancestor_reference_v2026_r0.py @@ -0,0 +1,16 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryAncestorReferenceV2026R0(BaseObject): + def __init__(self, id: str, type: str, **kwargs): + """ + :param id: The unique identifier of the ancestor entity. + :type id: str + :param type: The type of the ancestor entity. Possible value: folder. + :type type: str + """ + super().__init__(**kwargs) + self.id = id + self.type = type diff --git a/box_sdk_gen/schemas/v2026_r0/query_insight_entry_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_insight_entry_v2026_r0.py new file mode 100644 index 000000000..ccf391f0a --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_insight_entry_v2026_r0.py @@ -0,0 +1,47 @@ +from enum import Enum + +from typing import List + +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2026_r0.query_insight_metric_result_v2026_r0 import ( + QueryInsightMetricResultV2026R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryInsightEntryV2026R0TypeField(str, Enum): + GROUP = 'group' + OVERALL = 'overall' + OTHER = 'other' + + +class QueryInsightEntryV2026R0(BaseObject): + _discriminator = 'type', {'group', 'overall', 'other'} + + def __init__( + self, + key: List[str], + type: QueryInsightEntryV2026R0TypeField, + metrics: Dict[str, QueryInsightMetricResultV2026R0], + **kwargs + ): + """ + :param key: The grouping key values associated with the entry. Contains one value per + `group_by` field for `group` entries, and is empty for `overall` and + `other` entries. + :type key: List[str] + :param type: The type of insight entry, indicating how the associated metrics are + aggregated. + :type type: QueryInsightEntryV2026R0TypeField + :param metrics: A map of metric aliases to their computed results. For `other` entries, the + count is reported under the `totalCountBeyondTopGroups` key. + :type metrics: Dict[str, QueryInsightMetricResultV2026R0] + """ + super().__init__(**kwargs) + self.key = key + self.type = type + self.metrics = metrics diff --git a/box_sdk_gen/schemas/v2026_r0/query_insight_metric_result_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_insight_metric_result_v2026_r0.py new file mode 100644 index 000000000..d93966c58 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_insight_metric_result_v2026_r0.py @@ -0,0 +1,19 @@ +from typing import Dict + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryInsightMetricResultV2026R0(BaseObject): + def __init__(self, type: str, values: Dict[str, float], **kwargs): + """ + :param type: The metric type that was computed. + :type type: str + :param values: The computed metric result(s), keyed by the metric function (for example + `sum`, `avg`, `min`, `max`, or `count`). + :type values: Dict[str, float] + """ + super().__init__(**kwargs) + self.type = type + self.values = values diff --git a/box_sdk_gen/schemas/v2026_r0/query_insights_group_by_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_insights_group_by_v2026_r0.py new file mode 100644 index 000000000..1671b31f2 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_insights_group_by_v2026_r0.py @@ -0,0 +1,19 @@ +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryInsightsGroupByV2026R0(BaseObject): + def __init__(self, field: str, *, bucket_limit: Optional[int] = None, **kwargs): + """ + :param field: The fully qualified field name to group by. Supports metadata and item + properties. + :type field: str + :param bucket_limit: The maximum number of buckets to return for the grouping. Defaults to `5`., defaults to None + :type bucket_limit: Optional[int], optional + """ + super().__init__(**kwargs) + self.field = field + self.bucket_limit = bucket_limit diff --git a/box_sdk_gen/schemas/v2026_r0/query_insights_metric_definition_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_insights_metric_definition_v2026_r0.py new file mode 100644 index 000000000..796700679 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_insights_metric_definition_v2026_r0.py @@ -0,0 +1,30 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryInsightsMetricDefinitionV2026R0TypeField(str, Enum): + SUM = 'sum' + AVG = 'avg' + MIN = 'min' + MAX = 'max' + COUNT = 'count' + + +class QueryInsightsMetricDefinitionV2026R0(BaseObject): + _discriminator = 'type', {'sum', 'avg', 'min', 'max', 'count'} + + def __init__( + self, type: QueryInsightsMetricDefinitionV2026R0TypeField, field: str, **kwargs + ): + """ + :param type: The aggregation function to apply. + :type type: QueryInsightsMetricDefinitionV2026R0TypeField + :param field: The fully qualified field name on which the metric is computed. + :type field: str + """ + super().__init__(**kwargs) + self.type = type + self.field = field diff --git a/box_sdk_gen/schemas/v2026_r0/query_insights_request_body_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_insights_request_body_v2026_r0.py new file mode 100644 index 000000000..b4d776c24 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_insights_request_body_v2026_r0.py @@ -0,0 +1,78 @@ +from typing import Optional + +from typing import Dict + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2026_r0.query_ancestor_reference_v2026_r0 import ( + QueryAncestorReferenceV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.query_insights_group_by_v2026_r0 import ( + QueryInsightsGroupByV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.query_insights_metric_definition_v2026_r0 import ( + QueryInsightsMetricDefinitionV2026R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryInsightsRequestBodyV2026R0QueryField(BaseObject): + def __init__( + self, + predicate: str, + *, + params: Optional[Dict] = None, + ancestors: Optional[List[QueryAncestorReferenceV2026R0]] = None, + group_by: Optional[List[QueryInsightsGroupByV2026R0]] = None, + **kwargs + ): + """ + :param predicate: A logical expression used to filter the dataset prior to metric + computation, similar to an SQL `WHERE` clause. May include + named parameters referenced as `:placeholder`. + :type predicate: str + :param params: A map of placeholder names (without the `:` prefix) to their values. + Required only when the predicate contains parameter placeholders. The + type of each value must match the type of the field it is compared to., defaults to None + :type params: Optional[Dict], optional + :param ancestors: Restricts results to items contained within any of the specified + ancestors. The user must have access to every listed ancestor. When + omitted, insights are computed across all accessible items., defaults to None + :type ancestors: Optional[List[QueryAncestorReferenceV2026R0]], optional + :param group_by: Defines how data is grouped for insights computation. Currently only a + single grouping field is supported., defaults to None + :type group_by: Optional[List[QueryInsightsGroupByV2026R0]], optional + """ + super().__init__(**kwargs) + self.predicate = predicate + self.params = params + self.ancestors = ancestors + self.group_by = group_by + + +class QueryInsightsRequestBodyV2026R0(BaseObject): + def __init__( + self, + query: QueryInsightsRequestBodyV2026R0QueryField, + metrics: Dict[str, QueryInsightsMetricDefinitionV2026R0], + **kwargs + ): + """ + :param query: The filtering and grouping definition. Filters are applied first, followed + by grouping, before metrics are computed. + :type query: QueryInsightsRequestBodyV2026R0QueryField + :param metrics: A map of user-defined metric aliases to their definitions. A maximum of 10 + metrics may be defined. Each alias must be a unique, non-empty string of up + to 256 characters, containing only letters, digits, `_`, `-`, or `.`, and + must not start with a digit, `_`, `-`, or `.`. May be empty to request + only a total count. + :type metrics: Dict[str, QueryInsightsMetricDefinitionV2026R0] + """ + super().__init__(**kwargs) + self.query = query + self.metrics = metrics diff --git a/box_sdk_gen/schemas/v2026_r0/query_insights_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_insights_v2026_r0.py new file mode 100644 index 000000000..f052c008e --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_insights_v2026_r0.py @@ -0,0 +1,20 @@ +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2026_r0.query_insight_entry_v2026_r0 import ( + QueryInsightEntryV2026R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryInsightsV2026R0(BaseObject): + def __init__(self, insights: List[QueryInsightEntryV2026R0], **kwargs): + """ + :param insights: The list of computed insight entries. Each entry corresponds to a group, + the overall dataset, or the aggregate of groups outside the top results. + :type insights: List[QueryInsightEntryV2026R0] + """ + super().__init__(**kwargs) + self.insights = insights diff --git a/box_sdk_gen/schemas/v2026_r0/query_order_by_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_order_by_v2026_r0.py new file mode 100644 index 000000000..a63d7ab95 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_order_by_v2026_r0.py @@ -0,0 +1,25 @@ +from enum import Enum + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryOrderByV2026R0DirectionField(str, Enum): + ASC = 'asc' + DESC = 'desc' + + +class QueryOrderByV2026R0(BaseObject): + def __init__( + self, field_key: str, direction: QueryOrderByV2026R0DirectionField, **kwargs + ): + """ + :param field_key: The fully qualified field key to sort by. + :type field_key: str + :param direction: The direction in which results are ordered. + :type direction: QueryOrderByV2026R0DirectionField + """ + super().__init__(**kwargs) + self.field_key = field_key + self.direction = direction diff --git a/box_sdk_gen/schemas/v2026_r0/query_request_body_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_request_body_v2026_r0.py new file mode 100644 index 000000000..fadc43664 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_request_body_v2026_r0.py @@ -0,0 +1,86 @@ +from typing import Optional + +from typing import Dict + +from typing import List + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2026_r0.query_ancestor_reference_v2026_r0 import ( + QueryAncestorReferenceV2026R0, +) + +from box_sdk_gen.schemas.v2026_r0.query_order_by_v2026_r0 import QueryOrderByV2026R0 + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryRequestBodyV2026R0QueryField(BaseObject): + def __init__( + self, + predicate: str, + *, + params: Optional[Dict] = None, + ancestors: Optional[List[QueryAncestorReferenceV2026R0]] = None, + **kwargs + ): + """ + :param predicate: A logical expression used to filter the dataset, similar to an SQL + `WHERE` clause. May include named parameters referenced as + `:placeholder`. + :type predicate: str + :param params: A map of placeholder names (without the `:` prefix) to their values. + Required only when the predicate contains parameter placeholders. The + type of each value must match the type of the field it is compared to., defaults to None + :type params: Optional[Dict], optional + :param ancestors: Restricts results to the specified ancestor entities and their + recursive descendants. The user must have read access to every listed + ancestor., defaults to None + :type ancestors: Optional[List[QueryAncestorReferenceV2026R0]], optional + """ + super().__init__(**kwargs) + self.predicate = predicate + self.params = params + self.ancestors = ancestors + + +class QueryRequestBodyV2026R0(BaseObject): + def __init__( + self, + query: QueryRequestBodyV2026R0QueryField, + *, + order_by: Optional[List[QueryOrderByV2026R0]] = None, + limit: Optional[int] = None, + fields: Optional[List[str]] = None, + marker: Optional[str] = None, + **kwargs + ): + """ + :param query: The query definition, including the filtering predicate and its optional + parameters and ancestor restrictions. + :type query: QueryRequestBodyV2026R0QueryField + :param order_by: The sorting criteria for the result set. Entries are applied sequentially + to define multi-level sorting., defaults to None + :type order_by: Optional[List[QueryOrderByV2026R0]], optional + :param limit: The maximum number of results to return. Defaults to `50` when not + provided., defaults to None + :type limit: Optional[int], optional + :param fields: Controls which additional fields are included in each result entry. Each + value must be one of: a fully qualified item field key (for example + `box:item:name`), a metadata template key to hydrate the full template (for + example `enterprise_12345678:project`), or a specific metadata template + field key to hydrate a single field from the template (for example + `enterprise_12345678:project:name`). When omitted, entries include only the + item type and identifier., defaults to None + :type fields: Optional[List[str]], optional + :param marker: An opaque token returned from a previous response, used to continue + retrieval. When provided, all other request parameters must exactly match + those of the original request., defaults to None + :type marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.query = query + self.order_by = order_by + self.limit = limit + self.fields = fields + self.marker = marker diff --git a/box_sdk_gen/schemas/v2026_r0/query_result_entry_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_result_entry_v2026_r0.py new file mode 100644 index 000000000..f562c7056 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_result_entry_v2026_r0.py @@ -0,0 +1,17 @@ +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryResultEntryV2026R0(BaseObject): + def __init__(self, id: str, type: str, **kwargs): + """ + :param id: The unique identifier of the matching item. + :type id: str + :param type: The type of the matching item. + :type type: str + """ + super().__init__(**kwargs) + self.id = id + self.type = type + self.extra_data = kwargs diff --git a/box_sdk_gen/schemas/v2026_r0/query_results_v2026_r0.py b/box_sdk_gen/schemas/v2026_r0/query_results_v2026_r0.py new file mode 100644 index 000000000..120d05aa7 --- /dev/null +++ b/box_sdk_gen/schemas/v2026_r0/query_results_v2026_r0.py @@ -0,0 +1,36 @@ +from typing import List + +from typing import Optional + +from box_sdk_gen.internal.base_object import BaseObject + +from box_sdk_gen.schemas.v2026_r0.query_result_entry_v2026_r0 import ( + QueryResultEntryV2026R0, +) + +from box_sdk_gen.box.errors import BoxSDKError + + +class QueryResultsV2026R0(BaseObject): + def __init__( + self, + entries: List[QueryResultEntryV2026R0], + limit: int, + *, + next_marker: Optional[str] = None, + **kwargs + ): + """ + :param entries: The list of items matching the query predicate. + :type entries: List[QueryResultEntryV2026R0] + :param limit: The limit that was used for this request. This will be the same as the limit query + parameter unless that value exceeded the maximum value allowed. + :type limit: int + :param next_marker: The marker for the start of the next page of results. When `null`, there + are no further results available., defaults to None + :type next_marker: Optional[str], optional + """ + super().__init__(**kwargs) + self.entries = entries + self.limit = limit + self.next_marker = next_marker diff --git a/box_sdk_gen/schemas/web_link.py b/box_sdk_gen/schemas/web_link.py index 744df7c58..facbdf6a4 100644 --- a/box_sdk_gen/schemas/web_link.py +++ b/box_sdk_gen/schemas/web_link.py @@ -16,6 +16,8 @@ from box_sdk_gen.schemas.user_mini import UserMini +from box_sdk_gen.schemas.collection import Collection + from box_sdk_gen.box.errors import BoxSDKError from box_sdk_gen.internal.utils import DateTime @@ -172,6 +174,12 @@ class WebLinkItemStatusField(str, Enum): DELETED = 'deleted' +class WebLinkAllowedSharedLinkAccessLevelsField(str, Enum): + OPEN = 'open' + COMPANY = 'company' + COLLABORATORS = 'collaborators' + + class WebLink(WebLinkMini): _discriminator = 'type', {'web_link'} @@ -191,6 +199,10 @@ def __init__( owned_by: Optional[UserMini] = None, shared_link: Optional[WebLinkSharedLinkField] = None, item_status: Optional[WebLinkItemStatusField] = None, + collections: Optional[List[Collection]] = None, + allowed_shared_link_access_levels: Optional[ + List[WebLinkAllowedSharedLinkAccessLevelsField] + ] = None, url: Optional[str] = None, sequence_id: Optional[str] = None, name: Optional[str] = None, @@ -217,6 +229,18 @@ def __init__( `trashed` if the file has been moved to the trash, and `deleted` if the file has been permanently deleted., defaults to None :type item_status: Optional[WebLinkItemStatusField], optional + :param collections: The collections that this web link belongs to. + + For more information, see the + [collections guide](https://developer.box.com/guides/collections)., defaults to None + :type collections: Optional[List[Collection]], optional + :param allowed_shared_link_access_levels: The shared link access levels the authenticated user is allowed to + use when creating or updating a shared link for this web link. + + The list depends on item policy and user authorization, so it may be + narrower than the levels available to the owner. An empty array means + no access level is available to this user., defaults to None + :type allowed_shared_link_access_levels: Optional[List[WebLinkAllowedSharedLinkAccessLevelsField]], optional :param url: The URL this web link points to., defaults to None :type url: Optional[str], optional :param name: The name of the web link., defaults to None @@ -248,3 +272,5 @@ def __init__( self.owned_by = owned_by self.shared_link = shared_link self.item_status = item_status + self.collections = collections + self.allowed_shared_link_access_levels = allowed_shared_link_access_levels diff --git a/docs/README.md b/docs/README.md index e9d057eef..271ddc117 100644 --- a/docs/README.md +++ b/docs/README.md @@ -62,6 +62,7 @@ the SDK are available by topic: - [Metadata taxonomies](metadata_taxonomies.md) - [Metadata templates](metadata_templates.md) - [Notes](notes.md) +- [Query](query.md) - [Recent items](recent_items.md) - [Retention policies](retention_policies.md) - [Retention policy assignments](retention_policy_assignments.md) diff --git a/docs/query.md b/docs/query.md new file mode 100644 index 000000000..f26e1391f --- /dev/null +++ b/docs/query.md @@ -0,0 +1,71 @@ +# QueryManager + +- [Query for Box items](#query-for-box-items) +- [Create insights for Box items](#create-insights-for-box-items) + +## Query for Box items + +Runs a query to discover Box items using a logical predicate that can filter +across item fields and metadata templates. Results can be sorted, paginated, +and shaped to include additional item or metadata fields. + +This operation is performed by calling function `create_query_v2026_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2026.0/post-query/). + +_Currently we don't have an example for calling `create_query_v2026_r0` in integration tests_ + +### Arguments + +- query `CreateQueryV2026R0Query` + - The query definition, including the filtering predicate and its optional parameters and ancestor restrictions. +- order_by `Optional[List[QueryOrderByV2026R0]]` + - The sorting criteria for the result set. Entries are applied sequentially to define multi-level sorting. +- limit `Optional[int]` + - The maximum number of results to return. Defaults to `50` when not provided. +- fields `Optional[List[str]]` + - Controls which additional fields are included in each result entry. Each value must be one of: a fully qualified item field key (for example `box:item:name`), a metadata template key to hydrate the full template (for example `enterprise_12345678:project`), or a specific metadata template field key to hydrate a single field from the template (for example `enterprise_12345678:project:name`). When omitted, entries include only the item type and identifier. +- marker `Optional[str]` + - An opaque token returned from a previous response, used to continue retrieval. When provided, all other request parameters must exactly match those of the original request. +- box_version `BoxVersionHeaderV2026R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `QueryResultsV2026R0`. + +Returns a paginated list of items matching the query. + +## Create insights for Box items + +Computes aggregated metrics over Box items matching a query predicate. +Filters are applied first, followed by optional grouping, after which the +requested metrics (such as `sum`, `avg`, `min`, `max`, and `count`) are +computed for each resulting group or over the entire filtered dataset. + +This operation is performed by calling function `create_query_insight_v2026_r0`. + +See the endpoint docs at +[API Reference](https://developer.box.com/reference/v2026.0/post-query-insights/). + +_Currently we don't have an example for calling `create_query_insight_v2026_r0` in integration tests_ + +### Arguments + +- query `CreateQueryInsightV2026R0Query` + - The filtering and grouping definition. Filters are applied first, followed by grouping, before metrics are computed. +- metrics `Dict[str, QueryInsightsMetricDefinitionV2026R0]` + - A map of user-defined metric aliases to their definitions. A maximum of 10 metrics may be defined. Each alias must be a unique, non-empty string of up to 256 characters, containing only letters, digits, `_`, `-`, or `.`, and must not start with a digit, `_`, `-`, or `.`. May be empty to request only a total count. +- box_version `BoxVersionHeaderV2026R0` + - Version header. +- extra_headers `Optional[Dict[str, Optional[str]]]` + - Extra headers that will be included in the HTTP request. + +### Returns + +This function returns a value of type `QueryInsightsV2026R0`. + +Returns the computed insight entries. From 94120f3fa866c2521db42d62dc7471030c023871 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 31 Jul 2026 13:36:31 +0200 Subject: [PATCH 134/139] feat: Added two missing event types (box/box-openapi#611) (#1531) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/managers/events.py | 4 ++++ box_sdk_gen/schemas/event.py | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index d018b3409..782443e2c 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "2cee311", "specHash": "cc375ad", "version": "10.13.0" } +{ "engineHash": "2cee311", "specHash": "2aeae37", "version": "10.13.0" } diff --git a/box_sdk_gen/managers/events.py b/box_sdk_gen/managers/events.py index 9d3243373..482b3cbcd 100644 --- a/box_sdk_gen/managers/events.py +++ b/box_sdk_gen/managers/events.py @@ -150,6 +150,7 @@ class GetEventsEventType(str, Enum): SHARED_LINK_SEND = 'SHARED_LINK_SEND' SHARE_EXPIRATION = 'SHARE_EXPIRATION' SHIELD_ALERT = 'SHIELD_ALERT' + SHIELD_DOWNLOAD_BLOCKED = 'SHIELD_DOWNLOAD_BLOCKED' SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED = 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED' SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION = ( 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION' @@ -159,6 +160,7 @@ class GetEventsEventType(str, Enum): 'SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED_MISSING_JUSTIFICATION' ) SHIELD_JUSTIFICATION_APPROVAL = 'SHIELD_JUSTIFICATION_APPROVAL' + SHIELD_PREVIEW_BLOCKED = 'SHIELD_PREVIEW_BLOCKED' SHIELD_SHARED_LINK_ACCESS_BLOCKED = 'SHIELD_SHARED_LINK_ACCESS_BLOCKED' SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE = ( 'SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE' @@ -309,6 +311,7 @@ class GetEventStreamQueryParamsEventTypeField(str, Enum): SHARED_LINK_SEND = 'SHARED_LINK_SEND' SHARE_EXPIRATION = 'SHARE_EXPIRATION' SHIELD_ALERT = 'SHIELD_ALERT' + SHIELD_DOWNLOAD_BLOCKED = 'SHIELD_DOWNLOAD_BLOCKED' SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED = 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED' SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION = ( 'SHIELD_EXTERNAL_COLLAB_ACCESS_BLOCKED_MISSING_JUSTIFICATION' @@ -318,6 +321,7 @@ class GetEventStreamQueryParamsEventTypeField(str, Enum): 'SHIELD_EXTERNAL_COLLAB_INVITE_BLOCKED_MISSING_JUSTIFICATION' ) SHIELD_JUSTIFICATION_APPROVAL = 'SHIELD_JUSTIFICATION_APPROVAL' + SHIELD_PREVIEW_BLOCKED = 'SHIELD_PREVIEW_BLOCKED' SHIELD_SHARED_LINK_ACCESS_BLOCKED = 'SHIELD_SHARED_LINK_ACCESS_BLOCKED' SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE = ( 'SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE' diff --git a/box_sdk_gen/schemas/event.py b/box_sdk_gen/schemas/event.py index 1e71c0d56..9488475b0 100644 --- a/box_sdk_gen/schemas/event.py +++ b/box_sdk_gen/schemas/event.py @@ -216,6 +216,7 @@ class EventEventTypeField(str, Enum): 'SHIELD_INFORMATION_BARRIER_SHARED_ITEM_ACCESS_BLOCKED' ) SHIELD_JUSTIFICATION_APPROVAL = 'SHIELD_JUSTIFICATION_APPROVAL' + SHIELD_PREVIEW_BLOCKED = 'SHIELD_PREVIEW_BLOCKED' SHIELD_SHARED_LINK_ACCESS_BLOCKED = 'SHIELD_SHARED_LINK_ACCESS_BLOCKED' SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE = ( 'SHIELD_SHARED_LINK_STATUS_RESTRICTED_ON_CREATE' From 9d4542f1ca9ef8da5b273705e670c19e89d5ea8b Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Fri, 31 Jul 2026 13:39:02 +0200 Subject: [PATCH 135/139] feat: add `is_collaborated_content_available_when_owner_inactive` property (box/box-openapi#613) (#1537) Co-authored-by: box-sdk-build --- .codegen.json | 2 +- box_sdk_gen/managers/users.py | 8 ++++++++ box_sdk_gen/schemas/user_full.py | 8 ++++++++ docs/users.md | 2 ++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 782443e2c..cb58a4bf7 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "2cee311", "specHash": "2aeae37", "version": "10.13.0" } +{ "engineHash": "7146a71", "specHash": "86fcc6c", "version": "10.13.0" } diff --git a/box_sdk_gen/managers/users.py b/box_sdk_gen/managers/users.py index aa878aab1..63de618d2 100644 --- a/box_sdk_gen/managers/users.py +++ b/box_sdk_gen/managers/users.py @@ -499,6 +499,7 @@ def update_user_by_id( is_exempt_from_device_limits: Optional[bool] = None, is_exempt_from_login_verification: Optional[bool] = None, is_password_reset_required: Optional[bool] = None, + is_collaborated_content_available_when_owner_inactive: Optional[bool] = None, status: Optional[UpdateUserByIdStatus] = None, space_amount: Optional[int] = None, notification_email: Union[ @@ -564,6 +565,10 @@ def update_user_by_id( :type is_exempt_from_login_verification: Optional[bool], optional :param is_password_reset_required: Whether the user is required to reset their password., defaults to None :type is_password_reset_required: Optional[bool], optional + :param is_collaborated_content_available_when_owner_inactive: Whether collaborators can access content owned by the user when + the user is inactive. This setting preserves existing + collaborator access and does not grant new permissions., defaults to None + :type is_collaborated_content_available_when_owner_inactive: Optional[bool], optional :param status: The user's account status., defaults to None :type status: Optional[UpdateUserByIdStatus], optional :param space_amount: The user’s total available space in bytes. Set this to `-1` to @@ -616,6 +621,9 @@ def update_user_by_id( 'is_exempt_from_device_limits': is_exempt_from_device_limits, 'is_exempt_from_login_verification': is_exempt_from_login_verification, 'is_password_reset_required': is_password_reset_required, + 'is_collaborated_content_available_when_owner_inactive': ( + is_collaborated_content_available_when_owner_inactive + ), 'status': status, 'space_amount': space_amount, 'notification_email': notification_email, diff --git a/box_sdk_gen/schemas/user_full.py b/box_sdk_gen/schemas/user_full.py index e67367c46..ba5065ebb 100644 --- a/box_sdk_gen/schemas/user_full.py +++ b/box_sdk_gen/schemas/user_full.py @@ -74,6 +74,7 @@ def __init__( is_external_collab_restricted: Optional[bool] = None, is_exempt_from_device_limits: Optional[bool] = None, is_exempt_from_login_verification: Optional[bool] = None, + is_collaborated_content_available_when_owner_inactive: Optional[bool] = None, enterprise: Optional[UserFullEnterpriseField] = None, my_tags: Optional[List[str]] = None, hostname: Optional[str] = None, @@ -118,6 +119,10 @@ def __init__( :type is_exempt_from_device_limits: Optional[bool], optional :param is_exempt_from_login_verification: Whether the user must use two-factor authentication., defaults to None :type is_exempt_from_login_verification: Optional[bool], optional + :param is_collaborated_content_available_when_owner_inactive: Whether collaborators can access content owned by the user when the + user is inactive. This setting preserves existing collaborator access + and does not grant new permissions., defaults to None + :type is_collaborated_content_available_when_owner_inactive: Optional[bool], optional :param my_tags: Tags for all files and folders owned by the user. Values returned will only contain tags that were set by the requester., defaults to None :type my_tags: Optional[List[str]], optional @@ -194,6 +199,9 @@ def __init__( self.is_external_collab_restricted = is_external_collab_restricted self.is_exempt_from_device_limits = is_exempt_from_device_limits self.is_exempt_from_login_verification = is_exempt_from_login_verification + self.is_collaborated_content_available_when_owner_inactive = ( + is_collaborated_content_available_when_owner_inactive + ) self.enterprise = enterprise self.my_tags = my_tags self.hostname = hostname diff --git a/docs/users.md b/docs/users.md index 178dd4dc4..530b636c6 100644 --- a/docs/users.md +++ b/docs/users.md @@ -257,6 +257,8 @@ client.users.update_user_by_id(user.id, name=updated_user_name) - Whether the user must use two-factor authentication. - is_password_reset_required `Optional[bool]` - Whether the user is required to reset their password. +- is_collaborated_content_available_when_owner_inactive `Optional[bool]` + - Whether collaborators can access content owned by the user when the user is inactive. This setting preserves existing collaborator access and does not grant new permissions. - status `Optional[UpdateUserByIdStatus]` - The user's account status. - space_amount `Optional[int]` From bd917dd3764458b969995ebd3aa2ee5657532b99 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Mon, 3 Aug 2026 10:14:54 +0200 Subject: [PATCH 136/139] test: add query test (box/box-codegen#968) (#1539) --- .codegen.json | 2 +- docs/query.md | 32 ++++++++- test/query.py | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 test/query.py diff --git a/.codegen.json b/.codegen.json index cb58a4bf7..f34129208 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "7146a71", "specHash": "86fcc6c", "version": "10.13.0" } +{ "engineHash": "7b5a612", "specHash": "86fcc6c", "version": "10.13.0" } diff --git a/docs/query.md b/docs/query.md index f26e1391f..00faa1e8d 100644 --- a/docs/query.md +++ b/docs/query.md @@ -14,7 +14,19 @@ This operation is performed by calling function `create_query_v2026_r0`. See the endpoint docs at [API Reference](https://developer.box.com/reference/v2026.0/post-query/). -_Currently we don't have an example for calling `create_query_v2026_r0` in integration tests_ + + +```python +client.query.create_query_v2026_r0( + CreateQueryV2026R0Query( + predicate=predicate, + params={"name": "John", "age": 50}, + ancestors=[QueryAncestorReferenceV2026R0(id="0", type="folder")], + ), + limit=10, + fields=["box:item:name", search_from], +) +``` ### Arguments @@ -51,7 +63,23 @@ This operation is performed by calling function `create_query_insight_v2026_r0`. See the endpoint docs at [API Reference](https://developer.box.com/reference/v2026.0/post-query-insights/). -_Currently we don't have an example for calling `create_query_insight_v2026_r0` in integration tests_ + + +```python +client.query.create_query_insight_v2026_r0( + CreateQueryInsightV2026R0Query( + predicate=predicate, + params={"minAmount": 0}, + ancestors=[QueryAncestorReferenceV2026R0(id="0", type="folder")], + group_by=[ + QueryInsightsGroupByV2026R0( + field="".join([md_prefix, ".category"]), bucket_limit=5 + ) + ], + ), + metrics, +) +``` ### Arguments diff --git a/test/query.py b/test/query.py new file mode 100644 index 000000000..2dff528f9 --- /dev/null +++ b/test/query.py @@ -0,0 +1,182 @@ +from typing import Dict + +from box_sdk_gen.client import BoxClient + +from box_sdk_gen.schemas.metadata_template import MetadataTemplate + +from box_sdk_gen.managers.metadata_templates import CreateMetadataTemplateFields + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsTypeField, +) + +from box_sdk_gen.schemas.file_full import FileFull + +from box_sdk_gen.schemas.metadata_full import MetadataFull + +from box_sdk_gen.managers.file_metadata import CreateFileMetadataByIdScope + +from box_sdk_gen.schemas.v2026_r0.query_results_v2026_r0 import QueryResultsV2026R0 + +from box_sdk_gen.managers.query import CreateQueryV2026R0Query + +from box_sdk_gen.schemas.v2026_r0.query_ancestor_reference_v2026_r0 import ( + QueryAncestorReferenceV2026R0, +) + +from box_sdk_gen.managers.metadata_templates import DeleteMetadataTemplateScope + +from box_sdk_gen.managers.metadata_templates import ( + CreateMetadataTemplateFieldsOptionsField, +) + +from box_sdk_gen.schemas.v2026_r0.query_insights_metric_definition_v2026_r0 import ( + QueryInsightsMetricDefinitionV2026R0TypeField, +) + +from box_sdk_gen.schemas.v2026_r0.query_insights_v2026_r0 import QueryInsightsV2026R0 + +from box_sdk_gen.managers.query import CreateQueryInsightV2026R0Query + +from box_sdk_gen.schemas.v2026_r0.query_insights_group_by_v2026_r0 import ( + QueryInsightsGroupByV2026R0, +) + +from box_sdk_gen.internal.utils import get_uuid + +from box_sdk_gen.internal.utils import delay_in_seconds + +from test.commons import get_default_client + +from test.commons import upload_new_file + +from box_sdk_gen.schemas.v2026_r0.query_insights_metric_definition_v2026_r0 import ( + QueryInsightsMetricDefinitionV2026R0, +) + +client: BoxClient = get_default_client() + + +def testCreateQueryV2026R0(): + template_key: str = ''.join(['key', get_uuid()]) + template: MetadataTemplate = client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.STRING, + key='name', + display_name='name', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + key='age', + display_name='age', + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.DATE, + key='birthDate', + display_name='birthDate', + ), + ], + ) + assert template.template_key == template_key + file: FileFull = upload_new_file() + metadata: MetadataFull = client.file_metadata.create_file_metadata_by_id( + file.id, + CreateFileMetadataByIdScope.ENTERPRISE, + template_key, + {'name': 'John', 'age': 23, 'birthDate': '2001-01-03T02:20:50.520Z'}, + ) + assert metadata.template == template_key + assert metadata.scope == template.scope + delay_in_seconds(10) + search_from: str = ''.join([template.scope, ':', template.template_key]) + md_prefix: str = ''.join( + ['metadata.', template.scope, '."', template.template_key, '"'] + ) + predicate: str = ''.join( + [md_prefix, '.name = :name AND ', md_prefix, '.age < :age'] + ) + query_result: QueryResultsV2026R0 = client.query.create_query_v2026_r0( + CreateQueryV2026R0Query( + predicate=predicate, + params={'name': 'John', 'age': 50}, + ancestors=[QueryAncestorReferenceV2026R0(id='0', type='folder')], + ), + limit=10, + fields=['box:item:name', search_from], + ) + assert len(query_result.entries) >= 0 + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template.template_key + ) + client.files.delete_file_by_id(file.id) + + +def testCreateQueryInsightV2026R0(): + template_key: str = ''.join(['key', get_uuid()]) + template: MetadataTemplate = client.metadata_templates.create_metadata_template( + 'enterprise', + template_key, + template_key=template_key, + fields=[ + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.ENUM, + key='category', + display_name='category', + options=[ + CreateMetadataTemplateFieldsOptionsField(key='Sales'), + CreateMetadataTemplateFieldsOptionsField(key='Support'), + ], + ), + CreateMetadataTemplateFields( + type=CreateMetadataTemplateFieldsTypeField.FLOAT, + key='amount', + display_name='amount', + ), + ], + ) + assert template.template_key == template_key + file: FileFull = upload_new_file() + metadata: MetadataFull = client.file_metadata.create_file_metadata_by_id( + file.id, + CreateFileMetadataByIdScope.ENTERPRISE, + template_key, + {'category': 'Sales', 'amount': 150}, + ) + assert metadata.template == template_key + delay_in_seconds(5) + md_prefix: str = ''.join( + ['metadata.', template.scope, '."', template.template_key, '"'] + ) + predicate: str = ''.join([md_prefix, '.amount > :minAmount']) + metrics: Dict[str, QueryInsightsMetricDefinitionV2026R0] = { + 'totalAmount': QueryInsightsMetricDefinitionV2026R0( + type=QueryInsightsMetricDefinitionV2026R0TypeField.SUM, + field=''.join([md_prefix, '.amount']), + ), + 'countItems': QueryInsightsMetricDefinitionV2026R0( + type=QueryInsightsMetricDefinitionV2026R0TypeField.COUNT, + field=''.join([md_prefix, '.category']), + ), + } + insight_result: QueryInsightsV2026R0 = client.query.create_query_insight_v2026_r0( + CreateQueryInsightV2026R0Query( + predicate=predicate, + params={'minAmount': 0}, + ancestors=[QueryAncestorReferenceV2026R0(id='0', type='folder')], + group_by=[ + QueryInsightsGroupByV2026R0( + field=''.join([md_prefix, '.category']), bucket_limit=5 + ) + ], + ), + metrics, + ) + assert len(insight_result.insights) >= 0 + client.metadata_templates.delete_metadata_template( + DeleteMetadataTemplateScope.ENTERPRISE, template.template_key + ) + client.files.delete_file_by_id(file.id) From af1ee1f7f9a0773b6217dd9b16a400bfa6872e55 Mon Sep 17 00:00:00 2001 From: box-sdk-build <94016436+box-sdk-build@users.noreply.github.com> Date: Wed, 5 Aug 2026 12:53:25 +0200 Subject: [PATCH 137/139] chore: release version 10.14.0 (#1541) --- .codegen.json | 2 +- CHANGELOG.md | 9 +++++++++ box_sdk_gen/networking/version.py | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index f34129208..09e717030 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "7b5a612", "specHash": "86fcc6c", "version": "10.13.0" } +{ "engineHash": "7b5a612", "specHash": "86fcc6c", "version": "10.14.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index ea9d3f15b..11e6ae438 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [10.14.0](https://github.com/box/box-python-sdk/compare/v10.13.0...v10.14.0) (2026-08-05) + + +### New Features and Enhancements + +* add `is_collaborated_content_available_when_owner_inactive` property (box/box-openapi[#613](https://github.com/box/box-python-sdk/issues/613)) ([#1537](https://github.com/box/box-python-sdk/issues/1537)) ([9d4542f](https://github.com/box/box-python-sdk/commit/9d4542f1ca9ef8da5b273705e670c19e89d5ea8b)) +* Added two missing event types (box/box-openapi[#611](https://github.com/box/box-python-sdk/issues/611)) ([#1531](https://github.com/box/box-python-sdk/issues/1531)) ([94120f3](https://github.com/box/box-python-sdk/commit/94120f3fa866c2521db42d62dc7471030c023871)) +* Support query APIs (box/box-openapi[#610](https://github.com/box/box-python-sdk/issues/610)) ([#1529](https://github.com/box/box-python-sdk/issues/1529)) ([1f18e93](https://github.com/box/box-python-sdk/commit/1f18e931e8fcc5b13429acb9a2669e7bd9610c10)) + ## [10.13.0](https://github.com/box/box-python-sdk/compare/v10.12.0...v10.13.0) (2026-07-10) diff --git a/box_sdk_gen/networking/version.py b/box_sdk_gen/networking/version.py index 4a6c005a6..d70902e4b 100644 --- a/box_sdk_gen/networking/version.py +++ b/box_sdk_gen/networking/version.py @@ -1 +1 @@ -__version__ = '10.13.0' +__version__ = '10.14.0' From 42f0a9c66757c537354e6932c06949b210f388dd Mon Sep 17 00:00:00 2001 From: vstarastsenka-box Date: Wed, 5 Aug 2026 14:46:33 +0200 Subject: [PATCH 138/139] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 737d68388..7d9dcd18a 100644 --- a/README.md +++ b/README.md @@ -148,8 +148,6 @@ Migration guides which help you to migrate to supported major SDK versions can b We use a modified version of [Semantic Versioning](https://semver.org/) for all changes. See [version strategy](VERSIONS.md) for details which is effective from 30 July 2022. A current release is on the leading edge of our SDK development, and is intended for customers who are in active development and want the latest and greatest features. -Instead of stating a release date for a new feature, we set a fixed minor or patch release cadence of maximum 2-3 months (while we may release more often). -At the same time, there is no schedule for major or breaking release. Instead, we will communicate one quarter in advance the upcoming breaking change to allow customers to plan for the upgrade. We always recommend that all users run the latest available minor release for whatever major version is in use. We highly recommend upgrading to the latest SDK major release at the earliest convenient time and before the EOL date. From f777b02f7ea6374a2f65469487f547a558eda75f Mon Sep 17 00:00:00 2001 From: vstarastsenka-box Date: Wed, 5 Aug 2026 14:46:47 +0200 Subject: [PATCH 139/139] Update VERSIONS.md --- VERSIONS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/VERSIONS.md b/VERSIONS.md index 45c972031..06f2a9e5a 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -12,7 +12,6 @@ The MAJOR version is used to indicate the family of technology represented by th New MAJOR versions will be communicated in advance via: -- An email announcement is sent to affected accounts, announcing our plans to end support for the specific SDK version. The email will outline the path to end-of-support, specify the campaign timelines, and provide upgrade guidance. - Box SDK documentation, such as API reference documentation, user guides, SDK product marketing pages, and GitHub readme(s) are updated to indicate the campaign timeline and provide guidance on upgrading affected applications. - Deprecation warnings are added to the SDKs, outlining the path to end-of-support and linking to the SDK documentation.