Office365-REST-Python-Client 3.2.0__py3-none-any.whl → 3.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- office365/admin/admin.py +45 -0
- office365/admin/baseline_parameter.py +3 -0
- office365/admin/baseline_parameter_type.py +14 -0
- office365/admin/browser_shared_cookie.py +99 -0
- office365/admin/browser_site.py +101 -0
- office365/admin/browser_site_list.py +87 -0
- office365/admin/configuration_management.py +86 -0
- office365/admin/edge/edge.py +25 -0
- office365/admin/exchange/admin.py +35 -0
- office365/admin/exchange/exchange_message_trace.py +15 -1
- office365/admin/exchange/exchange_message_trace_status.py +2 -0
- office365/admin/internet_explorer_mode.py +28 -0
- office365/admin/message_tracing_root.py +28 -0
- office365/admin/microsoft365/apps.py +26 -0
- office365/admin/microsoft365/m365_apps_installation_options.py +47 -0
- office365/admin/people/insights_settings.py +21 -0
- office365/admin/people/profile_property_setting.py +27 -0
- office365/admin/people/profile_source.py +48 -0
- office365/admin/people/pronouns_settings.py +16 -0
- office365/admin/people/settings.py +58 -0
- office365/admin/report_settings.py +13 -0
- office365/admin/sharepoint/settings.py +88 -1
- office365/backuprestore/browse_session_base.py +101 -0
- office365/backuprestore/email_notifications_setting.py +40 -0
- office365/backuprestore/onedrive_for_business_browse_session.py +16 -0
- office365/backuprestore/onedrive_for_business_restore_session.py +68 -0
- office365/backuprestore/protection_unit_base.py +92 -0
- office365/backuprestore/restorepoints/collection.py +61 -0
- office365/backuprestore/restorepoints/search_result.py +6 -0
- office365/backuprestore/root.py +354 -0
- office365/backuprestore/serviceapps/service_app.py +74 -0
- office365/backuprestore/sharepoint_browse_session.py +16 -0
- office365/backuprestore/sharepoint_protection_policy.py +60 -0
- office365/backuprestore/sharepoint_restore_session.py +66 -0
- office365/booking/appointment.py +197 -0
- office365/booking/business/business.py +75 -1
- office365/booking/business/collection.py +22 -0
- office365/booking/currency.py +13 -0
- office365/booking/custom_question.py +40 -0
- office365/booking/customer.py +51 -0
- office365/booking/service.py +145 -1
- office365/booking/staff/member.py +76 -0
- office365/common/add_large_gallery_view_operation.py +8 -0
- office365/common/agent_identity.py +45 -0
- office365/common/agent_identity_blueprint.py +41 -0
- office365/common/agent_identity_blueprint_principal.py +34 -0
- office365/common/agent_user.py +7 -0
- office365/common/ai_interaction.py +108 -0
- office365/common/akamai_web_application_firewall_provider.py +31 -0
- office365/common/anonymous_calendar_sharing_free_busy_detail.py +7 -0
- office365/common/anonymous_calendar_sharing_free_busy_reviewer.py +7 -0
- office365/common/anonymous_calendar_sharing_free_busy_simple.py +7 -0
- office365/common/anonymous_guest_conversation_member.py +16 -0
- office365/common/apple_device_features_configuration_base.py +7 -0
- office365/common/apple_managed_identity_provider.py +31 -0
- office365/common/arkose_fraud_protection_provider.py +31 -0
- office365/common/attribute_set.py +21 -0
- office365/common/azure_communication_services_user_conversation_member.py +16 -0
- office365/common/b2_b_registration_metrics_base.py +27 -0
- office365/common/b2_b_sign_in_activity_metrics_base.py +37 -0
- office365/common/base_map_feature.py +7 -0
- office365/common/building.py +39 -0
- office365/common/building_map.py +41 -0
- office365/common/complex_extension_value.py +10 -0
- office365/common/configuration_baseline.py +40 -0
- office365/common/configuration_drift.py +68 -0
- office365/common/configuration_monitor.py +103 -0
- office365/common/configuration_monitoring_result.py +56 -0
- office365/common/configuration_snapshot_job.py +71 -0
- office365/common/contract.py +32 -0
- office365/common/cross_tenant_access_policy_configuration_default.py +163 -0
- office365/common/cross_tenant_access_policy_configuration_partner.py +188 -0
- office365/common/cross_tenant_calendar_availability_basic.py +7 -0
- office365/common/cross_tenant_calendar_availability_limited_details.py +7 -0
- office365/common/cross_tenant_calendar_sharing_free_busy_detail.py +7 -0
- office365/common/cross_tenant_calendar_sharing_free_busy_reviewer.py +7 -0
- office365/common/cross_tenant_calendar_sharing_free_busy_simple.py +7 -0
- office365/common/cross_tenant_identity_sync_policy_partner.py +35 -0
- office365/common/cross_tenant_mail_tips_all.py +7 -0
- office365/common/cross_tenant_mail_tips_limited.py +7 -0
- office365/common/cross_tenant_migration.py +7 -0
- office365/common/cross_tenant_open_profile_card.py +7 -0
- office365/common/cross_tenant_places_desk_booking.py +7 -0
- office365/common/cross_tenant_places_room_booking.py +7 -0
- office365/common/custom_authentication_extension.py +25 -0
- office365/common/custom_data_provided_resource.py +25 -0
- office365/common/custom_data_provided_resource_access_review_upload_session.py +7 -0
- office365/common/data_source_configuration.py +19 -0
- office365/common/delta_participants.py +31 -0
- office365/common/desk.py +51 -0
- office365/common/dictionaries.py +10 -0
- office365/common/directory_object_partner_reference.py +32 -0
- office365/common/domain_dns_mx_record.py +21 -0
- office365/common/domain_dns_srv_record.py +41 -0
- office365/common/domain_dns_txt_record.py +16 -0
- office365/common/domain_dns_unavailable_record.py +16 -0
- office365/common/drive_protection_unit.py +26 -0
- office365/common/drive_protection_units_bulk_addition_job.py +20 -0
- office365/common/drive_restore_artifact.py +26 -0
- office365/common/drive_restore_artifacts_bulk_addition_request.py +20 -0
- office365/common/edition_upgrade_configuration.py +47 -0
- office365/common/email_authentication_method_configuration.py +44 -0
- office365/common/emergency_call_event.py +31 -0
- office365/common/end_user_settings.py +32 -0
- office365/common/endpoint.py +36 -0
- office365/common/engagement_async_operation.py +31 -0
- office365/common/engagement_conversation_discussion_message.py +7 -0
- office365/common/engagement_conversation_question_message.py +16 -0
- office365/common/engagement_conversation_system_message.py +7 -0
- office365/common/engagement_role.py +33 -0
- office365/common/engagement_role_member.py +33 -0
- office365/common/enrollment_troubleshooting_event.py +63 -0
- office365/common/enterprise_code_signing_certificate.py +57 -0
- office365/common/error_detail.py +16 -0
- office365/common/event_message_request.py +54 -0
- office365/common/event_message_response.py +29 -0
- office365/common/exchange_protection_policy.py +60 -0
- office365/common/exchange_restore_session.py +68 -0
- office365/common/exchange_settings.py +16 -0
- office365/common/external_authentication_method.py +21 -0
- office365/common/external_authentication_method_configuration.py +54 -0
- office365/common/external_domain_name.py +7 -0
- office365/common/external_users_self_service_sign_up_events_flow.py +85 -0
- office365/common/fido2_authentication_method_configuration.py +72 -0
- office365/common/fido2_combination_configuration.py +15 -0
- office365/common/fixture_map.py +16 -0
- office365/common/floor.py +16 -0
- office365/common/footprint_map.py +7 -0
- office365/common/granular_drive_restore_artifact.py +16 -0
- office365/common/granular_mailbox_restore_artifact.py +21 -0
- office365/common/granular_restore_artifact_base.py +62 -0
- office365/common/granular_site_restore_artifact.py +16 -0
- office365/common/human_security_fraud_protection_provider.py +21 -0
- office365/common/identity_built_in_user_flow_attribute.py +7 -0
- office365/common/identity_custom_user_flow_attribute.py +7 -0
- office365/common/image_info.py +17 -0
- office365/common/imported_windows_autopilot_device_identity_upload.py +52 -0
- office365/common/intune_brand.py +29 -0
- office365/common/ip_named_location.py +27 -0
- office365/common/ipv4_range.py +15 -0
- office365/common/ipv6_range.py +15 -0
- office365/common/key_value.py +15 -0
- office365/common/learning_assignment.py +48 -0
- office365/common/level_map.py +52 -0
- office365/common/mailbox.py +26 -0
- office365/common/mailbox_folder.py +104 -0
- office365/common/mailbox_item.py +61 -0
- office365/common/mailbox_protection_rule.py +16 -0
- office365/common/mailbox_protection_unit.py +26 -0
- office365/common/mailbox_protection_units_bulk_addition_job.py +20 -0
- office365/common/mailbox_restore_artifact.py +26 -0
- office365/common/mailbox_restore_artifacts_bulk_addition_request.py +20 -0
- office365/common/managed_android_lob_app.py +40 -0
- office365/common/managed_android_store_app.py +35 -0
- office365/common/managed_app_status_raw.py +21 -0
- office365/common/managed_ios_lob_app.py +56 -0
- office365/common/managed_ios_store_app.py +45 -0
- office365/common/managed_mobile_lob_app.py +45 -0
- office365/common/microsoft_account_user_conversation_member.py +16 -0
- office365/common/microsoft_authenticator_authentication_method_configuration.py +51 -0
- office365/common/microsoft_authenticator_authentication_method_target.py +25 -0
- office365/common/microsoft_store_for_business_app.py +45 -0
- office365/common/mime_content.py +15 -0
- office365/common/mute_participant_operation.py +8 -0
- office365/common/oidc_identity_provider.py +69 -0
- office365/common/on_attribute_collection_listener.py +25 -0
- office365/common/on_attribute_collection_start_custom_extension.py +7 -0
- office365/common/on_attribute_collection_start_listener.py +25 -0
- office365/common/on_attribute_collection_submit_custom_extension.py +7 -0
- office365/common/on_attribute_collection_submit_listener.py +25 -0
- office365/common/on_authentication_method_load_start_listener.py +25 -0
- office365/common/on_email_otp_send_listener.py +21 -0
- office365/common/on_fraud_protection_load_start_listener.py +25 -0
- office365/common/on_interactive_auth_flow_start_listener.py +25 -0
- office365/common/on_otp_send_custom_extension.py +7 -0
- office365/common/on_password_submit_custom_extension.py +7 -0
- office365/common/on_password_submit_listener.py +25 -0
- office365/common/on_token_issuance_start_listener.py +25 -0
- office365/common/on_user_create_start_listener.py +25 -0
- office365/common/on_verified_id_claim_validation_listener.py +25 -0
- office365/common/operation.py +35 -0
- office365/common/os_version_count.py +17 -0
- office365/common/participant_joining_notification.py +22 -0
- office365/common/participant_left_notification.py +27 -0
- office365/common/passkey_authentication_method_target.py +15 -0
- office365/common/passkey_profile.py +50 -0
- office365/common/phone_user_conversation_member.py +16 -0
- office365/common/play_prompt_operation.py +8 -0
- office365/common/privacy.py +32 -0
- office365/common/prompt.py +10 -0
- office365/common/protection_units_bulk_job_base.py +64 -0
- office365/common/provision_response.py +15 -0
- office365/common/proxied_domain.py +15 -0
- office365/common/qr_code.py +46 -0
- office365/common/qr_code_pin_authentication_method.py +41 -0
- office365/common/qr_code_pin_authentication_method_configuration.py +42 -0
- office365/common/qr_pin.py +32 -0
- office365/common/reading_assignment_submission.py +123 -0
- office365/common/reading_coach_passage.py +63 -0
- office365/common/record_operation.py +22 -0
- office365/common/reflect_check_in_response.py +70 -0
- office365/common/related_tenants_refresh_request.py +16 -0
- office365/common/reports_root.py +79 -0
- office365/common/request.py +47 -0
- office365/common/reseller_delegated_admin_relationship.py +21 -0
- office365/common/restore_artifact_base.py +59 -0
- office365/common/restore_artifacts_bulk_request_base.py +104 -0
- office365/common/restore_point.py +43 -0
- office365/common/restore_session_base.py +93 -0
- office365/common/role_assignment.py +40 -0
- office365/common/role_definition.py +52 -0
- office365/common/saml_or_ws_fed_external_domain_federation.py +28 -0
- office365/common/section.py +7 -0
- office365/common/section_map.py +16 -0
- office365/common/send_dtmf_tones_operation.py +26 -0
- office365/common/service_announcement_attachment.py +37 -0
- office365/common/setting_source.py +17 -0
- office365/common/shared_pc_configuration.py +88 -0
- office365/common/sharepoint_embedded_configuration.py +14 -0
- office365/common/site_protection_rule.py +16 -0
- office365/common/site_protection_unit.py +26 -0
- office365/common/site_protection_units_bulk_addition_job.py +20 -0
- office365/common/site_restore_artifact.py +26 -0
- office365/common/site_restore_artifacts_bulk_addition_request.py +20 -0
- office365/common/skype_for_business_user_conversation_member.py +21 -0
- office365/common/skype_user_conversation_member.py +16 -0
- office365/common/software_oath_authentication_method.py +16 -0
- office365/common/software_oath_authentication_method_configuration.py +32 -0
- office365/common/speaker_assignment_submission.py +108 -0
- office365/common/storage_quota_breakdown.py +26 -0
- office365/common/subscribe_to_tone_operation.py +8 -0
- office365/common/task_file_attachment.py +11 -0
- office365/common/teamwork_hosted_content.py +21 -0
- office365/common/temporary_access_pass_authentication_method_configuration.py +57 -0
- office365/common/unit_map.py +16 -0
- office365/common/verifiable_credential_authentication_method_target.py +15 -0
- office365/common/verifiable_credentials_authentication_method_configuration.py +34 -0
- office365/common/voice_authentication_method_configuration.py +37 -0
- office365/common/web_app.py +21 -0
- office365/common/what_if_analysis_result.py +30 -0
- office365/communications/aad_user_conversation_member_result.py +14 -0
- office365/communications/aad_user_notification_recipient.py +14 -0
- office365/communications/accept_join_response.py +10 -0
- office365/communications/app_hosted_media_config.py +14 -0
- office365/communications/application_identity.py +15 -0
- office365/communications/application_instance_identity.py +15 -0
- office365/communications/attendance_interval.py +17 -0
- office365/communications/audio_conferencing.py +20 -0
- office365/communications/azure_communication_services_user_identity.py +14 -0
- office365/communications/broadcast_meeting_caption_settings.py +17 -0
- office365/communications/call_participant_info.py +15 -0
- office365/communications/callrecords/administrative_unit_info.py +14 -0
- office365/communications/callrecords/call_record.py +13 -1
- office365/communications/callrecords/client_user_agent.py +19 -0
- office365/communications/callrecords/collection.py +8 -0
- office365/communications/callrecords/device_info.py +34 -0
- office365/communications/callrecords/direct_routing_log_row.py +47 -0
- office365/communications/callrecords/endpoint.py +8 -1
- office365/communications/callrecords/failure_info.py +16 -0
- office365/communications/callrecords/feedback_token_set.py +10 -0
- office365/communications/callrecords/media.py +23 -0
- office365/communications/callrecords/media_stream.py +65 -0
- office365/communications/callrecords/network_info.py +53 -0
- office365/communications/callrecords/organizer.py +7 -0
- office365/communications/callrecords/participant.py +7 -0
- office365/communications/callrecords/participant_base.py +31 -0
- office365/communications/callrecords/participant_endpoint.py +23 -0
- office365/communications/callrecords/pstn_call_log_row.py +40 -0
- office365/communications/callrecords/segment.py +34 -0
- office365/communications/callrecords/service_endpoint.py +10 -0
- office365/communications/callrecords/service_user_agent.py +15 -0
- office365/communications/callrecords/session.py +13 -1
- office365/communications/callrecords/trace_route_hop.py +20 -0
- office365/communications/callrecords/user_agent.py +15 -0
- office365/communications/callrecords/user_feedback.py +18 -0
- office365/communications/callrecords/user_identity.py +14 -0
- office365/communications/callrecords/wifi_band.py +15 -0
- office365/communications/callrecords/wifi_radio_type.py +18 -0
- office365/communications/calls/ai_insight.py +63 -0
- office365/communications/calls/audio_routing_group.py +32 -0
- office365/communications/calls/call.py +81 -5
- office365/communications/calls/content_sharing_session.py +7 -0
- office365/communications/calls/ended_event_message_detail.py +6 -0
- office365/communications/calls/event.py +40 -0
- office365/communications/calls/incoming_context.py +14 -1
- office365/communications/calls/invitation_participant_info.py +4 -0
- office365/communications/calls/media_state.py +15 -0
- office365/communications/calls/options.py +8 -0
- office365/communications/calls/participant.py +48 -1
- office365/communications/calls/participant_info.py +17 -1
- office365/communications/calls/route.py +13 -1
- office365/communications/calls/transcription_info.py +17 -0
- office365/communications/chat_members_notification_recipient.py +14 -0
- office365/communications/chat_renamed_event_message_detail.py +17 -0
- office365/communications/chat_restrictions.py +14 -0
- office365/communications/cloud_communications.py +35 -1
- office365/communications/comms_notification.py +15 -0
- office365/communications/comms_notifications.py +18 -0
- office365/communications/conversation_member_role_updated_event_message_detail.py +19 -0
- office365/communications/emergency_caller_info.py +19 -0
- office365/communications/encrypted_identity.py +10 -0
- office365/communications/engagement_identity_set.py +16 -0
- office365/communications/guest_identity.py +14 -0
- office365/communications/incoming_call_options.py +10 -0
- office365/communications/invite_new_bot_response.py +14 -0
- office365/communications/join_meeting_id_settings.py +16 -0
- office365/communications/max_work_location_details.py +15 -0
- office365/communications/media_config.py +10 -0
- office365/communications/media_info.py +15 -0
- office365/communications/media_prompt.py +15 -0
- office365/communications/meetings/join_meeting_id_info.py +13 -0
- office365/communications/onlinemeetings/attendance_record.py +62 -0
- office365/communications/onlinemeetings/base.py +86 -5
- office365/communications/onlinemeetings/broadcast_settings.py +6 -1
- office365/communications/onlinemeetings/chatmode.py +8 -9
- office365/communications/onlinemeetings/collection.py +9 -1
- office365/communications/onlinemeetings/engagement_conversation.py +16 -2
- office365/communications/onlinemeetings/meeting_attendance_report.py +64 -0
- office365/communications/onlinemeetings/online_meeting_info.py +6 -0
- office365/communications/onlinemeetings/restricted.py +16 -0
- office365/communications/onlinemeetings/sensitivity_label_assignment.py +14 -0
- office365/communications/operations/comms.py +22 -1
- office365/communications/organizer_meeting_info.py +15 -0
- office365/communications/out_of_office_settings.py +15 -0
- office365/communications/outgoing_call_options.py +10 -0
- office365/communications/participant_joining_response.py +10 -0
- office365/communications/phone_identity.py +10 -0
- office365/communications/presences/presence.py +19 -1
- office365/communications/presences/status_message.py +4 -0
- office365/communications/recording_info.py +17 -0
- office365/communications/reject_join_response.py +15 -0
- office365/communications/removed_state.py +14 -0
- office365/communications/service_hosted_media_config.py +16 -0
- office365/communications/teleconference_device_audio_quality.py +10 -0
- office365/communications/teleconference_device_media_quality.py +38 -0
- office365/communications/teleconference_device_screen_sharing_quality.py +10 -0
- office365/communications/teleconference_device_video_quality.py +17 -0
- office365/communications/teleconferencedevicequality.py +32 -1
- office365/communications/token_meeting_info.py +14 -0
- office365/communications/tone_info.py +16 -0
- office365/communications/user_identity.py +14 -0
- office365/communications/user_work_location.py +18 -0
- office365/communications/virtualevents/external_information.py +15 -0
- office365/communications/virtualevents/external_registration_information.py +15 -0
- office365/communications/virtualevents/presenter.py +35 -0
- office365/communications/virtualevents/presenter_details.py +21 -0
- office365/communications/virtualevents/presenter_info.py +17 -0
- office365/communications/virtualevents/registration.py +109 -0
- office365/communications/virtualevents/registration_configuration.py +52 -0
- office365/communications/virtualevents/registration_custom_question.py +31 -0
- office365/communications/virtualevents/registration_predefined_question.py +25 -0
- office365/communications/virtualevents/registration_question_answer.py +19 -0
- office365/communications/virtualevents/registration_question_base.py +21 -0
- office365/communications/virtualevents/root.py +21 -0
- office365/communications/virtualevents/session.py +35 -0
- office365/communications/virtualevents/settings.py +6 -0
- office365/communications/virtualevents/townhall.py +83 -0
- office365/communications/virtualevents/townhall_registration_configuration.py +7 -0
- office365/communications/virtualevents/virtual_event.py +78 -1
- office365/communications/virtualevents/webinar.py +65 -0
- office365/communications/virtualevents/webinar_registration_configuration.py +7 -0
- office365/copilot/action_result_part.py +4 -1
- office365/copilot/admin_catalog.py +28 -0
- office365/copilot/admin_limited_mode.py +21 -0
- office365/copilot/admin_setting.py +24 -0
- office365/copilot/ai_interaction_history.py +9 -0
- office365/copilot/ai_online_meeting.py +26 -0
- office365/copilot/ai_user.py +39 -0
- office365/copilot/copilot_package_request_status.py +14 -0
- office365/copilot/copilot_package_request_type.py +15 -0
- office365/copilot/package.py +153 -0
- office365/copilot/package_access_entity.py +3 -0
- office365/copilot/package_detail.py +92 -0
- office365/copilot/report_root.py +9 -0
- office365/copilot/retrieval_data_source.py +2 -0
- office365/delta_collection.py +37 -1
- office365/directory/administrative_unit.py +51 -1
- office365/directory/allowed_value.py +16 -0
- office365/directory/application_permission_type.py +13 -0
- office365/directory/application_resource_permission.py +18 -0
- office365/directory/applications/application.py +217 -14
- office365/directory/applications/applications_required_resource_access.py +21 -0
- office365/directory/applications/collection.py +40 -2
- office365/directory/applications/custom_security_attribute_exemption.py +2 -2
- office365/directory/applications/instance_resource_access.py +19 -0
- office365/directory/approval_settings.py +22 -0
- office365/directory/audit/provisioning/object_summary.py +19 -3
- office365/directory/audit/signins/activity.py +2 -2
- office365/directory/authentication/attributecollection/start_custom_extension_handler.py +8 -0
- office365/directory/authentication/attributecollection/submit_custom_extension_handler.py +8 -0
- office365/directory/authentication/authentication.py +124 -4
- office365/directory/authentication/behaviors.py +2 -0
- office365/directory/authentication/conditions/application.py +13 -0
- office365/directory/authentication/conditions/event_listener.py +32 -0
- office365/directory/authentication/error_correction_level.py +2 -0
- office365/directory/authentication/fraud_protection_provider.py +16 -0
- office365/directory/authentication/fraud_protection_provider_configuration.py +8 -0
- office365/directory/authentication/methods/fido.py +46 -1
- office365/directory/authentication/methods/method.py +12 -0
- office365/directory/authentication/methods/microsoft_authenticator.py +34 -0
- office365/directory/authentication/methods/phone.py +30 -0
- office365/directory/authentication/password_reset_response.py +4 -0
- office365/directory/authentication/platform_credential_authentication_method.py +51 -0
- office365/directory/authentication/qr_code_image_details.py +5 -0
- office365/directory/authentication/temporary_access_pass_authentication_method.py +42 -0
- office365/directory/authentication/windows_hello_for_business_authentication_method.py +39 -0
- office365/directory/b2b_registration_metrics.py +42 -0
- office365/directory/b2b_registration_metrics_initial.py +17 -0
- office365/directory/b2b_registration_metrics_recent.py +17 -0
- office365/directory/b2b_sign_in_activity_metrics.py +42 -0
- office365/directory/b2b_sign_in_activity_metrics_initial.py +17 -0
- office365/directory/b2b_sign_in_activity_metrics_recent.py +17 -0
- office365/directory/certificates/auth_pki.py +50 -0
- office365/directory/certificates/authority_detail.py +74 -0
- office365/directory/certificates/x509/authentication_method_configuration.py +98 -0
- office365/directory/certificates/x509/combination_configuration.py +20 -0
- office365/directory/claim_binding.py +17 -0
- office365/directory/claim_binding_source.py +12 -0
- office365/directory/claim_validation.py +15 -0
- office365/directory/credential.py +106 -0
- office365/directory/cross_cloud_azure_active_directory_tenant.py +16 -0
- office365/directory/custom_security_attribute_definition.py +65 -0
- office365/directory/datapolicy/operation.py +2 -1
- office365/directory/delegated_administration_role_assignment.py +24 -0
- office365/directory/delegated_administration_role_assignment_snapshot.py +20 -0
- office365/directory/delegatedadmin/access_assignment.py +60 -0
- office365/directory/delegatedadmin/access_container.py +18 -0
- office365/directory/delegatedadmin/access_details.py +16 -0
- office365/directory/delegatedadmin/customer.py +44 -0
- office365/directory/delegatedadmin/relationship.py +136 -0
- office365/directory/delegatedadmin/relationship_customer_participant.py +15 -0
- office365/directory/delegatedadmin/relationship_operation.py +56 -0
- office365/directory/delegatedadmin/relationship_request.py +48 -0
- office365/directory/delegatedadmin/service_management_detail.py +21 -0
- office365/directory/delegatedadmin/service_provider_constraints.py +28 -0
- office365/directory/device_local_credential_info.py +18 -1
- office365/directory/directory.py +86 -0
- office365/directory/domains/collection.py +57 -0
- office365/directory/domains/dns_cname_record.py +13 -0
- office365/directory/domains/dns_record.py +47 -0
- office365/directory/domains/domain.py +90 -0
- office365/directory/domains/internal_domain_federation.py +78 -0
- office365/directory/drifted_property.py +17 -0
- office365/directory/entity_type_and_ids.py +17 -0
- office365/directory/external_domain_federation.py +16 -0
- office365/directory/external_sponsors.py +10 -0
- office365/directory/external_token_based_sap_iag_connection_info.py +19 -0
- office365/directory/governance_invitation.py +42 -0
- office365/directory/governance_relationship.py +65 -0
- office365/directory/governance_request.py +76 -0
- office365/directory/groups/collection.py +19 -2
- office365/directory/groups/group.py +64 -11
- office365/directory/groups/setting.py +14 -1
- office365/directory/groups/setting_template.py +31 -0
- office365/directory/identities/api_connector.py +18 -0
- office365/directory/identities/authentication_events_flow.py +35 -0
- office365/directory/identities/b2x_identity_user_flow.py +89 -0
- office365/directory/identities/conditional_access_root.py +32 -3
- office365/directory/identities/conditional_access_template.py +47 -0
- office365/directory/identities/container.py +80 -8
- office365/directory/identities/named_location.py +29 -0
- office365/directory/identities/on_otp_send_custom_extension_handler.py +6 -0
- office365/directory/identities/on_password_migration_custom_extension_handler.py +8 -0
- office365/directory/identities/on_token_issuance_start_custom_extension.py +29 -0
- office365/directory/identities/on_token_issuance_start_custom_extension_handler.py +8 -0
- office365/directory/identities/on_verified_id_claim_validation_custom_extension.py +7 -0
- office365/directory/identities/on_verified_id_claim_validation_custom_extension_handler.py +8 -0
- office365/directory/identities/provider.py +16 -0
- office365/directory/identities/providers/saml_or_wsfed.py +32 -1
- office365/directory/identities/riskPrevention/container.py +70 -0
- office365/directory/identities/userflows/attribute.py +44 -0
- office365/directory/identities/userflows/b2x/user_flow.py +0 -10
- office365/directory/identities/userflows/language_configuration.py +11 -0
- office365/directory/identities/userflows/user_attribute_assignment.py +67 -0
- office365/directory/identities/userflows/user_flow.py +22 -1
- office365/directory/identities/verified_id_root.py +28 -0
- office365/directory/identities/web_application_firewall_provider.py +16 -0
- office365/directory/identities/web_application_firewall_verification_model.py +69 -0
- office365/directory/identity_set.py +19 -0
- office365/directory/identitygovernance/access_package_suggestion.py +43 -0
- office365/directory/identitygovernance/accessreview/context_data.py +10 -0
- office365/directory/identitygovernance/accessreview/context_data_base.py +15 -0
- office365/directory/identitygovernance/accessreview/custom_data_provided_resource_upload_session_request.py +4 -1
- office365/directory/identitygovernance/accessreview/history/definition.py +23 -3
- office365/directory/identitygovernance/accessreview/instance.py +49 -3
- office365/directory/identitygovernance/accessreview/instance_decision_item.py +49 -1
- office365/directory/identitygovernance/accessreview/instance_decision_item_apply_result.py +16 -0
- office365/directory/identitygovernance/accessreview/instance_decision_item_custom_data_provided_resource.py +16 -0
- office365/directory/identitygovernance/accessreview/instance_decision_item_permission.py +17 -0
- office365/directory/identitygovernance/accessreview/instance_decision_item_resource.py +2 -0
- office365/directory/identitygovernance/accessreview/principal_scope.py +5 -0
- office365/directory/identitygovernance/accessreview/principal_scope_type.py +15 -0
- office365/directory/identitygovernance/accessreview/resource_scope.py +5 -0
- office365/directory/identitygovernance/accessreview/resource_scope_type.py +16 -0
- office365/directory/identitygovernance/accessreview/reviewer_scope.py +5 -0
- office365/directory/identitygovernance/accessreview/reviewer_scope_type.py +18 -0
- office365/directory/identitygovernance/accessreview/schedule_definition.py +64 -3
- office365/directory/identitygovernance/accessreview/set.py +34 -1
- office365/directory/identitygovernance/accessreview/stage.py +31 -1
- office365/directory/identitygovernance/activities_container.py +30 -0
- office365/directory/identitygovernance/apply_decision_context_data.py +10 -0
- office365/directory/identitygovernance/awaited_workflow_processing_result.py +6 -1
- office365/directory/identitygovernance/content_activity.py +35 -0
- office365/directory/identitygovernance/custom_callout_extension_type.py +15 -0
- office365/directory/identitygovernance/custom_extension_resource_type.py +14 -0
- office365/directory/identitygovernance/custom_task_extension.py +18 -1
- office365/directory/identitygovernance/custom_task_extension_callout_data.py +13 -2
- office365/directory/identitygovernance/custom_task_extension_reply_mode.py +14 -0
- office365/directory/identitygovernance/custom_task_extension_response_data.py +1 -1
- office365/directory/identitygovernance/data.py +10 -0
- office365/directory/identitygovernance/data_security_and_governance.py +23 -0
- office365/directory/identitygovernance/deleted_item_container.py +17 -0
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/assignment.py +37 -3
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/assignment_policy.py +109 -3
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/assignment_request.py +64 -4
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/catalog.py +74 -2
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/custom_data_provided_resource_file.py +27 -0
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/custom_data_provided_resource_upload_session.py +80 -0
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/multiple_choice_question.py +18 -1
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/package.py +51 -1
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/question.py +18 -1
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/resource.py +52 -1
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/resource_environment.py +11 -1
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/resource_role.py +11 -1
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/resource_role_scope.py +32 -0
- office365/directory/identitygovernance/entitlementmanagement/accesspackage/subject.py +31 -1
- office365/directory/identitygovernance/entitlementmanagement/available_access_package.py +42 -0
- office365/directory/identitygovernance/entitlementmanagement/connected_organization.py +16 -1
- office365/directory/identitygovernance/entitlementmanagement/control_configuration.py +37 -0
- office365/directory/identitygovernance/entitlementmanagement/entitlement_management.py +246 -1
- office365/directory/identitygovernance/entitlementmanagement/external_origin_resource_connector.py +58 -0
- office365/directory/identitygovernance/governance.py +39 -5
- office365/directory/identitygovernance/insights.py +3 -1
- office365/directory/identitygovernance/lifecycle_management_settings.py +15 -1
- office365/directory/identitygovernance/lifecycle_workflows_container.py +35 -2
- office365/directory/identitygovernance/lifecycletaskcategory.py +4 -0
- office365/directory/identitygovernance/lifecycleworkflows/category.py +4 -0
- office365/directory/identitygovernance/privilegedaccess/group.py +21 -0
- office365/directory/identitygovernance/privilegedaccess/group_resource.py +7 -0
- office365/directory/identitygovernance/privilegedaccess/role_management_custom_callout_extension.py +43 -0
- office365/directory/identitygovernance/privilegedaccess/root.py +27 -1
- office365/directory/identitygovernance/privilegedaccess/schedule/schedule.py +13 -1
- office365/directory/identitygovernance/privilegedaccess/schedule_request.py +22 -2
- office365/directory/identitygovernance/run.py +36 -5
- office365/directory/identitygovernance/subject_processing_result.py +121 -0
- office365/directory/identitygovernance/subject_type.py +13 -0
- office365/directory/identitygovernance/task_processing_result.py +18 -2
- office365/directory/identitygovernance/termsofuse/agreement.py +26 -4
- office365/directory/identitygovernance/termsofuse/agreement_file.py +4 -2
- office365/directory/identitygovernance/termsofuse/agreement_file_localization.py +4 -2
- office365/directory/identitygovernance/termsofuse/agreement_file_properties.py +15 -1
- office365/directory/identitygovernance/termsofuse/agreement_file_version.py +4 -2
- office365/directory/identitygovernance/user_data_security_and_governance.py +34 -0
- office365/directory/identitygovernance/user_protection_scope_container.py +9 -0
- office365/directory/identitygovernance/workflow/base.py +19 -3
- office365/directory/identitygovernance/workflow/executiontype.py +4 -0
- office365/directory/identitygovernance/workflow/setting.py +17 -0
- office365/directory/identitygovernance/workflow/version.py +28 -0
- office365/directory/identitygovernance/workflow/workflow.py +127 -0
- office365/directory/inheritable_permission.py +28 -0
- office365/directory/insights/identity.py +15 -0
- office365/directory/insights/resource_reference.py +4 -0
- office365/directory/insights/shared.py +27 -0
- office365/directory/insights/sharing_detail.py +4 -0
- office365/directory/insights/trending.py +22 -1
- office365/directory/insights/usage_details.py +4 -0
- office365/directory/insights/used.py +18 -0
- office365/directory/internal/sponsors.py +10 -0
- office365/directory/key_credential.py +9 -23
- office365/directory/licenses/collection.py +30 -0
- office365/directory/modified_property.py +16 -0
- office365/directory/multi_tenant_application_metrics.py +42 -0
- office365/directory/multi_tenant_application_metrics_base.py +27 -0
- office365/directory/multi_tenant_application_metrics_initial.py +17 -0
- office365/directory/multi_tenant_application_metrics_recent.py +17 -0
- office365/directory/multi_tenant_applications_to_provision.py +23 -0
- office365/directory/multi_tenant_applications_to_provision_snapshot.py +23 -0
- office365/directory/multi_tenant_organization_identity_sync_policy_template.py +37 -0
- office365/directory/multi_tenant_organization_join_request_record.py +54 -0
- office365/directory/multi_tenant_organization_member.py +76 -0
- office365/directory/multi_tenant_organization_partner_configuration_template.py +88 -0
- office365/directory/o_auth_consent_app_detail.py +17 -0
- office365/directory/objects/object.py +12 -0
- office365/directory/on_premises_directory_synchronization.py +37 -0
- office365/directory/password_credential.py +10 -23
- office365/directory/permissions/grants/condition_set.py +27 -1
- office365/directory/permissions/identity.py +4 -0
- office365/directory/permissions/identity_set.py +7 -25
- office365/directory/permissions/require_permission.py +78 -9
- office365/directory/policies/activity_based_timeout_policy.py +7 -0
- office365/directory/policies/admin_consent_request.py +50 -0
- office365/directory/policies/app_management.py +17 -1
- office365/directory/policies/authentication_flows.py +26 -1
- office365/directory/policies/authorization.py +12 -0
- office365/directory/policies/conditionalaccess/conditional_access.py +82 -0
- office365/directory/policies/crosstenant/access.py +43 -0
- office365/directory/policies/device_registration.py +69 -0
- office365/directory/policies/dlpaction.py +4 -0
- office365/directory/policies/feature_rollout.py +38 -0
- office365/directory/policies/federated_token_validation_policy.py +21 -0
- office365/directory/policies/ownerless_group_policy.py +59 -0
- office365/directory/policies/protected_application_metadata.py +1 -1
- office365/directory/policies/restrict_access_action_base.py +1 -1
- office365/directory/policies/root.py +142 -2
- office365/directory/policies/template.py +45 -0
- office365/directory/policies/tenant_app_management.py +39 -0
- office365/directory/policies/unifiedrolemanagement/policy.py +76 -1
- office365/directory/policies/unifiedrolemanagement/unified_role_assignment_schedule_request.py +145 -0
- office365/directory/policies/verified_id_profile_configuration.py +13 -1
- office365/directory/policies/verified_id_usage_configuration.py +5 -0
- office365/directory/profile_card_annotation.py +19 -0
- office365/directory/profile_source_localization.py +16 -0
- office365/directory/protection/bitlocker.py +32 -0
- office365/directory/protection/bitlocker_recovery_key.py +37 -0
- office365/directory/protection/information.py +18 -0
- office365/directory/protection/policy/base.py +82 -0
- office365/directory/protection/policy/one_drive_for_business.py +42 -0
- office365/directory/protection/riskyusers/risky_service_principal_history_item.py +15 -1
- office365/directory/protection/root.py +42 -0
- office365/directory/protection/rules/base.py +72 -0
- office365/directory/protection/rules/drive.py +13 -0
- office365/directory/protection/threatassessment/requests/email_file.py +26 -1
- office365/directory/protection/threatassessment/requests/file.py +11 -0
- office365/directory/protection/threatassessment/requests/mail.py +32 -0
- office365/directory/protection/threatassessment/requests/request.py +52 -1
- office365/directory/protection/threatassessment/requests/threat_assessment_result.py +36 -0
- office365/directory/protection/threatassessment/requests/url.py +6 -0
- office365/directory/recovery/change_object_base.py +31 -0
- office365/directory/recovery/job.py +26 -0
- office365/directory/recovery/job_base.py +59 -0
- office365/directory/recovery/job_entity_name_and_ids_filter.py +18 -0
- office365/directory/recovery/job_entity_names_filter.py +18 -0
- office365/directory/recovery/job_filtering_criteria_base.py +10 -0
- office365/directory/recovery/preview_job.py +9 -0
- office365/directory/recovery/recovery.py +37 -0
- office365/directory/related_tenant.py +88 -0
- office365/directory/related_tenants_refresh_status.py +17 -0
- office365/directory/relationship_creation_type.py +13 -0
- office365/directory/relationship_policy.py +29 -0
- office365/directory/relationship_status.py +14 -0
- office365/directory/remote_tenant_group.py +37 -0
- office365/directory/request_schedule.py +21 -0
- office365/directory/request_status.py +14 -0
- office365/directory/resource_visualization.py +21 -0
- office365/directory/role_template.py +15 -0
- office365/directory/role_type.py +15 -0
- office365/directory/rolemanagement/app_scope.py +18 -0
- office365/directory/rolemanagement/application.py +120 -0
- office365/directory/rolemanagement/resource_action.py +4 -0
- office365/directory/rolemanagement/roles/collection.py +26 -3
- office365/directory/rolemanagement/roles/permission.py +4 -0
- office365/directory/rolemanagement/unifiedrole/assignment.py +33 -1
- office365/directory/rolemanagement/unifiedrole/definition.py +30 -0
- office365/directory/service_provider_constraints.py +7 -0
- office365/directory/service_storage_quota_breakdown.py +7 -0
- office365/directory/serviceprincipals/approved_client_app.py +16 -0
- office365/directory/serviceprincipals/collection.py +48 -4
- office365/directory/serviceprincipals/identity.py +14 -0
- office365/directory/serviceprincipals/remote_desktop_security_configuration.py +39 -1
- office365/directory/serviceprincipals/service_principal.py +330 -19
- office365/directory/serviceprincipals/target_device_group.py +16 -0
- office365/directory/snapshot.py +50 -0
- office365/directory/status.py +15 -0
- office365/directory/subscriptions/company.py +75 -0
- office365/directory/synchronization/attribute_mapping.py +1 -1
- office365/directory/synchronization/attribute_mapping_function_schema.py +1 -1
- office365/directory/synchronization/directory_definition.py +1 -1
- office365/directory/synchronization/job.py +13 -1
- office365/directory/synchronization/job_subject.py +6 -3
- office365/directory/synchronization/linked_objects.py +8 -9
- office365/directory/synchronization/rule.py +4 -4
- office365/directory/synchronization/schema.py +19 -2
- office365/directory/synchronization/status.py +1 -1
- office365/directory/synchronization/synchronization.py +22 -0
- office365/directory/synchronization/template.py +20 -1
- office365/directory/tenant_governance.py +98 -0
- office365/directory/tenant_governance_policy_template.py +75 -0
- office365/directory/tenant_governance_setting.py +21 -0
- office365/directory/tenantinformation/multi_organization.py +72 -0
- office365/directory/tenantinformation/relationship.py +41 -0
- office365/directory/ticket_info.py +15 -0
- office365/directory/unified_approval_stage.py +25 -0
- office365/directory/unified_rbac_resource_action.py +41 -0
- office365/directory/unified_rbac_resource_namespace.py +37 -0
- office365/directory/unified_role.py +14 -0
- office365/directory/unified_role_assignment_schedule.py +47 -0
- office365/directory/unified_role_assignment_schedule_instance.py +60 -0
- office365/directory/unified_role_eligibility_schedule.py +26 -0
- office365/directory/unified_role_eligibility_schedule_instance.py +32 -0
- office365/directory/unified_role_eligibility_schedule_request.py +133 -0
- office365/directory/unified_role_management_policy_approval_rule.py +21 -0
- office365/directory/unified_role_management_policy_assignment.py +48 -0
- office365/directory/unified_role_management_policy_authentication_context_rule.py +21 -0
- office365/directory/unified_role_management_policy_custom_extension_rule.py +32 -0
- office365/directory/unified_role_management_policy_enablement_rule.py +15 -0
- office365/directory/unified_role_management_policy_expiration_rule.py +24 -0
- office365/directory/unified_role_management_policy_notification_rule.py +37 -0
- office365/directory/unified_role_management_policy_rule.py +25 -0
- office365/directory/unified_role_management_policy_rule_target.py +31 -0
- office365/directory/unified_role_schedule_base.py +96 -0
- office365/directory/unified_role_schedule_instance_base.py +75 -0
- office365/directory/unified_root.py +66 -0
- office365/directory/unified_storage_quota.py +62 -0
- office365/directory/users/activities/activity.py +24 -2
- office365/directory/users/activities/history_item.py +42 -1
- office365/directory/users/collection.py +17 -0
- office365/directory/users/identity.py +4 -0
- office365/directory/users/inference_classification.py +23 -0
- office365/directory/users/inference_classification_override.py +33 -0
- office365/directory/users/item_insights.py +7 -0
- office365/directory/users/note.py +101 -0
- office365/directory/users/provisioned_plan.py +16 -0
- office365/directory/users/settings.py +50 -1
- office365/directory/users/storage.py +15 -0
- office365/directory/users/user.py +146 -16
- office365/directory/users/windows_setting.py +50 -0
- office365/directory/users/windows_setting_instance.py +32 -0
- office365/directory/users/work_hours_and_locations_setting.py +53 -0
- office365/directory/users/work_plan_occurrence.py +56 -0
- office365/directory/users/work_plan_recurrence.py +51 -0
- office365/directory/verified_id_profile.py +91 -0
- office365/directory/visual_info.py +20 -0
- office365/education/challenging_word.py +15 -0
- office365/education/rubric_criterion.py +10 -0
- office365/education/rubric_level.py +15 -0
- office365/education/rubric_quality.py +21 -0
- office365/education/rubric_quality_feedback_model.py +14 -0
- office365/education/rubric_quality_selected_column_model.py +15 -0
- office365/entity.py +72 -3
- office365/entity_collection.py +33 -2
- office365/externalconnectors/acl.py +21 -0
- office365/externalconnectors/activity_settings.py +18 -0
- office365/externalconnectors/connection_operation.py +17 -2
- office365/externalconnectors/connector_type.py +12 -0
- office365/externalconnectors/content_category.py +18 -4
- office365/externalconnectors/display_template.py +20 -0
- office365/externalconnectors/external_activity.py +14 -1
- office365/externalconnectors/external_activity_result.py +12 -0
- office365/externalconnectors/external_group.py +15 -1
- office365/externalconnectors/external_item_content.py +19 -0
- office365/externalconnectors/external_item_information_protection_label.py +14 -0
- office365/externalconnectors/identity.py +19 -0
- office365/externalconnectors/item_id_resolver.py +16 -0
- office365/externalconnectors/principal.py +20 -0
- office365/externalconnectors/properties.py +10 -0
- office365/externalconnectors/property.py +28 -0
- office365/externalconnectors/property_rule.py +22 -0
- office365/externalconnectors/url_match_info.py +16 -0
- office365/externalconnectors/url_to_item_resolver_base.py +14 -0
- office365/graph_client.py +138 -33
- office365/graph_request.py +56 -4
- office365/intune/audit/actor.py +26 -0
- office365/intune/audit/event.py +48 -0
- office365/intune/audit/property.py +15 -0
- office365/intune/audit/resource.py +15 -1
- office365/intune/devices/and_app_management_role_assignment.py +15 -0
- office365/intune/devices/and_app_management_role_definition.py +7 -0
- office365/intune/devices/category.py +18 -0
- office365/intune/devices/compliance/android_compliance_policy.py +134 -0
- office365/intune/devices/compliance/android_work_profile_compliance_policy.py +134 -0
- office365/intune/devices/compliance/compliance.py +7 -0
- office365/intune/devices/compliance/information.py +19 -0
- office365/intune/devices/compliance/ios_compliance_policy.py +99 -0
- office365/intune/devices/compliance/mac_os_compliance_policy.py +114 -0
- office365/intune/devices/compliance/management_partner.py +91 -0
- office365/intune/devices/compliance/management_partner_assignment.py +17 -0
- office365/intune/devices/compliance/policy.py +155 -0
- office365/intune/devices/compliance/policy_state.py +61 -0
- office365/intune/devices/compliance/setting_state_device_summary.py +56 -0
- office365/intune/devices/compliance/windows10_compliance_policy.py +115 -0
- office365/intune/devices/compliance/windows10_mobile_compliance_policy.py +100 -0
- office365/intune/devices/compliance/windows81_compliance_policy.py +75 -0
- office365/intune/devices/compliance/windows_phone81_compliance_policy.py +75 -0
- office365/intune/devices/compliance_action_item.py +41 -0
- office365/intune/devices/compliance_device_overview.py +47 -0
- office365/intune/devices/compliance_device_status.py +52 -0
- office365/intune/devices/compliance_policy_assignment.py +25 -0
- office365/intune/devices/compliance_policy_device_state_summary.py +56 -0
- office365/intune/devices/compliance_policy_setting_state_summary.py +91 -0
- office365/intune/devices/compliance_scheduled_action_for_rule.py +39 -0
- office365/intune/devices/compliance_setting_state.py +72 -0
- office365/intune/devices/compliance_user_overview.py +47 -0
- office365/intune/devices/compliance_user_status.py +42 -0
- office365/intune/devices/configuration_assignment.py +25 -0
- office365/intune/devices/configuration_device_overview.py +47 -0
- office365/intune/devices/configuration_device_state_summary.py +46 -0
- office365/intune/devices/configuration_device_status.py +52 -0
- office365/intune/devices/configuration_state.py +63 -0
- office365/intune/devices/configuration_user_overview.py +47 -0
- office365/intune/devices/configuration_user_status.py +42 -0
- office365/intune/devices/configurations/android/android_lob_app.py +15 -1
- office365/intune/devices/configurations/android/android_store_app.py +15 -1
- office365/intune/devices/configurations/android/custom_configuration.py +22 -0
- office365/intune/devices/configurations/android/general_device_configuration.py +282 -0
- office365/intune/devices/configurations/android/minimum_operating_system.py +29 -0
- office365/intune/devices/configurations/android/mobile_app_identifier.py +14 -0
- office365/intune/devices/configurations/android/work_profile_custom_configuration.py +22 -0
- office365/intune/devices/configurations/android/work_profile_general_device_configuration.py +214 -0
- office365/intune/devices/configurations/configuration.py +136 -0
- office365/intune/devices/configurations/edge_search_engine.py +17 -0
- office365/intune/devices/configurations/edge_search_engine_base.py +10 -0
- office365/intune/devices/configurations/edge_search_engine_custom.py +14 -0
- office365/intune/devices/configurations/ios/certificate_profile.py +7 -0
- office365/intune/devices/configurations/ios/custom_configuration.py +26 -0
- office365/intune/devices/configurations/ios/features_configuration.py +62 -0
- office365/intune/devices/configurations/ios/general/device_configuration.py +761 -0
- office365/intune/devices/configurations/ios/home_screen_app.py +14 -0
- office365/intune/devices/configurations/ios/home_screen_folder.py +20 -0
- office365/intune/devices/configurations/ios/home_screen_folder_page.py +19 -0
- office365/intune/devices/configurations/ios/home_screen_item.py +14 -0
- office365/intune/devices/configurations/ios/home_screen_page.py +21 -0
- office365/intune/devices/configurations/ios/minimum_operating_system.py +21 -0
- office365/intune/devices/configurations/ios/network_usage_rule.py +18 -0
- office365/intune/devices/configurations/ios/notification_settings.py +25 -0
- office365/intune/devices/configurations/ios/update_configuration.py +38 -0
- office365/intune/devices/configurations/macos/mac_os_custom_configuration.py +26 -0
- office365/intune/devices/configurations/macos/mac_os_device_features_configuration.py +7 -0
- office365/intune/devices/configurations/macos/mac_os_general_device_configuration.py +88 -0
- office365/intune/devices/configurations/macos/mac_os_minimum_operating_system.py +25 -0
- office365/intune/devices/configurations/media_content_rating_australia.py +21 -0
- office365/intune/devices/configurations/media_content_rating_canada.py +21 -0
- office365/intune/devices/configurations/media_content_rating_france.py +21 -0
- office365/intune/devices/configurations/media_content_rating_germany.py +21 -0
- office365/intune/devices/configurations/media_content_rating_ireland.py +21 -0
- office365/intune/devices/configurations/media_content_rating_japan.py +21 -0
- office365/intune/devices/configurations/media_content_rating_new_zealand.py +21 -0
- office365/intune/devices/configurations/media_content_rating_united_kingdom.py +21 -0
- office365/intune/devices/configurations/media_content_rating_united_states.py +21 -0
- office365/intune/devices/configurations/oma_setting.py +16 -0
- office365/intune/devices/configurations/oma_setting_base64.py +15 -0
- office365/intune/devices/configurations/oma_setting_boolean.py +14 -0
- office365/intune/devices/configurations/oma_setting_date_time.py +15 -0
- office365/intune/devices/configurations/oma_setting_floating_point.py +14 -0
- office365/intune/devices/configurations/oma_setting_integer.py +14 -0
- office365/intune/devices/configurations/oma_setting_string.py +14 -0
- office365/intune/devices/configurations/oma_setting_string_xml.py +15 -0
- office365/intune/devices/configurations/rgb_color.py +16 -0
- office365/intune/devices/configurations/windows/bit_locker_removable_drive_policy.py +19 -0
- office365/intune/devices/configurations/windows/defender_advanced_threat_protection_configuration.py +21 -0
- office365/intune/devices/configurations/windows/firewall_network_profile.py +28 -0
- office365/intune/devices/configurations/windows/minimum_operating_system.py +16 -0
- office365/intune/devices/configurations/windows/out_of_box_experience_setting.py +25 -0
- office365/intune/devices/configurations/windows/phone81_custom_configuration.py +22 -0
- office365/intune/devices/configurations/windows/phone81_general_configuration.py +172 -0
- office365/intune/devices/configurations/windows/shared_pc_account_manager_policy.py +20 -0
- office365/intune/devices/configurations/windows/update_active_hours_install.py +16 -0
- office365/intune/devices/configurations/windows/update_for_business_configuration.py +253 -0
- office365/intune/devices/configurations/windows/update_install_schedule_type.py +10 -0
- office365/intune/devices/configurations/windows/update_scheduled_install.py +19 -0
- office365/intune/devices/configurations/windows/windows10_custom_configuration.py +22 -0
- office365/intune/devices/configurations/windows/windows10_endpoint_protection_configuration.py +255 -0
- office365/intune/devices/configurations/windows/windows10_enrollment_completion_page_configuration.py +16 -0
- office365/intune/devices/configurations/windows/windows10_enterprise_modern_app_management_configuration.py +16 -0
- office365/intune/devices/configurations/windows/windows10_general_configuration.py +1177 -0
- office365/intune/devices/configurations/windows/windows10_network_proxy_server.py +17 -0
- office365/intune/devices/configurations/windows/windows10_secure_assessment_configuration.py +36 -0
- office365/intune/devices/configurations/windows/windows10_team_general_configuration.py +128 -0
- office365/intune/devices/configurations/windows/windows81_general_configuration.py +210 -0
- office365/intune/devices/data.py +11 -0
- office365/intune/devices/device.py +138 -1
- office365/intune/devices/enrollment/configuration.py +48 -1
- office365/intune/devices/enrollment/configuration_assignment.py +22 -0
- office365/intune/devices/enrollment/limit_configuration.py +13 -0
- office365/intune/devices/enrollment_platform_restrictions_configuration.py +61 -0
- office365/intune/devices/enrollment_windows_hello_for_business_configuration.py +93 -0
- office365/intune/devices/install_state.py +61 -0
- office365/intune/devices/log_collection_response.py +60 -0
- office365/intune/devices/management/action_result.py +19 -0
- office365/intune/devices/management/android_managed_app_registration.py +7 -0
- office365/intune/devices/management/app_management.py +157 -3
- office365/intune/devices/management/apple_push_notification_certificate.py +52 -0
- office365/intune/devices/management/compliance_policy_setting_state.py +28 -0
- office365/intune/devices/management/configuration_manager_client_enabled_features.py +19 -0
- office365/intune/devices/management/configuration_manager_collection_assignment_target.py +14 -0
- office365/intune/devices/management/configuration_setting_state.py +28 -0
- office365/intune/devices/management/defender_detected_malware_actions.py +18 -0
- office365/intune/devices/management/delete_user_from_shared_apple_device_action_result.py +14 -0
- office365/intune/devices/management/detected_app.py +65 -0
- office365/intune/devices/management/enrollment_platform_restriction.py +17 -0
- office365/intune/devices/management/exchange_access_state_summary.py +18 -0
- office365/intune/devices/management/geo_location.py +22 -0
- office365/intune/devices/management/group_peer_outlier_recommendation_insight_settings.py +10 -0
- office365/intune/devices/management/health_attestation_state.py +46 -0
- office365/intune/devices/management/imported_windows_autopilot_device_identity.py +55 -0
- office365/intune/devices/management/imported_windows_autopilot_device_identity_state.py +22 -0
- office365/intune/devices/management/insight_value_double.py +14 -0
- office365/intune/devices/management/insight_value_int.py +14 -0
- office365/intune/devices/management/integer_range.py +15 -0
- office365/intune/devices/management/ios_managed_app_registration.py +7 -0
- office365/intune/devices/management/ios_update_device_status.py +79 -0
- office365/intune/devices/management/localized_notification_message.py +37 -0
- office365/intune/devices/management/locate_device_action_result.py +15 -0
- office365/intune/devices/management/m365_capability_base.py +32 -0
- office365/intune/devices/management/m365_capability_inbound_access.py +18 -0
- office365/intune/devices/management/m365_capability_resource_scope.py +16 -0
- office365/intune/devices/management/m365_capability_resource_scopes.py +23 -0
- office365/intune/devices/management/m365_resource_type.py +14 -0
- office365/intune/devices/management/managed/app/diagnostic_status.py +15 -0
- office365/intune/devices/management/managed/app/operation.py +32 -0
- office365/intune/devices/management/managed/app/protections/android.py +31 -1
- office365/intune/devices/management/managed/app/protections/default.py +34 -2
- office365/intune/devices/management/managed/app/protections/ios.py +34 -2
- office365/intune/devices/management/managed/app/protections/targeted.py +22 -1
- office365/intune/devices/management/managed/app/protections/targeted_managed_app_policy_assignment.py +25 -0
- office365/intune/devices/management/managed/app/protections/windows.py +230 -1
- office365/intune/devices/management/managed/app/registration.py +29 -2
- office365/intune/devices/management/managed/app_policy_deployment_summary.py +50 -0
- office365/intune/devices/management/managed/collection.py +26 -0
- office365/intune/devices/management/managed/device_mobile_app_configuration.py +138 -0
- office365/intune/devices/management/managed/device_mobile_app_configuration_assignment.py +25 -0
- office365/intune/devices/management/managed/device_mobile_app_configuration_device_status.py +52 -0
- office365/intune/devices/management/managed/device_mobile_app_configuration_device_summary.py +47 -0
- office365/intune/devices/management/managed/device_mobile_app_configuration_user_status.py +42 -0
- office365/intune/devices/management/managed/device_mobile_app_configuration_user_summary.py +47 -0
- office365/intune/devices/management/managed/device_overview.py +52 -0
- office365/intune/devices/management/managed/ebook/e_book_assignment.py +33 -0
- office365/intune/devices/management/managed/ebook/ebook.py +72 -1
- office365/intune/devices/management/managed/ebook/install_summary.py +41 -0
- office365/intune/devices/management/managed/ebook/user_install_state_summary.py +48 -0
- office365/intune/devices/management/managed/managed.py +87 -3
- office365/intune/devices/management/managed/mobile_app.py +26 -0
- office365/intune/devices/management/management.py +995 -6
- office365/intune/devices/management/mdm_windows_information_protection_policy.py +7 -0
- office365/intune/devices/management/mobile_threat_defense_connector.py +96 -0
- office365/intune/devices/management/mobileapps/app_configuration_setting_item.py +17 -0
- office365/intune/devices/management/mobileapps/app_list_item.py +17 -0
- office365/intune/devices/management/mobileapps/app_log_collection_download_details.py +19 -0
- office365/intune/devices/management/mobileapps/app_log_collection_request.py +40 -0
- office365/intune/devices/management/mobileapps/apps_installation_options_for_mac.py +15 -0
- office365/intune/devices/management/mobileapps/apps_installation_options_for_windows.py +17 -0
- office365/intune/devices/management/mobileapps/assignment.py +28 -0
- office365/intune/devices/management/mobileapps/content.py +24 -1
- office365/intune/devices/management/mobileapps/ios/ios_mobile_app_configuration.py +18 -1
- office365/intune/devices/management/mobileapps/ios/iosi_pad_os_web_clip.py +21 -0
- office365/intune/devices/management/mobileapps/ios/lob_app.py +58 -0
- office365/intune/devices/management/mobileapps/ios/lob_app_assignment_settings.py +16 -0
- office365/intune/devices/management/mobileapps/ios/lob_app_provisioning_configuration_assignment.py +25 -0
- office365/intune/devices/management/mobileapps/ios/mobile_app_identifier.py +14 -0
- office365/intune/devices/management/mobileapps/ios/store_app.py +47 -0
- office365/intune/devices/management/mobileapps/ios/store_app_assignment_settings.py +16 -0
- office365/intune/devices/management/mobileapps/ios/vpp_app.py +81 -0
- office365/intune/devices/management/mobileapps/ios/vpp_app_assignment_settings.py +15 -0
- office365/intune/devices/management/mobileapps/ios/vpp_e_book.py +53 -0
- office365/intune/devices/management/mobileapps/ios/vpp_e_book_assignment.py +7 -0
- office365/intune/devices/management/mobileapps/macos/mac_os_dmg_app.py +49 -0
- office365/intune/devices/management/mobileapps/macos/mac_os_included_app.py +15 -0
- office365/intune/devices/management/mobileapps/macos/mac_os_lob_app.py +70 -0
- office365/intune/devices/management/mobileapps/macos/mac_os_lob_app_assignment_settings.py +14 -0
- office365/intune/devices/management/mobileapps/macos/mac_os_lob_child_app.py +16 -0
- office365/intune/devices/management/mobileapps/macos/mac_os_microsoft_defender_app.py +7 -0
- office365/intune/devices/management/mobileapps/macos/mac_os_microsoft_edge_app.py +25 -0
- office365/intune/devices/management/mobileapps/macos/mac_os_office_suite_app.py +7 -0
- office365/intune/devices/management/mobileapps/managed/app_policy_deployment_summary_per_app.py +18 -0
- office365/intune/devices/management/mobileapps/microsoft_store_for_business_app_assignment_settings.py +14 -0
- office365/intune/devices/management/mobileapps/mobile_app.py +31 -2
- office365/intune/devices/management/mobileapps/mobile_app_assignment_settings.py +10 -0
- office365/intune/devices/management/mobileapps/mobile_app_identifier.py +10 -0
- office365/intune/devices/management/mobileapps/mobile_app_install_time_settings.py +17 -0
- office365/intune/devices/management/mobileapps/mobile_app_troubleshooting_event.py +28 -0
- office365/intune/devices/management/mobileapps/mobile_lob_app.py +20 -1
- office365/intune/devices/management/mobileapps/vpp_licensing_type.py +15 -0
- office365/intune/devices/management/mobileapps/win32/lob_app.py +115 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_assignment_settings.py +35 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_auto_update_settings.py +19 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_file_system_rule.py +25 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_install_experience.py +21 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_msi_information.py +23 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_power_shell_script_rule.py +32 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_product_code_rule.py +19 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_registry_rule.py +25 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_restart_settings.py +16 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_return_code.py +18 -0
- office365/intune/devices/management/mobileapps/win32/lob_app_rule.py +17 -0
- office365/intune/devices/management/mobileapps/windows/app_x.py +62 -0
- office365/intune/devices/management/mobileapps/windows/app_x_app_assignment_settings.py +14 -0
- office365/intune/devices/management/mobileapps/windows/microsoft_edge_app.py +30 -0
- office365/intune/devices/management/mobileapps/windows/mobile_msi.py +31 -0
- office365/intune/devices/management/mobileapps/windows/universal_app_x.py +93 -0
- office365/intune/devices/management/mobileapps/windows/universal_app_x_app_assignment_settings.py +14 -0
- office365/intune/devices/management/mobileapps/windows/universal_app_x_contained_app.py +16 -0
- office365/intune/devices/management/mobileapps/windows/web_app.py +16 -0
- office365/intune/devices/management/notification_message_template.py +68 -0
- office365/intune/devices/management/obliteration_behavior.py +15 -0
- office365/intune/devices/management/on_premises_conditional_access_settings.py +32 -0
- office365/intune/devices/management/operating_system_summary.py +25 -0
- office365/intune/devices/management/partner_assignment.py +17 -0
- office365/intune/devices/management/remote_lock_action_result.py +14 -0
- office365/intune/devices/management/remoteassistance/partner.py +41 -0
- office365/intune/devices/management/reports/reports.py +27 -0
- office365/intune/devices/management/reset_passcode_action_result.py +15 -0
- office365/intune/devices/management/resource_operation.py +26 -0
- office365/intune/devices/management/rotate_bit_locker_keys_device_action_result.py +14 -0
- office365/intune/devices/management/software_update_status_summary.py +86 -0
- office365/intune/devices/management/system_browser_enabled_on.py +16 -0
- office365/intune/devices/management/targeted_managed_app_configuration.py +68 -0
- office365/intune/devices/management/terms_and_conditions.py +87 -0
- office365/intune/devices/management/terms_and_conditions_acceptance_status.py +50 -0
- office365/intune/devices/management/terms_and_conditions_assignment.py +25 -0
- office365/intune/devices/management/update_windows_device_account_action_parameter.py +22 -0
- office365/intune/devices/management/user_last_sign_in_recommendation_insight_setting.py +22 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_app_health_app_performance_by_app_version_details.py +51 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_app_health_app_performance_by_app_version_device_id.py +52 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_app_health_app_performance_by_os_version.py +56 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_app_health_application_performance.py +56 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_app_health_device_model_performance.py +50 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_app_health_device_performance.py +76 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_app_health_device_performance_details.py +47 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_app_health_os_version_performance.py +36 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_autopilot_devices_summary.py +16 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_baseline.py +121 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_category.py +47 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_cloud_identity_devices_summary.py +14 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_cloud_management_devices_summary.py +16 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_device_performance.py +127 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_device_scores.py +65 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_device_startup_history.py +101 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_device_startup_process.py +36 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_device_startup_process_performance.py +41 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_insight.py +26 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_insight_value.py +10 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_metric.py +21 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_metric_history.py +27 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_model_scores.py +65 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_overview.py +28 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_score_history.py +17 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_windows10_devices_summary.py +14 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_work_from_anywhere_device.py +197 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_work_from_anywhere_devices_summary.py +47 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_work_from_anywhere_hardware_readiness_metric.py +66 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_work_from_anywhere_metric.py +34 -0
- office365/intune/devices/management/userexperienceanalytics/user_experience_analytics_work_from_anywhere_model_performance.py +65 -0
- office365/intune/devices/management/virtualendpoint/virtual_endpoint.py +137 -0
- office365/intune/devices/management/windows_autopilot_deployment_profile.py +106 -0
- office365/intune/devices/management/windows_autopilot_deployment_profile_assignment.py +7 -0
- office365/intune/devices/management/windows_autopilot_device_identity.py +97 -0
- office365/intune/devices/management/windows_defender_scan_action_result.py +14 -0
- office365/intune/devices/management/windows_device_account.py +14 -0
- office365/intune/devices/management/windows_device_ad_account.py +15 -0
- office365/intune/devices/management/windows_device_azure_ad_account.py +14 -0
- office365/intune/devices/management/windows_information_protection_app.py +18 -0
- office365/intune/devices/management/windows_information_protection_app_learning_summary.py +35 -0
- office365/intune/devices/management/windows_information_protection_app_locker_file.py +31 -0
- office365/intune/devices/management/windows_information_protection_data_recovery_certificate.py +18 -0
- office365/intune/devices/management/windows_information_protection_desktop_app.py +16 -0
- office365/intune/devices/management/windows_information_protection_ip_range_collection.py +17 -0
- office365/intune/devices/management/windows_information_protection_network_learning_summary.py +21 -0
- office365/intune/devices/management/windows_information_protection_pin_character_requirements.py +13 -0
- office365/intune/devices/management/windows_information_protection_policy.py +76 -0
- office365/intune/devices/management/windows_information_protection_proxied_domain_collection.py +19 -0
- office365/intune/devices/management/windows_information_protection_resource_collection.py +16 -0
- office365/intune/devices/management/windows_information_protection_store_app.py +10 -0
- office365/intune/devices/management/windows_malware_overview.py +34 -1
- office365/intune/devices/management/windows_protection_state.py +152 -0
- office365/intune/devices/management/windowsmalware/malware_state_for_windows_device.py +58 -0
- office365/intune/devices/management/windowsmalware/windows_device_malware_state.py +99 -0
- office365/intune/devices/management/windowsmalware/windows_malware_category_count.py +22 -0
- office365/intune/devices/management/windowsmalware/windows_malware_execution_state_count.py +20 -0
- office365/intune/devices/management/windowsmalware/windows_malware_information.py +72 -0
- office365/intune/devices/management/windowsmalware/windows_malware_name_count.py +18 -0
- office365/intune/devices/management/windowsmalware/windows_malware_severity_count.py +21 -0
- office365/intune/devices/management/windowsmalware/windows_malware_state_count.py +22 -0
- office365/intune/devices/management_cached_report_configuration.py +7 -0
- office365/intune/devices/management_exchange_connector.py +73 -0
- office365/intune/devices/management_export_job.py +82 -0
- office365/intune/devices/management_partner.py +84 -0
- office365/intune/devices/management_troubleshooting_event.py +22 -0
- office365/intune/organizations/branding.py +29 -0
- office365/intune/organizations/branding_properties.py +187 -0
- office365/intune/organizations/contact.py +132 -0
- office365/intune/organizations/content_customization.py +9 -1
- office365/intune/organizations/organization.py +125 -1
- office365/intune/organizations/organizational_branding_localization.py +7 -0
- office365/intune/print/air_print_settings.py +17 -0
- office365/intune/print/archived_print_job.py +26 -0
- office365/intune/print/certificate_signing_request.py +15 -0
- office365/intune/print/connectors/connector.py +44 -0
- office365/intune/print/document.py +37 -0
- office365/intune/print/document_upload_properties.py +16 -0
- office365/intune/print/job.py +91 -0
- office365/intune/print/job_configuration.py +46 -0
- office365/intune/print/job_status.py +22 -0
- office365/intune/print/margin.py +17 -0
- office365/intune/print/operation.py +28 -0
- office365/intune/print/operation_status.py +18 -0
- office365/intune/print/print.py +65 -0
- office365/intune/print/printers/base.py +66 -0
- office365/intune/print/printers/capabilities.py +82 -0
- office365/intune/print/printers/create_operation.py +28 -0
- office365/intune/print/printers/defaults.py +40 -0
- office365/intune/print/printers/discovery_settings.py +15 -0
- office365/intune/print/printers/location.py +31 -0
- office365/intune/print/printers/printer.py +70 -0
- office365/intune/print/printers/share_viewpoint.py +15 -0
- office365/intune/print/printers/status.py +23 -0
- office365/intune/print/services/endpoint.py +18 -0
- office365/intune/print/settings.py +16 -0
- office365/intune/print/shares/share.py +60 -0
- office365/intune/print/task.py +47 -0
- office365/intune/print/task_definition.py +38 -0
- office365/intune/print/task_status.py +16 -0
- office365/intune/print/task_trigger.py +32 -0
- office365/intune/print/user_print.py +7 -0
- office365/intune/servicecommunications/announcement_base.py +40 -0
- office365/intune/servicecommunications/health/health.py +42 -0
- office365/intune/servicecommunications/issues/issue.py +48 -1
- office365/intune/servicecommunications/messages/update.py +105 -0
- office365/intune/servicecommunications/service_health_issue_post.py +19 -0
- office365/intune/servicecommunications/service_update_message_viewpoint.py +16 -0
- office365/migration/__init__.py +24 -2
- office365/migration/adapters/__init__.py +36 -6
- office365/migration/base.py +105 -8
- office365/migration/checkpoint.py +17 -16
- office365/migration/delta.py +205 -0
- office365/migration/fidelity.py +283 -0
- office365/migration/job.py +172 -13
- office365/migration/logging.py +373 -0
- office365/migration/manifest.py +8 -3
- office365/migration/map.py +84 -0
- office365/migration/package/builder.py +80 -14
- office365/migration/package/manifest_xml.py +19 -4
- office365/migration/runner.py +156 -27
- office365/migration/server_job.py +79 -1
- office365/migration/session.py +335 -18
- office365/migration/settings.py +67 -3
- office365/migration/sharepoint/__init__.py +148 -0
- office365/migration/sharepoint/adapters.py +2883 -114
- office365/migration/sharepoint/branding.py +260 -0
- office365/migration/sharepoint/handoff.py +252 -0
- office365/migration/sharepoint/navigation.py +197 -0
- office365/migration/sharepoint/package_target.py +132 -3
- office365/migration/sharepoint/pages.py +853 -0
- office365/migration/sharepoint/resolvers.py +695 -30
- office365/migration/sharepoint/security.py +504 -0
- office365/migration/sharepoint/site_scope.py +222 -0
- office365/migration/sharepoint/site_settings.py +196 -0
- office365/migration/sharepoint/site_verify.py +207 -0
- office365/migration/sharepoint/structure.py +1426 -0
- office365/migration/sharepoint/verify.py +186 -0
- office365/migration/teams/_extract.py +2 -1
- office365/migration/teams/models.py +1 -0
- office365/migration/throttling.py +215 -0
- office365/migration/validators.py +250 -15
- office365/onedrive/columns/definition.py +11 -0
- office365/onedrive/contenttypes/content_type.py +24 -0
- office365/onedrive/deleted.py +14 -0
- office365/onedrive/driveitems/collection.py +30 -0
- office365/onedrive/driveitems/copy_result.py +115 -0
- office365/onedrive/driveitems/driveItem.py +315 -41
- office365/onedrive/driveitems/retention_label_settings.py +1 -1
- office365/onedrive/drives/drive.py +8 -9
- office365/onedrive/filestorage/containers/container.py +157 -1
- office365/onedrive/filestorage/containers/custom_property_value.py +2 -0
- office365/onedrive/filestorage/containertypes/registration_settings.py +2 -0
- office365/onedrive/filestorage/containertypes/settings.py +2 -0
- office365/onedrive/filestorage/containertypes/settings_override.py +2 -0
- office365/onedrive/internal/paths/children.py +7 -1
- office365/onedrive/listitems/collection.py +34 -4
- office365/onedrive/listitems/list_item.py +11 -0
- office365/onedrive/lists/list.py +28 -1
- office365/onedrive/operations/long_running.py +34 -2
- office365/onedrive/operations/public_inner_error.py +7 -17
- office365/onedrive/operations/rich_long_running.py +27 -1
- office365/onedrive/permissions/permission.py +10 -0
- office365/onedrive/sharepoint/migration/jobs/error_event.py +35 -1
- office365/onedrive/shares/drive_item.py +2 -3
- office365/onedrive/sitepages/collection.py +34 -4
- office365/onedrive/sitepages/site_page.py +1 -1
- office365/onedrive/sites/site.py +140 -1
- office365/onedrive/sites/sites_with_root.py +21 -2
- office365/onedrive/storage/settings.py +13 -0
- office365/onedrive/termstore/groups/collection.py +23 -3
- office365/onedrive/termstore/groups/group.py +26 -3
- office365/onedrive/termstore/relation.py +8 -0
- office365/onedrive/termstore/sets/collection.py +27 -12
- office365/onedrive/termstore/sets/name.py +4 -0
- office365/onedrive/termstore/sets/set.py +16 -3
- office365/onedrive/termstore/store.py +27 -3
- office365/onedrive/termstore/store_manager.py +42 -10
- office365/onedrive/termstore/terms/description.py +4 -0
- office365/onedrive/termstore/terms/label.py +4 -0
- office365/onedrive/termstore/terms/term.py +6 -1
- office365/onedrive/wellknown_list_name.py +11 -0
- office365/onedrive/workbooks/charts/area_format.py +27 -0
- office365/onedrive/workbooks/charts/axis.py +79 -0
- office365/onedrive/workbooks/charts/axis_format.py +32 -0
- office365/onedrive/workbooks/charts/axis_title.py +38 -0
- office365/onedrive/workbooks/charts/axis_title_format.py +22 -0
- office365/onedrive/workbooks/charts/chart.py +44 -1
- office365/onedrive/workbooks/charts/data_label_format.py +30 -0
- office365/onedrive/workbooks/charts/data_labels.py +54 -1
- office365/onedrive/workbooks/charts/fill.py +6 -0
- office365/onedrive/workbooks/charts/font.py +38 -0
- office365/onedrive/workbooks/charts/gridlines.py +33 -0
- office365/onedrive/workbooks/charts/gridlines_format.py +24 -0
- office365/onedrive/workbooks/charts/legend/format.py +27 -0
- office365/onedrive/workbooks/charts/legend/legend.py +24 -1
- office365/onedrive/workbooks/charts/line_format.py +16 -0
- office365/onedrive/workbooks/charts/point.py +31 -0
- office365/onedrive/workbooks/charts/point_format.py +22 -0
- office365/onedrive/workbooks/charts/series/format.py +29 -0
- office365/onedrive/workbooks/charts/series/series.py +13 -0
- office365/onedrive/workbooks/charts/title.py +23 -0
- office365/onedrive/workbooks/charts/title_format.py +27 -0
- office365/onedrive/workbooks/comments/reply.py +18 -0
- office365/onedrive/workbooks/filter.py +4 -0
- office365/onedrive/workbooks/functions/functions.py +6 -0
- office365/onedrive/workbooks/functions/result.py +13 -0
- office365/onedrive/workbooks/names/named_item.py +27 -1
- office365/onedrive/workbooks/operations/workbook.py +2 -1
- office365/onedrive/workbooks/ranges/fill.py +6 -0
- office365/onedrive/workbooks/ranges/format.py +6 -0
- office365/onedrive/workbooks/ranges/format_protection.py +18 -0
- office365/onedrive/workbooks/ranges/range.py +58 -1
- office365/onedrive/workbooks/ranges/sort.py +6 -0
- office365/onedrive/workbooks/ranges/view.py +67 -1
- office365/onedrive/workbooks/tables/columns/column.py +18 -0
- office365/onedrive/workbooks/tables/rows/row.py +6 -0
- office365/onedrive/workbooks/tables/sort.py +22 -1
- office365/onedrive/workbooks/tables/table.py +16 -0
- office365/onedrive/workbooks/workbook.py +4 -0
- office365/onedrive/workbooks/worksheets/protection.py +11 -0
- office365/onedrive/workbooks/worksheets/worksheet.py +16 -0
- office365/onenote/entity_base_model.py +13 -0
- office365/onenote/notebooks/collection.py +38 -0
- office365/onenote/notebooks/notebook.py +45 -4
- office365/onenote/onenote.py +3 -3
- office365/onenote/operations/onenote.py +24 -1
- office365/onenote/pages/collection.py +36 -20
- office365/onenote/pages/page.py +32 -0
- office365/onenote/resources/resource.py +11 -0
- office365/onenote/sectiongroups/section_group.py +20 -5
- office365/onenote/sections/collection.py +47 -0
- office365/onenote/sections/section.py +9 -5
- office365/outlook/attachment_info.py +11 -0
- office365/outlook/calendar/check_in_claim.py +32 -0
- office365/outlook/calendar/events/event.py +29 -2
- office365/outlook/calendar/place.py +56 -1
- office365/outlook/calendar/place_collection.py +34 -0
- office365/outlook/calendar/rooms/room.py +60 -1
- office365/outlook/calendar/rooms/workspace.py +38 -0
- office365/outlook/calendar/sharing_message.py +30 -0
- office365/outlook/contacts/folders/collection.py +23 -3
- office365/outlook/mail/attachments/attachment.py +11 -0
- office365/outlook/mail/attachments/file.py +12 -1
- office365/outlook/mail/attachments/type.py +7 -2
- office365/outlook/mail/export_item_response.py +1 -1
- office365/outlook/mail/folders/collection.py +38 -0
- office365/outlook/mail/folders/folder.py +8 -3
- office365/outlook/mail/messages/event_message.py +71 -0
- office365/outlook/mail/messages/message.py +32 -13
- office365/outlook/mail/post.py +118 -1
- office365/outlook/people/person_type.py +2 -0
- office365/outlook/people/profile_card_property.py +29 -0
- office365/outlook/person.py +29 -1
- office365/partners/billing/billed_reconciliation.py +3 -1
- office365/partners/billing/billed_usage.py +3 -1
- office365/partners/billing/billing.py +1 -1
- office365/partners/billing/blob.py +15 -0
- office365/partners/billing/export_success_operation.py +2 -1
- office365/partners/billing/failed_operation.py +14 -1
- office365/partners/billing/manifest.py +12 -1
- office365/partners/billing/metrics.py +34 -0
- office365/partners/billing/metrics_base.py +53 -0
- office365/partners/billing/metrics_initial.py +17 -0
- office365/partners/billing/metrics_recent.py +17 -0
- office365/partners/billing/operation.py +35 -1
- office365/partners/billing/reconciliation.py +40 -0
- office365/partners/billing/running_operation.py +2 -1
- office365/partners/billing/unbilled_reconciliation.py +3 -1
- office365/partners/billing/unbilled_usage.py +3 -1
- office365/partners/partnertenanttype.py +8 -12
- office365/planner/applied_categories.py +10 -0
- office365/planner/assigned_to_task_board_task_format.py +30 -0
- office365/planner/assignment.py +31 -0
- office365/planner/assignments.py +45 -1
- office365/planner/bucket_task_board_task_format.py +16 -0
- office365/planner/buckets/bucket.py +21 -8
- office365/planner/buckets/collection.py +56 -15
- office365/planner/category_descriptions.py +59 -0
- office365/planner/entity.py +52 -0
- office365/planner/external_reference.py +20 -0
- office365/planner/internal/paths/collections.py +27 -0
- office365/planner/order_hints_by_assignee.py +10 -0
- office365/planner/planner.py +2 -2
- office365/planner/plans/container.py +4 -0
- office365/planner/plans/details.py +6 -2
- office365/planner/plans/plan.py +36 -25
- office365/planner/progress_task_board_task_format.py +16 -0
- office365/planner/tasks/check_list_item.py +28 -1
- office365/planner/tasks/check_list_items.py +46 -9
- office365/planner/tasks/collection.py +67 -32
- office365/planner/tasks/task.py +178 -24
- office365/planner/tasks/task_details.py +54 -5
- office365/planner/user.py +8 -6
- office365/reports/internal/queries/create_report_query.py +3 -24
- office365/reports/partners.py +11 -0
- office365/reports/report.py +4 -0
- office365/reports/report_export.py +197 -0
- office365/reports/report_result.py +55 -0
- office365/reports/root.py +90 -45
- office365/reports/security/root.py +12 -6
- office365/runtime/auth/authentication_context.py +90 -19
- office365/runtime/auth/certificate.py +105 -0
- office365/runtime/auth/entra/authentication_context.py +130 -9
- office365/runtime/client_object.py +145 -5
- office365/runtime/client_object_collection.py +350 -10
- office365/runtime/client_request.py +323 -22
- office365/runtime/client_request_exception.py +64 -2
- office365/runtime/client_result.py +80 -2
- office365/runtime/client_runtime_context.py +667 -30
- office365/runtime/client_value.py +29 -10
- office365/runtime/client_value_collection.py +30 -15
- office365/runtime/converters/scalars.py +27 -0
- office365/runtime/converters/upsert.py +3 -3
- office365/runtime/converters/value.py +31 -4
- office365/runtime/display.py +232 -0
- office365/runtime/exceptions.py +65 -4
- office365/runtime/http/prefer.py +123 -2
- office365/runtime/http/request_options.py +10 -3
- office365/runtime/http/throttling.py +151 -21
- office365/runtime/imports.py +160 -34
- office365/runtime/lro.py +751 -0
- office365/runtime/odata/batch_util.py +33 -1
- office365/runtime/odata/literals.py +49 -0
- office365/runtime/odata/request.py +9 -4
- office365/runtime/odata/type.py +10 -4
- office365/runtime/odata/v3/batch_request.py +174 -26
- office365/runtime/odata/v4/batch_request.py +146 -15
- office365/runtime/odata/v4/upload_session_request.py +18 -0
- office365/runtime/paths/alternate_key.py +44 -0
- office365/runtime/paths/appid.py +8 -8
- office365/runtime/paths/builder.py +11 -5
- office365/runtime/persistence.py +71 -0
- office365/runtime/pollable.py +234 -0
- office365/runtime/progress.py +52 -0
- office365/runtime/queries/batch.py +13 -5
- office365/runtime/queries/client_query.py +27 -1
- office365/runtime/queries/deferred.py +7 -0
- office365/runtime/queries/get_or_create.py +19 -3
- office365/runtime/queries/upsert_entity.py +103 -0
- office365/runtime/query_capabilities.py +333 -0
- office365/runtime/record_collection.py +176 -4
- office365/runtime/respond_async.py +132 -0
- office365/runtime/retry.py +124 -11
- office365/runtime/transport/base.py +160 -4
- office365/runtime/transport/httpx_transport.py +263 -0
- office365/runtime/transport/offload.py +156 -0
- office365/runtime/transport/requests_transport.py +163 -18
- office365/runtime/transport/throttled_transport.py +29 -6
- office365/runtime/verification.py +41 -9
- office365/search/acronyms/acronym.py +19 -0
- office365/search/aggregation_option.py +12 -0
- office365/search/alteration_response.py +18 -0
- office365/search/altered_query_token.py +16 -0
- office365/search/answers/keyword.py +17 -0
- office365/search/answers/variant.py +19 -0
- office365/search/bookmarks/bookmark.py +21 -1
- office365/search/bucket.py +4 -0
- office365/search/bucket_aggregation_definition.py +23 -0
- office365/search/bucket_aggregation_range.py +15 -0
- office365/search/collapse_property.py +16 -0
- office365/search/connection_item.py +14 -0
- office365/search/entity_type.py +1 -1
- office365/search/external/connection.py +88 -0
- office365/search/external/item.py +29 -0
- office365/search/external/item_configuration.py +18 -0
- office365/search/external/search_settings.py +11 -1
- office365/search/hits/container.py +4 -0
- office365/search/hits/hit.py +10 -0
- office365/search/identity.py +16 -0
- office365/search/identity_set.py +8 -1
- office365/search/qnas/qna.py +21 -1
- office365/search/query.py +4 -0
- office365/search/recommended_action.py +16 -0
- office365/search/request.py +17 -0
- office365/search/response.py +10 -0
- office365/search/result_template.py +16 -0
- office365/search/result_template_dictionary.py +10 -0
- office365/search/result_template_option.py +14 -0
- office365/search/retrieval_extract.py +15 -0
- office365/search/retrieval_hit.py +27 -0
- office365/search/retrieval_response.py +18 -0
- office365/search/search_alteration.py +21 -0
- office365/search/search_alteration_options.py +15 -0
- office365/search/search_resource_metadata_dictionary.py +10 -0
- office365/search/search_result.py +14 -0
- office365/search/sharepoint_onedrive_options.py +7 -0
- office365/search/sort_property.py +4 -0
- office365/security/account.py +24 -0
- office365/security/account_target_content.py +17 -0
- office365/security/activity_metadata.py +15 -0
- office365/security/address_book_account_target_content.py +15 -0
- office365/security/ai_agent_evidence.py +19 -0
- office365/security/alerts/alert.py +459 -0
- office365/security/alerts/blob_container_evidence.py +17 -0
- office365/security/alerts/blob_evidence.py +21 -0
- office365/security/alerts/collection.py +49 -0
- office365/security/alerts/detection.py +16 -0
- office365/security/alerts/evidence.py +28 -0
- office365/security/alerts/file_security_state.py +23 -0
- office365/security/alerts/history_state.py +31 -0
- office365/security/alerts/malware_evidence.py +22 -0
- office365/security/alerts/process_evidence.py +27 -0
- office365/security/alerts/sas_token_evidence.py +26 -0
- office365/security/alerts/trigger.py +16 -0
- office365/security/analyzed_message_evidence.py +34 -0
- office365/security/article.py +75 -0
- office365/security/assigned_training_info.py +16 -0
- office365/security/attacksimulations/automation.py +90 -0
- office365/security/attacksimulations/automation_run.py +43 -0
- office365/security/attacksimulations/event.py +22 -0
- office365/security/attacksimulations/landing_page.py +105 -0
- office365/security/attacksimulations/landing_page_detail.py +26 -0
- office365/security/attacksimulations/login_page.py +83 -0
- office365/security/attacksimulations/operations/operation.py +37 -0
- office365/security/attacksimulations/payload.py +192 -0
- office365/security/attacksimulations/repeat_offender.py +21 -0
- office365/security/attacksimulations/reports/overview.py +30 -0
- office365/security/attacksimulations/reports/report.py +29 -0
- office365/security/attacksimulations/root.py +124 -0
- office365/security/attacksimulations/simulation.py +220 -0
- office365/security/attacksimulations/training_user_coverage.py +22 -0
- office365/security/attacksimulations/user.py +20 -0
- office365/security/attacksimulations/user_coverage.py +26 -0
- office365/security/attacksimulations/users/details.py +48 -0
- office365/security/attribute_definition.py +37 -0
- office365/security/attribute_mapping.py +11 -0
- office365/security/attribute_mapping_source.py +25 -0
- office365/security/audit/audit_data.py +15 -0
- office365/security/audit/core_root.py +26 -0
- office365/security/audit/log_query.py +114 -0
- office365/security/audit/log_record.py +84 -0
- office365/security/audit/whois_record.py +22 -0
- office365/security/average_comparative_score.py +15 -0
- office365/security/base_end_user_notification.py +20 -0
- office365/security/cases/case.py +48 -0
- office365/security/cases/ediscovery.py +136 -0
- office365/security/cases/operation.py +67 -0
- office365/security/cases/root.py +21 -0
- office365/security/category_template.py +30 -0
- office365/security/certification_control.py +15 -0
- office365/security/cloud_app_security_state.py +16 -0
- office365/security/cloud_application_evidence.py +20 -0
- office365/security/cloud_clipboard_item.py +44 -0
- office365/security/cloud_clipboard_item_payload.py +15 -0
- office365/security/cloud_clipboard_root.py +28 -0
- office365/security/cloud_flare_rule_model.py +16 -0
- office365/security/cloud_flare_ruleset_model.py +16 -0
- office365/security/cloud_flare_verified_details_model.py +25 -0
- office365/security/cloud_flare_web_application_firewall_provider.py +21 -0
- office365/security/cloud_logon_session_evidence.py +24 -0
- office365/security/cloud_pc_audit_event.py +105 -0
- office365/security/cloud_pc_device_image.py +95 -0
- office365/security/cloud_pc_gallery_image.py +71 -0
- office365/security/cloud_pc_on_premises_connection.py +130 -0
- office365/security/cloud_pc_provisioning_policy.py +156 -0
- office365/security/cloud_pc_provisioning_policy_assignment.py +38 -0
- office365/security/cloud_pc_report.py +9 -0
- office365/security/cloud_pc_service_plan.py +31 -0
- office365/security/cloud_pc_user_setting.py +70 -0
- office365/security/cloud_pc_user_setting_assignment.py +31 -0
- office365/security/cloud_video_interop_info.py +16 -0
- office365/security/coachmark_location.py +18 -0
- office365/security/coachmark_location_type.py +17 -0
- office365/security/collaboration/analyzed_email.py +288 -0
- office365/security/collaboration/analyzed_email_attachment.py +25 -0
- office365/security/collaboration/analyzed_email_delivery_detail.py +21 -0
- office365/security/collaboration/analyzed_email_url.py +21 -0
- office365/security/collaboration/collaboration_root.py +22 -0
- office365/security/compromise_indicator.py +17 -0
- office365/security/container_evidence.py +28 -0
- office365/security/container_image_evidence.py +17 -0
- office365/security/control_score.py +17 -0
- office365/security/custom_training_setting.py +24 -0
- office365/security/cvss_summary.py +18 -0
- office365/security/data_source_container.py +44 -0
- office365/security/datasecurity/classifcation_error_base.py +18 -0
- office365/security/datasecurity/classification_error.py +20 -0
- office365/security/datasecurity/compute_rights_and_inheritance_result.py +25 -0
- office365/security/datasecurity/governance.py +42 -0
- office365/security/datasecurity/label_content_right.py +44 -0
- office365/security/datasecurity/policy_scope_base.py +26 -0
- office365/security/datasecurity/policy_tenant_scope.py +15 -0
- office365/security/datasecurity/policy_tip_action.py +16 -0
- office365/security/datasecurity/process_content_batch_request.py +17 -0
- office365/security/datasecurity/process_content_request.py +33 -0
- office365/security/datasecurity/process_content_response.py +24 -0
- office365/security/datasecurity/process_content_responses.py +16 -0
- office365/security/datasecurity/process_file_metadata.py +16 -0
- office365/security/datasecurity/sensitivity_label.py +117 -0
- office365/security/datasecurity/tenant_protection_scope_container.py +9 -0
- office365/security/datasecurity/usage_rights_included.py +31 -0
- office365/security/detonation_details.py +34 -0
- office365/security/device_evidence.py +50 -0
- office365/security/dns_evidence.py +19 -0
- office365/security/ediscovery/case_member.py +31 -0
- office365/security/ediscovery/custodian.py +76 -0
- office365/security/ediscovery/ediscovery_case_settings.py +57 -0
- office365/security/ediscovery/ediscovery_estimate_operation.py +66 -0
- office365/security/ediscovery/ediscovery_export_operation.py +64 -0
- office365/security/ediscovery/ediscovery_index_operation.py +8 -0
- office365/security/ediscovery/ediscovery_review_tag.py +34 -0
- office365/security/ediscovery/noncustodial_data_source.py +37 -0
- office365/security/ediscovery/operations/add_to_review_set.py +64 -0
- office365/security/ediscovery/operations/hold.py +8 -0
- office365/security/ediscovery/operations/hold_policy_sync.py +23 -0
- office365/security/ediscovery/operations/purge_data.py +23 -0
- office365/security/ediscovery/operations/search_export.py +83 -0
- office365/security/ediscovery/operations/tag.py +8 -0
- office365/security/ediscovery/review_set.py +22 -0
- office365/security/ediscovery/review_set_query.py +9 -0
- office365/security/ediscovery/search.py +81 -0
- office365/security/email_payload_detail.py +17 -0
- office365/security/end_user_notification.py +110 -0
- office365/security/end_user_notification_detail.py +46 -0
- office365/security/end_user_notification_setting.py +25 -0
- office365/security/evaluation_outcome.py +13 -0
- office365/security/event_propagation_result.py +18 -0
- office365/security/event_query.py +17 -0
- office365/security/expression_input_object.py +20 -0
- office365/security/file_evidence.py +18 -0
- office365/security/file_hash.py +21 -0
- office365/security/file_hash_evidence.py +16 -0
- office365/security/file_plan_applied_category.py +15 -0
- office365/security/filter.py +22 -0
- office365/security/filter_clause.py +17 -0
- office365/security/filter_group.py +17 -0
- office365/security/formatted_content.py +16 -0
- office365/security/google_cloud_resource_evidence.py +21 -0
- office365/security/health_issue.py +81 -0
- office365/security/host_security_state.py +22 -0
- office365/security/host_ssl_certificate_port.py +17 -0
- office365/security/hunting_query_results.py +24 -0
- office365/security/hyperlink.py +15 -0
- office365/security/identity_accounts.py +47 -0
- office365/security/identity_provider.py +14 -0
- office365/security/incidents/incident.py +148 -0
- office365/security/include_all_account_target_content.py +10 -0
- office365/security/indicator.py +28 -0
- office365/security/intelligence_profile_country_or_region_of_origin.py +15 -0
- office365/security/investigation_security_state.py +15 -0
- office365/security/invoke_action_result.py +24 -0
- office365/security/io_t_device_evidence.py +46 -0
- office365/security/ip_address.py +36 -0
- office365/security/ip_evidence.py +19 -0
- office365/security/kubernetes_cluster_evidence.py +20 -0
- office365/security/kubernetes_controller_evidence.py +19 -0
- office365/security/kubernetes_namespace_evidence.py +18 -0
- office365/security/kubernetes_pod_evidence.py +38 -0
- office365/security/kubernetes_secret_evidence.py +17 -0
- office365/security/kubernetes_service_account_evidence.py +16 -0
- office365/security/kubernetes_service_evidence.py +29 -0
- office365/security/kubernetes_service_port.py +20 -0
- office365/security/labels/retention/collection.py +68 -0
- office365/security/labels/retention/duration_in_days.py +16 -0
- office365/security/labels/retention/fileplan_descriptor.py +104 -0
- office365/security/labels/retention/label.py +158 -0
- office365/security/labels/root.py +83 -0
- office365/security/mailbox_configuration_evidence.py +24 -0
- office365/security/mailbox_evidence.py +18 -0
- office365/security/malware_state.py +18 -0
- office365/security/message_security_state.py +23 -0
- office365/security/microsoft_custom_training_setting.py +26 -0
- office365/security/microsoft_managed_training_setting.py +19 -0
- office365/security/microsoft_training_assignment_mapping.py +26 -0
- office365/security/monitor_mode.py +12 -0
- office365/security/monitor_run_status.py +14 -0
- office365/security/monitor_status.py +13 -0
- office365/security/network_connection.py +39 -0
- office365/security/network_connection_evidence.py +21 -0
- office365/security/nic_evidence.py +18 -0
- office365/security/no_training_notification_setting.py +15 -0
- office365/security/no_training_setting.py +10 -0
- office365/security/object_definition.py +25 -0
- office365/security/object_mapping.py +33 -0
- office365/security/parse_expression_response.py +22 -0
- office365/security/passive_dns_record.py +47 -0
- office365/security/payload_coachmark.py +21 -0
- office365/security/payload_detail.py +20 -0
- office365/security/positive_reinforcement_notification.py +17 -0
- office365/security/process.py +28 -0
- office365/security/processing_error.py +15 -0
- office365/security/registry_key_state.py +26 -0
- office365/security/retention_event.py +98 -0
- office365/security/retention_event_status.py +18 -0
- office365/security/scorecontrol/profile.py +135 -0
- office365/security/search/data_source.py +34 -0
- office365/security/securescores/control_state_update.py +19 -0
- office365/security/securescores/secure_score.py +85 -0
- office365/security/security.py +208 -0
- office365/security/security_resource.py +18 -0
- office365/security/security_vendor_information.py +17 -0
- office365/security/sensitivity_label_info.py +18 -0
- office365/security/sensor.py +84 -0
- office365/security/sensor_candidate_activation_configuration.py +25 -0
- office365/security/sensor_settings.py +20 -0
- office365/security/service_principal_evidence.py +21 -0
- office365/security/settings_container.py +19 -0
- office365/security/simulation_event.py +15 -0
- office365/security/simulation_events_content.py +19 -0
- office365/security/simulation_notification.py +17 -0
- office365/security/ssl_certificate.py +78 -0
- office365/security/ssl_certificate_entity.py +24 -0
- office365/security/string_key_attribute_mapping_source_value_pair.py +19 -0
- office365/security/subdomain.py +26 -0
- office365/security/subjectrightsrequests/authored_note.py +35 -0
- office365/security/subjectrightsrequests/request.py +255 -0
- office365/security/teams_message_evidence.py +52 -0
- office365/security/threatintelligence/host.py +138 -0
- office365/security/threatintelligence/host_component.py +46 -0
- office365/security/threatintelligence/host_cookie.py +41 -0
- office365/security/threatintelligence/host_logon_session_evidence.py +21 -0
- office365/security/threatintelligence/host_pair.py +41 -0
- office365/security/threatintelligence/host_port.py +106 -0
- office365/security/threatintelligence/host_port_component.py +19 -0
- office365/security/threatintelligence/host_reputation.py +43 -0
- office365/security/threatintelligence/host_reputation_rule.py +21 -0
- office365/security/threatintelligence/host_ssl_certificate.py +56 -0
- office365/security/threatintelligence/host_tracker.py +41 -0
- office365/security/threatintelligence/hosts/__init__.py +0 -0
- office365/security/threatintelligence/profile.py +114 -0
- office365/security/threatintelligence/threat_intelligence.py +193 -0
- office365/security/threatintelligence/whoisrecords/__init__.py +0 -0
- office365/security/timeline_event.py +25 -0
- office365/security/training.py +123 -0
- office365/security/training_language_detail.py +64 -0
- office365/security/training_notification_setting.py +21 -0
- office365/security/training_reminder_notification.py +17 -0
- office365/security/training_setting.py +17 -0
- office365/security/triggers/__init__.py +0 -0
- office365/security/triggers/root.py +30 -0
- office365/security/triggertypes/__init__.py +0 -0
- office365/security/triggertypes/collection.py +9 -0
- office365/security/triggertypes/root.py +24 -0
- office365/security/unified_group_source.py +22 -0
- office365/security/uri_click_security_state.py +20 -0
- office365/security/user.py +21 -0
- office365/security/user_account.py +27 -0
- office365/security/user_evidence.py +17 -0
- office365/security/user_security_state.py +33 -0
- office365/security/user_simulation_event_info.py +21 -0
- office365/security/user_source.py +27 -0
- office365/security/user_training_content_event_info.py +19 -0
- office365/security/user_training_event_info.py +22 -0
- office365/security/user_training_status_info.py +19 -0
- office365/security/vm_metadata.py +19 -0
- office365/security/vulnerability.py +134 -0
- office365/security/vulnerability_state.py +16 -0
- office365/security/whois_base_record.py +127 -0
- office365/security/whois_contact.py +20 -0
- office365/security/whois_nameserver.py +21 -0
- office365/sharepoint/activities/entity.py +9 -0
- office365/sharepoint/administration/sitemove/service.py +70 -1
- office365/sharepoint/administration/sitemove/systemsitelockexpres.py +8 -0
- office365/sharepoint/administration/spfarm.py +8 -0
- office365/sharepoint/administration/spwebapplication.py +23 -1
- office365/sharepoint/alerts/alert.py +10 -0
- office365/sharepoint/approvals/approvals.py +262 -1
- office365/sharepoint/apps/access_requests.py +69 -0
- office365/sharepoint/apps/appsitecontextutility.py +23 -1
- office365/sharepoint/apps/apptileproperties.py +63 -1
- office365/sharepoint/apps/catalog.py +18 -1
- office365/sharepoint/apps/instance.py +87 -1
- office365/sharepoint/apps/management/principal_info.py +19 -0
- office365/sharepoint/attachments/collection.py +20 -3
- office365/sharepoint/audit/audit.py +5 -0
- office365/sharepoint/authentication/auth_file_detonation_request_response.py +15 -0
- office365/sharepoint/authpolicy/events/event.py +20 -0
- office365/sharepoint/authpolicy/tenant/compliancereportentitydata.py +12 -0
- office365/sharepoint/automations/automations.py +682 -1
- office365/sharepoint/brandcenter/brand_center.py +277 -0
- office365/sharepoint/businessdata/infrastructure/securestore/connection_settings.py +42 -0
- office365/sharepoint/businessdata/runtime/subscription.py +16 -0
- office365/sharepoint/changes/alert.py +8 -0
- office365/sharepoint/changes/app_consent_principal.py +9 -0
- office365/sharepoint/changes/change.py +7 -0
- office365/sharepoint/changes/content_type.py +8 -0
- office365/sharepoint/changes/field.py +8 -0
- office365/sharepoint/changes/folder.py +7 -0
- office365/sharepoint/changes/item.py +53 -0
- office365/sharepoint/changes/list.py +23 -0
- office365/sharepoint/client_context.py +291 -14
- office365/sharepoint/clientsidecomponent/hostedapps/add_response.py +19 -0
- office365/sharepoint/comments/collection.py +1 -1
- office365/sharepoint/comments/comment.py +20 -1
- office365/sharepoint/compliance/sensitivitylabels/collection.py +33 -1
- office365/sharepoint/content_solution.py +18 -0
- office365/sharepoint/contentcenter/machinelearning/enabled.py +37 -0
- office365/sharepoint/contentcenter/machinelearning/hub.py +57 -8
- office365/sharepoint/contentcenter/machinelearning/models/entity_data.py +135 -0
- office365/sharepoint/contentcenter/machinelearning/models/model.py +20 -2
- office365/sharepoint/contentcenter/machinelearning/publications/publication.py +7 -0
- office365/sharepoint/contentcenter/machinelearning/samples/entity_data.py +14 -0
- office365/sharepoint/contentcenter/machinelearning/samples/sample.py +7 -0
- office365/sharepoint/contentcenter/machinelearning/workitementitydata.py +7 -0
- office365/sharepoint/contentcenter/machinelearning/workitems/item.py +12 -0
- office365/sharepoint/contentcenter/sp_machine_learning_column_autofill_prompt_suggestion_data.py +25 -0
- office365/sharepoint/contentcenter/sp_machine_learning_column_autofill_prompt_suggestion_result.py +16 -0
- office365/sharepoint/contentcenter/sp_machine_learning_publications_result.py +38 -0
- office365/sharepoint/contentcenter/syntex_models_landing_info.py +24 -0
- office365/sharepoint/contentcenter/template_api.py +195 -0
- office365/sharepoint/contenttypes/collection.py +54 -17
- office365/sharepoint/contenttypes/content_type.py +146 -5
- office365/sharepoint/contenttypes/content_type_id.py +4 -0
- office365/sharepoint/contenttypes/fieldlinks/collection.py +1 -2
- office365/sharepoint/contenttypes/fieldlinks/field_link.py +20 -2
- office365/sharepoint/directory/conditional_access_policy.py +5 -0
- office365/sharepoint/directory/group_and_user_status.py +9 -0
- office365/sharepoint/directory/groups/group.py +203 -6
- office365/sharepoint/directory/link.py +20 -0
- office365/sharepoint/directory/members_info.py +15 -0
- office365/sharepoint/directory/multi_conditional_access_policy.py +5 -0
- office365/sharepoint/directory/my_groups_result.py +14 -0
- office365/sharepoint/directory/named_location_policy.py +15 -0
- office365/sharepoint/directory/provider/provider.py +141 -0
- office365/sharepoint/directory/session.py +69 -0
- office365/sharepoint/directory/tenant.py +37 -0
- office365/sharepoint/directory/users/user.py +343 -1
- office365/sharepoint/document_location.py +14 -0
- office365/sharepoint/documentmanagement/document_set.py +4 -0
- office365/sharepoint/dynamiccontent/updateservice.py +207 -1
- office365/sharepoint/edgeworth_settings_apis.py +7 -0
- office365/sharepoint/engage_distribution_info.py +17 -0
- office365/sharepoint/entity.py +94 -4
- office365/sharepoint/entity_collection.py +86 -1
- office365/sharepoint/esign/digitallegacy.py +371 -1
- office365/sharepoint/eventreceivers/definition.py +26 -0
- office365/sharepoint/exceptions.py +2 -2
- office365/sharepoint/features/definitions/definition.py +8 -0
- office365/sharepoint/fields/calculated.py +22 -0
- office365/sharepoint/fields/choice.py +9 -0
- office365/sharepoint/fields/collection.py +61 -7
- office365/sharepoint/fields/field.py +109 -1
- office365/sharepoint/fields/lookup.py +23 -0
- office365/sharepoint/fields/multi_choice.py +7 -0
- office365/sharepoint/fields/multi_line_text.py +12 -0
- office365/sharepoint/fields/number.py +27 -0
- office365/sharepoint/fields/rating_scale.py +27 -0
- office365/sharepoint/fields/user.py +12 -0
- office365/sharepoint/files/checked_out_file.py +15 -0
- office365/sharepoint/files/collection.py +269 -38
- office365/sharepoint/files/download_operation.py +466 -0
- office365/sharepoint/files/file.py +648 -20
- office365/sharepoint/files/file_url_virus_status_update.py +9 -0
- office365/sharepoint/files/publish/status.py +43 -0
- office365/sharepoint/files/recent_file_collection.py +41 -0
- office365/sharepoint/files/versions/collection.py +1 -1
- office365/sharepoint/files/versions/event.py +13 -0
- office365/sharepoint/flows/synchronization_result.py +13 -1
- office365/sharepoint/folders/coloring.py +29 -0
- office365/sharepoint/folders/folder.py +173 -4
- office365/sharepoint/fontpackages/font_package.py +69 -0
- office365/sharepoint/fontpackages/oob_settings.py +47 -0
- office365/sharepoint/forms/form.py +8 -0
- office365/sharepoint/glutamate_controller.py +44 -0
- office365/sharepoint/htmlpages/html_page.py +5 -0
- office365/sharepoint/item_reference.py +17 -0
- office365/sharepoint/largeoperation/operation.py +26 -1
- office365/sharepoint/likes/user_entity.py +7 -0
- office365/sharepoint/list_creation_information.py +18 -0
- office365/sharepoint/listhome/favorite_lists.py +130 -0
- office365/sharepoint/listhome/favoritelistssubstrate.py +106 -1
- office365/sharepoint/listhome/item2.py +7 -0
- office365/sharepoint/listhome/itemcollection.py +99 -1
- office365/sharepoint/listhome/nucleus.py +15 -1
- office365/sharepoint/listhome/pinneditems.py +127 -1
- office365/sharepoint/listitems/collection.py +28 -0
- office365/sharepoint/listitems/favoriteitemcollection.py +20 -1
- office365/sharepoint/listitems/listitem.py +529 -27
- office365/sharepoint/listitems/relateditemmanager.py +231 -1
- office365/sharepoint/listitems/versions/version.py +9 -0
- office365/sharepoint/lists/apihubconnector.py +18 -1
- office365/sharepoint/lists/batch_convert_asset_result.py +19 -0
- office365/sharepoint/lists/batch_convert_classic_wiki_page_input.py +15 -0
- office365/sharepoint/lists/batch_convert_item_result.py +21 -1
- office365/sharepoint/lists/bloom_filter.py +33 -0
- office365/sharepoint/lists/list.py +1255 -31
- office365/sharepoint/lists/list_forms_api.py +106 -1
- office365/sharepoint/lists/playlist.py +33 -1
- office365/sharepoint/lists/recentlist.py +7 -0
- office365/sharepoint/lists/recentlistcollection.py +76 -1
- office365/sharepoint/lists/recentlistproxy.py +31 -1
- office365/sharepoint/lists/recommendationcollection.py +71 -1
- office365/sharepoint/lists/smart_wiki_library_utility.py +102 -0
- office365/sharepoint/lists/smart_wiki_metadata_update_parameters.py +18 -0
- office365/sharepoint/lists/templates/template.py +58 -0
- office365/sharepoint/lists/version_policy_manager.py +14 -0
- office365/sharepoint/migrationcenter/agentgroupentity.py +24 -1
- office365/sharepoint/migrationcenter/common/task.py +8 -1
- office365/sharepoint/migrationcenter/common/task_entity_data.py +231 -0
- office365/sharepoint/migrationcenter/common/teams/channel_resource.py +7 -0
- office365/sharepoint/migrationcenter/common/teams/team_resource.py +8 -0
- office365/sharepoint/migrationcenter/common/url_parser.py +37 -1
- office365/sharepoint/migrationcenter/credentialentitydata.py +7 -0
- office365/sharepoint/migrationcenter/device.py +7 -0
- office365/sharepoint/migrationcenter/deviceentitydata.py +8 -0
- office365/sharepoint/migrationcenter/service/credential.py +21 -0
- office365/sharepoint/migrationcenter/service/services.py +77 -0
- office365/sharepoint/mount/folder_info.py +34 -0
- office365/sharepoint/mount/onedriveitemcollection.py +26 -1
- office365/sharepoint/mount/placesinformation.py +18 -1
- office365/sharepoint/multigeo/service/allowed_data_location.py +7 -0
- office365/sharepoint/multigeo/service/cross_geo_user_placement_job.py +7 -0
- office365/sharepoint/multigeo/service/df_deprecation_job.py +18 -0
- office365/sharepoint/multigeo/service/geo_tenant_instance_information.py +7 -0
- office365/sharepoint/multigeo/service/job_entity_data.py +21 -0
- office365/sharepoint/multigeo/service/microfeedpostdefinitionmanager.py +91 -0
- office365/sharepoint/multigeo/service/move_job_entity_data.py +111 -0
- office365/sharepoint/multigeo/service/move_site_ro_state.py +18 -0
- office365/sharepoint/multigeo/service/multigeoservicesbeta.py +55 -13
- office365/sharepoint/multigeo/service/sbsitemovejob.py +26 -0
- office365/sharepoint/multigeo/service/site_move_job_entity_data.py +75 -0
- office365/sharepoint/multigeo/service/storage_quota.py +49 -0
- office365/sharepoint/multigeo/service/tenant_information.py +40 -0
- office365/sharepoint/multigeo/service/unified_group.py +35 -0
- office365/sharepoint/multigeo/service/user_personal_site_location.py +30 -0
- office365/sharepoint/multigeo/service/usermovejob.py +11 -0
- office365/sharepoint/navigation/home_site_navigation_settings.py +6 -1
- office365/sharepoint/navigation/nodes/creationinformation.py +2 -0
- office365/sharepoint/navigation/nodes/node.py +22 -1
- office365/sharepoint/navigation/quickaccessitemcollection.py +37 -1
- office365/sharepoint/news/ngspdatacollection.py +24 -1
- office365/sharepoint/ngsp_ai_settings.py +32 -0
- office365/sharepoint/oauth/token_response.py +56 -0
- office365/sharepoint/onboarding/job_entity_data.py +14 -0
- office365/sharepoint/onboarding/tenantrename/job.py +7 -0
- office365/sharepoint/onboarding/tenantrename/jobentitydata.py +10 -0
- office365/sharepoint/permissions/irm/effective_settings.py +78 -0
- office365/sharepoint/permissions/irm/file_settings.py +47 -0
- office365/sharepoint/permissions/irm/settings.py +16 -0
- office365/sharepoint/permissions/roles/definitions/definition.py +12 -0
- office365/sharepoint/permissions/securable_object.py +42 -0
- office365/sharepoint/portal/groups/site_manager.py +28 -4
- office365/sharepoint/portal/home/page_context.py +4 -0
- office365/sharepoint/portal/home/service_context.py +57 -0
- office365/sharepoint/portal/organization_news.py +37 -0
- office365/sharepoint/portal/theme_manager.py +17 -0
- office365/sharepoint/portal/userprofiles/sharedwithme/document.py +267 -0
- office365/sharepoint/portal/userprofiles/sharedwithme/item_collection.py +147 -0
- office365/sharepoint/portal/webcontrols/recentdocument.py +7 -0
- office365/sharepoint/principal/groups/group.py +11 -1
- office365/sharepoint/publishing/amplify/clientamplifyanywhereresults.py +1 -1
- office365/sharepoint/publishing/amplify/historyresult.py +1 -1
- office365/sharepoint/publishing/amplify/result.py +1 -1
- office365/sharepoint/publishing/announcements/data.py +2 -2
- office365/sharepoint/publishing/approval/request.py +7 -0
- office365/sharepoint/publishing/campaign/campaign.py +9 -0
- office365/sharepoint/publishing/channelannouncement.py +3 -3
- office365/sharepoint/publishing/customproperties/base.py +19 -0
- office365/sharepoint/publishing/customproperties/bigint.py +9 -0
- office365/sharepoint/publishing/diagnostics/page_result.py +2 -2
- office365/sharepoint/publishing/embed_data_v1.py +7 -0
- office365/sharepoint/publishing/highlights_info.py +14 -0
- office365/sharepoint/publishing/pagecopywithassets/response.py +79 -0
- office365/sharepoint/publishing/pages/_3d/__init__.py +0 -0
- office365/sharepoint/publishing/pages/campaign/publication.py +104 -16
- office365/sharepoint/publishing/pages/canvas.py +81 -0
- office365/sharepoint/publishing/pages/collection.py +39 -5
- office365/sharepoint/publishing/pages/metadata.py +119 -0
- office365/sharepoint/publishing/pages/move_response.py +64 -0
- office365/sharepoint/publishing/pages/page.py +6 -0
- office365/sharepoint/publishing/pages/reposts/metadata.py +30 -0
- office365/sharepoint/publishing/person/magazineuserprofile.py +3 -3
- office365/sharepoint/publishing/personmagazine/userprofiledata.py +1 -1
- office365/sharepoint/publishing/point/site_status.py +25 -0
- office365/sharepoint/publishing/point/user.py +7 -0
- office365/sharepoint/publishing/profiledata.py +1 -1
- office365/sharepoint/publishing/profilefullproperties.py +3 -3
- office365/sharepoint/publishing/profilevieweditpolicies.py +24 -24
- office365/sharepoint/publishing/publicationmetadata.py +2 -2
- office365/sharepoint/publishing/ruleerrordetails.py +1 -1
- office365/sharepoint/publishing/ruleresult.py +1 -1
- office365/sharepoint/publishing/search.py +8 -0
- office365/sharepoint/publishing/sitepagstreamdata.py +1 -1
- office365/sharepoint/publishing/spotlightchannel.py +19 -1
- office365/sharepoint/publishing/spotlightvideo.py +19 -1
- office365/sharepoint/publishing/spstatusresponse.py +1 -1
- office365/sharepoint/publishing/statusresponse.py +2 -4
- office365/sharepoint/publishing/user_info.py +8 -0
- office365/sharepoint/publishing/validations/prepublishvalidationserrorcodesforvivaengage.py +1 -1
- office365/sharepoint/publishing/video/channel.py +7 -0
- office365/sharepoint/publishing/video/item.py +11 -2
- office365/sharepoint/publishing/video/service_manager.py +66 -2
- office365/sharepoint/pushnotifications/subscriber.py +7 -0
- office365/sharepoint/recyclebin/item.py +16 -0
- office365/sharepoint/recyclebin/item_collection.py +1 -1
- office365/sharepoint/request.py +67 -8
- office365/sharepoint/request_context.py +41 -2
- office365/sharepoint/role_definition_creation_information.py +14 -0
- office365/sharepoint/search/relevant_results.py +7 -0
- office365/sharepoint/search/result.py +6 -0
- office365/sharepoint/search/searchresultitemcollection.py +86 -1
- office365/sharepoint/search/semantic/enqueueproxy.py +68 -1
- office365/sharepoint/search/semantic/itemcollection.py +35 -1
- office365/sharepoint/search/service.py +7 -0
- office365/sharepoint/search/simple_data_row.py +8 -0
- office365/sharepoint/securestore/target_application.py +17 -0
- office365/sharepoint/securestore/target_application_field.py +19 -0
- office365/sharepoint/sharing/information.py +299 -1
- office365/sharepoint/sharing/internal/sharing_restrictions.py +30 -0
- office365/sharepoint/sharing/object_sharing_information.py +35 -0
- office365/sharepoint/sharing/object_sharing_information_user.py +67 -0
- office365/sharepoint/sharing/object_sharing_settings.py +143 -0
- office365/sharepoint/sharing/permission_information.py +22 -0
- office365/sharepoint/sharing/picker_settings.py +24 -0
- office365/sharepoint/sharing/principal_info.py +25 -0
- office365/sharepoint/sharing/shared_document_info.py +161 -0
- office365/sharepoint/sharing/sharepoint_sharing_settings.py +85 -0
- office365/sharepoint/sitedesigns/design_package_menu_contents.py +20 -0
- office365/sharepoint/sitedesigns/principal.py +19 -0
- office365/sharepoint/sitedesigns/run.py +43 -0
- office365/sharepoint/sitedesigns/userfirstrun.py +53 -0
- office365/sharepoint/sites/home/site.py +279 -0
- office365/sharepoint/sites/language_collection.py +18 -0
- office365/sharepoint/sites/migration/job_status.py +15 -0
- office365/sharepoint/sites/site.py +8 -2
- office365/sharepoint/sites/team_site_data.py +45 -0
- office365/sharepoint/sites/version_policy_manager.py +35 -0
- office365/sharepoint/smartcache/item.py +106 -0
- office365/sharepoint/social/feed/manager.py +208 -1
- office365/sharepoint/social/following/manager.py +82 -1
- office365/sharepoint/social/following/rest_manager.py +186 -0
- office365/sharepoint/social/rest_actor.py +25 -0
- office365/sharepoint/social/rest_thread.py +39 -1
- office365/sharepoint/sp_playlist_subscriber.py +12 -0
- office365/sharepoint/taxonomy/create_xml_parameters.py +5 -5
- office365/sharepoint/taxonomy/field.py +20 -19
- office365/sharepoint/taxonomy/field_value_col.py +21 -0
- office365/sharepoint/taxonomy/groups/collection.py +19 -2
- office365/sharepoint/teams/channel_manager.py +272 -0
- office365/sharepoint/tenant/administration/app_service_principal_public.py +46 -0
- office365/sharepoint/tenant/administration/audit/data.py +4 -1
- office365/sharepoint/tenant/administration/consumer_quota_info_api.py +7 -0
- office365/sharepoint/tenant/administration/container/filterorder.py +1 -1
- office365/sharepoint/tenant/administration/container/properties.py +1 -1
- office365/sharepoint/tenant/administration/container/type/configurationproperties.py +1 -1
- office365/sharepoint/tenant/administration/copilot/reportrow.py +1 -1
- office365/sharepoint/tenant/administration/dataset_metadata_request_info.py +4 -1
- office365/sharepoint/tenant/administration/dataset_metadata_response.py +9 -2
- office365/sharepoint/tenant/administration/endpoints.py +5 -0
- office365/sharepoint/tenant/administration/groups/group.py +130 -0
- office365/sharepoint/tenant/administration/hubsites/hub_site.py +62 -0
- office365/sharepoint/tenant/administration/hubsites/properties.py +53 -0
- office365/sharepoint/tenant/administration/internal/aad/permission_grant.py +39 -0
- office365/sharepoint/tenant/administration/internal/appservice/permission_request.py +7 -0
- office365/sharepoint/tenant/administration/internal/appservice/principal.py +87 -2
- office365/sharepoint/tenant/administration/licensed_onedrives_summary_metrics.py +16 -0
- office365/sharepoint/tenant/administration/malware/file.py +35 -0
- office365/sharepoint/tenant/administration/onedrives_count_and_size.py +15 -0
- office365/sharepoint/tenant/administration/onedrives_summary_metrics.py +23 -0
- office365/sharepoint/tenant/administration/onedrives_summary_metrics_by_unlicensed_reason.py +25 -0
- office365/sharepoint/tenant/administration/owner_not_in_entra_onedrives_summary_metrics.py +22 -0
- office365/sharepoint/tenant/administration/policies/content_security_configuration.py +67 -0
- office365/sharepoint/tenant/administration/settings/service.py +240 -0
- office365/sharepoint/tenant/administration/siteadministratorsfieldsdata.py +1 -1
- office365/sharepoint/tenant/administration/sites/properties.py +21 -2
- office365/sharepoint/tenant/administration/spcontenteventscustomemailproperty.py +1 -1
- office365/sharepoint/tenant/administration/spdatagovernanceinsightresponse.py +6 -6
- office365/sharepoint/tenant/administration/spofileversionfiletypepolicysettings.py +1 -1
- office365/sharepoint/tenant/administration/spom365agentinsightsm365agentsonsitesdetails.py +4 -0
- office365/sharepoint/tenant/administration/storage_usage_metrics_snapshot.py +8 -1
- office365/sharepoint/tenant/administration/syntex/applicationproperties.py +19 -3
- office365/sharepoint/tenant/administration/syntex/getmodifiedlistresponse.py +1 -1
- office365/sharepoint/tenant/administration/tenant.py +5931 -34
- office365/sharepoint/tenant/administration/theme_properties.py +24 -0
- office365/sharepoint/tenant/administration/unlicensed_onedrives_summary_metrics.py +20 -0
- office365/sharepoint/tenant/apps/instance.py +20 -0
- office365/sharepoint/tenant/apps/user_experience_state.py +25 -0
- office365/sharepoint/tenant/insights/reports/metadata.py +8 -0
- office365/sharepoint/tenant/insights/reports/report.py +23 -0
- office365/sharepoint/tenant/management/externalusers/results/remove.py +13 -0
- office365/sharepoint/tenant/management/externalusers/results/session_revocation.py +9 -0
- office365/sharepoint/tenant/management/office365_tenant.py +99 -8
- office365/sharepoint/tenantadministration/sp_data_governance_insight_rest_api_client.py +475 -0
- office365/sharepoint/tenantadministration/sp_data_governance_sar_rest_api_client.py +88 -0
- office365/sharepoint/tenantadministration/spo_copilot_agent_insights_rest_api_client.py +170 -0
- office365/sharepoint/tenantadministration/spo_file_archive_policy_info.py +4 -1
- office365/sharepoint/tenantadministration/spo_restricted_content_discoverability_client.py +90 -0
- office365/sharepoint/tenantadministration/spom365_agent_insights_rest_api_client.py +166 -0
- office365/sharepoint/tenantadministration/spom365_agent_insights_top20_agents_details.py +11 -0
- office365/sharepoint/tenantmanagement/group_creation_params.py +32 -0
- office365/sharepoint/token.py +7 -0
- office365/sharepoint/user_ai_tokens.py +56 -0
- office365/sharepoint/user_photos.py +32 -0
- office365/sharepoint/usercustomactions/action.py +109 -1
- office365/sharepoint/userprofiles/followed_content.py +164 -0
- office365/sharepoint/userprofiles/my_site_links.py +22 -0
- office365/sharepoint/userprofiles/people_manager.py +68 -0
- office365/sharepoint/userprofiles/peoplecollection.py +40 -1
- office365/sharepoint/userprofiles/person_properties.py +22 -0
- office365/sharepoint/userprofiles/personal_cache.py +246 -1
- office365/sharepoint/userprofiles/personallistsproxy.py +33 -1
- office365/sharepoint/userprofiles/profile_loader.py +16 -0
- office365/sharepoint/userprofiles/user_profile.py +42 -0
- office365/sharepoint/utilities/file_handler_wopi_properties.py +24 -0
- office365/sharepoint/utilities/move_copy_util.py +44 -3
- office365/sharepoint/utilities/upload_status.py +8 -0
- office365/sharepoint/views/collection.py +135 -14
- office365/sharepoint/views/field_collection.py +3 -0
- office365/sharepoint/views/view.py +113 -0
- office365/sharepoint/viva/app_configuration.py +34 -1
- office365/sharepoint/viva/employee_engagement.py +149 -1
- office365/sharepoint/viva/employee_experience_controller.py +97 -1
- office365/sharepoint/viva/knowledgehub.py +37 -1
- office365/sharepoint/viva/resource_get_endpoint.py +15 -0
- office365/sharepoint/viva/visualizationappsynchronizationresult.py +18 -1
- office365/sharepoint/webhooks/subscription.py +2 -1
- office365/sharepoint/webhooks/subscription_information.py +2 -1
- office365/sharepoint/webparts/client/webpart.py +15 -0
- office365/sharepoint/webparts/webpart.py +12 -0
- office365/sharepoint/webs/apptile.py +12 -0
- office365/sharepoint/webs/dataleakage_prevention_status_info.py +24 -0
- office365/sharepoint/webs/information.py +7 -0
- office365/sharepoint/webs/modernize_homepage_result.py +7 -0
- office365/sharepoint/webs/multilingual_settings.py +19 -1
- office365/sharepoint/webs/regional_settings.py +27 -0
- office365/sharepoint/webs/remote_web.py +19 -0
- office365/sharepoint/webs/templates/template.py +7 -0
- office365/sharepoint/webs/theme_info.py +14 -0
- office365/sharepoint/webs/time_zone.py +15 -0
- office365/sharepoint/webs/web.py +1167 -0
- office365/sharepoint/workflow/associations/association.py +5 -0
- office365/sharepoint/workflow/instances/instance.py +41 -0
- office365/sharepoint/workflow/sp_workflow_task.py +618 -0
- office365/sharepoint/workflow/template.py +8 -0
- office365/subscriptions/change_notification.py +17 -0
- office365/subscriptions/change_notification_collection.py +4 -0
- office365/subscriptions/changetype.py +4 -0
- office365/subscriptions/collection.py +10 -6
- office365/subscriptions/subscription.py +57 -1
- office365/teams/admin_root.py +35 -0
- office365/teams/administration/number_assignment.py +74 -1
- office365/teams/administration/telephone_number_long_running_operation.py +26 -3
- office365/teams/administration/telephone_number_management_root.py +20 -1
- office365/teams/administration/user_configuration.py +17 -3
- office365/teams/app_settings.py +21 -0
- office365/teams/apps/app.py +11 -0
- office365/teams/apps/definition.py +38 -0
- office365/teams/apps/installation.py +18 -0
- office365/teams/assignment_type.py +13 -0
- office365/teams/channels/channel.py +59 -0
- office365/teams/channels/collection.py +62 -2
- office365/teams/channels/layout_type.py +13 -0
- office365/teams/channels/shared_team_info.py +6 -0
- office365/teams/chats/chat.py +40 -1
- office365/teams/chats/collection.py +11 -3
- office365/teams/chats/deleted.py +6 -0
- office365/teams/chats/messages/info.py +42 -0
- office365/teams/chats/messages/message.py +39 -1
- office365/teams/chats/pinned_chat_message_info.py +22 -0
- office365/teams/chats/targeted_chat_message.py +21 -0
- office365/teams/collection.py +76 -11
- office365/teams/community_privacy.py +13 -0
- office365/teams/info.py +11 -0
- office365/teams/members/aad_user_conversation.py +16 -0
- office365/teams/members/conversation.py +17 -0
- office365/teams/members/conversation_collection.py +19 -7
- office365/teams/migration_mode.py +13 -0
- office365/teams/operations/async_operation.py +102 -1
- office365/teams/operations/asyncoperationtype.py +10 -15
- office365/teams/policy_assignment.py +23 -0
- office365/teams/schedule/change_request.py +47 -1
- office365/teams/schedule/day_note.py +34 -0
- office365/teams/schedule/groups/group.py +16 -0
- office365/teams/schedule/open_shift.py +38 -0
- office365/teams/schedule/schedule.py +165 -0
- office365/teams/schedule/shifts/open/change_request.py +13 -0
- office365/teams/schedule/shifts/shift.py +23 -0
- office365/teams/schedule/swap_shifts_change_request.py +16 -0
- office365/teams/schedule/time_card.py +74 -0
- office365/teams/schedule/time_off.py +38 -0
- office365/teams/schedule/time_off_reason.py +35 -0
- office365/teams/schedule/time_off_request.py +27 -0
- office365/teams/tabs/tab.py +18 -0
- office365/teams/team.py +2 -2
- office365/teams/teamwork/shiftmanagement/user_solution_root.py +22 -0
- office365/teams/teamwork/shiftmanagement/working_time_schedule.py +9 -0
- office365/teams/teamwork/tags/member.py +16 -0
- office365/teams/telephone_number_long_running_operation_details.py +19 -0
- office365/teams/viva/community.py +42 -1
- office365/teams/viva/employee_experience.py +34 -0
- office365/teams/viva/employee_experience_user.py +19 -0
- office365/teams/viva/engagement/conversation.py +33 -1
- office365/teams/viva/engagement/conversation_message.py +112 -0
- office365/teams/viva/engagement/conversation_message_reaction.py +41 -0
- office365/teams/viva/learning/content.py +111 -0
- office365/teams/viva/learning/courses/activity.py +49 -0
- office365/teams/viva/learning/courses/self_initiated.py +14 -0
- office365/teams/viva/learning/provider.py +60 -0
- office365/teams/work.py +43 -1
- office365/teams/workforce_integration.py +69 -0
- office365/teams/workforce_integration_encryption.py +18 -0
- office365/todo/attachments/base.py +11 -0
- office365/todo/attachments/session.py +4 -0
- office365/todo/attachments/task_file.py +2 -5
- office365/todo/attachments/type.py +7 -10
- office365/todo/linked_resource.py +6 -0
- office365/todo/tasks/collection.py +45 -0
- office365/todo/tasks/lists/collection.py +1 -0
- office365/todo/tasks/task.py +1 -1
- office365_rest_python_client-3.3.0.dist-info/METADATA +635 -0
- office365_rest_python_client-3.3.0.dist-info/RECORD +6789 -0
- office365/directory/identitygovernance/workflow.py +0 -106
- office365/directory/identitygovernance/workflow_setting.py +0 -15
- office365/directory/identitygovernance/workflow_version.py +0 -16
- office365/directory/security/account.py +0 -14
- office365/directory/security/ai_agent_evidence.py +0 -16
- office365/directory/security/alerts/alert.py +0 -205
- office365/directory/security/alerts/blob_container_evidence.py +0 -17
- office365/directory/security/alerts/blob_evidence.py +0 -21
- office365/directory/security/alerts/collection.py +0 -49
- office365/directory/security/alerts/evidence.py +0 -28
- office365/directory/security/alerts/file_security_state.py +0 -5
- office365/directory/security/alerts/history_state.py +0 -16
- office365/directory/security/alerts/malware_evidence.py +0 -22
- office365/directory/security/alerts/process_evidence.py +0 -27
- office365/directory/security/alerts/sas_token_evidence.py +0 -26
- office365/directory/security/analyzed_message_evidence.py +0 -34
- office365/directory/security/article.py +0 -56
- office365/directory/security/attacksimulations/automation.py +0 -30
- office365/directory/security/attacksimulations/automation_run.py +0 -12
- office365/directory/security/attacksimulations/event.py +0 -5
- office365/directory/security/attacksimulations/landing_page.py +0 -5
- office365/directory/security/attacksimulations/operations/operation.py +0 -5
- office365/directory/security/attacksimulations/repeat_offender.py +0 -17
- office365/directory/security/attacksimulations/reports/overview.py +0 -5
- office365/directory/security/attacksimulations/reports/report.py +0 -25
- office365/directory/security/attacksimulations/root.py +0 -67
- office365/directory/security/attacksimulations/simulation.py +0 -23
- office365/directory/security/attacksimulations/user.py +0 -16
- office365/directory/security/attacksimulations/user_coverage.py +0 -10
- office365/directory/security/attacksimulations/users/details.py +0 -20
- office365/directory/security/attribute_definition.py +0 -37
- office365/directory/security/attribute_mapping.py +0 -23
- office365/directory/security/attribute_mapping_source.py +0 -24
- office365/directory/security/audit/audit_data.py +0 -15
- office365/directory/security/audit/whois_record.py +0 -22
- office365/directory/security/cases/case.py +0 -48
- office365/directory/security/cases/ediscovery.py +0 -99
- office365/directory/security/cases/operation.py +0 -10
- office365/directory/security/cases/root.py +0 -21
- office365/directory/security/category_template.py +0 -9
- office365/directory/security/cloud_application_evidence.py +0 -20
- office365/directory/security/cloud_logon_session_evidence.py +0 -24
- office365/directory/security/collaboration/analyzed_email.py +0 -133
- office365/directory/security/collaboration/analyzed_email_attachment.py +0 -22
- office365/directory/security/collaboration/analyzed_email_delivery_detail.py +0 -15
- office365/directory/security/collaboration/analyzed_email_url.py +0 -18
- office365/directory/security/collaboration/collaboration_root.py +0 -22
- office365/directory/security/compromise_indicator.py +0 -14
- office365/directory/security/container_evidence.py +0 -23
- office365/directory/security/container_image_evidence.py +0 -17
- office365/directory/security/cvss_summary.py +0 -15
- office365/directory/security/data_source_container.py +0 -44
- office365/directory/security/datasecurity/classifcation_error_base.py +0 -18
- office365/directory/security/datasecurity/classification_error.py +0 -12
- office365/directory/security/datasecurity/compute_rights_and_inheritance_result.py +0 -17
- office365/directory/security/datasecurity/governance.py +0 -17
- office365/directory/security/datasecurity/policy_scope_base.py +0 -26
- office365/directory/security/datasecurity/policy_tenant_scope.py +0 -15
- office365/directory/security/datasecurity/policy_tip_action.py +0 -16
- office365/directory/security/datasecurity/process_content_batch_request.py +0 -17
- office365/directory/security/datasecurity/process_content_request.py +0 -24
- office365/directory/security/datasecurity/process_content_response.py +0 -24
- office365/directory/security/datasecurity/process_content_responses.py +0 -16
- office365/directory/security/datasecurity/process_file_metadata.py +0 -16
- office365/directory/security/datasecurity/sensitivity_label.py +0 -31
- office365/directory/security/detonation_details.py +0 -34
- office365/directory/security/device_evidence.py +0 -47
- office365/directory/security/dns_evidence.py +0 -19
- office365/directory/security/ediscovery/case_member.py +0 -21
- office365/directory/security/ediscovery/custodian.py +0 -22
- office365/directory/security/ediscovery/ediscovery_case_settings.py +0 -21
- office365/directory/security/ediscovery/ediscovery_estimate_operation.py +0 -54
- office365/directory/security/ediscovery/ediscovery_export_operation.py +0 -50
- office365/directory/security/ediscovery/ediscovery_index_operation.py +0 -7
- office365/directory/security/ediscovery/ediscovery_review_tag.py +0 -34
- office365/directory/security/ediscovery/noncustodial_data_source.py +0 -18
- office365/directory/security/ediscovery/operations/add_to_review_set.py +0 -52
- office365/directory/security/ediscovery/operations/hold.py +0 -7
- office365/directory/security/ediscovery/operations/hold_policy_sync.py +0 -9
- office365/directory/security/ediscovery/operations/purge_data.py +0 -9
- office365/directory/security/ediscovery/operations/search_export.py +0 -62
- office365/directory/security/ediscovery/operations/tag.py +0 -7
- office365/directory/security/ediscovery/review_set.py +0 -22
- office365/directory/security/ediscovery/review_set_query.py +0 -7
- office365/directory/security/ediscovery/search.py +0 -56
- office365/directory/security/event_propagation_result.py +0 -18
- office365/directory/security/event_query.py +0 -14
- office365/directory/security/expression_input_object.py +0 -20
- office365/directory/security/file_evidence.py +0 -18
- office365/directory/security/file_hash.py +0 -16
- office365/directory/security/file_hash_evidence.py +0 -16
- office365/directory/security/file_plan_applied_category.py +0 -15
- office365/directory/security/filter.py +0 -22
- office365/directory/security/filter_clause.py +0 -17
- office365/directory/security/filter_group.py +0 -17
- office365/directory/security/formatted_content.py +0 -16
- office365/directory/security/google_cloud_resource_evidence.py +0 -21
- office365/directory/security/health_issue.py +0 -81
- office365/directory/security/hunting_query_results.py +0 -24
- office365/directory/security/identity_accounts.py +0 -36
- office365/directory/security/incidents/incident.py +0 -59
- office365/directory/security/indicator.py +0 -16
- office365/directory/security/invoke_action_result.py +0 -15
- office365/directory/security/io_t_device_evidence.py +0 -46
- office365/directory/security/ip_address.py +0 -26
- office365/directory/security/ip_evidence.py +0 -19
- office365/directory/security/kubernetes_cluster_evidence.py +0 -20
- office365/directory/security/kubernetes_controller_evidence.py +0 -19
- office365/directory/security/kubernetes_namespace_evidence.py +0 -18
- office365/directory/security/kubernetes_pod_evidence.py +0 -35
- office365/directory/security/kubernetes_secret_evidence.py +0 -17
- office365/directory/security/kubernetes_service_account_evidence.py +0 -16
- office365/directory/security/kubernetes_service_evidence.py +0 -29
- office365/directory/security/kubernetes_service_port.py +0 -20
- office365/directory/security/labels/retention/collection.py +0 -68
- office365/directory/security/labels/retention/duration_in_days.py +0 -16
- office365/directory/security/labels/retention/fileplan_descriptor.py +0 -52
- office365/directory/security/labels/retention/label.py +0 -158
- office365/directory/security/labels/root.py +0 -83
- office365/directory/security/mailbox_configuration_evidence.py +0 -19
- office365/directory/security/mailbox_evidence.py +0 -18
- office365/directory/security/network_connection_evidence.py +0 -18
- office365/directory/security/nic_evidence.py +0 -18
- office365/directory/security/object_definition.py +0 -25
- office365/directory/security/object_mapping.py +0 -26
- office365/directory/security/parse_expression_response.py +0 -19
- office365/directory/security/passive_dns_record.py +0 -47
- office365/directory/security/processing_error.py +0 -15
- office365/directory/security/retention_event.py +0 -63
- office365/directory/security/retention_event_status.py +0 -15
- office365/directory/security/scorecontrol/profile.py +0 -23
- office365/directory/security/search/data_source.py +0 -34
- office365/directory/security/security.py +0 -157
- office365/directory/security/sensor.py +0 -72
- office365/directory/security/sensor_candidate_activation_configuration.py +0 -9
- office365/directory/security/sensor_settings.py +0 -20
- office365/directory/security/service_principal_evidence.py +0 -18
- office365/directory/security/settings_container.py +0 -19
- office365/directory/security/ssl_certificate.py +0 -59
- office365/directory/security/string_key_attribute_mapping_source_value_pair.py +0 -16
- office365/directory/security/subdomain.py +0 -26
- office365/directory/security/subjectrightsrequests/identity.py +0 -21
- office365/directory/security/subjectrightsrequests/request.py +0 -6
- office365/directory/security/teams_message_evidence.py +0 -42
- office365/directory/security/threatintelligence/host.py +0 -138
- office365/directory/security/threatintelligence/host_component.py +0 -46
- office365/directory/security/threatintelligence/host_cookie.py +0 -41
- office365/directory/security/threatintelligence/host_logon_session_evidence.py +0 -21
- office365/directory/security/threatintelligence/host_pair.py +0 -41
- office365/directory/security/threatintelligence/host_port.py +0 -57
- office365/directory/security/threatintelligence/host_port_component.py +0 -19
- office365/directory/security/threatintelligence/host_reputation.py +0 -16
- office365/directory/security/threatintelligence/host_reputation_rule.py +0 -16
- office365/directory/security/threatintelligence/host_ssl_certificate.py +0 -43
- office365/directory/security/threatintelligence/host_tracker.py +0 -41
- office365/directory/security/threatintelligence/profile.py +0 -56
- office365/directory/security/threatintelligence/threat_intelligence.py +0 -193
- office365/directory/security/timeline_event.py +0 -19
- office365/directory/security/triggers/root.py +0 -9
- office365/directory/security/triggertypes/collection.py +0 -9
- office365/directory/security/triggertypes/root.py +0 -24
- office365/directory/security/unified_group_source.py +0 -22
- office365/directory/security/user_account.py +0 -27
- office365/directory/security/user_evidence.py +0 -17
- office365/directory/security/user_source.py +0 -27
- office365/directory/security/vm_metadata.py +0 -16
- office365/directory/security/vulnerability.py +0 -74
- office365/directory/security/whois_base_record.py +0 -59
- office365/externalconnectors/binary_operator.py +0 -12
- office365/externalconnectors/connection_operation_status.py +0 -15
- office365/externalconnectors/connection_state.py +0 -15
- office365/externalconnectors/external_activity_type.py +0 -15
- office365/externalconnectors/external_item_content_type.py +0 -13
- office365/intune/brand.py +0 -21
- office365/intune/instance_resource_access.py +0 -19
- office365/intune/provisioned_plan.py +0 -27
- office365/onenote/operations/operation.py +0 -35
- office365/partners/billing/billing_period.py +0 -13
- office365/partners/billing/billing_reconciliation.py +0 -26
- office365/sharepoint/contentcenter/machinelearning/publications/result.py +0 -9
- office365/sharepoint/customactions/action.py +0 -36
- office365_rest_python_client-3.2.0.dist-info/METADATA +0 -605
- office365_rest_python_client-3.2.0.dist-info/RECORD +0 -5743
- /office365/{directory/security → common}/__init__.py +0 -0
- /office365/{intune → directory/applications}/resource_permission.py +0 -0
- /office365/directory/identitygovernance/{workflow_subject.py → workflow/subject.py} +0 -0
- /office365/{directory/security/alerts → intune/devices/configurations/macos}/__init__.py +0 -0
- /office365/{directory/security/attacksimulations → intune/devices/configurations/windows/_10}/__init__.py +0 -0
- /office365/intune/devices/configurations/windows/{10 → _10}/editiontype.py +0 -0
- /office365/intune/{enrollment_state.py → devices/management/enrollment_state.py} +0 -0
- /office365/{directory/security/attacksimulations/operations → intune/devices/management/mobileapps/macos}/__init__.py +0 -0
- /office365/intune/{package_status.py → devices/management/package_status.py} +0 -0
- /office365/intune/{process_integrity_level.py → devices/management/process_integrity_level.py} +0 -0
- /office365/{directory/security/attacksimulations/reports → planner/internal}/__init__.py +0 -0
- /office365/{directory/security/attacksimulations/users → planner/internal/paths}/__init__.py +0 -0
- /office365/runtime/{decorators.py → deprecated.py} +0 -0
- /office365/{directory/security/audit → security}/__init__.py +0 -0
- /office365/{directory/security → security}/a365_ai_inference_call.py +0 -0
- /office365/{directory/security → security}/a365_ai_invoke_agent.py +0 -0
- /office365/{directory/security → security}/a365_ai_run_summary.py +0 -0
- /office365/{directory/security → security}/a365_span_outputs.py +0 -0
- /office365/{directory/security → security}/action.py +0 -0
- /office365/{directory/security → security}/aed_audit_record.py +0 -0
- /office365/{directory/security → security}/agent_admin_activity_record.py +0 -0
- /office365/{directory/security → security}/agent_setting_admin_activity.py +0 -0
- /office365/{directory/security → security}/ai_agent_platform.py +0 -0
- /office365/{directory/security → security}/aip_discover.py +0 -0
- /office365/{directory/security → security}/aip_file_deleted.py +0 -0
- /office365/{directory/security → security}/aip_heart_beat.py +0 -0
- /office365/{directory/security → security}/aip_protection_action_log_request.py +0 -0
- /office365/{directory/security → security}/aip_scanner_discover_event.py +0 -0
- /office365/{directory/security → security}/aip_sensitivity_label_action_log_request.py +0 -0
- /office365/{directory/security → security}/air_admin_action_investigation_data.py +0 -0
- /office365/{directory/security → security}/air_investigation_data.py +0 -0
- /office365/{directory/security/cases → security/alerts}/__init__.py +0 -0
- /office365/{directory/security → security}/alerts/active_directory_domain_evidence.py +0 -0
- /office365/{directory/security → security}/alerts/antispamteamsdirection.py +0 -0
- /office365/{directory/security → security}/alerts/azure_resource_evidence.py +0 -0
- /office365/{directory/security → security}/alerts/classification.py +0 -0
- /office365/{directory/security → security}/alerts/comment.py +0 -0
- /office365/{directory/security → security}/alerts/connection_direction.py +0 -0
- /office365/{directory/security → security}/alerts/connectionstatus.py +0 -0
- /office365/{directory/security → security}/alerts/defenderavstatus.py +0 -0
- /office365/{directory/security → security}/alerts/detectionsource.py +0 -0
- /office365/{directory/security → security}/alerts/detectionstatus.py +0 -0
- /office365/{directory/security → security}/alerts/determination.py +0 -0
- /office365/{directory/security → security}/alerts/emailrole.py +0 -0
- /office365/{directory/security → security}/alerts/evidenceremediationstatus.py +0 -0
- /office365/{directory/security → security}/alerts/evidencerole.py +0 -0
- /office365/{directory/security → security}/alerts/evidenceverdict.py +0 -0
- /office365/{directory/security → security}/alerts/feedback.py +0 -0
- /office365/{directory/security → security}/alerts/googlecloudlocationtype.py +0 -0
- /office365/{directory/security → security}/alerts/iotdeviceimportancetype.py +0 -0
- /office365/{directory/security → security}/alerts/kubernetesplatform.py +0 -0
- /office365/{directory/security → security}/alerts/kubernetesservicetype.py +0 -0
- /office365/{directory/security → security}/alerts/logontype.py +0 -0
- /office365/{directory/security → security}/alerts/registryhive.py +0 -0
- /office365/{directory/security → security}/alerts/registryoperation.py +0 -0
- /office365/{directory/security → security}/alerts/registryvaluetype.py +0 -0
- /office365/{directory/security → security}/alerts/securitynetworkprotocol.py +0 -0
- /office365/{directory/security → security}/alerts/securityresourcetype.py +0 -0
- /office365/{directory/security → security}/alerts/severity.py +0 -0
- /office365/{directory/security → security}/alerts/status.py +0 -0
- /office365/{directory/security → security}/alerts/useraccountsecuritytype.py +0 -0
- /office365/{directory/security → security}/amazon_resource_evidence.py +0 -0
- /office365/{directory/security → security}/antispam_directionality.py +0 -0
- /office365/{directory/security → security}/artifact.py +0 -0
- /office365/{directory/security/collaboration → security/attacksimulations}/__init__.py +0 -0
- /office365/{directory/security → security}/attacksimulations/account_target_content_type.py +0 -0
- /office365/{directory/security → security}/attacksimulations/clicksource.py +0 -0
- /office365/{directory/security → security}/attacksimulations/endusernotificationpreference.py +0 -0
- /office365/{directory/security → security}/attacksimulations/endusernotificationsettingtype.py +0 -0
- /office365/{directory/security → security}/attacksimulations/endusernotificationtype.py +0 -0
- /office365/{directory/security → security}/attacksimulations/notificationdeliveryfrequency.py +0 -0
- /office365/{directory/security → security}/attacksimulations/notificationdeliverypreference.py +0 -0
- /office365/{directory/security → security}/attacksimulations/oauthappscope.py +0 -0
- /office365/{directory/security/datasecurity → security/attacksimulations/operations}/__init__.py +0 -0
- /office365/{directory/security → security}/attacksimulations/operations/type.py +0 -0
- /office365/{directory/security → security}/attacksimulations/payloadbrand.py +0 -0
- /office365/{directory/security → security}/attacksimulations/payloadcomplexity.py +0 -0
- /office365/{directory/security → security}/attacksimulations/payloaddeliveryplatform.py +0 -0
- /office365/{directory/security → security}/attacksimulations/payloadindustry.py +0 -0
- /office365/{directory/security → security}/attacksimulations/payloadtheme.py +0 -0
- /office365/{directory/security/ediscovery → security/attacksimulations/reports}/__init__.py +0 -0
- /office365/{directory/security → security}/attacksimulations/simulationattacktechnique.py +0 -0
- /office365/{directory/security → security}/attacksimulations/simulationattacktype.py +0 -0
- /office365/{directory/security → security}/attacksimulations/simulationautomationrunstatus.py +0 -0
- /office365/{directory/security → security}/attacksimulations/simulationautomationstatus.py +0 -0
- /office365/{directory/security → security}/attacksimulations/simulationcontentsource.py +0 -0
- /office365/{directory/security → security}/attacksimulations/simulationcontentstatus.py +0 -0
- /office365/{directory/security → security}/attacksimulations/simulationstatus.py +0 -0
- /office365/{directory/security → security}/attacksimulations/targettedusertype.py +0 -0
- /office365/{directory/security → security}/attacksimulations/trainingassignedto.py +0 -0
- /office365/{directory/security → security}/attacksimulations/trainingavailabilitystatus.py +0 -0
- /office365/{directory/security → security}/attacksimulations/trainingcompletionduration.py +0 -0
- /office365/{directory/security → security}/attacksimulations/trainingsettingtype.py +0 -0
- /office365/{directory/security → security}/attacksimulations/trainingstatus.py +0 -0
- /office365/{directory/security → security}/attacksimulations/trainingtype.py +0 -0
- /office365/{directory/security/ediscovery/operations → security/attacksimulations/users}/__init__.py +0 -0
- /office365/{directory/security → security}/attribute_definition_metadata_entry.py +0 -0
- /office365/{directory/security → security}/attribute_mapping_parameter_schema.py +0 -0
- /office365/{directory/security/healthissues → security/audit}/__init__.py +0 -0
- /office365/{directory/security → security}/audit/a365_ai_execute_tool.py +0 -0
- /office365/{directory/security → security}/audit/aad_risk_detection_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/ai_app_interaction_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/ai_execute_tool_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/ai_interactions_change_notification_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/ai_interactions_export_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/ai_interactions_subscription_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/ai_invoke_agent_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/ai_span_outputs_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/air_manual_investigation_data.py +0 -0
- /office365/{directory/security → security}/audit/alert_submission_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/alert_submission_result_detail_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/attack_sim_admin_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/attack_sim_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/audit_config_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/audit_log_query_status.py +0 -0
- /office365/{directory/security → security}/audit/audit_record_type_dictionary.py +0 -0
- /office365/{directory/security → security}/audit/audit_search_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/azfw_application_rule_aggregation_event_record.py +0 -0
- /office365/{directory/security → security}/audit/azfw_dns_query_event_record.py +0 -0
- /office365/{directory/security → security}/audit/azfw_network_rule_event_record.py +0 -0
- /office365/{directory/security → security}/audit/azure_active_directory_account_logon_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/azure_active_directory_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/azure_active_directory_sts_logon_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/azure_ai_search_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/campaign_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/ccrai_policy_violation_record.py +0 -0
- /office365/{directory/security → security}/audit/cdp_classifier_health_record.py +0 -0
- /office365/{directory/security → security}/audit/cdp_cold_crawl_status_record.py +0 -0
- /office365/{directory/security → security}/audit/cdp_consumption_resource_record.py +0 -0
- /office365/{directory/security → security}/audit/cdp_content_explorer_aggregate_record.py +0 -0
- /office365/{directory/security → security}/audit/cdp_dlp_sensitive_endpoint_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/cdp_log_record.py +0 -0
- /office365/{directory/security → security}/audit/cdp_ocr_billing_record.py +0 -0
- /office365/{directory/security → security}/audit/cdp_resource_scope_change_event_record.py +0 -0
- /office365/{directory/security → security}/audit/cdpdlmai_interaction_insights_record.py +0 -0
- /office365/{directory/security → security}/audit/cloud_update_device_config_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/cloud_update_profile_config_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/cloud_update_tenant_config_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_connector_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlm_exchange_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlm_share_point_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_applications_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_applications_classification_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_endpoint_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_endpoint_discovery_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_enforcement_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_exchange_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_exchange_classification_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_exchange_classification_cdp_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_exchange_discovery_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_share_point_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_share_point_classification_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_share_point_classification_cdp_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_dlp_share_point_classification_extended_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_exchange_ocr_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_manager_action_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_policy_grading_share_point_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_settings_change_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_sit_grading_share_point_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/compliance_supervision_exchange_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/connected_ai_app_interaction_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/consumption_resource_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/content_store_metadata_record.py +0 -0
- /office365/{directory/security → security}/audit/copilot_agent_management_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/copilot_for_security_logging_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/copilot_for_security_trigger_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/copilot_interaction_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/copilot_plugin_setting_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/copilot_prompt_book_setting_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/copilot_session_sharing_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/copilot_setting_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/copilot_workspace_setting_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/core_reporting_settings_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/critical_asset_management_classification_record.py +0 -0
- /office365/{directory/security → security}/audit/crm_entity_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/cross_tenant_access_policy_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/device_discovery_settings_authenticated_scans_record.py +0 -0
- /office365/{directory/security → security}/audit/device_discovery_settings_exclusion_record.py +0 -0
- /office365/{directory/security → security}/audit/device_discovery_settings_record.py +0 -0
- /office365/{directory/security → security}/audit/discovery_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/dlp_endpoint_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/dlp_import_result_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/dragon_copilot_access_record.py +0 -0
- /office365/{directory/security → security}/audit/dragon_copilot_admin_record.py +0 -0
- /office365/{directory/security → security}/audit/dragon_copilot_clinical_data_record.py +0 -0
- /office365/{directory/security → security}/audit/dragon_copilot_session_record.py +0 -0
- /office365/{directory/security → security}/audit/dynamics365_business_central_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/ehr_connector_audit_base_record.py +0 -0
- /office365/{directory/security → security}/audit/eop_submission_feed_entity_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/exchange_admin_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/exchange_aggregated_mailbox_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/exchange_aggregated_operation_record.py +0 -0
- /office365/{directory/security → security}/audit/exchange_mailbox_audit_group_record.py +0 -0
- /office365/{directory/security → security}/audit/exchange_mailbox_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/hosted_rpa_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/hr_signal_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/hygiene_event_record.py +0 -0
- /office365/{directory/security → security}/audit/information_barrier_policy_application_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/information_worker_protection_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/insider_risk_scoped_user_insights_record.py +0 -0
- /office365/{directory/security → security}/audit/insider_risk_scoped_users_record.py +0 -0
- /office365/{directory/security → security}/audit/microsoft_stream_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/microsoft_teams_admin_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/microsoft_teams_analytics_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/microsoft_teams_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/microsoft_teams_device_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/microsoft_teams_retention_label_action_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/microsoft_teams_sensitivity_label_action_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/microsoft_teams_shifts_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/microsoft_teams_user_concern_audit_record.py +0 -0
- /office365/{directory/security → security}/audit/multi_tenant_organization_join_request_transition_details.py +0 -0
- /office365/{directory/security → security}/audit/multi_tenant_organization_member_transition_details.py +0 -0
- /office365/{directory/security → security}/audit/whois_domain_status.py +0 -0
- /office365/{directory/security → security}/audit/whois_history_record.py +0 -0
- /office365/{directory/security → security}/authority_template.py +0 -0
- /office365/{directory/security → security}/auto_auditing_configuration.py +0 -0
- /office365/{directory/security → security}/autonomous_system.py +0 -0
- /office365/{directory/security/incidents → security/cases}/__init__.py +0 -0
- /office365/{directory/security → security}/cases/action.py +0 -0
- /office365/{directory/security → security}/cases/additionaldataoptions.py +0 -0
- /office365/{directory/security → security}/cases/additionaloptions.py +0 -0
- /office365/{directory/security → security}/cases/exportcriteria.py +0 -0
- /office365/{directory/security → security}/cases/exportfilestructure.py +0 -0
- /office365/{directory/security → security}/cases/exportformat.py +0 -0
- /office365/{directory/security → security}/cases/exportlocation.py +0 -0
- /office365/{directory/security → security}/cases/exportoptions.py +0 -0
- /office365/{directory/security → security}/cases/itemstoinclude.py +0 -0
- /office365/{directory/security → security}/cases/operationstatus.py +0 -0
- /office365/{directory/security → security}/cases/reviewsetsettings.py +0 -0
- /office365/{directory/security → security}/cases/statisticsoptions.py +0 -0
- /office365/{directory/security → security}/cases/status.py +0 -0
- /office365/{directory/security → security}/cases/type.py +0 -0
- /office365/{directory/security → security}/childselectability.py +0 -0
- /office365/{directory/security → security}/citation_template.py +0 -0
- /office365/{directory/security → security}/cloud_logon_request_evidence.py +0 -0
- /office365/{directory/security → security}/cloudattachmentversion.py +0 -0
- /office365/{directory/security/labels → security/collaboration}/__init__.py +0 -0
- /office365/{directory/security → security}/collaboration/analyzed_email_authentication_detail.py +0 -0
- /office365/{directory/security → security}/collaboration/analyzed_email_dlp_rule_info.py +0 -0
- /office365/{directory/security → security}/collaboration/analyzed_email_exchange_transport_rule_info.py +0 -0
- /office365/{directory/security → security}/collaboration/analyzed_email_recipient_detail.py +0 -0
- /office365/{directory/security → security}/collaboration/analyzed_email_sender_detail.py +0 -0
- /office365/{directory/security → security}/container_filter.py +0 -0
- /office365/{directory/security → security}/container_registry_evidence.py +0 -0
- /office365/{directory/security → security}/containerportprotocol.py +0 -0
- /office365/{directory/security → security}/contentformat.py +0 -0
- /office365/{directory/security → security}/contentprocessingerrortype.py +0 -0
- /office365/{directory/security → security}/correlation_reason.py +0 -0
- /office365/{directory/security → security}/cortana_briefing_audit_record.py +0 -0
- /office365/{directory/security → security}/custom_security_attribute_comparison_operator.py +0 -0
- /office365/{directory/security → security}/customer_key_service_encryption_audit_record.py +0 -0
- /office365/{directory/security → security}/data_center_security_cmdlet_audit_record.py +0 -0
- /office365/{directory/security → security}/data_governance_audit_record.py +0 -0
- /office365/{directory/security → security}/data_insights_rest_api_audit_record.py +0 -0
- /office365/{directory/security → security}/data_lake_export_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/data_set.py +0 -0
- /office365/{directory/security → security}/data_share_operation_audit_record.py +0 -0
- /office365/{directory/security/labels/retention → security/datasecurity}/__init__.py +0 -0
- /office365/{directory/security → security}/datasecurity/classification_inner_error.py +0 -0
- /office365/{directory/security → security}/datasecurity/custom_metadata_dictionary.py +0 -0
- /office365/{directory/security → security}/datasecurity/dlp_action_info.py +0 -0
- /office365/{directory/security → security}/datasecurity/integrated_application_metadata.py +0 -0
- /office365/{directory/security/data_security_investigation_audit_record.py → security/datasecurity/investigation_audit_record.py} +0 -0
- /office365/{directory/security → security}/datasecurity/policy_binding.py +0 -0
- /office365/{directory/security → security}/datasecurity/policy_location.py +0 -0
- /office365/{directory/security → security}/datasecurity/policy_location_application.py +0 -0
- /office365/{directory/security → security}/datasecurity/policy_location_domain.py +0 -0
- /office365/{directory/security → security}/datasecurity/policy_location_url.py +0 -0
- /office365/{directory/security → security}/datasecurity/policy_user_scope.py +0 -0
- /office365/{directory/security → security}/datasecurity/process_conversation_metadata.py +0 -0
- /office365/{directory/security → security}/datasecurity/protected_content.py +0 -0
- /office365/{directory/security → security}/default_audit_data.py +0 -0
- /office365/{directory/security → security}/defender_case_management_audit_record.py +0 -0
- /office365/{directory/security → security}/defender_preview_features_record.py +0 -0
- /office365/{directory/security → security}/defender_security_for_ai_configuration_audit_record.py +0 -0
- /office365/{directory/security → security}/delivery_action.py +0 -0
- /office365/{directory/security → security}/delivery_location.py +0 -0
- /office365/{directory/security → security}/department_template.py +0 -0
- /office365/{directory/security → security}/deploy_feature_activity_record.py +0 -0
- /office365/{directory/security → security}/deployment_access_key_type.py +0 -0
- /office365/{directory/security → security}/detonation_behaviour_details.py +0 -0
- /office365/{directory/security → security}/detonation_chain.py +0 -0
- /office365/{directory/security → security}/detonation_observables.py +0 -0
- /office365/{directory/security → security}/dictionary.py +0 -0
- /office365/{directory/security → security}/dlp_sensitive_information_type_rule_package_cmdlet_record.py +0 -0
- /office365/{directory/security → security}/dynamic_column_value.py +0 -0
- /office365/{directory/security/scorecontrol → security/ediscovery}/__init__.py +0 -0
- /office365/{directory/security/search → security/ediscovery/operations}/__init__.py +0 -0
- /office365/{directory/security → security}/email_sender.py +0 -0
- /office365/{directory/security → security}/event_source.py +0 -0
- /office365/{directory/security → security}/export_file_metadata.py +0 -0
- /office365/{directory/security → security}/fabric_audit_record.py +0 -0
- /office365/{directory/security → security}/fabric_policy_record.py +0 -0
- /office365/{directory/security → security}/file_details.py +0 -0
- /office365/{directory/security → security}/file_plan_authority.py +0 -0
- /office365/{directory/security → security}/file_plan_citation.py +0 -0
- /office365/{directory/security → security}/file_plan_department.py +0 -0
- /office365/{directory/security → security}/file_plan_descriptor_base.py +0 -0
- /office365/{directory/security → security}/file_plan_descriptor_template.py +0 -0
- /office365/{directory/security → security}/file_plan_reference.py +0 -0
- /office365/{directory/security → security}/file_plan_reference_template.py +0 -0
- /office365/{directory/security → security}/file_plan_subcategory.py +0 -0
- /office365/{directory/security → security}/filehashalgorithm.py +0 -0
- /office365/{directory/security → security}/filter_operand.py +0 -0
- /office365/{directory/security → security}/geo_location.py +0 -0
- /office365/{directory/security → security}/git_hub_organization_evidence.py +0 -0
- /office365/{directory/security → security}/git_hub_repo_evidence.py +0 -0
- /office365/{directory/security → security}/git_hub_user_evidence.py +0 -0
- /office365/{directory/security → security}/group_filter.py +0 -0
- /office365/{directory/security → security}/healthcare_signal_record.py +0 -0
- /office365/{directory/security/securescores → security/healthissues}/__init__.py +0 -0
- /office365/{directory/security → security}/healthissues/deploymentstatus.py +0 -0
- /office365/{directory/security → security}/healthissues/sensorhealthstatus.py +0 -0
- /office365/{directory/security → security}/healthissues/sensortype.py +0 -0
- /office365/{directory/security → security}/healthissues/severity.py +0 -0
- /office365/{directory/security → security}/healthissues/sourcetype.py +0 -0
- /office365/{directory/security → security}/healthissues/status.py +0 -0
- /office365/{directory/security → security}/healthissues/type.py +0 -0
- /office365/{directory/security → security}/hunting_row_result.py +0 -0
- /office365/{directory/security/subjectrightsrequests → security/incidents}/__init__.py +0 -0
- /office365/{directory/security → security}/incidents/status.py +0 -0
- /office365/{directory/security → security}/indicator_source.py +0 -0
- /office365/{directory/security → security}/integrated_apps_app_admin_activity_audit_record.py +0 -0
- /office365/{directory/security → security}/integrated_apps_app_settings_admin_activity_audit_record.py +0 -0
- /office365/{directory/security → security}/intelligence_profile_indicator.py +0 -0
- /office365/{directory/security → security}/intelligence_profile_kind.py +0 -0
- /office365/{directory/security → security}/investigation_state.py +0 -0
- /office365/{directory/security → security}/irm_activity_audit_trail_record.py +0 -0
- /office365/{directory/security → security}/irm_user_defined_detection_record.py +0 -0
- /office365/{directory/security → security}/kaizala_audit_record.py +0 -0
- /office365/{directory/security/threatintelligence → security/labels}/__init__.py +0 -0
- /office365/{directory/security/label_analytics_aggregate_audit_record.py → security/labels/analytics_aggregate_audit_record.py} +0 -0
- /office365/{directory/security/label_content_explorer_audit_record.py → security/labels/content_explorer_audit_record.py} +0 -0
- /office365/{directory/security/threatintelligence/hosts → security/labels/retention}/__init__.py +0 -0
- /office365/{directory/security → security}/labels/retention/actionafterretentionperiod.py +0 -0
- /office365/{directory/security → security}/labels/retention/behaviorduringretentionperiod.py +0 -0
- /office365/{directory/security → security}/labels/retention/defaultrecordbehavior.py +0 -0
- /office365/{directory/security → security}/labels/retention/disposition_review_stage.py +0 -0
- /office365/{directory/security → security}/labels/retention/duration.py +0 -0
- /office365/{directory/security → security}/labels/retention/trigger.py +0 -0
- /office365/{directory/security → security}/large_content_metadata_audit_record.py +0 -0
- /office365/{directory/security → security}/logged_on_user.py +0 -0
- /office365/{directory/security → security}/m365_compliance_connector_audit_record.py +0 -0
- /office365/{directory/security → security}/m365_search_sections_record.py +0 -0
- /office365/{directory/security → security}/m365daad_audit_record.py +0 -0
- /office365/{directory/security → security}/m365odsp_asset_metadata_record.py +0 -0
- /office365/{directory/security → security}/mail_cluster_evidence.py +0 -0
- /office365/{directory/security → security}/mail_submission_data.py +0 -0
- /office365/{directory/security → security}/mailbox_configuration_type.py +0 -0
- /office365/{directory/security → security}/managed_services_audit_record.py +0 -0
- /office365/{directory/security → security}/managed_tenants_audit_record.py +0 -0
- /office365/{directory/security → security}/mapg_alerts_audit_record.py +0 -0
- /office365/{directory/security → security}/mapg_onboard_audit_record.py +0 -0
- /office365/{directory/security → security}/mapg_policy_audit_record.py +0 -0
- /office365/{directory/security → security}/mapg_remediation_audit_record.py +0 -0
- /office365/{directory/security → security}/mcas_alerts_audit_record.py +0 -0
- /office365/{directory/security → security}/mda_audit_record.py +0 -0
- /office365/{directory/security → security}/mda_data_security_signal_record.py +0 -0
- /office365/{directory/security → security}/mdash_audit_record.py +0 -0
- /office365/{directory/security → security}/mdatp_audit_record.py +0 -0
- /office365/{directory/security → security}/mdc_events_record.py +0 -0
- /office365/{directory/security → security}/mdi_audit_record.py +0 -0
- /office365/{directory/security → security}/merge_response.py +0 -0
- /office365/{directory/security → security}/mesh_worlds_audit_record.py +0 -0
- /office365/{directory/security → security}/microsoft365_backup_backup_item_audit_record.py +0 -0
- /office365/{directory/security → security}/microsoft365_backup_backup_policy_audit_record.py +0 -0
- /office365/{directory/security → security}/microsoft365_backup_granular_browse_task_audit_record.py +0 -0
- /office365/{directory/security → security}/microsoft365_backup_restore_item_audit_record.py +0 -0
- /office365/{directory/security → security}/microsoft365_backup_restore_task_audit_record.py +0 -0
- /office365/{directory/security → security}/microsoft365_copilot_scheduled_prompt_audit_record.py +0 -0
- /office365/{directory/security → security}/microsoft_defender_experts_xdr_audit_record.py +0 -0
- /office365/{directory/security → security}/microsoft_flow_audit_record.py +0 -0
- /office365/{directory/security → security}/microsoft_forms_audit_record.py +0 -0
- /office365/{directory/security → security}/microsoft_graph_data_connect_consent.py +0 -0
- /office365/{directory/security → security}/microsoft_graph_data_connect_operation.py +0 -0
- /office365/{directory/security → security}/microsoft_purview_data_catalog_operation_record.py +0 -0
- /office365/{directory/security → security}/microsoft_purview_data_map_operation_record.py +0 -0
- /office365/{directory/security → security}/microsoft_purview_metadata_policy_operation_record.py +0 -0
- /office365/{directory/security → security}/microsoft_purview_policy_operation_record.py +0 -0
- /office365/{directory/security → security}/microsoft_purview_privacy_audit_event.py +0 -0
- /office365/{directory/security → security}/microsoft_purview_unified_catalog_operation_record.py +0 -0
- /office365/{directory/security → security}/mip_auto_label_exchange_item_audit_record.py +0 -0
- /office365/{directory/security → security}/mip_auto_label_progress_feedback_audit_record.py +0 -0
- /office365/{directory/security → security}/mip_auto_label_share_point_item_audit_record.py +0 -0
- /office365/{directory/security → security}/mip_auto_label_share_point_policy_location_audit_record.py +0 -0
- /office365/{directory/security → security}/mip_auto_label_simulation_share_point_completion_record.py +0 -0
- /office365/{directory/security → security}/mip_auto_label_simulation_share_point_progress_record.py +0 -0
- /office365/{directory/security → security}/mip_auto_label_simulation_statistics_record.py +0 -0
- /office365/{directory/security → security}/mip_exact_data_match_audit_record.py +0 -0
- /office365/{directory/security → security}/mip_label_analytics_audit_record.py +0 -0
- /office365/{directory/security → security}/mip_label_audit_record.py +0 -0
- /office365/{directory/security → security}/mos_agent_info_record.py +0 -0
- /office365/{directory/security → security}/mos_agent_info_record_v2.py +0 -0
- /office365/{directory/security → security}/ms365d_custom_detection_audit_record.py +0 -0
- /office365/{directory/security → security}/ms365d_incident_audit_record.py +0 -0
- /office365/{directory/security → security}/ms365d_suppression_rule_audit_record.py +0 -0
- /office365/{directory/security → security}/msde_custom_collection_audit_record.py +0 -0
- /office365/{directory/security → security}/msde_general_settings_audit_record.py +0 -0
- /office365/{directory/security → security}/msde_indicators_settings_audit_record.py +0 -0
- /office365/{directory/security → security}/msde_response_actions_audit_record.py +0 -0
- /office365/{directory/security → security}/msde_roles_settings_audit_record.py +0 -0
- /office365/{directory/security → security}/msp_vector_search_content_metadata_audit_record.py +0 -0
- /office365/{directory/security → security}/mstic_nation_state_notification_record.py +0 -0
- /office365/{directory/security → security}/multi_stage_disposition_audit_record.py +0 -0
- /office365/{directory/security → security}/my_analytics_settings_audit_record.py +0 -0
- /office365/{directory/security → security}/network_adapter.py +0 -0
- /office365/{directory/security → security}/noisy_alert_policy_audit_record.py +0 -0
- /office365/{directory/security → security}/oauth_application_evidence.py +0 -0
- /office365/{directory/security → security}/object_definition_metadata_entry.py +0 -0
- /office365/{directory/security → security}/object_mapping_metadata_entry.py +0 -0
- /office365/{directory/security → security}/ocr_settings.py +0 -0
- /office365/{directory/security → security}/office_native_audit_record.py +0 -0
- /office365/{directory/security → security}/ome_portal_audit_record.py +0 -0
- /office365/{directory/security → security}/on_demand_share_point_classification_audit_record.py +0 -0
- /office365/{directory/security → security}/on_premises_file_share_scanner_dlp_audit_record.py +0 -0
- /office365/{directory/security → security}/on_premises_share_point_scanner_dlp_audit_record.py +0 -0
- /office365/{directory/security → security}/onboarding_status.py +0 -0
- /office365/{directory/security → security}/one_drive_audit_record.py +0 -0
- /office365/{directory/security → security}/organizational_data_in_m365_audit_record.py +0 -0
- /office365/{directory/security → security}/outlook_copilot_automation_audit_record.py +0 -0
- /office365/{directory/security → security}/owa_get_access_token_for_resource_audit_record.py +0 -0
- /office365/{directory/security → security}/p4_ai_risk_score_record.py +0 -0
- /office365/{directory/security → security}/p4ai_assessment_category_record.py +0 -0
- /office365/{directory/security → security}/p4ai_assessment_fabric_scanner_record.py +0 -0
- /office365/{directory/security → security}/p4ai_assessment_location_result_record.py +0 -0
- /office365/{directory/security → security}/p4ai_assessment_record.py +0 -0
- /office365/{directory/security → security}/people_admin_settings_audit_record.py +0 -0
- /office365/{directory/security → security}/physical_badging_signal_audit_record.py +0 -0
- /office365/{directory/security → security}/places_directory_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_chat_message_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_chat_message_list_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_copy_plan_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_goal_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_goal_list_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_plan_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_plan_list_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_plan_sensitivity_label_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_roster_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_roster_sensitivity_label_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_task_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_task_list_audit_record.py +0 -0
- /office365/{directory/security → security}/planner_tenant_settings_audit_record.py +0 -0
- /office365/{directory/security → security}/policy_config_change_audit_record.py +0 -0
- /office365/{directory/security → security}/policypivotproperty.py +0 -0
- /office365/{directory/security → security}/power_apps_audit_app_record.py +0 -0
- /office365/{directory/security → security}/power_apps_audit_plan_record.py +0 -0
- /office365/{directory/security → security}/power_apps_audit_resource_record.py +0 -0
- /office365/{directory/security → security}/power_bi_audit_record.py +0 -0
- /office365/{directory/security → security}/power_bi_dlp_audit_record.py +0 -0
- /office365/{directory/security → security}/power_pages_site_audit_record.py +0 -0
- /office365/{directory/security → security}/power_platform_admin_dlp_audit_record.py +0 -0
- /office365/{directory/security → security}/power_platform_admin_environment_audit_record.py +0 -0
- /office365/{directory/security → security}/power_platform_administrator_activity_record.py +0 -0
- /office365/{directory/security → security}/power_platform_lockbox_resource_access_request_audit_record.py +0 -0
- /office365/{directory/security → security}/power_platform_lockbox_resource_command_audit_record.py +0 -0
- /office365/{directory/security → security}/power_platform_service_activity_audit_record.py +0 -0
- /office365/{directory/security → security}/power_platform_tenant_isolation_record.py +0 -0
- /office365/{directory/security → security}/priva_privacy_assessment_operation_record.py +0 -0
- /office365/{directory/security → security}/priva_privacy_consent_operation_record.py +0 -0
- /office365/{directory/security → security}/privacy_data_match_audit_record.py +0 -0
- /office365/{directory/security → security}/privacy_data_minimization_record.py +0 -0
- /office365/{directory/security → security}/privacy_digest_email_record.py +0 -0
- /office365/{directory/security → security}/privacy_open_access_audit_record.py +0 -0
- /office365/{directory/security → security}/privacy_portal_audit_record.py +0 -0
- /office365/{directory/security → security}/privacy_remediation_action_record.py +0 -0
- /office365/{directory/security → security}/privacy_remediation_record.py +0 -0
- /office365/{directory/security → security}/project_audit_record.py +0 -0
- /office365/{directory/security → security}/project_for_the_web_assigned_to_me_settings_audit_record.py +0 -0
- /office365/{directory/security → security}/project_for_the_web_project_audit_record.py +0 -0
- /office365/{directory/security → security}/project_for_the_web_project_settings_audit_record.py +0 -0
- /office365/{directory/security → security}/project_for_the_web_roadmap_audit_record.py +0 -0
- /office365/{directory/security → security}/project_for_the_web_roadmap_item_audit_record.py +0 -0
- /office365/{directory/security → security}/project_for_the_web_roadmap_settings_audit_record.py +0 -0
- /office365/{directory/security → security}/project_for_the_web_task_audit_record.py +0 -0
- /office365/{directory/security → security}/protectionscopestate.py +0 -0
- /office365/{directory/security → security}/protocol_type.py +0 -0
- /office365/{directory/security → security}/public_error_response.py +0 -0
- /office365/{directory/security → security}/public_folder_audit_record.py +0 -0
- /office365/{directory/security → security}/purgeareas.py +0 -0
- /office365/{directory/security → security}/purgetype.py +0 -0
- /office365/{directory/security → security}/purview_insider_risk_alerts_record.py +0 -0
- /office365/{directory/security → security}/purview_insider_risk_cases_record.py +0 -0
- /office365/{directory/security → security}/purview_mc_recommendation_record.py +0 -0
- /office365/{directory/security → security}/purview_posture_agent_audit_record.py +0 -0
- /office365/{directory/security → security}/quarantine_audit_record.py +0 -0
- /office365/{directory/security → security}/query_type.py +0 -0
- /office365/{directory/security → security}/recipient_type.py +0 -0
- /office365/{directory/security → security}/records_management_audit_record.py +0 -0
- /office365/{directory/security → security}/redundancy_detection_settings.py +0 -0
- /office365/{directory/security → security}/referenced_object.py +0 -0
- /office365/{directory/security → security}/registry_key_evidence.py +0 -0
- /office365/{directory/security → security}/registry_value_evidence.py +0 -0
- /office365/{directory/security → security}/remediation_action.py +0 -0
- /office365/{directory/security → security}/remediation_severity.py +0 -0
- /office365/{directory/security → security}/report_file_metadata.py +0 -0
- /office365/{directory/security → security}/report_submission.py +0 -0
- /office365/{directory/security → security}/report_submission_result_detail.py +0 -0
- /office365/{directory/security → security}/resource_access_event.py +0 -0
- /office365/{directory/security → security}/restricted_mode_audit_record.py +0 -0
- /office365/{directory/security → security}/restrictionaction.py +0 -0
- /office365/{directory/security → security}/retention_duration_forever.py +0 -0
- /office365/{directory/security → security}/retention_policy_audit_record.py +0 -0
- /office365/{directory/security → security}/riskscore.py +0 -0
- /office365/{directory/security → security}/rti_operations_agent_audit_record.py +0 -0
- /office365/{directory/security → security}/sbp_configuration_event_record.py +0 -0
- /office365/{directory/security → security}/sbp_usage_event_record.py +0 -0
- /office365/{directory/security → security}/score_evidence.py +0 -0
- /office365/{directory/security → security}/score_platform_generic_audit_record.py +0 -0
- /office365/{directory/security/threatintelligence/whoisrecords → security/scorecontrol}/__init__.py +0 -0
- /office365/{directory/security → security}/script_run_audit_record.py +0 -0
- /office365/{directory/security/triggers → security/search}/__init__.py +0 -0
- /office365/{directory/security/search_audit_record.py → security/search/audit_record.py} +0 -0
- /office365/{directory/security → security}/search/datasourcecontainerstatus.py +0 -0
- /office365/{directory/security → security}/search/datasourceholdstatus.py +0 -0
- /office365/{directory/security → security}/search/datasourcescopes.py +0 -0
- /office365/{directory/security → security}/search/documentversion.py +0 -0
- /office365/{directory/security → security}/search.py +0 -0
- /office365/{directory/security/triggertypes → security/securescores}/__init__.py +0 -0
- /office365/{directory/security → security}/security_compliance_alert_record.py +0 -0
- /office365/{directory/security → security}/security_compliance_center_eop_cmdlet_audit_record.py +0 -0
- /office365/{directory/security → security}/security_compliance_insights_audit_record.py +0 -0
- /office365/{directory/security → security}/security_compliance_rbac_audit_record.py +0 -0
- /office365/{directory/security → security}/security_compliance_user_change_audit_record.py +0 -0
- /office365/{directory/security → security}/security_copilot_identity_management_audit_record.py +0 -0
- /office365/{directory/security → security}/security_development_lifecycle_ai_log_audit_record.py +0 -0
- /office365/{directory/security → security}/security_group_evidence.py +0 -0
- /office365/{directory/security → security}/sensitive_info_remediation_agent_data_record.py +0 -0
- /office365/{directory/security → security}/sensitivity_label_action_audit_record.py +0 -0
- /office365/{directory/security → security}/sensitivity_label_policy_match_audit_record.py +0 -0
- /office365/{directory/security → security}/sensitivity_labeled_file_action_audit_record.py +0 -0
- /office365/{directory/security → security}/sensor_candidate.py +0 -0
- /office365/{directory/security → security}/sensor_candidate_activation_mode.py +0 -0
- /office365/{directory/security → security}/sensor_deployment_package.py +0 -0
- /office365/{directory/security → security}/sentinel_ai_tool_audit_record.py +0 -0
- /office365/{directory/security → security}/sentinel_graph_audit_record.py +0 -0
- /office365/{directory/security → security}/sentinel_job_audit_record.py +0 -0
- /office365/{directory/security → security}/sentinel_kql_on_lake_audit_record.py +0 -0
- /office365/{directory/security → security}/sentinel_lake_data_onboarding_audit_record.py +0 -0
- /office365/{directory/security → security}/sentinel_lake_encryption_audit_record.py +0 -0
- /office365/{directory/security → security}/sentinel_lake_onboarding_audit_record.py +0 -0
- /office365/{directory/security → security}/sentinel_notebook_on_lake_audit_record.py +0 -0
- /office365/{directory/security → security}/sentinel_package_audit_record.py +0 -0
- /office365/{directory/security → security}/service_principal_type.py +0 -0
- /office365/{directory/security → security}/service_source.py +0 -0
- /office365/{directory/security → security}/share_point_app_permission_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/share_point_audit_record.py +0 -0
- /office365/{directory/security → security}/share_point_comment_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/share_point_content_security_policy_audit_record.py +0 -0
- /office365/{directory/security → security}/share_point_content_type_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/share_point_e_signature_audit_record.py +0 -0
- /office365/{directory/security → security}/share_point_field_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/share_point_file_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/share_point_list_item_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/share_point_list_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/share_point_sharing_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/single_property_schema.py +0 -0
- /office365/{directory/security → security}/site_source.py +0 -0
- /office365/{directory/security → security}/skype_for_business_cmdlets_audit_record.py +0 -0
- /office365/{directory/security → security}/skype_for_business_pstn_usage_audit_record.py +0 -0
- /office365/{directory/security → security}/skype_for_business_users_blocked_audit_record.py +0 -0
- /office365/{directory/security → security}/sonar_detonation_content_metadata.py +0 -0
- /office365/{directory/security → security}/sonar_detonation_entity_audit_record.py +0 -0
- /office365/{directory/security → security}/sonar_file_detonation_entity_audit_record.py +0 -0
- /office365/{directory/security → security}/sonar_submission_entity_audit_record.py +0 -0
- /office365/{directory/security → security}/sonar_url_detonation_entity_audit_record.py +0 -0
- /office365/{directory/security → security}/spark_core_custom_live_pool_record.py +0 -0
- /office365/{directory/security → security}/stream.py +0 -0
- /office365/{directory/security → security}/string_key_long_value_pair.py +0 -0
- /office365/{directory/security → security}/string_key_object_value_pair.py +0 -0
- /office365/{directory/security → security}/string_key_string_value_pair.py +0 -0
- /office365/{directory/security → security}/string_value_dictionary.py +0 -0
- /office365/{directory/security → security}/subcategory_template.py +0 -0
- /office365/{intune/devices/configurations/windows/10 → security/subjectrightsrequests}/__init__.py +0 -0
- /office365/{directory/security → security}/subjectrightsrequests/all_mailbox_location.py +0 -0
- /office365/{directory/security → security}/subjectrightsrequests/all_site_location.py +0 -0
- /office365/{directory/security → security}/subjectrightsrequests/data_subject.py +0 -0
- /office365/{directory/security → security}/subjectrightsrequests/detail.py +0 -0
- /office365/{directory/security → security}/subjectrightsrequests/enumerated_mailbox_location.py +0 -0
- /office365/{directory/security → security}/subjectrightsrequests/enumerated_site_location.py +0 -0
- /office365/{directory/security → security}/subjectrightsrequests/history.py +0 -0
- /office365/{directory/security → security}/subjectrightsrequests/mailbox_location.py +0 -0
- /office365/{directory/security → security}/subjectrightsrequests/site_location.py +0 -0
- /office365/{directory/security → security}/subjectrightsrequests/stage_detail.py +0 -0
- /office365/{directory/security → security}/submission_entity_audit_record.py +0 -0
- /office365/{directory/security → security}/submission_mail_evidence.py +0 -0
- /office365/{directory/security → security}/supervisory_review_day_x_insights_audit_record.py +0 -0
- /office365/{directory/security → security}/synthetic_probe_audit_record.py +0 -0
- /office365/{directory/security → security}/tag.py +0 -0
- /office365/{directory/security → security}/team_copilot_interaction_audit_record.py +0 -0
- /office365/{directory/security → security}/teams_delivery_location.py +0 -0
- /office365/{directory/security → security}/teams_easy_approvals_audit_record.py +0 -0
- /office365/{directory/security → security}/teams_eval_data_hub_admin_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/teams_eval_data_hub_data_access_audit_record.py +0 -0
- /office365/{directory/security → security}/teams_eval_data_hub_permission_change_audit_record.py +0 -0
- /office365/{directory/security → security}/teams_healthcare_audit_record.py +0 -0
- /office365/{directory/security → security}/teams_message_delivery_action.py +0 -0
- /office365/{directory/security → security}/teams_updates_audit_record.py +0 -0
- /office365/{directory/security → security}/tenant_allow_block_list_audit_record.py +0 -0
- /office365/{directory/security → security}/threat_detection_detail.py +0 -0
- /office365/{directory/security → security}/threat_finder_audit_record.py +0 -0
- /office365/{directory/security → security}/threat_type.py +0 -0
- /office365/{sharepoint/publishing/pages/3d → security/threatintelligence}/__init__.py +0 -0
- /office365/{directory/security → security}/threatintelligence/article_indicator.py +0 -0
- /office365/{directory/security/threat_intelligence_atp_content_data.py → security/threatintelligence/atp_content_data.py} +0 -0
- /office365/{directory/security/threat_intelligence_export_audit_record.py → security/threatintelligence/export_audit_record.py} +0 -0
- /office365/{directory/security → security}/threatintelligence/host_port_banner.py +0 -0
- /office365/{directory/security → security}/threatintelligence/host_port_protocol.py +0 -0
- /office365/{directory/security → security}/threatintelligence/host_port_status.py +0 -0
- /office365/{directory/security → security}/threatintelligence/host_reputation_classification.py +0 -0
- /office365/{directory/security → security}/threatintelligence/host_reputation_rule_severity.py +0 -0
- /office365/{directory/security → security}/threatintelligence/hostname.py +0 -0
- /office365/{directory/security/threat_intelligence_mail_data.py → security/threatintelligence/mail_data.py} +0 -0
- /office365/{directory/security/threat_intelligence_object_audit_record.py → security/threatintelligence/object_audit_record.py} +0 -0
- /office365/{directory/security/threat_intelligence_url_click_data.py → security/threatintelligence/url_click_data.py} +0 -0
- /office365/{directory/security → security}/timeline_event_type.py +0 -0
- /office365/{directory/security → security}/todo_audit_record.py +0 -0
- /office365/{directory/security → security}/topic_modeling_settings.py +0 -0
- /office365/{directory/security → security}/trainable_classifier_audit_record.py +0 -0
- /office365/{directory/security → security}/triggers/eventpropagationstatus.py +0 -0
- /office365/{directory/security → security}/triggers/eventstatustype.py +0 -0
- /office365/{directory/security → security}/triggertypes/event_type.py +0 -0
- /office365/{directory/security → security}/uam_operation_audit_record.py +0 -0
- /office365/{directory/security → security}/unclassified_artifact.py +0 -0
- /office365/{directory/security → security}/unified_group_audit_record.py +0 -0
- /office365/{directory/security → security}/unified_simulation_matched_item_audit_record.py +0 -0
- /office365/{directory/security → security}/unified_simulation_summary_audit_record.py +0 -0
- /office365/{directory/security → security}/universal_print_management_audit_record.py +0 -0
- /office365/{directory/security → security}/universal_print_print_job_audit_record.py +0 -0
- /office365/{directory/security → security}/urbac_assignment_audit_record.py +0 -0
- /office365/{directory/security → security}/urbac_enable_state_audit_record.py +0 -0
- /office365/{directory/security → security}/urbac_role_audit_record.py +0 -0
- /office365/{directory/security → security}/url_evidence.py +0 -0
- /office365/{directory/security → security}/user_training_audit_record.py +0 -0
- /office365/{directory/security → security}/useractivitytype.py +0 -0
- /office365/{directory/security → security}/useractivitytypes.py +0 -0
- /office365/{directory/security → security}/userscopetype.py +0 -0
- /office365/{directory/security → security}/usx_workspace_onboarding_audit_record.py +0 -0
- /office365/{directory/security → security}/verdict_category.py +0 -0
- /office365/{directory/security → security}/vfam_create_policy_audit_record.py +0 -0
- /office365/{directory/security → security}/vfam_delete_policy_audit_record.py +0 -0
- /office365/{directory/security → security}/vfam_update_policy_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_amplify_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_engage_events_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_engage_network_association_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_engage_segment_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_advanced_configuration_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_agentic_campaign_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_feedback_program_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_organizational_data_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_pulse_program_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_pulse_program_respondent_rate_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_question_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_role_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_rubicon_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_support_access_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_system_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_user_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_glint_user_group_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_goals_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_learning_admin_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_learning_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_pulse_admin_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_pulse_organizer_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_pulse_report_audit_record.py +0 -0
- /office365/{directory/security → security}/viva_pulse_response_audit_record.py +0 -0
- /office365/{directory/security → security}/vm_cloud_provider.py +0 -0
- /office365/{directory/security → security}/vulnerability_component.py +0 -0
- /office365/{directory/security → security}/vulnerability_severity.py +0 -0
- /office365/{directory/security → security}/wdatp_alerts_audit_record.py +0 -0
- /office365/{directory/security → security}/web_content_filtering_audit_record.py +0 -0
- /office365/{directory/security → security}/windows365_customer_lockbox_audit_record.py +0 -0
- /office365/{directory/security → security}/workplace_analytics_audit_record.py +0 -0
- /office365/{directory/security → security}/yammer_audit_record.py +0 -0
- /office365/{directory/security → security}/yammer_user_hiding_audit_record.py +0 -0
- /office365/sharepoint/publishing/pages/{3d → _3d}/page.py +0 -0
- /office365/sharepoint/publishing/pages/{3d → _3d}/types.py +0 -0
- /office365/{intune/print → todo/tasks}/taskstatus.py +0 -0
- {office365_rest_python_client-3.2.0.dist-info → office365_rest_python_client-3.3.0.dist-info}/WHEEL +0 -0
|
@@ -12,20 +12,33 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import hashlib
|
|
14
14
|
import io
|
|
15
|
+
import json
|
|
15
16
|
from collections.abc import Callable
|
|
16
|
-
from datetime import timezone
|
|
17
|
-
from typing import TYPE_CHECKING, cast
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
19
|
+
|
|
20
|
+
from typing_extensions import TypeAlias
|
|
18
21
|
|
|
19
22
|
from office365.migration.adapters import MigrationProgress
|
|
20
|
-
from office365.migration.base import
|
|
23
|
+
from office365.migration.base import (
|
|
24
|
+
MigrationItem,
|
|
25
|
+
PermissionEntry,
|
|
26
|
+
Principal,
|
|
27
|
+
conflict_to_on_conflict,
|
|
28
|
+
normalize_puid,
|
|
29
|
+
)
|
|
30
|
+
from office365.migration.sharepoint.structure import normalize_guid
|
|
21
31
|
from office365.migration.sharepoint.transfer import Failure
|
|
22
|
-
from office365.runtime.converters.
|
|
32
|
+
from office365.runtime.converters.records import records_from_items
|
|
23
33
|
from office365.runtime.converters.scalars import iso_or_none, parse_datetime, parse_int
|
|
24
34
|
from office365.runtime.limits import DEFAULT_BATCH_SIZE
|
|
25
35
|
from office365.runtime.operations import emit_progress
|
|
26
36
|
from office365.sharepoint.fields.builtin_field_name import SYSTEM_FIELD_NAMES
|
|
37
|
+
from office365.sharepoint.fields.user_value import FieldUserValue
|
|
38
|
+
from office365.sharepoint.principal.type import PrincipalType
|
|
27
39
|
|
|
28
40
|
if TYPE_CHECKING:
|
|
41
|
+
from office365.migration.map import ContentTypeMap
|
|
29
42
|
from office365.sharepoint.files.file import File
|
|
30
43
|
from office365.sharepoint.folders.folder import Folder
|
|
31
44
|
from office365.sharepoint.lists.list import List as SPList
|
|
@@ -33,24 +46,496 @@ if TYPE_CHECKING:
|
|
|
33
46
|
|
|
34
47
|
_TAXONOMY_FIELD_TYPES = {"TaxonomyFieldType", "TaxonomyFieldTypeMulti"}
|
|
35
48
|
|
|
49
|
+
|
|
50
|
+
def _cloned_context(obj):
|
|
51
|
+
"""A fresh ``ClientContext`` clone for ``obj`` (same auth + transport, own queue)."""
|
|
52
|
+
ctx = obj.context
|
|
53
|
+
return ctx.clone(ctx.base_url)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _cloned_list(sp_list: "SPList", context=None) -> "SPList":
|
|
57
|
+
"""The same list, addressed by id and loaded on ``context`` (or a clone of it)."""
|
|
58
|
+
list_id = sp_list.id
|
|
59
|
+
if not list_id:
|
|
60
|
+
raise ValueError("cannot rebind a list that was never loaded")
|
|
61
|
+
ctx = context if context is not None else _cloned_context(sp_list)
|
|
62
|
+
return ctx.web.lists.get_by_id(list_id).get().execute_query()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _cloned_folder(folder: "Folder", context=None) -> "Folder":
|
|
66
|
+
"""The same folder, addressed by server-relative path and loaded on ``context``."""
|
|
67
|
+
server_relative_url = folder.server_relative_url
|
|
68
|
+
if not server_relative_url:
|
|
69
|
+
raise ValueError("cannot rebind a folder that was never loaded")
|
|
70
|
+
ctx = context if context is not None else _cloned_context(folder)
|
|
71
|
+
return ctx.web.get_folder_by_server_relative_path(server_relative_url).get().execute_query()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
#: The target's typed columns: ``(lookup: name -> referenced list id, user names,
|
|
75
|
+
#: multi-lookup: name -> referenced list id, multi-user names, taxonomy:
|
|
76
|
+
#: name -> (allows many, hidden note field internal name), multi-choice names)``.
|
|
77
|
+
_TypedColumns: TypeAlias = (
|
|
78
|
+
"tuple[dict[str, str], set[str], dict[str, str], set[str], dict[str, tuple[bool, str | None]], set[str]]"
|
|
79
|
+
)
|
|
80
|
+
|
|
36
81
|
# Always-projected system metadata (reliable author/editor identity).
|
|
37
|
-
_METADATA_SELECT = ["AuthorId", "EditorId"]
|
|
82
|
+
_METADATA_SELECT = ["AuthorId", "EditorId", "HasUniqueRoleAssignments"]
|
|
83
|
+
#: Projected system metadata that is read-only and must not be written back.
|
|
84
|
+
_NON_WRITABLE_SELECT = frozenset({"HasUniqueRoleAssignments"})
|
|
85
|
+
|
|
86
|
+
#: Navigation properties a ``*`` read returns that carry no scalar content and can
|
|
87
|
+
#: never be written back; they are dropped silently. A genuine typed value we
|
|
88
|
+
#: cannot remap (a lookup/person column) is still surfaced as a warning.
|
|
89
|
+
_NAVIGATION_SELECT = frozenset(
|
|
90
|
+
{
|
|
91
|
+
"ParentList",
|
|
92
|
+
"ContentType",
|
|
93
|
+
"Folder",
|
|
94
|
+
"File",
|
|
95
|
+
"FieldValuesAsHtml",
|
|
96
|
+
"FieldValuesAsText",
|
|
97
|
+
"FieldValuesForEdit",
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
#: Marker column added to a migrated list recording each item's source id. It makes
|
|
102
|
+
#: a re-run idempotent and lets post-run verification pair source and target items.
|
|
103
|
+
DEFAULT_LIST_IDENTITY_FIELD = "MigrationSourceId"
|
|
104
|
+
# Authorship has its own fidelity path (``apply_authorship``); the built-in Author/
|
|
105
|
+
# Editor companion columns are neither content nor Person values to remap.
|
|
106
|
+
_PERSON_EXCLUDED = frozenset({"Author", "Editor"})
|
|
107
|
+
|
|
108
|
+
#: Content-approval state: record key (OData escaped form) -> ``ValidateUpdateListItem``
|
|
109
|
+
#: field name. It is ordinary record content, but a *system update* re-approves the
|
|
110
|
+
#: item, so every metadata write that restores timestamps/authorship would silently
|
|
111
|
+
#: discard it; the captured state is therefore folded into that same write
|
|
112
|
+
#: (live-probed: both fields are accepted, and no version is created).
|
|
113
|
+
_APPROVAL_FIELDS = {
|
|
114
|
+
"OData__ModerationStatus": "_ModerationStatus",
|
|
115
|
+
"OData__ModerationComments": "_ModerationComments",
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#: Approval status of an approved item (0 = draft/pending, 1 = rejected, 2 = approved).
|
|
119
|
+
#: A freshly uploaded document is approved, so only the other states are worth writing.
|
|
120
|
+
_APPROVED_STATUS = "2"
|
|
121
|
+
|
|
122
|
+
#: Moderation columns of a list item. They only exist on a list whose
|
|
123
|
+
#: ``EnableModeration`` is on, so selecting them unconditionally fails outright.
|
|
124
|
+
_MODERATION_FIELDS = ("OData__ModerationStatus", "OData__ModerationComments")
|
|
125
|
+
|
|
126
|
+
#: How many paths a per-run warning names before it summarizes the rest.
|
|
127
|
+
_WARNING_SAMPLE = 5
|
|
128
|
+
|
|
129
|
+
#: Multi-value typed columns. Their ``<name>Id`` companion is a collection of ids
|
|
130
|
+
#: that a ``*`` projection omits (LookupMulti entirely; UserMulti unreliably), so the
|
|
131
|
+
#: source selects them explicitly and the target writes ``{"results": [...]}``.
|
|
132
|
+
_MULTI_VALUE_TYPES = frozenset({"LookupMulti", "UserMulti"})
|
|
133
|
+
|
|
134
|
+
#: Typed columns whose values are remapped (lookup item ids, person logins).
|
|
135
|
+
_TYPED_VALUE_TYPES = frozenset({"Lookup", "LookupMulti", "User", "UserMulti"})
|
|
136
|
+
|
|
137
|
+
#: Properties the source column scans need. Selecting them explicitly keeps the read
|
|
138
|
+
#: independent of whatever ``$select`` a previous caller left on the shared
|
|
139
|
+
#: ``fields`` collection — ``List.fields`` survives a ``capture_list_structure``
|
|
140
|
+
#: read (which selects a different, narrower set), and a field payload that omits
|
|
141
|
+
#: ``AllowMultipleValues`` makes a multi-value column look single-value.
|
|
142
|
+
_SOURCE_FIELD_SELECT = [
|
|
143
|
+
"Id",
|
|
144
|
+
"InternalName",
|
|
145
|
+
"TypeAsString",
|
|
146
|
+
"SchemaXml",
|
|
147
|
+
"Hidden",
|
|
148
|
+
"ReadOnlyField",
|
|
149
|
+
"TextField",
|
|
150
|
+
"AllowMultipleValues",
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
#: Choice column types; a multi-choice column stores a collection of strings.
|
|
155
|
+
_CHOICE_TYPES = frozenset({"Choice", "MultiChoice"})
|
|
156
|
+
|
|
157
|
+
#: Column types whose value is a small REST object instead of a scalar.
|
|
158
|
+
_VALUE_OBJECT_TYPES = frozenset({"URL"})
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _allows_multiple_values(field: Any) -> bool:
|
|
162
|
+
"""Whether a Lookup/Person column holds many values.
|
|
163
|
+
|
|
164
|
+
``TypeAsString`` is not authoritative here. A column created from a
|
|
165
|
+
``Type="Lookup" Mult="TRUE"`` (or ``Type="User" Mult="TRUE"``) schema keeps that
|
|
166
|
+
spelling, so SharePoint reports the *single-value* type and only
|
|
167
|
+
``AllowMultipleValues`` tells the truth (live-verified). Classifying such a
|
|
168
|
+
column as single-value left its collection of ids unremapped: a multi-lookup
|
|
169
|
+
kept source item ids and a multi-person kept source site-user ids on the target,
|
|
170
|
+
and verification could not see the difference.
|
|
171
|
+
"""
|
|
172
|
+
if getattr(field, "type_as_string", None) in _MULTI_VALUE_TYPES:
|
|
173
|
+
return True
|
|
174
|
+
return field.properties.get("AllowMultipleValues") is True
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _is_multi_choice_column(field: Any) -> bool:
|
|
178
|
+
"""Whether a Choice column stores a collection of values.
|
|
179
|
+
|
|
180
|
+
``TypeAsString == "MultiChoice"`` is the only reliable signal. Live-probed: a
|
|
181
|
+
column created from a ``Type="Choice" Mult="TRUE"`` schema reads back as
|
|
182
|
+
``"Choice"``, stores one value in a single ``nvarchar`` slot (a scalar write
|
|
183
|
+
round-trips, an array is rejected) and does not project ``AllowMultipleValues``
|
|
184
|
+
at all — its schema ``Mult`` flag is misleading, so it is deliberately not used.
|
|
185
|
+
"""
|
|
186
|
+
field_type = getattr(field, "type_as_string", None)
|
|
187
|
+
if field_type == "MultiChoice":
|
|
188
|
+
return True
|
|
189
|
+
return field_type in _CHOICE_TYPES and _allows_multiple_values(field)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _is_value_object_column(field: Any) -> bool:
|
|
193
|
+
"""Whether a column holds a REST value object rather than a scalar.
|
|
194
|
+
|
|
195
|
+
A Hyperlink column is the common case: a read returns
|
|
196
|
+
``{"Url": ..., "Description": ...}``, which a scalar-only record projection
|
|
197
|
+
dropped (a plain string is accepted on write but then makes every subsequent
|
|
198
|
+
*read* of the column fail, live-probed).
|
|
199
|
+
"""
|
|
200
|
+
return getattr(field, "type_as_string", None) in _VALUE_OBJECT_TYPES
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _scalar_mapping(value: Any) -> dict | None:
|
|
204
|
+
"""A value object as a plain mapping with scalar members, or ``None``.
|
|
205
|
+
|
|
206
|
+
Only a shallow mapping of scalars is accepted, so a navigation property (a
|
|
207
|
+
``ClientObject``) or a typed payload carrying a nested ``__metadata`` is never
|
|
208
|
+
mistaken for a column value.
|
|
209
|
+
"""
|
|
210
|
+
if not isinstance(value, dict) or not value:
|
|
211
|
+
return None
|
|
212
|
+
if any(item is not None and not isinstance(item, (str, int, float, bool)) for item in value.values()):
|
|
213
|
+
return None
|
|
214
|
+
return dict(value)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
#: Record columns excluded from the content checksum: server-assigned identity
|
|
218
|
+
#: (``Id``/``GUID``), facets verified separately (timestamps, authorship,
|
|
219
|
+
#: permissions), values the target remaps (``ContentTypeId``), and bookkeeping
|
|
220
|
+
#: (version label, server-derived embed URLs, the navigation ``ParentList``). What
|
|
221
|
+
#: remains is the item's *content* columns, which a faithful copy preserves.
|
|
222
|
+
_CONTENT_EXCLUDED = frozenset(
|
|
223
|
+
{
|
|
224
|
+
"Id",
|
|
225
|
+
"ID",
|
|
226
|
+
"GUID",
|
|
227
|
+
"FileSystemObjectType",
|
|
228
|
+
"Created",
|
|
229
|
+
"Modified",
|
|
230
|
+
"AuthorId",
|
|
231
|
+
"EditorId",
|
|
232
|
+
"HasUniqueRoleAssignments",
|
|
233
|
+
"ContentTypeId",
|
|
234
|
+
"OData__UIVersionString",
|
|
235
|
+
"_UIVersionString",
|
|
236
|
+
"ServerRedirectedEmbedUri",
|
|
237
|
+
"ServerRedirectedEmbedUrl",
|
|
238
|
+
"ParentList",
|
|
239
|
+
"AttachmentFiles",
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
#: Field types whose value is server-derived: a computed column (a link filename, a
|
|
244
|
+
#: doc icon) is a view of the item's identity, not metadata a folder carries.
|
|
245
|
+
_FOLDER_COMPUTED_TYPES = frozenset({"Attachments", "Computed", "File", "WorkflowStatus"})
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _folder_value_columns(fields: list[Any]) -> set[str]:
|
|
249
|
+
"""The columns a folder's own metadata can live in (writable, non-system).
|
|
250
|
+
|
|
251
|
+
A read of a folder also returns computed and read-only columns (link filenames,
|
|
252
|
+
compliance flags, version labels); writing those is rejected and comparing them
|
|
253
|
+
would only echo the folder's own name back.
|
|
254
|
+
"""
|
|
255
|
+
return {
|
|
256
|
+
field.internal_name
|
|
257
|
+
for field in fields
|
|
258
|
+
if field.internal_name
|
|
259
|
+
# An underscore-prefixed internal name is SharePoint's own storage column; it
|
|
260
|
+
# is not reliably projectable (the same rule the safe fallback projection uses).
|
|
261
|
+
and not field.internal_name.startswith("_")
|
|
262
|
+
and not field.hidden
|
|
263
|
+
and not field.read_only_field
|
|
264
|
+
and field.type_as_string not in _FOLDER_COMPUTED_TYPES
|
|
265
|
+
and field.internal_name not in _FOLDER_RECORD_EXCLUDED
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
#: Folder (document library) record columns that never travel to the target: the
|
|
270
|
+
#: server-assigned identity and path columns, system metadata (restored by the
|
|
271
|
+
#: fidelity steps), and the folder-specific bookkeeping a read projects. A
|
|
272
|
+
#: ``ContentTypeId`` is kept — a document set's content type is part of what makes
|
|
273
|
+
#: the folder what it is.
|
|
274
|
+
_FOLDER_RECORD_EXCLUDED = (_CONTENT_EXCLUDED - {"ContentTypeId"}) | {
|
|
275
|
+
"FileRef",
|
|
276
|
+
"FileDirRef",
|
|
277
|
+
"FileLeafRef",
|
|
278
|
+
"FSObjType",
|
|
279
|
+
"UniqueId",
|
|
280
|
+
"SortBehavior",
|
|
281
|
+
"SMTotalFileStreamSize",
|
|
282
|
+
"SMTotalFileCount",
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _content_checksum(record: dict, extra_excluded: "frozenset[str]" = frozenset()) -> str:
|
|
287
|
+
"""MD5 of an item's content columns in a stable, order-independent form.
|
|
288
|
+
|
|
289
|
+
The list source and target both use this so record content can be reconciled:
|
|
290
|
+
only scalar, multi-value and value-object columns are hashed (navigation
|
|
291
|
+
properties and excluded keys are dropped) and keys are sorted, so the two sides
|
|
292
|
+
agree regardless of property iteration order. A null column is treated as
|
|
293
|
+
absent: the source drops an empty multi-value column while the target reads it
|
|
294
|
+
back as ``None``, and both mean "no value".
|
|
295
|
+
"""
|
|
296
|
+
content = {}
|
|
297
|
+
for key, value in record.items():
|
|
298
|
+
if key in _CONTENT_EXCLUDED or key in extra_excluded or value is None:
|
|
299
|
+
continue
|
|
300
|
+
if isinstance(value, (str, int, float, bool, list, tuple)):
|
|
301
|
+
content[key] = value
|
|
302
|
+
continue
|
|
303
|
+
mapping = _scalar_mapping(value)
|
|
304
|
+
if mapping is not None:
|
|
305
|
+
content[key] = mapping
|
|
306
|
+
payload = json.dumps(content, sort_keys=True, default=str)
|
|
307
|
+
return hashlib.md5(payload.encode("utf-8")).hexdigest()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _folder_record_values(record: dict) -> dict:
|
|
311
|
+
"""The column values of a folder item that travel to (or compare with) the target.
|
|
312
|
+
|
|
313
|
+
The projection already narrows the read to the folder's own writable columns; here
|
|
314
|
+
only the identity/system columns are dropped, and an empty value is treated as
|
|
315
|
+
absent on both sides — a source read reports ``""`` where a target read reports
|
|
316
|
+
``None``, and both mean "no value".
|
|
317
|
+
"""
|
|
318
|
+
return {
|
|
319
|
+
key: value for key, value in record.items() if key not in _FOLDER_RECORD_EXCLUDED and value not in (None, "")
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _id_list(value: Any) -> list[Any] | None:
|
|
324
|
+
"""Normalize a multi-value ``...Id`` collection into a plain list of ids.
|
|
325
|
+
|
|
326
|
+
A live read returns the collection as a positional mapping (``{0: 1, 1: 2}``);
|
|
327
|
+
a payload this client built may hold ``{"results": [...]}``. Both reduce to the
|
|
328
|
+
same list, which is the form the checksum and the remap work with.
|
|
329
|
+
"""
|
|
330
|
+
if isinstance(value, dict):
|
|
331
|
+
if "results" in value:
|
|
332
|
+
value = value["results"]
|
|
333
|
+
else:
|
|
334
|
+
try:
|
|
335
|
+
return [value[i] for i in range(len(value))]
|
|
336
|
+
except (KeyError, TypeError):
|
|
337
|
+
return list(value.values())
|
|
338
|
+
if isinstance(value, (list, tuple)):
|
|
339
|
+
return list(value)
|
|
340
|
+
return None
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _multi_values(value: Any) -> list[Any] | None:
|
|
344
|
+
"""A multi-value cell as a plain list of its values, or ``None`` when empty.
|
|
345
|
+
|
|
346
|
+
Accepts every shape a live read or a built payload produces: a positional
|
|
347
|
+
mapping (``{0: "red", 1: "green"}``), a ``{"results": [...]}`` wrapper, a bare
|
|
348
|
+
list, and — because a collection cell may hold exactly one selection — a bare
|
|
349
|
+
scalar.
|
|
350
|
+
"""
|
|
351
|
+
if value is None:
|
|
352
|
+
return None
|
|
353
|
+
if isinstance(value, (str, int, float, bool)):
|
|
354
|
+
return [value]
|
|
355
|
+
return _id_list(value)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _taxonomy_terms(value: Any) -> list[dict[str, str]]:
|
|
359
|
+
"""Normalize a taxonomy value to portable ``{"TermGuid": ...}`` terms.
|
|
360
|
+
|
|
361
|
+
A live read shapes a single value as a dict (``{"Label", "TermGuid", "WssId"}``)
|
|
362
|
+
and a multi-value one as a positional mapping (``{0: {...}, 1: {...}}``); a
|
|
363
|
+
payload this client built holds a bare list. All reduce to a list of terms.
|
|
364
|
+
Only ``TermGuid`` is carried: a per-site ``WssId`` is meaningless elsewhere and
|
|
365
|
+
``Label`` is not authoritative (a read may return the WssId string, and the
|
|
366
|
+
server resolves a write by term id regardless of the label).
|
|
367
|
+
"""
|
|
368
|
+
if value is None:
|
|
369
|
+
return []
|
|
370
|
+
if isinstance(value, dict):
|
|
371
|
+
if "TermGuid" in value:
|
|
372
|
+
items: list[Any] = [value]
|
|
373
|
+
elif "results" in value and isinstance(value["results"], (list, tuple)):
|
|
374
|
+
items = list(value["results"])
|
|
375
|
+
else:
|
|
376
|
+
try:
|
|
377
|
+
items = [value[i] for i in range(len(value))]
|
|
378
|
+
except (KeyError, TypeError):
|
|
379
|
+
items = list(value.values())
|
|
380
|
+
elif isinstance(value, (list, tuple)):
|
|
381
|
+
items = list(value)
|
|
382
|
+
else:
|
|
383
|
+
return []
|
|
384
|
+
terms: list[dict[str, str]] = []
|
|
385
|
+
for item in items:
|
|
386
|
+
if isinstance(item, dict):
|
|
387
|
+
guid = item.get("TermGuid")
|
|
388
|
+
else:
|
|
389
|
+
guid = getattr(item, "TermGuid", None)
|
|
390
|
+
if guid:
|
|
391
|
+
terms.append({"TermGuid": str(guid)})
|
|
392
|
+
return terms
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _is_taxonomy_value(value: Any) -> bool:
|
|
396
|
+
"""Whether a projected value is a taxonomy term (single dict or multi mapping/list)."""
|
|
397
|
+
return isinstance(value, (dict, list, tuple)) and bool(_taxonomy_terms(value))
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _taxonomy_guids(value: Any) -> list[str]:
|
|
401
|
+
return [term["TermGuid"] for term in _taxonomy_terms(value)]
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _canonicalize_taxonomy_record(record: dict) -> dict:
|
|
405
|
+
"""Replace taxonomy values with a sorted list of term ids for comparison.
|
|
406
|
+
|
|
407
|
+
``Label`` is cosmetic and ``WssId`` is site-local, so neither belongs in the
|
|
408
|
+
content checksum; the term id is the portable identity. Sorting also keeps the
|
|
409
|
+
checksum independent of the order a read happens to return the terms in.
|
|
410
|
+
"""
|
|
411
|
+
if not any(_is_taxonomy_value(value) for value in record.values()):
|
|
412
|
+
return record
|
|
413
|
+
canonical = dict(record)
|
|
414
|
+
for key, value in record.items():
|
|
415
|
+
if _is_taxonomy_value(value):
|
|
416
|
+
canonical[key] = sorted(_taxonomy_guids(value))
|
|
417
|
+
return canonical
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _collection_payload(ids: list) -> dict:
|
|
421
|
+
"""The JSON shape SharePoint accepts when *creating* a multi-value column.
|
|
422
|
+
|
|
423
|
+
Live-probed: a bare array is rejected on create
|
|
424
|
+
(``InvalidClientQueryException``: "a 'PrimitiveValue' or 'StartObject' node was
|
|
425
|
+
expected") while ``{"results": [...]}`` is accepted. A MERGE *update* is the
|
|
426
|
+
opposite — it accepts the bare array and rejects ``{"results": [...]}`` — so
|
|
427
|
+
only the create path goes through this helper.
|
|
428
|
+
"""
|
|
429
|
+
return {"results": ids}
|
|
38
430
|
|
|
39
431
|
|
|
40
|
-
def
|
|
41
|
-
"""Normalize
|
|
432
|
+
def _sp_utc_timestamp(value: str | None) -> str | None:
|
|
433
|
+
"""Normalize a timestamp to the UTC form a *list item* honors.
|
|
42
434
|
|
|
43
|
-
``ValidateUpdateListItem``
|
|
44
|
-
|
|
435
|
+
Live-probed: ``ValidateUpdateListItem`` on a plain list item parses an
|
|
436
|
+
ISO-8601 value with a trailing ``Z`` as UTC (and ``datesInUTC=True`` keeps it
|
|
437
|
+
that way), so this form round-trips the exact instant.
|
|
45
438
|
"""
|
|
46
439
|
if not value:
|
|
47
440
|
return None
|
|
48
441
|
parsed = parse_datetime(value)
|
|
49
442
|
if parsed is None:
|
|
50
443
|
return value
|
|
51
|
-
if parsed.tzinfo is
|
|
52
|
-
parsed = parsed.
|
|
53
|
-
return parsed.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
444
|
+
if parsed.tzinfo is None:
|
|
445
|
+
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
446
|
+
return parsed.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _sp_local_timestamp(value: datetime | str | None) -> str | None:
|
|
450
|
+
"""Format a *web-local* value as the date form a document item accepts.
|
|
451
|
+
|
|
452
|
+
Live-probed: document-library items ignore ``datesInUTC`` and parse every date
|
|
453
|
+
string in the web's locale, rejecting a trailing ``Z`` (``...must specify a
|
|
454
|
+
valid date within the range of 1/1/1900 and 12/31/8900.``). The space-separated
|
|
455
|
+
``YYYY-MM-DD HH:MM:SS`` form is accepted and interpreted as local wall-clock,
|
|
456
|
+
so callers must pass the instant already converted to web-local time (see
|
|
457
|
+
``SharePointLibraryTarget._local_timestamp``).
|
|
458
|
+
"""
|
|
459
|
+
if value is None:
|
|
460
|
+
return None
|
|
461
|
+
if isinstance(value, datetime):
|
|
462
|
+
return value.strftime("%Y-%m-%d %H:%M:%S")
|
|
463
|
+
return str(value)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _report_rejected_fields(result, warnings: list[str], label: str) -> None:
|
|
467
|
+
"""Warn about ``ValidateUpdateListItem`` fields the server rejected.
|
|
468
|
+
|
|
469
|
+
A rejected field does not raise over HTTP — the response carries a per-field
|
|
470
|
+
``HasException``/``ErrorMessage`` — and worse, SharePoint rolls the whole batch
|
|
471
|
+
back, so a sibling ``Created``/``Modified`` write is discarded too. Surfacing it
|
|
472
|
+
keeps that invisible loss honest (and testable).
|
|
473
|
+
"""
|
|
474
|
+
values = getattr(result, "value", None) or []
|
|
475
|
+
for value in values:
|
|
476
|
+
if getattr(value, "HasException", False):
|
|
477
|
+
warnings.append(f"{label}: {value.FieldName} rejected ({value.ErrorMessage})")
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _moderation_state(properties: dict) -> dict:
|
|
481
|
+
"""The approval fields of a list item, in ``ValidateUpdateListItem`` form.
|
|
482
|
+
|
|
483
|
+
Only present ones are returned: an empty ``_ModerationComments`` normalizes to
|
|
484
|
+
``None`` server-side, so a blank comment is not carried.
|
|
485
|
+
"""
|
|
486
|
+
status = properties.get("OData__ModerationStatus")
|
|
487
|
+
state: dict = {}
|
|
488
|
+
if status is not None:
|
|
489
|
+
state["_ModerationStatus"] = str(status)
|
|
490
|
+
comments = properties.get("OData__ModerationComments")
|
|
491
|
+
if comments:
|
|
492
|
+
state["_ModerationComments"] = str(comments)
|
|
493
|
+
return state
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _version_sort_key(label: str) -> tuple[int, ...]:
|
|
497
|
+
"""Sort key for a version label like ``"2.0"`` (numeric parts, non-numeric last)."""
|
|
498
|
+
try:
|
|
499
|
+
return tuple(int(part) for part in str(label).split("."))
|
|
500
|
+
except ValueError:
|
|
501
|
+
return (0,)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
#: Library-relative paths SharePoint owns and recreates on the target (its forms
|
|
505
|
+
#: folder and the folder rendering file). Migrating them collides server-side and
|
|
506
|
+
#: clobbers system pages client-side, so the source adapter skips them.
|
|
507
|
+
_LIBRARY_SYSTEM_PATHS = ("Forms", "desktop.ini")
|
|
508
|
+
|
|
509
|
+
#: SiteAssets is the one library that also holds per-site infrastructure — the
|
|
510
|
+
#: site icon and the auto-provisioned OneNote folder.
|
|
511
|
+
_SITE_ASSETS_URL_SUFFIX = "/SiteAssets"
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _site_assets_scaffolding(folder) -> tuple[str, ...]:
|
|
515
|
+
"""SiteAssets-only SharePoint-owned entries for this web (empty otherwise).
|
|
516
|
+
|
|
517
|
+
Every modern site provisions ``SiteAssets/<web title> Notebook``; migrating
|
|
518
|
+
it (or reporting it) yields a phantom missing/extra pair because the target's
|
|
519
|
+
folder carries the *target* web's name. The site icon is site branding whose
|
|
520
|
+
bytes are meaningless on another site. Both are skipped on each side.
|
|
521
|
+
"""
|
|
522
|
+
url = (folder.server_relative_url or "").rstrip("/")
|
|
523
|
+
if not url.endswith(_SITE_ASSETS_URL_SUFFIX):
|
|
524
|
+
return ()
|
|
525
|
+
web = folder.context.web
|
|
526
|
+
if not web.properties.get("Title"):
|
|
527
|
+
web.ensure_properties(["Title"]).execute_query()
|
|
528
|
+
title = web.title
|
|
529
|
+
names = ["__siteIcon__.png"]
|
|
530
|
+
if title:
|
|
531
|
+
names.append(f"{title} Notebook")
|
|
532
|
+
return tuple(names)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def _is_system_path(rel: str, extra: tuple[str, ...] = ()) -> bool:
|
|
536
|
+
"""Whether a library-relative path is SharePoint-owned scaffolding, not user content."""
|
|
537
|
+
entries = (*_LIBRARY_SYSTEM_PATHS, *extra)
|
|
538
|
+
return any(rel == entry or rel.startswith(entry + "/") for entry in entries)
|
|
54
539
|
|
|
55
540
|
|
|
56
541
|
def is_taxonomy_validation(exc: Exception) -> bool:
|
|
@@ -78,13 +563,367 @@ def taxonomy_internal_names(fields) -> list[str]:
|
|
|
78
563
|
return list(dict.fromkeys(names))
|
|
79
564
|
|
|
80
565
|
|
|
566
|
+
def _perm_mask(role_definition: Any) -> str | None:
|
|
567
|
+
"""The decimal ``PermMask`` of a role definition (``BasePermissions`` High/Low)."""
|
|
568
|
+
base = getattr(role_definition, "base_permissions", None)
|
|
569
|
+
if base is None:
|
|
570
|
+
return None
|
|
571
|
+
try:
|
|
572
|
+
return str((int(base.High) << 32) | int(base.Low))
|
|
573
|
+
except (TypeError, ValueError):
|
|
574
|
+
return None
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def _is_user_principal(member: Any) -> bool:
|
|
578
|
+
"""Whether a role-assignment member is a user (not a group)."""
|
|
579
|
+
ptype = getattr(member, "principal_type", None)
|
|
580
|
+
if ptype is None:
|
|
581
|
+
return False
|
|
582
|
+
if ptype == PrincipalType.User:
|
|
583
|
+
return True
|
|
584
|
+
# ``PrincipalType`` is a plain ``Enum`` (not ``IntEnum``); accept a raw int too.
|
|
585
|
+
return getattr(ptype, "value", ptype) == PrincipalType.User.value
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _is_system_principal(user: Any) -> bool:
|
|
589
|
+
"""Whether a resolved user is a tenant system/app account.
|
|
590
|
+
|
|
591
|
+
The Migration API cannot set ``Author``/``Editor`` to a system account, and a
|
|
592
|
+
``ValidateUpdateListItem`` batch containing one is rejected wholesale — which
|
|
593
|
+
would silently discard the ``Created``/``Modified`` restoration we send in the
|
|
594
|
+
same call. Items created by the migration app itself are authored by such an
|
|
595
|
+
account, so setting it is a no-op anyway; skipping it keeps the timestamp write
|
|
596
|
+
intact.
|
|
597
|
+
"""
|
|
598
|
+
login = (getattr(user, "login_name", None) or "").lower()
|
|
599
|
+
return "app@sharepoint" in login or "sharepoint\\system" in login
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def _has_unique_role_assignments(securable, *, refresh: bool = False) -> bool:
|
|
603
|
+
"""Whether a securable has unique (broken-inheritance) role assignments.
|
|
604
|
+
|
|
605
|
+
SharePoint REST returns *inherited* role assignments too, so reading them
|
|
606
|
+
verbatim would break inheritance on every item and replicate the parent ACL.
|
|
607
|
+
Only items with unique assignments are read/applied (and compared by the
|
|
608
|
+
verifier); inherited items stay inherited on the target.
|
|
609
|
+
|
|
610
|
+
A plain entity read does not return the flag, so an object loaded *before* its
|
|
611
|
+
ACL changed keeps a stale value. The target side passes ``refresh=True``: this
|
|
612
|
+
value decides whether an ACL is applied or compared at all, so a stale
|
|
613
|
+
``False`` would break inheritance on an item that is already unique (wiping the
|
|
614
|
+
assignments it was supposed to keep) and a stale ``False`` read back after a
|
|
615
|
+
write would report a permission mismatch for an ACL that did land.
|
|
616
|
+
"""
|
|
617
|
+
unique = getattr(securable, "has_unique_role_assignments", None)
|
|
618
|
+
if unique is not None and not refresh:
|
|
619
|
+
return bool(unique)
|
|
620
|
+
options = getattr(securable, "query_options", None)
|
|
621
|
+
previous = list(getattr(options, "select", None) or [])
|
|
622
|
+
try:
|
|
623
|
+
securable.select(["HasUniqueRoleAssignments"]).get().execute_query()
|
|
624
|
+
except Exception: # noqa: BLE001 — an unreadable flag is treated as "not unique"
|
|
625
|
+
return False
|
|
626
|
+
finally:
|
|
627
|
+
if options is not None:
|
|
628
|
+
# The projection lives on the object's query options: leaving it on
|
|
629
|
+
# would narrow every later read of the same item.
|
|
630
|
+
options.select = previous
|
|
631
|
+
return bool(securable.properties.get("HasUniqueRoleAssignments"))
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _replay_role_assignments(
|
|
635
|
+
securable,
|
|
636
|
+
principals: "_TargetPrincipals",
|
|
637
|
+
permissions: list[PermissionEntry],
|
|
638
|
+
warnings: list[str],
|
|
639
|
+
label: str,
|
|
640
|
+
) -> None:
|
|
641
|
+
"""Replay an item's unique ACLs onto a target item: additive, never destructive.
|
|
642
|
+
|
|
643
|
+
Every principal is resolved on the target first (users via ``EnsureUser``,
|
|
644
|
+
groups by name, default groups by role kind). Inheritance is broken only when
|
|
645
|
+
the target item still inherits *and* at least one assignment resolved: an item
|
|
646
|
+
whose every principal is unresolvable (a cross-tenant account, an app
|
|
647
|
+
principal) would otherwise end up with an **empty** ACL — inheritance broken,
|
|
648
|
+
nobody granted — which is worse than leaving it inheriting, so that case is
|
|
649
|
+
reported and the item is left alone.
|
|
650
|
+
|
|
651
|
+
A re-run therefore adds only what is missing and never removes an assignment
|
|
652
|
+
the source does not grant. The caller executes the queued requests.
|
|
653
|
+
"""
|
|
654
|
+
resolved: list[tuple[Any, Any]] = []
|
|
655
|
+
for entry in permissions:
|
|
656
|
+
principal = principals.principal(entry.principal_name)
|
|
657
|
+
if principal is None:
|
|
658
|
+
continue
|
|
659
|
+
for role_name in entry.roles:
|
|
660
|
+
role = principals.role(role_name)
|
|
661
|
+
if role is None:
|
|
662
|
+
warnings.append(f"permissions {label}: could not resolve role {role_name!r}")
|
|
663
|
+
continue
|
|
664
|
+
resolved.append((principal, role))
|
|
665
|
+
if not resolved:
|
|
666
|
+
warnings.append(
|
|
667
|
+
f"permissions for {label!r} were not restored: no assignment could be resolved on the "
|
|
668
|
+
"target, and breaking inheritance would leave the item with an empty ACL — it still inherits"
|
|
669
|
+
)
|
|
670
|
+
return
|
|
671
|
+
if not _has_unique_role_assignments(securable, refresh=True):
|
|
672
|
+
securable.break_role_inheritance(copy_role_assignments=False, clear_sub_scopes=False)
|
|
673
|
+
for principal, role in resolved:
|
|
674
|
+
securable.add_role_assignment(principal, role)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def _user_login(web, user_id: Any, cache: dict[int, str | None]) -> str | None:
|
|
678
|
+
"""Resolve a target user id to its login name, cached."""
|
|
679
|
+
key = parse_int(user_id)
|
|
680
|
+
if key is None:
|
|
681
|
+
return None
|
|
682
|
+
if key in cache:
|
|
683
|
+
return cache[key]
|
|
684
|
+
login: str | None = None
|
|
685
|
+
try:
|
|
686
|
+
user = web.site_users.get_by_id(key).get().execute_query()
|
|
687
|
+
login = user.login_name or None
|
|
688
|
+
except Exception: # noqa: BLE001 — an unresolvable id yields no login
|
|
689
|
+
login = None
|
|
690
|
+
cache[key] = login
|
|
691
|
+
return login
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
class _MultiValueCreateTarget:
|
|
695
|
+
"""An upsert target that reshapes multi-value columns only when creating.
|
|
696
|
+
|
|
697
|
+
A multi-value Lookup/Person column is created with the ``{"results": [...]}``
|
|
698
|
+
wire shape but updated with a bare array (live-probed), so ``keyed_queue`` needs
|
|
699
|
+
to reshape the creates and leave the updates alone. Everything else delegates.
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
def __init__(self, target: Any, reshape: Callable[[dict], dict]) -> None:
|
|
703
|
+
self._target = target
|
|
704
|
+
self._reshape = reshape
|
|
705
|
+
|
|
706
|
+
@property
|
|
707
|
+
def key_field(self) -> str:
|
|
708
|
+
return self._target.key_field
|
|
709
|
+
|
|
710
|
+
def ensure_key_field(self) -> None:
|
|
711
|
+
self._target.ensure_key_field()
|
|
712
|
+
|
|
713
|
+
def load_keys(self) -> Any:
|
|
714
|
+
return self._target.load_keys()
|
|
715
|
+
|
|
716
|
+
def create_records(self, records: list[dict]) -> None:
|
|
717
|
+
self._target.create_records([self._reshape(record) for record in records])
|
|
718
|
+
|
|
719
|
+
def update_record(self, item_id: Any, record: dict) -> None:
|
|
720
|
+
self._target.update_record(item_id, record)
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
class _DefaultGroups:
|
|
724
|
+
"""Maps a web's associated default groups (Owners/Members/Visitors).
|
|
725
|
+
|
|
726
|
+
Default groups are titled after the site (``team Owners``), so a source and
|
|
727
|
+
target in the same tenant rarely share the exact name. These groups are used
|
|
728
|
+
both to resolve a source name onto the target *and* to normalize the name to a
|
|
729
|
+
canonical ``default:<Kind>`` token so the permission diff compares like with
|
|
730
|
+
like (a renamed site still verifies as preserved). Loaded once, lazily.
|
|
731
|
+
"""
|
|
732
|
+
|
|
733
|
+
ATTRS = (
|
|
734
|
+
("Owners", "associated_owner_group"),
|
|
735
|
+
("Members", "associated_member_group"),
|
|
736
|
+
("Visitors", "associated_visitor_group"),
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
def __init__(self, web) -> None:
|
|
740
|
+
self._web = web
|
|
741
|
+
self._by_kind: dict[str, Any] | None = None
|
|
742
|
+
self._by_title: dict[str, str] = {}
|
|
743
|
+
|
|
744
|
+
def _load(self) -> None:
|
|
745
|
+
if self._by_kind is not None:
|
|
746
|
+
return
|
|
747
|
+
self._by_kind = {}
|
|
748
|
+
for kind, attr in self.ATTRS:
|
|
749
|
+
group = getattr(self._web, attr, None)
|
|
750
|
+
if group is None:
|
|
751
|
+
continue
|
|
752
|
+
try:
|
|
753
|
+
group.get().execute_query()
|
|
754
|
+
except Exception: # noqa: BLE001 — an unreadable group is skipped
|
|
755
|
+
continue
|
|
756
|
+
self._by_kind[kind] = group
|
|
757
|
+
title = getattr(group, "title", None) or getattr(group, "login_name", None)
|
|
758
|
+
if title:
|
|
759
|
+
self._by_title[title] = kind
|
|
760
|
+
|
|
761
|
+
def group(self, kind: str):
|
|
762
|
+
self._load()
|
|
763
|
+
return self._by_kind.get(kind) if self._by_kind else None
|
|
764
|
+
|
|
765
|
+
def normalize(self, name: str) -> str:
|
|
766
|
+
self._load()
|
|
767
|
+
kind = self._by_title.get(name)
|
|
768
|
+
return f"default:{kind}" if kind else name
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
class _TargetPrincipals:
|
|
772
|
+
"""Resolve source principals and role definitions on a target web (cached).
|
|
773
|
+
|
|
774
|
+
Users are ensured on the site (``EnsureUser``) so their numeric id can be used
|
|
775
|
+
for authorship and role assignments; groups are matched by name, and the
|
|
776
|
+
default SharePoint groups (Owners/Members/Visitors) fall back to the target
|
|
777
|
+
web's associated groups so a renamed site (``team`` -> ``team_out``) still
|
|
778
|
+
maps its role assignments. Anything that cannot be resolved is skipped with a
|
|
779
|
+
warning (same-tenant, best-effort).
|
|
780
|
+
"""
|
|
781
|
+
|
|
782
|
+
def __init__(self, web, warnings: list[str]) -> None:
|
|
783
|
+
self._web = web
|
|
784
|
+
self._warnings = warnings
|
|
785
|
+
self._users: dict[str, Any] = {}
|
|
786
|
+
self._roles: "dict[str, RoleDefinition] | None" = None
|
|
787
|
+
self._defaults = _DefaultGroups(web)
|
|
788
|
+
|
|
789
|
+
@staticmethod
|
|
790
|
+
def _strip_claims(name: str) -> str:
|
|
791
|
+
return name.rsplit("|", 1)[-1] if name.startswith("i:0#") else name
|
|
792
|
+
|
|
793
|
+
def normalize(self, name: str) -> str:
|
|
794
|
+
"""Canonical principal name (default groups collapse to ``default:<Kind>``)."""
|
|
795
|
+
return self._defaults.normalize(name)
|
|
796
|
+
|
|
797
|
+
def user(self, login: str | None):
|
|
798
|
+
"""Ensure a user login on the target site and return its ``User`` object."""
|
|
799
|
+
if not login:
|
|
800
|
+
return None
|
|
801
|
+
login = self._strip_claims(login)
|
|
802
|
+
if login in self._users:
|
|
803
|
+
return self._users[login]
|
|
804
|
+
obj = None
|
|
805
|
+
try:
|
|
806
|
+
obj = self._web.ensure_user(login).execute_query()
|
|
807
|
+
except Exception as e: # noqa: BLE001 — an unresolvable principal is skipped
|
|
808
|
+
self._warnings.append(f"could not ensure target user {login!r}: {e}")
|
|
809
|
+
self._users[login] = obj
|
|
810
|
+
return obj
|
|
811
|
+
|
|
812
|
+
def principal(self, name: str):
|
|
813
|
+
"""Resolve a source principal (user or group) to a target principal object."""
|
|
814
|
+
if not name:
|
|
815
|
+
return None
|
|
816
|
+
login = self._strip_claims(name)
|
|
817
|
+
if login.startswith("default:"):
|
|
818
|
+
return self._defaults.group(login.split(":", 1)[1])
|
|
819
|
+
if "@" in login:
|
|
820
|
+
return self.user(login)
|
|
821
|
+
group = None
|
|
822
|
+
try:
|
|
823
|
+
group = self._web.site_groups.get_by_name(login).get().execute_query()
|
|
824
|
+
except Exception: # noqa: BLE001 — fall back to the default groups below
|
|
825
|
+
group = None
|
|
826
|
+
if group is None:
|
|
827
|
+
kind = login.rsplit(" ", 1)[-1]
|
|
828
|
+
if kind in {"Owners", "Members", "Visitors"}:
|
|
829
|
+
group = self._defaults.group(kind)
|
|
830
|
+
if group is not None:
|
|
831
|
+
return group
|
|
832
|
+
self._warnings.append(f"could not resolve principal {name!r} on the target")
|
|
833
|
+
return None
|
|
834
|
+
|
|
835
|
+
def role(self, name: str) -> "RoleDefinition | None":
|
|
836
|
+
"""Resolve (and cache) a target role definition by name."""
|
|
837
|
+
if self._roles is None:
|
|
838
|
+
loaded = self._web.role_definitions.get().execute_query()
|
|
839
|
+
self._roles = {rd.name: rd for rd in loaded if rd.name}
|
|
840
|
+
return self._roles.get(name)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def _read_role_assignments(
|
|
844
|
+
securable,
|
|
845
|
+
resolve: Callable[[Any], "Principal | None"] | None = None,
|
|
846
|
+
normalize: Callable[[str], str] | None = None,
|
|
847
|
+
) -> list[PermissionEntry]:
|
|
848
|
+
"""Read a securable object's role assignments into ``PermissionEntry``s.
|
|
849
|
+
|
|
850
|
+
One entry per principal (user or group), keyed by login name; the target uses
|
|
851
|
+
the same shape so the permission diff compares like with like. ``normalize``
|
|
852
|
+
(when supplied) canonicalizes a principal name — default groups collapse to
|
|
853
|
+
``default:<Kind>`` so a renamed site still compares as preserved. Role names are
|
|
854
|
+
paired with their decimal ``PermMask`` (``role_masks``), and — when ``resolve``
|
|
855
|
+
is supplied — a user principal is enriched with its PUID/display name/email so
|
|
856
|
+
the server-side package target can emit a faithful ``UserGroup.xml`` entry.
|
|
857
|
+
"""
|
|
858
|
+
assignments = securable.role_assignments.expand(["Member", "RoleDefinitionBindings"]).get().execute_query()
|
|
859
|
+
result: list[PermissionEntry] = []
|
|
860
|
+
for assignment in assignments:
|
|
861
|
+
member = assignment.member
|
|
862
|
+
name = member.login_name or member.user_principal_name
|
|
863
|
+
if not name:
|
|
864
|
+
continue
|
|
865
|
+
roles: list[str] = []
|
|
866
|
+
role_masks: dict[str, str] = {}
|
|
867
|
+
for role in assignment.role_definition_bindings:
|
|
868
|
+
if not role.name:
|
|
869
|
+
continue
|
|
870
|
+
roles.append(role.name)
|
|
871
|
+
mask = _perm_mask(role)
|
|
872
|
+
if mask is not None:
|
|
873
|
+
role_masks[role.name] = mask
|
|
874
|
+
entry = PermissionEntry(
|
|
875
|
+
principal_name=normalize(name) if normalize is not None else name,
|
|
876
|
+
roles=roles,
|
|
877
|
+
role_masks=role_masks,
|
|
878
|
+
)
|
|
879
|
+
if resolve is not None and _is_user_principal(member):
|
|
880
|
+
principal = resolve(getattr(member, "id", None))
|
|
881
|
+
if principal is not None:
|
|
882
|
+
entry.system_id = principal.system_id
|
|
883
|
+
entry.display_name = principal.display_name
|
|
884
|
+
entry.email = principal.email
|
|
885
|
+
result.append(entry)
|
|
886
|
+
return result
|
|
887
|
+
|
|
888
|
+
|
|
81
889
|
class SharePointListSource:
|
|
82
890
|
"""Enumerates a SharePoint list's items into record migration items."""
|
|
83
891
|
|
|
892
|
+
# Marks this source as emitting list-item attachments as extra items (see DataSource).
|
|
893
|
+
supports_attachments = True
|
|
894
|
+
|
|
895
|
+
# Marks this source as annotating Person columns with a portable login name so the
|
|
896
|
+
# target can resolve them (see ``_annotate_person_logins``).
|
|
897
|
+
supports_typed_values = True
|
|
898
|
+
|
|
899
|
+
def rebind(self) -> "SharePointListSource":
|
|
900
|
+
"""This source on its own cloned context, so another thread can drive it.
|
|
901
|
+
|
|
902
|
+
A context owns one pending-query queue: two adapters sharing it interleave
|
|
903
|
+
their deferred queries. Re-addressing the same list (by id) on a ``clone``
|
|
904
|
+
— which shares the authenticated transport — lets a session run several
|
|
905
|
+
list jobs in parallel over one connection pool. Read caches are per-run and
|
|
906
|
+
deliberately not carried over.
|
|
907
|
+
"""
|
|
908
|
+
return SharePointListSource(_cloned_list(self._list), self._select)
|
|
909
|
+
|
|
84
910
|
def __init__(self, source_list: "SPList", select: list[str] | None = None) -> None:
|
|
85
911
|
self._list = source_list
|
|
86
912
|
self._select = select
|
|
87
913
|
self._records: dict[str, dict] = {}
|
|
914
|
+
self._items: dict[str, Any] = {}
|
|
915
|
+
self._attachments: dict[str, Any] = {}
|
|
916
|
+
self._users: dict[int, Principal | None] = {}
|
|
917
|
+
self._default_groups: _DefaultGroups | None = None
|
|
918
|
+
self._source_fields_cache: list[Any] | None = None
|
|
919
|
+
self._user_columns_cache: set[str] | None = None
|
|
920
|
+
self._multi_columns_cache: dict[str, str] | None = None
|
|
921
|
+
self._skipped_columns_cache: set[str] | None = None
|
|
922
|
+
self._taxonomy_columns_cache: dict[str, bool] | None = None
|
|
923
|
+
self._taxonomy_note_columns_cache: set[str] | None = None
|
|
924
|
+
self._multi_choice_columns_cache: set[str] | None = None
|
|
925
|
+
self._value_object_columns_cache: set[str] | None = None
|
|
926
|
+
self._folder_records_cache: dict[str, dict] | None = None
|
|
88
927
|
self.warnings: list[str] = []
|
|
89
928
|
|
|
90
929
|
def label(self) -> str:
|
|
@@ -112,12 +951,29 @@ class SharePointListSource:
|
|
|
112
951
|
loaded = self._load_items(["Id", "Title"])
|
|
113
952
|
return self._project(loaded, progress)
|
|
114
953
|
|
|
115
|
-
def _load_items(self, select: list[str] | None):
|
|
954
|
+
def _load_items(self, select: list[str] | None, folder_only: bool = False):
|
|
116
955
|
items = self._list.items
|
|
117
956
|
# A full read projects ``*`` plus the system metadata so author/editor ids
|
|
118
|
-
# are captured
|
|
119
|
-
|
|
120
|
-
|
|
957
|
+
# and the unique-ACL flag are captured in one query (``*`` omits them);
|
|
958
|
+
# an explicit/safe projection is honored as-is.
|
|
959
|
+
if select is None:
|
|
960
|
+
columns = ["*", *_METADATA_SELECT]
|
|
961
|
+
# A ``*`` projection also omits multi-value Lookup ids (and returns
|
|
962
|
+
# multi-person ids only inconsistently); select each ``<name>Id``
|
|
963
|
+
# explicitly so the values are not silently lost.
|
|
964
|
+
for name in self._multi_columns():
|
|
965
|
+
key = f"{name}Id"
|
|
966
|
+
if key not in columns:
|
|
967
|
+
columns.append(key)
|
|
968
|
+
# Expand attachments so they can be migrated alongside their item.
|
|
969
|
+
query = items.select(columns).expand(["AttachmentFiles"])
|
|
970
|
+
if folder_only:
|
|
971
|
+
query = query.filter("FSObjType eq 1")
|
|
972
|
+
return query.get_all().execute_query()
|
|
973
|
+
query = items.select(select)
|
|
974
|
+
if folder_only:
|
|
975
|
+
query = query.filter("FSObjType eq 1")
|
|
976
|
+
return query.get_all().execute_query()
|
|
121
977
|
|
|
122
978
|
def _safe_select(self) -> list[str]:
|
|
123
979
|
"""Fallback projection: visible, non-system columns minus Managed Metadata.
|
|
@@ -142,34 +998,487 @@ class SharePointListSource:
|
|
|
142
998
|
return columns
|
|
143
999
|
|
|
144
1000
|
def _project(self, loaded, progress: MigrationProgress) -> list[MigrationItem]:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# Reuse the shared record projection (raw values, no JSON coercion) so the
|
|
148
|
-
# migration payload matches the data pipeline's.
|
|
149
|
-
records = iter_records(loaded, raw=True)
|
|
1001
|
+
records = self._project_records(loaded)
|
|
150
1002
|
result: list[MigrationItem] = []
|
|
1003
|
+
pending_attachments: list[tuple[str, Any]] = []
|
|
151
1004
|
for item, record in zip(loaded, records):
|
|
152
|
-
|
|
1005
|
+
item_id = str(item.id)
|
|
1006
|
+
self._records[item_id] = record
|
|
1007
|
+
self._items[item_id] = item
|
|
1008
|
+
author_id = parse_int(record.get("AuthorId"))
|
|
1009
|
+
editor_id = parse_int(record.get("EditorId"))
|
|
1010
|
+
planned = MigrationItem(
|
|
1011
|
+
source_path=f"{self._list.title}/{item_id}",
|
|
1012
|
+
dest_path=item_id,
|
|
1013
|
+
item_type="record",
|
|
1014
|
+
modified=iso_or_none(record.get("Modified")),
|
|
1015
|
+
created=iso_or_none(record.get("Created")),
|
|
1016
|
+
author_id=author_id,
|
|
1017
|
+
editor_id=editor_id,
|
|
1018
|
+
)
|
|
1019
|
+
# Resolve the source author/editor once so an authorship-preserving
|
|
1020
|
+
# target can restore them by login name (ids are tenant-local).
|
|
1021
|
+
if author_id is not None:
|
|
1022
|
+
planned.author = self._principal_for(author_id)
|
|
1023
|
+
if editor_id is not None:
|
|
1024
|
+
planned.editor = self._principal_for(editor_id)
|
|
1025
|
+
result.append(planned)
|
|
1026
|
+
for attachment in item.properties.get("AttachmentFiles") or []:
|
|
1027
|
+
pending_attachments.append((item_id, attachment))
|
|
1028
|
+
emit_progress(progress, done=len(result), stage="planning", items=[item])
|
|
1029
|
+
# Attachments come after every record so the target can create all parent
|
|
1030
|
+
# items before it resolves and uploads their attachments.
|
|
1031
|
+
for item_id, attachment in pending_attachments:
|
|
1032
|
+
name = getattr(attachment, "file_name", None)
|
|
1033
|
+
if not name:
|
|
1034
|
+
self.warnings.append(f"skipped an attachment without a file name on item {item_id}")
|
|
1035
|
+
continue
|
|
1036
|
+
dest_path = f"{item_id}/{name}"
|
|
1037
|
+
self._attachments[dest_path] = attachment
|
|
153
1038
|
result.append(
|
|
154
1039
|
MigrationItem(
|
|
155
|
-
source_path=f"{self._list.title}/{
|
|
156
|
-
dest_path=
|
|
157
|
-
item_type="
|
|
158
|
-
modified=iso_or_none(record.get("Modified")),
|
|
159
|
-
created=iso_or_none(record.get("Created")),
|
|
160
|
-
author_id=parse_int(record.get("AuthorId")),
|
|
161
|
-
editor_id=parse_int(record.get("EditorId")),
|
|
1040
|
+
source_path=f"{self._list.title}/{dest_path}",
|
|
1041
|
+
dest_path=dest_path,
|
|
1042
|
+
item_type="attachment",
|
|
162
1043
|
)
|
|
163
1044
|
)
|
|
164
|
-
emit_progress(progress, done=len(result), stage="planning", items=[
|
|
1045
|
+
emit_progress(progress, done=len(result), stage="planning", items=[attachment])
|
|
165
1046
|
return result
|
|
166
1047
|
|
|
167
|
-
def
|
|
1048
|
+
def _project_records(self, loaded, report_skipped: bool = True) -> list[dict]:
|
|
1049
|
+
# Project every loaded property — not the collection's literal ``select``.
|
|
1050
|
+
# Passing ``["*", ...]`` straight through emitted ``{"*": None, ...}``, so
|
|
1051
|
+
# migrated records carried no field values at all. ``AttachmentFiles`` is a
|
|
1052
|
+
# navigation property (objects, not a scalar column) and is excluded; the
|
|
1053
|
+
# target writes attachments separately.
|
|
1054
|
+
select = sorted(
|
|
1055
|
+
{key for item in loaded for key in item.properties if key != "AttachmentFiles"} - _NON_WRITABLE_SELECT
|
|
1056
|
+
)
|
|
1057
|
+
records = records_from_items(list(loaded), select, set(), raw=True)
|
|
1058
|
+
# A REST item create accepts scalar column values only. A full read also
|
|
1059
|
+
# returns navigation properties (``ParentList``) and typed values
|
|
1060
|
+
# (lookups/persons, e.g. ``PredecessorsId`` / ``AssignedTo``) whose JSON
|
|
1061
|
+
# objects carry no ``__metadata.type`` here, so the create batch is
|
|
1062
|
+
# rejected with ``InvalidClientQueryException``. Typed-value remapping is
|
|
1063
|
+
# not supported yet, so drop them and say so rather than fail the run.
|
|
1064
|
+
dropped: set[str] = set()
|
|
1065
|
+
multi = self._multi_columns()
|
|
1066
|
+
multi_keys = {f"{name}Id" for name in multi} | {f"{name}StringId" for name in multi}
|
|
1067
|
+
choices = self._multi_choice_columns()
|
|
1068
|
+
taxonomy = self._taxonomy_columns()
|
|
1069
|
+
value_objects = self._value_object_columns()
|
|
1070
|
+
for record in records:
|
|
1071
|
+
for key, value in list(record.items()):
|
|
1072
|
+
if key in multi_keys:
|
|
1073
|
+
continue # handled by ``_normalize_multi_values``
|
|
1074
|
+
if key in choices:
|
|
1075
|
+
continue # handled by ``_normalize_multi_choice_values``
|
|
1076
|
+
if key in taxonomy:
|
|
1077
|
+
continue # handled by ``_project_taxonomy_values``
|
|
1078
|
+
if key in value_objects and _scalar_mapping(value) is not None:
|
|
1079
|
+
record[key] = _scalar_mapping(value)
|
|
1080
|
+
continue # a value object (``{"Url", "Description"}``) is writable as-is
|
|
1081
|
+
if value is not None and not isinstance(value, (str, int, float, bool)):
|
|
1082
|
+
record.pop(key)
|
|
1083
|
+
if key not in _NAVIGATION_SELECT:
|
|
1084
|
+
dropped.add(key)
|
|
1085
|
+
if dropped:
|
|
1086
|
+
self.warnings.append(
|
|
1087
|
+
f"skipped non-scalar column(s) with no typed-value mapping: {', '.join(sorted(dropped))}"
|
|
1088
|
+
)
|
|
1089
|
+
# Structure provisioning skips hidden columns, so their typed values (and
|
|
1090
|
+
# the text companion) have nowhere to land on the target; drop both rather
|
|
1091
|
+
# than fail the create on an unknown column.
|
|
1092
|
+
skipped = self._skipped_columns()
|
|
1093
|
+
if skipped:
|
|
1094
|
+
skipped_keys = {f"{name}Id" for name in skipped} | {f"{name}StringId" for name in skipped}
|
|
1095
|
+
for record in records:
|
|
1096
|
+
for key in skipped_keys:
|
|
1097
|
+
record.pop(key, None)
|
|
1098
|
+
if report_skipped:
|
|
1099
|
+
self.warnings.append(
|
|
1100
|
+
f"skipped hidden typed column(s) not replicated by structure provisioning: "
|
|
1101
|
+
f"{', '.join(sorted(skipped))}"
|
|
1102
|
+
)
|
|
1103
|
+
self._project_taxonomy_values(records)
|
|
1104
|
+
self._normalize_multi_values(records)
|
|
1105
|
+
self._normalize_multi_choice_values(records)
|
|
1106
|
+
self._annotate_person_logins(records)
|
|
1107
|
+
return records
|
|
1108
|
+
|
|
1109
|
+
def folder_records(self) -> dict[str, dict]:
|
|
1110
|
+
"""Column values of this list's folders, keyed by their server-relative URL.
|
|
1111
|
+
|
|
1112
|
+
A document library's folders are list items too: a document set or a managed
|
|
1113
|
+
folder carries its own column values, so a target that only created the empty
|
|
1114
|
+
folder would silently lose them. The values go through the same projection as
|
|
1115
|
+
an item's (typed values normalized, system and navigation columns dropped), so
|
|
1116
|
+
the target can write them onto the folder's underlying item. The read is
|
|
1117
|
+
narrowed to the folder's writable columns (see :meth:`folder_projection`): a
|
|
1118
|
+
``*`` read also returns system and computed columns (compliance flags, link
|
|
1119
|
+
filenames, version labels) that an update would reject and that carry no
|
|
1120
|
+
portable value.
|
|
1121
|
+
"""
|
|
1122
|
+
if self._folder_records_cache is None:
|
|
1123
|
+
records: dict[str, dict] = {}
|
|
1124
|
+
try:
|
|
1125
|
+
loaded = self._load_items(self.folder_projection(), folder_only=True)
|
|
1126
|
+
except Exception as e: # noqa: BLE001 — folder values are best-effort
|
|
1127
|
+
self.warnings.append(f"folder column values could not be read: {e}")
|
|
1128
|
+
loaded = []
|
|
1129
|
+
# The folder projection already excluded the columns an item record would
|
|
1130
|
+
# report as unmigratable, so those messages would describe nothing.
|
|
1131
|
+
for record in self._project_records(loaded, report_skipped=False) if loaded else []:
|
|
1132
|
+
url = record.get("FileRef")
|
|
1133
|
+
if not isinstance(url, str) or not url:
|
|
1134
|
+
continue
|
|
1135
|
+
values = _folder_record_values(record)
|
|
1136
|
+
if values:
|
|
1137
|
+
records[url.rstrip("/")] = values
|
|
1138
|
+
self._folder_records_cache = records
|
|
1139
|
+
return self._folder_records_cache
|
|
1140
|
+
|
|
1141
|
+
def folder_projection(self) -> list[str]:
|
|
1142
|
+
"""The columns that carry a folder's own metadata, plus what typed values need.
|
|
1143
|
+
|
|
1144
|
+
A folder has no separate create payload: its values are read back as a record,
|
|
1145
|
+
written onto the folder's item and compared the same way — so the projection
|
|
1146
|
+
must include the companions the typed-value normalization reads (``<name>Id``
|
|
1147
|
+
for Lookup/Person values, the hidden note of a Managed Metadata column).
|
|
1148
|
+
"""
|
|
1149
|
+
writable = _folder_value_columns(self._source_fields())
|
|
1150
|
+
# ``FileRef`` keys the record (it is the folder's identity, not a value).
|
|
1151
|
+
columns = ["ContentTypeId", "FileRef", *sorted(writable)]
|
|
1152
|
+
# Lookup and Person values are carried as item ids / logins under
|
|
1153
|
+
# ``<name>Id``; the column itself is carried in the record too.
|
|
1154
|
+
single_lookups = {
|
|
1155
|
+
field.internal_name
|
|
1156
|
+
for field in self._source_fields()
|
|
1157
|
+
if field.internal_name and field.type_as_string == "Lookup" and not _allows_multiple_values(field)
|
|
1158
|
+
}
|
|
1159
|
+
for name in sorted((single_lookups | self._user_columns() | set(self._multi_columns())) & writable):
|
|
1160
|
+
key = f"{name}Id"
|
|
1161
|
+
if key not in columns:
|
|
1162
|
+
columns.append(key)
|
|
1163
|
+
for note in self._taxonomy_note_columns():
|
|
1164
|
+
if note not in columns:
|
|
1165
|
+
columns.append(note)
|
|
1166
|
+
return columns
|
|
1167
|
+
|
|
1168
|
+
def _source_fields(self) -> list[Any]:
|
|
1169
|
+
"""The source list's field definitions, read once and shared by every scan."""
|
|
1170
|
+
if self._source_fields_cache is None:
|
|
1171
|
+
self._source_fields_cache = list(self._list.fields.select(list(_SOURCE_FIELD_SELECT)).get().execute_query())
|
|
1172
|
+
return self._source_fields_cache
|
|
1173
|
+
|
|
1174
|
+
def _taxonomy_columns(self) -> dict[str, bool]:
|
|
1175
|
+
"""The source's Managed Metadata columns (internal name -> allows many)."""
|
|
1176
|
+
if self._taxonomy_columns_cache is None:
|
|
1177
|
+
columns: dict[str, bool] = {}
|
|
1178
|
+
notes: set[str] = set()
|
|
1179
|
+
try:
|
|
1180
|
+
fields = self._source_fields()
|
|
1181
|
+
by_id = {
|
|
1182
|
+
normalize_guid(getattr(field, "id", None)): getattr(field, "internal_name", None) for field in fields
|
|
1183
|
+
}
|
|
1184
|
+
for field in fields:
|
|
1185
|
+
internal_name = getattr(field, "internal_name", None)
|
|
1186
|
+
if not internal_name:
|
|
1187
|
+
continue
|
|
1188
|
+
field_type = getattr(field, "type_as_string", None)
|
|
1189
|
+
schema = getattr(field, "schema_xml", None) or ""
|
|
1190
|
+
if field_type not in _TAXONOMY_FIELD_TYPES and "TermSetId" not in schema:
|
|
1191
|
+
continue
|
|
1192
|
+
columns[internal_name] = field_type == "TaxonomyFieldTypeMulti" or 'Mult="TRUE"' in schema
|
|
1193
|
+
note = by_id.get(normalize_guid(field.properties.get("TextField")))
|
|
1194
|
+
if note:
|
|
1195
|
+
notes.add(note)
|
|
1196
|
+
except Exception as e: # noqa: BLE001 — values stay un-remapped, never fatal
|
|
1197
|
+
self.warnings.append(f"could not read source columns for taxonomy remapping: {e}")
|
|
1198
|
+
self._taxonomy_columns_cache = columns
|
|
1199
|
+
self._taxonomy_note_columns_cache = notes
|
|
1200
|
+
return self._taxonomy_columns_cache
|
|
1201
|
+
|
|
1202
|
+
def _taxonomy_note_columns(self) -> set[str]:
|
|
1203
|
+
"""The hidden note companions of the source's taxonomy columns."""
|
|
1204
|
+
self._taxonomy_columns()
|
|
1205
|
+
return self._taxonomy_note_columns_cache or set()
|
|
1206
|
+
|
|
1207
|
+
def _project_taxonomy_values(self, records: list[dict]) -> None:
|
|
1208
|
+
"""Project taxonomy columns as bare ``[{"TermGuid": ...}]`` term lists.
|
|
1209
|
+
|
|
1210
|
+
The target recreates the column against the same term set and rebuilds the
|
|
1211
|
+
write payload, so only the portable term id travels. The hidden note
|
|
1212
|
+
companion (if a projection happens to include it) is dropped.
|
|
1213
|
+
"""
|
|
1214
|
+
columns = self._taxonomy_columns()
|
|
1215
|
+
if not columns:
|
|
1216
|
+
return
|
|
1217
|
+
notes = self._taxonomy_note_columns()
|
|
1218
|
+
for record in records:
|
|
1219
|
+
for note in notes:
|
|
1220
|
+
record.pop(note, None)
|
|
1221
|
+
for name in columns:
|
|
1222
|
+
terms = _taxonomy_terms(record.get(name))
|
|
1223
|
+
if terms:
|
|
1224
|
+
record[name] = terms
|
|
1225
|
+
else:
|
|
1226
|
+
record.pop(name, None)
|
|
1227
|
+
|
|
1228
|
+
def _typed_columns(self) -> tuple[set[str], dict[str, str], set[str]]:
|
|
1229
|
+
"""The source's Person and multi-value columns, read once.
|
|
1230
|
+
|
|
1231
|
+
Returns ``(single-value User columns, {multi-value column: field type},
|
|
1232
|
+
hidden typed columns)``. The field type tells a multi-lookup (remapped by
|
|
1233
|
+
item id) from a multi-person (remapped by login). Hidden typed columns are
|
|
1234
|
+
reported separately because structure provisioning does not replicate them.
|
|
1235
|
+
"""
|
|
1236
|
+
if self._user_columns_cache is None or self._multi_columns_cache is None or self._skipped_columns_cache is None:
|
|
1237
|
+
users: set[str] = set()
|
|
1238
|
+
multi: dict[str, str] = {}
|
|
1239
|
+
skipped: set[str] = set()
|
|
1240
|
+
try:
|
|
1241
|
+
for field in self._source_fields():
|
|
1242
|
+
internal_name = getattr(field, "internal_name", None)
|
|
1243
|
+
if not internal_name or internal_name in _PERSON_EXCLUDED:
|
|
1244
|
+
continue
|
|
1245
|
+
field_type = getattr(field, "type_as_string", None)
|
|
1246
|
+
if field_type not in _TYPED_VALUE_TYPES:
|
|
1247
|
+
continue
|
|
1248
|
+
# Structure replay skips hidden columns, so the target never
|
|
1249
|
+
# gets them; projecting their values would fail the write.
|
|
1250
|
+
if getattr(field, "hidden", None) is True:
|
|
1251
|
+
skipped.add(internal_name)
|
|
1252
|
+
elif field_type.startswith("User"):
|
|
1253
|
+
if _allows_multiple_values(field):
|
|
1254
|
+
multi[internal_name] = "UserMulti"
|
|
1255
|
+
else:
|
|
1256
|
+
users.add(internal_name)
|
|
1257
|
+
elif _allows_multiple_values(field):
|
|
1258
|
+
multi[internal_name] = "LookupMulti"
|
|
1259
|
+
except Exception as e: # noqa: BLE001 — without field types ids stay raw
|
|
1260
|
+
self.warnings.append(f"could not read source columns for typed-value remapping: {e}")
|
|
1261
|
+
self._user_columns_cache = users
|
|
1262
|
+
self._multi_columns_cache = multi
|
|
1263
|
+
self._skipped_columns_cache = skipped
|
|
1264
|
+
return self._user_columns_cache, self._multi_columns_cache, self._skipped_columns_cache
|
|
1265
|
+
|
|
1266
|
+
def _user_columns(self) -> set[str]:
|
|
1267
|
+
"""The source's single-value Person columns (internal names)."""
|
|
1268
|
+
return self._typed_columns()[0]
|
|
1269
|
+
|
|
1270
|
+
def _multi_columns(self) -> dict[str, str]:
|
|
1271
|
+
"""The source's multi-value Lookup/Person columns (internal name -> field type)."""
|
|
1272
|
+
return self._typed_columns()[1]
|
|
1273
|
+
|
|
1274
|
+
def _skipped_columns(self) -> set[str]:
|
|
1275
|
+
"""The source's hidden Person/multi-value columns (not replicated on the target)."""
|
|
1276
|
+
return self._typed_columns()[2]
|
|
1277
|
+
|
|
1278
|
+
def _multi_choice_columns(self) -> set[str]:
|
|
1279
|
+
"""The source's multi-choice columns (internal names)."""
|
|
1280
|
+
if self._multi_choice_columns_cache is None:
|
|
1281
|
+
names: set[str] = set()
|
|
1282
|
+
try:
|
|
1283
|
+
names = {
|
|
1284
|
+
field.internal_name
|
|
1285
|
+
for field in self._source_fields()
|
|
1286
|
+
if getattr(field, "internal_name", None) and _is_multi_choice_column(field)
|
|
1287
|
+
}
|
|
1288
|
+
except Exception as e: # noqa: BLE001 — values stay un-normalized, never fatal
|
|
1289
|
+
self.warnings.append(f"could not read source columns for multi-choice values: {e}")
|
|
1290
|
+
self._multi_choice_columns_cache = names
|
|
1291
|
+
return self._multi_choice_columns_cache
|
|
1292
|
+
|
|
1293
|
+
def _value_object_columns(self) -> set[str]:
|
|
1294
|
+
"""The source's columns that hold a REST value object (e.g. a hyperlink)."""
|
|
1295
|
+
if self._value_object_columns_cache is None:
|
|
1296
|
+
names: set[str] = set()
|
|
1297
|
+
try:
|
|
1298
|
+
names = {
|
|
1299
|
+
field.internal_name
|
|
1300
|
+
for field in self._source_fields()
|
|
1301
|
+
if getattr(field, "internal_name", None) and _is_value_object_column(field)
|
|
1302
|
+
}
|
|
1303
|
+
except Exception as e: # noqa: BLE001 — values are dropped, never fatal
|
|
1304
|
+
self.warnings.append(f"could not read source columns for value-object values: {e}")
|
|
1305
|
+
self._value_object_columns_cache = names
|
|
1306
|
+
return self._value_object_columns_cache
|
|
1307
|
+
|
|
1308
|
+
def _annotate_person_logins(self, records: list[dict]) -> None:
|
|
1309
|
+
"""Replace each Person column's site-user id with the user's login name.
|
|
1310
|
+
|
|
1311
|
+
A site-user id is only meaningful inside its own site collection, so it is
|
|
1312
|
+
meaningless on the target. The login *is* portable, and the target resolves
|
|
1313
|
+
it back to (a possibly different) target site-user id with ``EnsureUser``.
|
|
1314
|
+
A person the source cannot resolve is dropped and reported rather than
|
|
1315
|
+
copied as a wrong id.
|
|
1316
|
+
"""
|
|
1317
|
+
columns = self._user_columns()
|
|
1318
|
+
if not columns:
|
|
1319
|
+
return
|
|
1320
|
+
for record in records:
|
|
1321
|
+
for name in columns:
|
|
1322
|
+
# ``<name>StringId`` is a read-only text companion holding the user
|
|
1323
|
+
# id; leaving it in the payload makes REST ignore the remapped
|
|
1324
|
+
# ``<name>Id`` and keep the source id.
|
|
1325
|
+
record.pop(f"{name}StringId", None)
|
|
1326
|
+
key = f"{name}Id"
|
|
1327
|
+
user_id = parse_int(record.get(key))
|
|
1328
|
+
if user_id is None:
|
|
1329
|
+
continue
|
|
1330
|
+
principal = self._principal_for(user_id)
|
|
1331
|
+
login = principal.login_name if principal else None
|
|
1332
|
+
if login:
|
|
1333
|
+
record[key] = login
|
|
1334
|
+
else:
|
|
1335
|
+
record.pop(key, None)
|
|
1336
|
+
self.warnings.append(
|
|
1337
|
+
f"person column '{name}' value {user_id} could not be resolved to a login; left empty"
|
|
1338
|
+
)
|
|
1339
|
+
|
|
1340
|
+
def _normalize_multi_values(self, records: list[dict]) -> None:
|
|
1341
|
+
"""Project each multi-value column's ids as a plain list the target can remap.
|
|
1342
|
+
|
|
1343
|
+
A multi-person column additionally carries the portable login names instead
|
|
1344
|
+
of the site-local ids, matching the single-value Person handling.
|
|
1345
|
+
"""
|
|
1346
|
+
multi = self._multi_columns()
|
|
1347
|
+
if not multi:
|
|
1348
|
+
return
|
|
1349
|
+
for record in records:
|
|
1350
|
+
for name, field_type in multi.items():
|
|
1351
|
+
record.pop(f"{name}StringId", None)
|
|
1352
|
+
key = f"{name}Id"
|
|
1353
|
+
ids = _id_list(record.get(key))
|
|
1354
|
+
parsed: list[int] = []
|
|
1355
|
+
for value in ids or []:
|
|
1356
|
+
user_id = parse_int(value)
|
|
1357
|
+
if user_id is None:
|
|
1358
|
+
parsed = []
|
|
1359
|
+
break
|
|
1360
|
+
parsed.append(user_id)
|
|
1361
|
+
if not parsed:
|
|
1362
|
+
record.pop(key, None)
|
|
1363
|
+
continue
|
|
1364
|
+
if field_type == "UserMulti":
|
|
1365
|
+
logins: list[str] = []
|
|
1366
|
+
for user_id in parsed:
|
|
1367
|
+
principal = self._principal_for(user_id)
|
|
1368
|
+
login = principal.login_name if principal else None
|
|
1369
|
+
if login:
|
|
1370
|
+
logins.append(login)
|
|
1371
|
+
else:
|
|
1372
|
+
self.warnings.append(
|
|
1373
|
+
f"multi-person column '{name}' value {user_id} could not be resolved "
|
|
1374
|
+
"to a login; the value was left out"
|
|
1375
|
+
)
|
|
1376
|
+
if logins:
|
|
1377
|
+
record[key] = logins
|
|
1378
|
+
else:
|
|
1379
|
+
record.pop(key, None)
|
|
1380
|
+
else:
|
|
1381
|
+
record[key] = parsed
|
|
1382
|
+
|
|
1383
|
+
def _normalize_multi_choice_values(self, records: list[dict]) -> None:
|
|
1384
|
+
"""Project each multi-choice column as a plain list of its selected values.
|
|
1385
|
+
|
|
1386
|
+
A live read returns the collection as a positional mapping
|
|
1387
|
+
(``{0: "red", 1: "green"}``); the source records it as a bare list, which is
|
|
1388
|
+
what the checksum compares and what the target wraps for a create. An empty
|
|
1389
|
+
selection is dropped so the column reads back empty rather than as an empty
|
|
1390
|
+
collection.
|
|
1391
|
+
"""
|
|
1392
|
+
names = self._multi_choice_columns()
|
|
1393
|
+
if not names:
|
|
1394
|
+
return
|
|
1395
|
+
for record in records:
|
|
1396
|
+
for name in names:
|
|
1397
|
+
if name not in record:
|
|
1398
|
+
continue
|
|
1399
|
+
values = _multi_values(record.get(name))
|
|
1400
|
+
if values:
|
|
1401
|
+
record[name] = values
|
|
1402
|
+
else:
|
|
1403
|
+
record.pop(name, None)
|
|
1404
|
+
|
|
1405
|
+
def read(self, item: MigrationItem) -> object:
|
|
1406
|
+
if item.item_type == "attachment":
|
|
1407
|
+
return self._attachment_content(item)
|
|
168
1408
|
return self._records.get(item.dest_path, {})
|
|
169
1409
|
|
|
1410
|
+
def _attachment_content(self, item: MigrationItem) -> bytes:
|
|
1411
|
+
attachment = self._attachments.get(item.dest_path)
|
|
1412
|
+
if attachment is None:
|
|
1413
|
+
raise FileNotFoundError(item.source_path)
|
|
1414
|
+
return cast(bytes, attachment.get_content().execute_query().value)
|
|
1415
|
+
|
|
170
1416
|
def checksum(self, item: MigrationItem) -> str:
|
|
171
|
-
|
|
172
|
-
|
|
1417
|
+
if item.item_type == "attachment":
|
|
1418
|
+
return hashlib.md5(self._attachment_content(item)).hexdigest()
|
|
1419
|
+
return _content_checksum(_canonicalize_taxonomy_record(self._records.get(item.dest_path, {})))
|
|
1420
|
+
|
|
1421
|
+
def read_metadata(self, item: MigrationItem) -> dict | None:
|
|
1422
|
+
"""Comparable attributes of the source item (for the post-run metadata diff)."""
|
|
1423
|
+
source_item = self._items.get(item.dest_path)
|
|
1424
|
+
if source_item is None:
|
|
1425
|
+
return None
|
|
1426
|
+
properties = source_item.properties
|
|
1427
|
+
return {
|
|
1428
|
+
"created": iso_or_none(properties.get("Created")),
|
|
1429
|
+
"modified": iso_or_none(properties.get("Modified")),
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
def read_authorship(self, item: MigrationItem) -> tuple[str | None, str | None]:
|
|
1433
|
+
"""The source author/editor login names (for the authorship diff)."""
|
|
1434
|
+
return (
|
|
1435
|
+
item.author.login_name if item.author else None,
|
|
1436
|
+
item.editor.login_name if item.editor else None,
|
|
1437
|
+
)
|
|
1438
|
+
|
|
1439
|
+
def _normalize_name(self, name: str) -> str:
|
|
1440
|
+
"""Canonicalize a principal name (default groups -> ``default:<Kind>``)."""
|
|
1441
|
+
context = getattr(self._list, "context", None)
|
|
1442
|
+
web = getattr(context, "web", None) if context is not None else None
|
|
1443
|
+
if web is None:
|
|
1444
|
+
return name
|
|
1445
|
+
if self._default_groups is None:
|
|
1446
|
+
self._default_groups = _DefaultGroups(web)
|
|
1447
|
+
return self._default_groups.normalize(name)
|
|
1448
|
+
|
|
1449
|
+
def read_permissions(self, item: MigrationItem) -> list[PermissionEntry]:
|
|
1450
|
+
"""Read the item's *unique* role assignments (best-effort ACL preservation).
|
|
1451
|
+
|
|
1452
|
+
Inherited items return ``[]`` — REST would otherwise report the parent's
|
|
1453
|
+
ACL, and copying it would break inheritance on every item.
|
|
1454
|
+
"""
|
|
1455
|
+
source_item = self._items.get(item.dest_path)
|
|
1456
|
+
if source_item is None or not _has_unique_role_assignments(source_item):
|
|
1457
|
+
return []
|
|
1458
|
+
return _read_role_assignments(source_item, resolve=self._principal_for, normalize=self._normalize_name)
|
|
1459
|
+
|
|
1460
|
+
def _principal_for(self, user_id: Any) -> "Principal | None":
|
|
1461
|
+
"""Resolve a site-user id to a :class:`Principal` (login + PUID), cached."""
|
|
1462
|
+
key = parse_int(user_id)
|
|
1463
|
+
if key is None:
|
|
1464
|
+
return None
|
|
1465
|
+
if key in self._users:
|
|
1466
|
+
return self._users[key]
|
|
1467
|
+
principal: "Principal | None" = None
|
|
1468
|
+
try:
|
|
1469
|
+
user = self._list.context.web.site_users.get_by_id(cast("str", key)).get().execute_query()
|
|
1470
|
+
user_id_info = user.properties.get("UserId")
|
|
1471
|
+
system_id = normalize_puid(getattr(user_id_info, "NameId", None))
|
|
1472
|
+
principal = Principal(
|
|
1473
|
+
login_name=user.login_name or "",
|
|
1474
|
+
system_id=system_id,
|
|
1475
|
+
display_name=user.title,
|
|
1476
|
+
email=user.properties.get("Email"),
|
|
1477
|
+
)
|
|
1478
|
+
except Exception as e: # noqa: BLE001 — a missing user degrades to no authorship
|
|
1479
|
+
self.warnings.append(f"user {key} could not be resolved: {e}")
|
|
1480
|
+
self._users[key] = principal
|
|
1481
|
+
return principal
|
|
173
1482
|
|
|
174
1483
|
def close(self) -> None:
|
|
175
1484
|
pass
|
|
@@ -180,34 +1489,132 @@ class SharePointListTarget:
|
|
|
180
1489
|
|
|
181
1490
|
Pass ``key`` (one or more natural-key columns) to make the target idempotent:
|
|
182
1491
|
a hash of those columns is stored in ``key_field`` and each payload is created
|
|
183
|
-
or (``on_conflict="
|
|
1492
|
+
or (``on_conflict="update"``) updated, so re-runs never duplicate. Without a
|
|
184
1493
|
key, records are appended and idempotency relies on the checkpoint/manifest.
|
|
1494
|
+
|
|
1495
|
+
``on_conflict`` may be passed explicitly; otherwise it is derived from
|
|
1496
|
+
``MigrationOptions.conflict_resolution`` by :meth:`configure` (the runner calls
|
|
1497
|
+
it before writes): ``OVERWRITE`` → ``"update"``, anything else → ``"skip"``.
|
|
1498
|
+
|
|
1499
|
+
Every migrated item also gets a source-id marker (``identity_field``, default
|
|
1500
|
+
``MigrationSourceId``): the default ``SKIP`` policy then recognizes items already
|
|
1501
|
+
on the target (so re-runs do not duplicate) and :meth:`list_paths` pairs source
|
|
1502
|
+
and target items for verification. Pass ``identity_field=None`` to opt out.
|
|
1503
|
+
List-item attachments are written after their parent records are flushed.
|
|
1504
|
+
|
|
1505
|
+
Pass ``content_type_map`` (a :class:`~office365.migration.map.ContentTypeMap`)
|
|
1506
|
+
when the target list's content types were recreated with fresh identifiers:
|
|
1507
|
+
every record's ``ContentTypeId`` is rewritten to the target id so items land
|
|
1508
|
+
under the recreated content type instead of the wrong (or a missing) one.
|
|
185
1509
|
"""
|
|
186
1510
|
|
|
1511
|
+
# Marks this target as accepting list-item attachments as extra items (see DataTarget).
|
|
1512
|
+
supports_attachments = True
|
|
1513
|
+
|
|
1514
|
+
# Marks this target as remapping Lookup values across migrated lists and Person
|
|
1515
|
+
# values by login (see ``_remap_typed_values``).
|
|
1516
|
+
supports_typed_values = True
|
|
1517
|
+
|
|
1518
|
+
# Marks this target as replaying a list item's approval state (``_ModerationStatus``
|
|
1519
|
+
# and its comments) in the metadata write, which a system update would otherwise
|
|
1520
|
+
# reset. Check-out and draft-version state are still not carried.
|
|
1521
|
+
supports_item_state = True
|
|
1522
|
+
|
|
1523
|
+
#: The single-item :meth:`write` only queues a create; a chunk must be flushed
|
|
1524
|
+
#: before it is durable. The runner therefore routes this target through the
|
|
1525
|
+
#: chunked path even at ``concurrency == 1``, so checkpoints never record
|
|
1526
|
+
#: ``DONE`` ahead of the actual write.
|
|
1527
|
+
deferred_writes = True
|
|
1528
|
+
|
|
1529
|
+
def rebind(self) -> "SharePointListTarget":
|
|
1530
|
+
"""This target on its own cloned context (see :meth:`SharePointListSource.rebind`)."""
|
|
1531
|
+
return SharePointListTarget(
|
|
1532
|
+
_cloned_list(self._list),
|
|
1533
|
+
key=list(self._key_columns) or None,
|
|
1534
|
+
key_field=self._key_field,
|
|
1535
|
+
on_conflict=self._on_conflict_override,
|
|
1536
|
+
identity_field=self._identity_field,
|
|
1537
|
+
content_type_map=self._content_type_map,
|
|
1538
|
+
)
|
|
1539
|
+
|
|
187
1540
|
def __init__(
|
|
188
1541
|
self,
|
|
189
1542
|
target_list: "SPList",
|
|
190
1543
|
key: str | list[str] | None = None,
|
|
191
1544
|
key_field: str = "MigrationKey",
|
|
192
|
-
on_conflict: str =
|
|
1545
|
+
on_conflict: str | None = None,
|
|
1546
|
+
identity_field: str | None = DEFAULT_LIST_IDENTITY_FIELD,
|
|
1547
|
+
content_type_map: "ContentTypeMap | None" = None,
|
|
193
1548
|
) -> None:
|
|
194
1549
|
self._list = target_list
|
|
195
1550
|
self._key_columns = [key] if isinstance(key, str) else (list(key) if key else [])
|
|
196
1551
|
self._key_field = key_field
|
|
197
|
-
self.
|
|
1552
|
+
self._on_conflict_override = on_conflict
|
|
1553
|
+
self._on_conflict = on_conflict or "skip"
|
|
198
1554
|
self._target = None
|
|
199
1555
|
self._existing: dict = {}
|
|
1556
|
+
self._identity_field = identity_field
|
|
1557
|
+
self._identity_ensured = False
|
|
1558
|
+
self._identity_index: dict | None = None
|
|
1559
|
+
self._identity_index_stale = False
|
|
1560
|
+
self._identity_index_max_id = 0
|
|
1561
|
+
self._attachment_path_cache: set[str] | None = None
|
|
1562
|
+
self._content_type_map = content_type_map
|
|
1563
|
+
self._login_cache: dict[int, str | None] = {}
|
|
1564
|
+
# Author/Editor values already written by ``apply_authorship`` but pending a
|
|
1565
|
+
# shared final write (see ``apply_timestamps``); avoids the ValidateUpdateListItem
|
|
1566
|
+
# call that restores timestamps silently resetting ``Editor`` to the caller.
|
|
1567
|
+
self._pending_authorship: dict[str, dict] = {}
|
|
1568
|
+
# Approval state read from the payload, replayed in the metadata write (a
|
|
1569
|
+
# system update approves the item); same pending-then-pop lifecycle as above.
|
|
1570
|
+
self._pending_approval: dict[str, dict[str, str]] = {}
|
|
1571
|
+
self.warnings: list[str] = []
|
|
1572
|
+
self._principals_cache: _TargetPrincipals | None = None
|
|
1573
|
+
# Lookup and Person columns carry an id that is only meaningful on the
|
|
1574
|
+
# source (list-local item id / site-user id). The target remaps each to its
|
|
1575
|
+
# own value on write and back to the source value on verification.
|
|
1576
|
+
self._columns_cache: _TypedColumns | None = None
|
|
1577
|
+
self._lookup_indexes: dict[str, tuple[dict[int, int], dict[int, int]]] = {}
|
|
1578
|
+
self._unresolved_lookups: set[tuple[str, int]] = set()
|
|
1579
|
+
self._unresolved_users: set[tuple[str, str]] = set()
|
|
1580
|
+
self._user_logins: dict[int, str] | None = None
|
|
1581
|
+
|
|
1582
|
+
@property
|
|
1583
|
+
def on_conflict(self) -> str:
|
|
1584
|
+
"""The active record conflict policy (``"skip"`` or ``"update"``)."""
|
|
1585
|
+
return self._on_conflict
|
|
1586
|
+
|
|
1587
|
+
def configure(self, options) -> None:
|
|
1588
|
+
"""Derive ``on_conflict`` from ``MigrationOptions.conflict_resolution``.
|
|
1589
|
+
|
|
1590
|
+
Called by :class:`MigrationRunner` before the run, so a single
|
|
1591
|
+
``conflict_resolution`` drives both file and record targets. An
|
|
1592
|
+
``on_conflict`` passed to the constructor is honored as-is.
|
|
1593
|
+
"""
|
|
1594
|
+
if self._on_conflict_override is None:
|
|
1595
|
+
self._on_conflict = conflict_to_on_conflict(options.conflict_resolution)
|
|
200
1596
|
|
|
201
1597
|
def label(self) -> str:
|
|
202
1598
|
return f"list:{self._list.title}"
|
|
203
1599
|
|
|
204
1600
|
def exists(self, item: MigrationItem) -> bool:
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
|
|
1601
|
+
# With the identity marker, both records and attachments are recognized on a
|
|
1602
|
+
# re-run, so the default ``SKIP`` policy is genuinely idempotent. Without it
|
|
1603
|
+
# (``identity_field=None``) nothing is recognized and records append.
|
|
1604
|
+
if not self._identity_field:
|
|
1605
|
+
return False
|
|
1606
|
+
self._ensure_identity()
|
|
1607
|
+
if not self._identity_field:
|
|
1608
|
+
return False
|
|
1609
|
+
if item.item_type == "attachment":
|
|
1610
|
+
return str(item.dest_path) in set(self._attachment_paths())
|
|
1611
|
+
return self._find_by_identity(item.dest_path) is not None
|
|
208
1612
|
|
|
209
1613
|
def write(self, item: MigrationItem, payload: object) -> None:
|
|
210
|
-
|
|
1614
|
+
if item.item_type == "attachment":
|
|
1615
|
+
self._write_attachment(item, payload)
|
|
1616
|
+
return
|
|
1617
|
+
self._queue([self._record_for(item, payload)])
|
|
211
1618
|
|
|
212
1619
|
def write_many(
|
|
213
1620
|
self,
|
|
@@ -219,19 +1626,67 @@ class SharePointListTarget:
|
|
|
219
1626
|
|
|
220
1627
|
Flushing per chunk (instead of only at :meth:`commit`) keeps a large list
|
|
221
1628
|
migration memory-bounded — the queued creates never accumulate for the
|
|
222
|
-
whole run.
|
|
1629
|
+
whole run. Attachments in the chunk are written after its records are
|
|
1630
|
+
flushed, so their parent items already exist.
|
|
223
1631
|
|
|
224
1632
|
Returns:
|
|
225
1633
|
No failures (a batch failure raises; there are no partial successes).
|
|
226
1634
|
"""
|
|
227
|
-
|
|
228
|
-
|
|
1635
|
+
records: list[dict] = []
|
|
1636
|
+
attachments: list[tuple[MigrationItem, object]] = []
|
|
1637
|
+
for item, payload in zip(items, payloads):
|
|
1638
|
+
if item.item_type == "attachment":
|
|
1639
|
+
attachments.append((item, payload))
|
|
1640
|
+
else:
|
|
1641
|
+
records.append(self._record_for(item, payload))
|
|
1642
|
+
if records:
|
|
1643
|
+
self._queue(records)
|
|
1644
|
+
self._flush(len(records), concurrency)
|
|
1645
|
+
for item, payload in attachments:
|
|
1646
|
+
self._write_attachment(item, payload)
|
|
229
1647
|
return []
|
|
230
1648
|
|
|
1649
|
+
def _record_for(self, item: MigrationItem, payload: object) -> dict:
|
|
1650
|
+
"""The payload with the target content-type id and source-id marker applied."""
|
|
1651
|
+
record = dict(cast(dict, payload))
|
|
1652
|
+
if self._content_type_map:
|
|
1653
|
+
content_type_id = record.get("ContentTypeId")
|
|
1654
|
+
record["ContentTypeId"] = self._content_type_map.resolve(content_type_id)
|
|
1655
|
+
if self._identity_field:
|
|
1656
|
+
record[self._identity_field] = item.dest_path
|
|
1657
|
+
approval = {
|
|
1658
|
+
field_name: str(record[record_key])
|
|
1659
|
+
for record_key, field_name in _APPROVAL_FIELDS.items()
|
|
1660
|
+
if record.get(record_key) is not None
|
|
1661
|
+
}
|
|
1662
|
+
if approval:
|
|
1663
|
+
self._pending_approval[item.dest_path] = approval
|
|
1664
|
+
return record
|
|
1665
|
+
|
|
1666
|
+
def translate_record(self, record: dict) -> dict:
|
|
1667
|
+
"""A source record mapped onto this list without writing it.
|
|
1668
|
+
|
|
1669
|
+
A document library folder is a list item that already exists on the target,
|
|
1670
|
+
so its column values cannot go through the create queue: the record is mapped
|
|
1671
|
+
here (target content-type id, Lookup ids, Person logins, multi-value shapes)
|
|
1672
|
+
and applied with an update instead.
|
|
1673
|
+
"""
|
|
1674
|
+
mapped = dict(record)
|
|
1675
|
+
if self._content_type_map:
|
|
1676
|
+
resolved = self._content_type_map.resolve(mapped.get("ContentTypeId"))
|
|
1677
|
+
if resolved:
|
|
1678
|
+
mapped["ContentTypeId"] = resolved
|
|
1679
|
+
else:
|
|
1680
|
+
mapped.pop("ContentTypeId", None)
|
|
1681
|
+
self._remap_typed_values([mapped])
|
|
1682
|
+
return mapped
|
|
1683
|
+
|
|
231
1684
|
def _queue(self, records: list[dict]) -> None:
|
|
1685
|
+
self._ensure_identity()
|
|
1686
|
+
self._remap_typed_values(records)
|
|
232
1687
|
if not self._key_columns:
|
|
233
1688
|
for record in records:
|
|
234
|
-
self._list.items.queue_records([record])
|
|
1689
|
+
self._list.items.queue_records([self._multi_value_create(record)])
|
|
235
1690
|
return
|
|
236
1691
|
from office365.runtime.converters.upsert import keyed_queue
|
|
237
1692
|
from office365.sharepoint.fields.name import internal_field_name
|
|
@@ -245,30 +1700,700 @@ class SharePointListTarget:
|
|
|
245
1700
|
self._target = target
|
|
246
1701
|
key_columns = [internal_field_name(c) for c in self._key_columns]
|
|
247
1702
|
keyed_queue(
|
|
248
|
-
target,
|
|
1703
|
+
_MultiValueCreateTarget(target, self._multi_value_create),
|
|
249
1704
|
records,
|
|
250
1705
|
key_columns=key_columns,
|
|
251
1706
|
existing=self._existing,
|
|
252
1707
|
on_conflict=self._on_conflict,
|
|
253
1708
|
)
|
|
254
1709
|
|
|
1710
|
+
def _multi_id_keys(self) -> set[str]:
|
|
1711
|
+
"""The ``...Id`` record keys of the multi-value Lookup/Person columns."""
|
|
1712
|
+
names = (*self._multi_lookup_columns(), *self._multi_user_columns())
|
|
1713
|
+
return {f"{name}Id" for name in names}
|
|
1714
|
+
|
|
1715
|
+
def _multi_value_create(self, record: dict) -> dict:
|
|
1716
|
+
"""Reshape multi-value columns for a *create* (``{"results": [...]}``).
|
|
1717
|
+
|
|
1718
|
+
A bare list is the semantic value the source records and the checksum work
|
|
1719
|
+
with, and it is also what a MERGE update wants; only a create needs the
|
|
1720
|
+
``{"results": [...]}`` wrapper (live-probed). Lists without multi-value
|
|
1721
|
+
columns are passed through untouched.
|
|
1722
|
+
"""
|
|
1723
|
+
keys = self._multi_id_keys() | self._multi_choice_columns()
|
|
1724
|
+
if not keys:
|
|
1725
|
+
return record
|
|
1726
|
+
wrapped = dict(record)
|
|
1727
|
+
for key in keys:
|
|
1728
|
+
values = _multi_values(wrapped.get(key))
|
|
1729
|
+
if values is not None:
|
|
1730
|
+
wrapped[key] = _collection_payload(values)
|
|
1731
|
+
return wrapped
|
|
1732
|
+
|
|
1733
|
+
def _ensure_identity(self) -> None:
|
|
1734
|
+
"""Create the source-id marker column (once), best-effort."""
|
|
1735
|
+
if self._identity_ensured or not self._identity_field:
|
|
1736
|
+
return
|
|
1737
|
+
self._identity_ensured = True
|
|
1738
|
+
name = self._identity_field
|
|
1739
|
+
from office365.sharepoint.fields.creation_information import FieldCreationInformation
|
|
1740
|
+
from office365.sharepoint.fields.type import FieldType
|
|
1741
|
+
|
|
1742
|
+
try:
|
|
1743
|
+
self._list.fields.ensure(FieldCreationInformation(name, FieldType.Text)).execute_query()
|
|
1744
|
+
except Exception as e: # noqa: BLE001 — identity is best-effort; content still migrates
|
|
1745
|
+
self._identity_field = None
|
|
1746
|
+
self.warnings.append(f"could not create the {name!r} identity column: {e}")
|
|
1747
|
+
|
|
1748
|
+
def _columns(self) -> _TypedColumns:
|
|
1749
|
+
"""The target's typed columns.
|
|
1750
|
+
|
|
1751
|
+
Returns ``(lookup: name -> referenced list id, user names, multi-lookup:
|
|
1752
|
+
name -> referenced list id, multi-user names, taxonomy: name -> (allows
|
|
1753
|
+
many, hidden note field internal name), multi-choice names)``.
|
|
1754
|
+
"""
|
|
1755
|
+
if self._columns_cache is None:
|
|
1756
|
+
self._columns_cache = self._load_columns()
|
|
1757
|
+
return self._columns_cache
|
|
1758
|
+
|
|
1759
|
+
def _lookup_columns(self) -> dict[str, str]:
|
|
1760
|
+
return self._columns()[0]
|
|
1761
|
+
|
|
1762
|
+
def _user_columns(self) -> set[str]:
|
|
1763
|
+
return self._columns()[1]
|
|
1764
|
+
|
|
1765
|
+
def _multi_lookup_columns(self) -> dict[str, str]:
|
|
1766
|
+
return self._columns()[2]
|
|
1767
|
+
|
|
1768
|
+
def _multi_user_columns(self) -> set[str]:
|
|
1769
|
+
return self._columns()[3]
|
|
1770
|
+
|
|
1771
|
+
def _taxonomy_columns(self) -> dict[str, tuple[bool, str | None]]:
|
|
1772
|
+
return self._columns()[4]
|
|
1773
|
+
|
|
1774
|
+
def _multi_choice_columns(self) -> set[str]:
|
|
1775
|
+
return self._columns()[5]
|
|
1776
|
+
|
|
1777
|
+
def _load_columns(self) -> _TypedColumns:
|
|
1778
|
+
lookups: dict[str, str] = {}
|
|
1779
|
+
users: set[str] = set()
|
|
1780
|
+
multi_lookups: dict[str, str] = {}
|
|
1781
|
+
multi_users: set[str] = set()
|
|
1782
|
+
taxonomy: dict[str, tuple[bool, str | None]] = {}
|
|
1783
|
+
multi_choices: set[str] = set()
|
|
1784
|
+
try:
|
|
1785
|
+
fields = (
|
|
1786
|
+
self._list.fields.select(
|
|
1787
|
+
["InternalName", "TypeAsString", "LookupList", "Id", "TextField", "AllowMultipleValues"]
|
|
1788
|
+
)
|
|
1789
|
+
.get()
|
|
1790
|
+
.execute_query()
|
|
1791
|
+
)
|
|
1792
|
+
except Exception as e: # noqa: BLE001 — without columns we cannot remap; content still migrates
|
|
1793
|
+
self.warnings.append(f"could not read target columns for typed-value remapping: {e}")
|
|
1794
|
+
return lookups, users, multi_lookups, multi_users, taxonomy, multi_choices
|
|
1795
|
+
by_id = {normalize_guid(getattr(field, "id", None)): getattr(field, "internal_name", None) for field in fields}
|
|
1796
|
+
for field in fields:
|
|
1797
|
+
internal_name = getattr(field, "internal_name", None)
|
|
1798
|
+
if not internal_name or internal_name in _PERSON_EXCLUDED:
|
|
1799
|
+
continue
|
|
1800
|
+
field_type = getattr(field, "type_as_string", None)
|
|
1801
|
+
if field_type in _TAXONOMY_FIELD_TYPES:
|
|
1802
|
+
# The companion note column that actually carries the write payload
|
|
1803
|
+
# has an unpredictable name; resolve it through the column's
|
|
1804
|
+
# ``TextField`` id.
|
|
1805
|
+
note_name = by_id.get(normalize_guid(field.properties.get("TextField")))
|
|
1806
|
+
taxonomy[internal_name] = (field_type == "TaxonomyFieldTypeMulti", note_name)
|
|
1807
|
+
elif field_type in _TYPED_VALUE_TYPES:
|
|
1808
|
+
multiple = _allows_multiple_values(field)
|
|
1809
|
+
if field_type.startswith("User"):
|
|
1810
|
+
if multiple:
|
|
1811
|
+
multi_users.add(internal_name)
|
|
1812
|
+
else:
|
|
1813
|
+
users.add(internal_name)
|
|
1814
|
+
else:
|
|
1815
|
+
list_id = normalize_guid(field.properties.get("LookupList"))
|
|
1816
|
+
if not list_id:
|
|
1817
|
+
continue
|
|
1818
|
+
if multiple:
|
|
1819
|
+
multi_lookups[internal_name] = list_id
|
|
1820
|
+
else:
|
|
1821
|
+
lookups[internal_name] = list_id
|
|
1822
|
+
elif _is_multi_choice_column(field):
|
|
1823
|
+
multi_choices.add(internal_name)
|
|
1824
|
+
return lookups, users, multi_lookups, multi_users, taxonomy, multi_choices
|
|
1825
|
+
|
|
1826
|
+
def _lookup_index(self, target_list_id: str) -> tuple[dict[int, int], dict[int, int]]:
|
|
1827
|
+
"""``(source id -> target id, target id -> source id)`` for a referenced list."""
|
|
1828
|
+
cached = self._lookup_indexes.get(target_list_id)
|
|
1829
|
+
if cached is not None:
|
|
1830
|
+
return cached
|
|
1831
|
+
forward: dict[int, int] = {}
|
|
1832
|
+
backward: dict[int, int] = {}
|
|
1833
|
+
self._flush_pending()
|
|
1834
|
+
try:
|
|
1835
|
+
referenced = self._list.context.web.lists.get_by_id(target_list_id)
|
|
1836
|
+
items = referenced.items.select(["Id", DEFAULT_LIST_IDENTITY_FIELD]).get_all().execute_query()
|
|
1837
|
+
except Exception as e: # noqa: BLE001 — an unreadable referenced list is reported, not fatal
|
|
1838
|
+
self.warnings.append(f"lookup target list {target_list_id} could not be read: {e}")
|
|
1839
|
+
items = []
|
|
1840
|
+
for referenced_item in items:
|
|
1841
|
+
source_id = parse_int(referenced_item.properties.get(DEFAULT_LIST_IDENTITY_FIELD))
|
|
1842
|
+
target_id = parse_int(referenced_item.id)
|
|
1843
|
+
if source_id is None or target_id is None:
|
|
1844
|
+
continue
|
|
1845
|
+
forward[source_id] = target_id
|
|
1846
|
+
backward[target_id] = source_id
|
|
1847
|
+
self._lookup_indexes[target_list_id] = (forward, backward)
|
|
1848
|
+
return forward, backward
|
|
1849
|
+
|
|
1850
|
+
def _remap_typed_values(self, records: list[dict]) -> None:
|
|
1851
|
+
"""Translate each record's source lookup/person ids into target values.
|
|
1852
|
+
|
|
1853
|
+
A lookup references another migrated list (matched on its items' source-id
|
|
1854
|
+
marker); a person carries the *login name* the source annotated, resolved
|
|
1855
|
+
here with ``EnsureUser``. Either value with no match is dropped and reported
|
|
1856
|
+
once rather than written as a wrong id.
|
|
1857
|
+
"""
|
|
1858
|
+
self._remap_lookups(records)
|
|
1859
|
+
self._remap_users(records)
|
|
1860
|
+
self._remap_multi_lookups(records)
|
|
1861
|
+
self._remap_multi_users(records)
|
|
1862
|
+
self._remap_taxonomies(records)
|
|
1863
|
+
|
|
1864
|
+
def _remap_lookups(self, records: list[dict]) -> None:
|
|
1865
|
+
columns = self._lookup_columns()
|
|
1866
|
+
if not columns:
|
|
1867
|
+
return
|
|
1868
|
+
for record in records:
|
|
1869
|
+
for internal_name, target_list_id in columns.items():
|
|
1870
|
+
key = f"{internal_name}Id"
|
|
1871
|
+
source_id = parse_int(record.get(key))
|
|
1872
|
+
if source_id is None:
|
|
1873
|
+
continue
|
|
1874
|
+
forward, _ = self._lookup_index(target_list_id)
|
|
1875
|
+
target_id = forward.get(source_id)
|
|
1876
|
+
if target_id is None:
|
|
1877
|
+
record.pop(key, None)
|
|
1878
|
+
self._report_unresolved_lookup(internal_name, source_id)
|
|
1879
|
+
continue
|
|
1880
|
+
record[key] = target_id
|
|
1881
|
+
|
|
1882
|
+
def _remap_users(self, records: list[dict]) -> None:
|
|
1883
|
+
columns = self._user_columns()
|
|
1884
|
+
if not columns:
|
|
1885
|
+
return
|
|
1886
|
+
for record in records:
|
|
1887
|
+
for internal_name in columns:
|
|
1888
|
+
key = f"{internal_name}Id"
|
|
1889
|
+
login = record.get(key)
|
|
1890
|
+
if not isinstance(login, str) or not login:
|
|
1891
|
+
continue
|
|
1892
|
+
user = self._principals().user(login)
|
|
1893
|
+
if user is None or getattr(user, "id", None) is None:
|
|
1894
|
+
record.pop(key, None)
|
|
1895
|
+
self._report_unresolved_user(internal_name, login)
|
|
1896
|
+
continue
|
|
1897
|
+
record[key] = user.id
|
|
1898
|
+
|
|
1899
|
+
def _remap_multi_lookups(self, records: list[dict]) -> None:
|
|
1900
|
+
columns = self._multi_lookup_columns()
|
|
1901
|
+
if not columns:
|
|
1902
|
+
return
|
|
1903
|
+
for record in records:
|
|
1904
|
+
for internal_name, target_list_id in columns.items():
|
|
1905
|
+
key = f"{internal_name}Id"
|
|
1906
|
+
source_ids = _id_list(record.get(key))
|
|
1907
|
+
if not source_ids:
|
|
1908
|
+
continue
|
|
1909
|
+
forward, _ = self._lookup_index(target_list_id)
|
|
1910
|
+
target_ids: list[int] = []
|
|
1911
|
+
for value in source_ids:
|
|
1912
|
+
source_id = parse_int(value)
|
|
1913
|
+
target_id = forward.get(source_id) if source_id is not None else None
|
|
1914
|
+
if target_id is None:
|
|
1915
|
+
if source_id is not None:
|
|
1916
|
+
self._report_unresolved_lookup(internal_name, source_id)
|
|
1917
|
+
continue
|
|
1918
|
+
target_ids.append(target_id)
|
|
1919
|
+
if target_ids:
|
|
1920
|
+
record[key] = target_ids
|
|
1921
|
+
else:
|
|
1922
|
+
record.pop(key, None)
|
|
1923
|
+
|
|
1924
|
+
def _remap_multi_users(self, records: list[dict]) -> None:
|
|
1925
|
+
columns = self._multi_user_columns()
|
|
1926
|
+
if not columns:
|
|
1927
|
+
return
|
|
1928
|
+
for record in records:
|
|
1929
|
+
for internal_name in columns:
|
|
1930
|
+
key = f"{internal_name}Id"
|
|
1931
|
+
logins = record.get(key)
|
|
1932
|
+
if not isinstance(logins, (list, tuple)) or not logins:
|
|
1933
|
+
continue
|
|
1934
|
+
target_ids: list[int] = []
|
|
1935
|
+
for login in logins:
|
|
1936
|
+
user = self._principals().user(login) if isinstance(login, str) and login else None
|
|
1937
|
+
user_id = getattr(user, "id", None)
|
|
1938
|
+
if user is None or user_id is None:
|
|
1939
|
+
self._report_unresolved_user(internal_name, str(login))
|
|
1940
|
+
continue
|
|
1941
|
+
target_ids.append(user_id)
|
|
1942
|
+
if target_ids:
|
|
1943
|
+
record[key] = target_ids
|
|
1944
|
+
else:
|
|
1945
|
+
record.pop(key, None)
|
|
1946
|
+
|
|
1947
|
+
def _remap_taxonomies(self, records: list[dict]) -> None:
|
|
1948
|
+
"""Write taxonomy terms in the form SharePoint accepts on the target.
|
|
1949
|
+
|
|
1950
|
+
A single-value column takes a typed ``SP.Taxonomy.TaxonomyFieldValue``
|
|
1951
|
+
object; a multi-value column only accepts the hidden note companion's text
|
|
1952
|
+
form ``-1;#Label|guid;#...`` (typed-collection JSON is rejected server-side).
|
|
1953
|
+
The term id is authoritative — the label can be left blank. A column with no
|
|
1954
|
+
target counterpart is dropped and reported rather than sent as an unknown
|
|
1955
|
+
property.
|
|
1956
|
+
"""
|
|
1957
|
+
columns = self._taxonomy_columns()
|
|
1958
|
+
unresolved: set[str] = set()
|
|
1959
|
+
for record in records:
|
|
1960
|
+
for internal_name, value in list(record.items()):
|
|
1961
|
+
if not _is_taxonomy_value(value):
|
|
1962
|
+
continue
|
|
1963
|
+
guids = _taxonomy_guids(value)
|
|
1964
|
+
column = columns.get(internal_name)
|
|
1965
|
+
if column is None:
|
|
1966
|
+
record.pop(internal_name, None)
|
|
1967
|
+
unresolved.add(internal_name)
|
|
1968
|
+
continue
|
|
1969
|
+
allow_many, note_name = column
|
|
1970
|
+
if not guids:
|
|
1971
|
+
record.pop(internal_name, None)
|
|
1972
|
+
continue
|
|
1973
|
+
if allow_many and note_name:
|
|
1974
|
+
record.pop(internal_name, None)
|
|
1975
|
+
record[note_name] = ";#".join(f"-1;#|{guid}" for guid in guids)
|
|
1976
|
+
elif allow_many:
|
|
1977
|
+
record.pop(internal_name, None)
|
|
1978
|
+
unresolved.add(internal_name)
|
|
1979
|
+
else:
|
|
1980
|
+
record[internal_name] = {
|
|
1981
|
+
"__metadata": {"type": "SP.Taxonomy.TaxonomyFieldValue"},
|
|
1982
|
+
"Label": "",
|
|
1983
|
+
"TermGuid": guids[0],
|
|
1984
|
+
"WssId": -1,
|
|
1985
|
+
}
|
|
1986
|
+
if unresolved:
|
|
1987
|
+
self.warnings.append(
|
|
1988
|
+
f"taxonomy column(s) not available on the target, values left empty: {', '.join(sorted(unresolved))}"
|
|
1989
|
+
)
|
|
1990
|
+
|
|
1991
|
+
def _report_unresolved_user(self, internal_name: str, login: str) -> None:
|
|
1992
|
+
marker = (internal_name, login)
|
|
1993
|
+
if marker in self._unresolved_users:
|
|
1994
|
+
return
|
|
1995
|
+
self._unresolved_users.add(marker)
|
|
1996
|
+
self.warnings.append(
|
|
1997
|
+
f"person column '{internal_name}' login {login!r} could not be resolved on the target; "
|
|
1998
|
+
"the value was left empty"
|
|
1999
|
+
)
|
|
2000
|
+
|
|
2001
|
+
def _target_user_logins(self) -> dict[int, str]:
|
|
2002
|
+
"""The target web's ``{site-user id: login name}`` map (cached)."""
|
|
2003
|
+
if self._user_logins is None:
|
|
2004
|
+
mapping: dict[int, str] = {}
|
|
2005
|
+
try:
|
|
2006
|
+
for site_user in self._list.context.web.site_users.get().execute_query():
|
|
2007
|
+
user_id = parse_int(site_user.id)
|
|
2008
|
+
if user_id is not None and site_user.login_name:
|
|
2009
|
+
mapping[user_id] = site_user.login_name
|
|
2010
|
+
except Exception as e: # noqa: BLE001 — without the map persons are left as ids
|
|
2011
|
+
self.warnings.append(f"could not read target site users for person verification: {e}")
|
|
2012
|
+
self._user_logins = mapping
|
|
2013
|
+
return self._user_logins
|
|
2014
|
+
|
|
2015
|
+
def _report_unresolved_lookup(self, internal_name: str, source_id: int) -> None:
|
|
2016
|
+
marker = (internal_name, source_id)
|
|
2017
|
+
if marker in self._unresolved_lookups:
|
|
2018
|
+
return
|
|
2019
|
+
self._unresolved_lookups.add(marker)
|
|
2020
|
+
self.warnings.append(
|
|
2021
|
+
f"lookup column '{internal_name}' value {source_id} could not be mapped to a target item "
|
|
2022
|
+
"(the referenced list's items were not migrated first); the value was left empty"
|
|
2023
|
+
)
|
|
2024
|
+
|
|
2025
|
+
def _canonicalize_lookups(self, properties: dict) -> None:
|
|
2026
|
+
"""Rewrite target lookup ids back to the source ids (symmetric to the write).
|
|
2027
|
+
|
|
2028
|
+
The source checksum holds the source item id; the target's fresh item holds
|
|
2029
|
+
the target id. Mapping the latter back lets the content spot-check compare
|
|
2030
|
+
the two like for like instead of reporting a false mismatch.
|
|
2031
|
+
"""
|
|
2032
|
+
for internal_name, target_list_id in self._lookup_columns().items():
|
|
2033
|
+
key = f"{internal_name}Id"
|
|
2034
|
+
target_id = parse_int(properties.get(key))
|
|
2035
|
+
if target_id is None:
|
|
2036
|
+
continue
|
|
2037
|
+
_, backward = self._lookup_index(target_list_id)
|
|
2038
|
+
source_id = backward.get(target_id)
|
|
2039
|
+
if source_id is not None:
|
|
2040
|
+
properties[key] = source_id
|
|
2041
|
+
for internal_name, target_list_id in self._multi_lookup_columns().items():
|
|
2042
|
+
key = f"{internal_name}Id"
|
|
2043
|
+
target_ids = _id_list(properties.get(key))
|
|
2044
|
+
if not target_ids:
|
|
2045
|
+
continue
|
|
2046
|
+
_, backward = self._lookup_index(target_list_id)
|
|
2047
|
+
mapped: list[Any] = []
|
|
2048
|
+
for value in target_ids:
|
|
2049
|
+
target_id = parse_int(value)
|
|
2050
|
+
mapped.append(backward.get(target_id, value) if target_id is not None else value)
|
|
2051
|
+
properties[key] = mapped
|
|
2052
|
+
|
|
2053
|
+
def _canonicalize_users(self, properties: dict) -> None:
|
|
2054
|
+
"""Rewrite target person ids back to the login names the source recorded."""
|
|
2055
|
+
columns = self._user_columns()
|
|
2056
|
+
logins = self._target_user_logins() if columns or self._multi_user_columns() else {}
|
|
2057
|
+
for internal_name in columns:
|
|
2058
|
+
key = f"{internal_name}Id"
|
|
2059
|
+
user_id = parse_int(properties.get(key))
|
|
2060
|
+
if user_id is None:
|
|
2061
|
+
continue
|
|
2062
|
+
login = logins.get(user_id)
|
|
2063
|
+
if login is not None:
|
|
2064
|
+
properties[key] = login
|
|
2065
|
+
for internal_name in self._multi_user_columns():
|
|
2066
|
+
key = f"{internal_name}Id"
|
|
2067
|
+
user_ids = _id_list(properties.get(key))
|
|
2068
|
+
if not user_ids:
|
|
2069
|
+
continue
|
|
2070
|
+
mapped_logins: list[Any] = []
|
|
2071
|
+
for value in user_ids:
|
|
2072
|
+
user_id = parse_int(value)
|
|
2073
|
+
mapped_logins.append(logins.get(user_id, value) if user_id is not None else value)
|
|
2074
|
+
properties[key] = mapped_logins
|
|
2075
|
+
|
|
2076
|
+
def _canonicalize_multi_choices(self, properties: dict) -> None:
|
|
2077
|
+
"""Reduce a read-back multi-choice cell to the bare list the source records.
|
|
2078
|
+
|
|
2079
|
+
A live read returns the selected values as a positional mapping
|
|
2080
|
+
(``{0: "red", 1: "green"}``); the source checksum holds a bare list, so the
|
|
2081
|
+
two are only comparable after the same projection.
|
|
2082
|
+
"""
|
|
2083
|
+
for internal_name in self._multi_choice_columns():
|
|
2084
|
+
if internal_name not in properties:
|
|
2085
|
+
continue
|
|
2086
|
+
values = _multi_values(properties.get(internal_name))
|
|
2087
|
+
if values:
|
|
2088
|
+
properties[internal_name] = values
|
|
2089
|
+
else:
|
|
2090
|
+
properties.pop(internal_name, None)
|
|
2091
|
+
|
|
2092
|
+
def _write_attachment(self, item: MigrationItem, payload: object) -> None:
|
|
2093
|
+
if not self._identity_field:
|
|
2094
|
+
raise NotImplementedError("attachments need the identity column, which could not be created")
|
|
2095
|
+
parent_id, _, filename = str(item.dest_path).partition("/")
|
|
2096
|
+
if not filename:
|
|
2097
|
+
raise ValueError(f"invalid attachment destination {item.dest_path!r}")
|
|
2098
|
+
self._flush_pending()
|
|
2099
|
+
parent = self._find_by_identity(parent_id)
|
|
2100
|
+
if parent is None:
|
|
2101
|
+
raise ValueError(f"no target item for source id {parent_id!r}; cannot attach {filename!r}")
|
|
2102
|
+
content = payload if isinstance(payload, bytes) else str(payload).encode("utf-8")
|
|
2103
|
+
parent.attachment_files.add({"filename": filename, "content": content}).execute_query()
|
|
2104
|
+
if self._attachment_path_cache is not None:
|
|
2105
|
+
self._attachment_path_cache.add(str(item.dest_path))
|
|
2106
|
+
|
|
2107
|
+
def _flush_pending(self) -> None:
|
|
2108
|
+
if self._list.context.has_pending_request:
|
|
2109
|
+
self._flush(DEFAULT_BATCH_SIZE, 1)
|
|
2110
|
+
|
|
2111
|
+
def _find_by_identity(self, source_id: str):
|
|
2112
|
+
if self._identity_index is None or self._identity_index_stale:
|
|
2113
|
+
self._refresh_identity_index()
|
|
2114
|
+
return self._identity_index.get(str(source_id)) if self._identity_index else None
|
|
2115
|
+
|
|
2116
|
+
def _refresh_identity_index(self) -> None:
|
|
2117
|
+
"""Load, or incrementally extend, the source-id -> target item map.
|
|
2118
|
+
|
|
2119
|
+
After a chunk is flushed only the newly created items are re-read
|
|
2120
|
+
(``Id gt <max>``) instead of re-scanning the whole list, so refreshing the
|
|
2121
|
+
index per chunk stays linear rather than O(n²).
|
|
2122
|
+
"""
|
|
2123
|
+
if not self._identity_field:
|
|
2124
|
+
self._identity_index = {}
|
|
2125
|
+
return
|
|
2126
|
+
if self._identity_index is not None and self._identity_index_max_id > 0:
|
|
2127
|
+
self._index_identity_items(self._query_identity_items(f"Id gt {self._identity_index_max_id}"))
|
|
2128
|
+
return
|
|
2129
|
+
self._identity_index = {}
|
|
2130
|
+
self._identity_index_max_id = 0
|
|
2131
|
+
self._index_identity_items(self._query_identity_items())
|
|
2132
|
+
|
|
2133
|
+
def _query_identity_items(self, filter_expr: str | None = None):
|
|
2134
|
+
# A read query would implicitly flush any queued writes on this context;
|
|
2135
|
+
# flush them explicitly first so the cached index stays consistent.
|
|
2136
|
+
self._flush_pending()
|
|
2137
|
+
columns = ["Id", self._identity_field, "Created", "Modified", "AuthorId", "EditorId"]
|
|
2138
|
+
query = self._list.items.select(columns)
|
|
2139
|
+
if filter_expr:
|
|
2140
|
+
query = query.filter(filter_expr)
|
|
2141
|
+
return query.get_all().execute_query()
|
|
2142
|
+
|
|
2143
|
+
def _index_identity_items(self, items) -> None:
|
|
2144
|
+
if self._identity_index is None:
|
|
2145
|
+
self._identity_index = {}
|
|
2146
|
+
for target_item in items:
|
|
2147
|
+
value = target_item.properties.get(self._identity_field)
|
|
2148
|
+
if value is None:
|
|
2149
|
+
continue
|
|
2150
|
+
self._identity_index[str(value)] = target_item
|
|
2151
|
+
item_id = parse_int(getattr(target_item, "id", None))
|
|
2152
|
+
if item_id is not None and item_id > self._identity_index_max_id:
|
|
2153
|
+
self._identity_index_max_id = item_id
|
|
2154
|
+
self._identity_index_stale = False
|
|
2155
|
+
|
|
2156
|
+
def _attachment_paths(self) -> list[str]:
|
|
2157
|
+
if not self._identity_field:
|
|
2158
|
+
return []
|
|
2159
|
+
if self._attachment_path_cache is None:
|
|
2160
|
+
self._attachment_path_cache = set(self._query_attachment_paths())
|
|
2161
|
+
return list(self._attachment_path_cache)
|
|
2162
|
+
|
|
2163
|
+
def _query_attachment_paths(self) -> list[str]:
|
|
2164
|
+
if not self._identity_field:
|
|
2165
|
+
return []
|
|
2166
|
+
self._flush_pending()
|
|
2167
|
+
items = (
|
|
2168
|
+
self._list.items.select(["Id", self._identity_field]).expand(["AttachmentFiles"]).get_all().execute_query()
|
|
2169
|
+
)
|
|
2170
|
+
paths: list[str] = []
|
|
2171
|
+
for target_item in items:
|
|
2172
|
+
value = target_item.properties.get(self._identity_field)
|
|
2173
|
+
if value is None:
|
|
2174
|
+
continue
|
|
2175
|
+
for attachment in target_item.properties.get("AttachmentFiles") or []:
|
|
2176
|
+
name = getattr(attachment, "file_name", None)
|
|
2177
|
+
if name:
|
|
2178
|
+
paths.append(f"{value}/{name}")
|
|
2179
|
+
return paths
|
|
2180
|
+
|
|
255
2181
|
def list_paths(self) -> list[str]:
|
|
256
|
-
|
|
2182
|
+
if not self._identity_field:
|
|
2183
|
+
return [str(i.id) for i in self._list.items.get().execute_query()]
|
|
2184
|
+
# An empty source list never queues a write, so the marker column was not
|
|
2185
|
+
# created during migration; ensure it before verification queries it.
|
|
2186
|
+
self._ensure_identity()
|
|
2187
|
+
if not self._identity_field:
|
|
2188
|
+
return [str(i.id) for i in self._list.items.get().execute_query()]
|
|
2189
|
+
if self._identity_index is None or self._identity_index_stale:
|
|
2190
|
+
self._refresh_identity_index()
|
|
2191
|
+
index = self._identity_index or {}
|
|
2192
|
+
return list(index.keys()) + self._attachment_paths()
|
|
257
2193
|
|
|
258
2194
|
def checksum(self, item: MigrationItem) -> str:
|
|
259
|
-
|
|
2195
|
+
if item.item_type == "attachment":
|
|
2196
|
+
return self._attachment_checksum(item)
|
|
2197
|
+
target_item = self._fresh_target_item(item)
|
|
2198
|
+
if target_item is None:
|
|
2199
|
+
return ""
|
|
2200
|
+
return self.content_checksum(dict(target_item.properties))
|
|
2201
|
+
|
|
2202
|
+
def content_checksum(self, properties: dict) -> str:
|
|
2203
|
+
"""The comparable content hash of an item's read-back properties.
|
|
2204
|
+
|
|
2205
|
+
Used for both a written record and a folder's item, so a library folder's
|
|
2206
|
+
column values can be reconciled against the source record that produced them.
|
|
2207
|
+
"""
|
|
2208
|
+
properties = _canonicalize_taxonomy_record(dict(properties))
|
|
2209
|
+
self._canonicalize_lookups(properties)
|
|
2210
|
+
self._canonicalize_users(properties)
|
|
2211
|
+
self._canonicalize_multi_choices(properties)
|
|
2212
|
+
extra = {self._identity_field} if self._identity_field else set()
|
|
2213
|
+
# The hidden taxonomy note companion is not part of the source's portable
|
|
2214
|
+
# content; it is verified through the term ids on the column itself.
|
|
2215
|
+
extra.update(note for _, note in self._taxonomy_columns().values() if note)
|
|
2216
|
+
# The target derives its own ``<name>StringId`` companion; it is not part of
|
|
2217
|
+
# the source's canonical content.
|
|
2218
|
+
extra.update(f"{name}StringId" for name in self._user_columns())
|
|
2219
|
+
extra.update(f"{name}StringId" for name in self._multi_user_columns())
|
|
2220
|
+
return _content_checksum(properties, frozenset(extra))
|
|
2221
|
+
|
|
2222
|
+
def _attachment_checksum(self, item: MigrationItem) -> str:
|
|
2223
|
+
parent_id, _, filename = str(item.dest_path).partition("/")
|
|
2224
|
+
parent = self._find_by_identity(parent_id)
|
|
2225
|
+
if parent is None:
|
|
2226
|
+
return ""
|
|
2227
|
+
try:
|
|
2228
|
+
data = parent.attachment_files.get_by_filename(filename).get_content().execute_query().value
|
|
2229
|
+
except Exception: # noqa: BLE001 — a missing/unreadable attachment is unverifiable
|
|
2230
|
+
return ""
|
|
2231
|
+
return hashlib.md5(data).hexdigest() if data else ""
|
|
260
2232
|
|
|
261
2233
|
def commit(self, options=None) -> None:
|
|
262
2234
|
"""Flush any remaining queued record writes through an OData batch."""
|
|
263
2235
|
batch_size = getattr(options, "batch_size", None) or DEFAULT_BATCH_SIZE
|
|
264
2236
|
concurrency = getattr(options, "concurrency", None) or 1
|
|
265
2237
|
self._flush(batch_size, concurrency)
|
|
2238
|
+
self._pending_authorship.clear()
|
|
2239
|
+
self._pending_approval.clear()
|
|
266
2240
|
|
|
267
2241
|
def _flush(self, batch_size: int, concurrency: int) -> None:
|
|
268
2242
|
if not self._list.context.has_pending_request:
|
|
269
2243
|
return
|
|
270
2244
|
self._list.context.execute_batch(items_per_batch=batch_size, concurrency=concurrency)
|
|
271
2245
|
self._list.items.clear()
|
|
2246
|
+
# Records were just created/updated, so the cached source-id index needs an
|
|
2247
|
+
# incremental refresh (new items only) before the next lookup.
|
|
2248
|
+
if self._identity_index is not None:
|
|
2249
|
+
self._identity_index_stale = True
|
|
2250
|
+
|
|
2251
|
+
def _principals(self) -> _TargetPrincipals:
|
|
2252
|
+
"""The (lazily built) target principal/role resolver."""
|
|
2253
|
+
if self._principals_cache is None:
|
|
2254
|
+
self._principals_cache = _TargetPrincipals(self._list.context.web, self.warnings)
|
|
2255
|
+
return self._principals_cache
|
|
2256
|
+
|
|
2257
|
+
def _target_item(self, item: MigrationItem):
|
|
2258
|
+
"""The written target item for a record (``None`` for attachments/folders)."""
|
|
2259
|
+
if item.item_type == "attachment":
|
|
2260
|
+
return None
|
|
2261
|
+
return self._find_by_identity(item.dest_path)
|
|
2262
|
+
|
|
2263
|
+
def apply_timestamps(self, item: MigrationItem) -> None:
|
|
2264
|
+
"""Best-effort: restore ``Created``/``Modified`` on the written item.
|
|
2265
|
+
|
|
2266
|
+
Uses ``ValidateUpdateListItem`` with ``bNewDocumentUpdate=True`` -- a
|
|
2267
|
+
system update (the CSOM ``UpdateOverwriteVersion`` path), so the metadata
|
|
2268
|
+
lands **without** creating a new version and a migrated record keeps its
|
|
2269
|
+
original version instead of accumulating one version per fidelity write.
|
|
2270
|
+
``datesInUTC=True`` requires the ``...Z`` UTC form. Authorship values
|
|
2271
|
+
resolved for the same item are folded into this final write so both land
|
|
2272
|
+
atomically, in one system update.
|
|
2273
|
+
"""
|
|
2274
|
+
target_item = self._target_item(item)
|
|
2275
|
+
if target_item is None:
|
|
2276
|
+
return
|
|
2277
|
+
form_values: dict = {}
|
|
2278
|
+
created = _sp_utc_timestamp(item.created)
|
|
2279
|
+
modified = _sp_utc_timestamp(item.modified)
|
|
2280
|
+
if created:
|
|
2281
|
+
form_values["Created"] = created
|
|
2282
|
+
if modified:
|
|
2283
|
+
form_values["Modified"] = modified
|
|
2284
|
+
form_values.update(self._pending_authorship.pop(item.dest_path, {}))
|
|
2285
|
+
form_values.update(self._pending_approval.pop(item.dest_path, {}))
|
|
2286
|
+
if form_values:
|
|
2287
|
+
result = target_item.validate_update_list_item(
|
|
2288
|
+
form_values, dates_in_utc=True, new_document_update=True
|
|
2289
|
+
).execute_query()
|
|
2290
|
+
_report_rejected_fields(result, self.warnings, f"timestamps {item.dest_path}")
|
|
2291
|
+
|
|
2292
|
+
def apply_authorship(self, item: MigrationItem) -> None:
|
|
2293
|
+
"""Best-effort: resolve ``Author``/``Editor`` and cache them for one write.
|
|
2294
|
+
|
|
2295
|
+
A principal is applied only when its login name resolves to a target user
|
|
2296
|
+
(``EnsureUser``); unresolved principals are skipped (same-tenant only).
|
|
2297
|
+
|
|
2298
|
+
Nothing is sent here. ``ValidateUpdateListItem`` is one request per call, so
|
|
2299
|
+
writing the resolved principals immediately and then again with the
|
|
2300
|
+
timestamps cost every preserved item two system updates. The values are
|
|
2301
|
+
cached and folded into the timestamps write, or written by
|
|
2302
|
+
:meth:`finalize_fidelity` when timestamps are not preserved.
|
|
2303
|
+
"""
|
|
2304
|
+
target_item = self._target_item(item)
|
|
2305
|
+
if target_item is None:
|
|
2306
|
+
return
|
|
2307
|
+
author = self._principals().user(item.author.login_name if item.author else None)
|
|
2308
|
+
editor = self._principals().user(item.editor.login_name if item.editor else None)
|
|
2309
|
+
if author is not None and _is_system_principal(author):
|
|
2310
|
+
author = None
|
|
2311
|
+
if editor is not None and _is_system_principal(editor):
|
|
2312
|
+
editor = None
|
|
2313
|
+
if author is None and editor is None and not self._pending_approval.get(item.dest_path):
|
|
2314
|
+
return
|
|
2315
|
+
form_values: dict = {}
|
|
2316
|
+
if author is not None:
|
|
2317
|
+
form_values["Author"] = FieldUserValue(author.id, author.login_name)
|
|
2318
|
+
if editor is not None:
|
|
2319
|
+
form_values["Editor"] = FieldUserValue(editor.id, editor.login_name)
|
|
2320
|
+
# Replay the approval state here too: this write is itself a system update, so
|
|
2321
|
+
# on a run that restores authorship without timestamps it is the last one.
|
|
2322
|
+
form_values.update(self._pending_approval.get(item.dest_path, {}))
|
|
2323
|
+
self._pending_authorship[item.dest_path] = dict(form_values)
|
|
2324
|
+
|
|
2325
|
+
def finalize_fidelity(self, item: MigrationItem) -> None:
|
|
2326
|
+
"""Write the fidelity values that were only cached for this item.
|
|
2327
|
+
|
|
2328
|
+
The last hook wins the write: with timestamps preserved that is
|
|
2329
|
+
:meth:`apply_timestamps`; without them it is this method, so the item still
|
|
2330
|
+
gets one system update instead of one per property group. It runs before
|
|
2331
|
+
the approval write, which has to stay last (a system update re-approves).
|
|
2332
|
+
"""
|
|
2333
|
+
form_values = self._pending_authorship.pop(item.dest_path, {})
|
|
2334
|
+
form_values.update(self._pending_approval.pop(item.dest_path, {}))
|
|
2335
|
+
if not form_values:
|
|
2336
|
+
return
|
|
2337
|
+
target_item = self._target_item(item)
|
|
2338
|
+
if target_item is None:
|
|
2339
|
+
return
|
|
2340
|
+
result = target_item.validate_update_list_item(
|
|
2341
|
+
form_values, dates_in_utc=True, new_document_update=True
|
|
2342
|
+
).execute_query()
|
|
2343
|
+
_report_rejected_fields(result, self.warnings, f"authorship {item.dest_path}")
|
|
2344
|
+
|
|
2345
|
+
def apply_permissions(self, item: MigrationItem, permissions: list[PermissionEntry]) -> None:
|
|
2346
|
+
"""Best-effort: replay the source item's unique ACLs onto the target item.
|
|
2347
|
+
|
|
2348
|
+
Same-tenant only — cross-tenant identity mapping is out of scope. See
|
|
2349
|
+
:func:`_replay_role_assignments` for what happens to principals, roles and
|
|
2350
|
+
inheritance that cannot be resolved.
|
|
2351
|
+
"""
|
|
2352
|
+
target_item = self._target_item(item)
|
|
2353
|
+
if target_item is None:
|
|
2354
|
+
return
|
|
2355
|
+
_replay_role_assignments(target_item, self._principals(), permissions, self.warnings, item.dest_path)
|
|
2356
|
+
self._list.context.execute_query()
|
|
2357
|
+
|
|
2358
|
+
def _fresh_target_item(self, item: MigrationItem):
|
|
2359
|
+
"""The written target item with freshly-read properties (verification hooks).
|
|
2360
|
+
|
|
2361
|
+
The cached identity-index object predates the fidelity writes
|
|
2362
|
+
(``apply_timestamps``/``apply_authorship``), so re-read it before diffing.
|
|
2363
|
+
"""
|
|
2364
|
+
target_item = self._target_item(item)
|
|
2365
|
+
if target_item is not None:
|
|
2366
|
+
target_item.get().execute_query()
|
|
2367
|
+
return target_item
|
|
2368
|
+
|
|
2369
|
+
def read_metadata(self, item: MigrationItem) -> dict | None:
|
|
2370
|
+
"""Comparable attributes of the written item (for the metadata diff)."""
|
|
2371
|
+
target_item = self._fresh_target_item(item)
|
|
2372
|
+
if target_item is None:
|
|
2373
|
+
return None
|
|
2374
|
+
properties = target_item.properties
|
|
2375
|
+
return {
|
|
2376
|
+
"created": iso_or_none(properties.get("Created")),
|
|
2377
|
+
"modified": iso_or_none(properties.get("Modified")),
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
def read_authorship(self, item: MigrationItem) -> tuple[str | None, str | None]:
|
|
2381
|
+
"""The written item's author/editor login names (for the authorship diff)."""
|
|
2382
|
+
target_item = self._fresh_target_item(item)
|
|
2383
|
+
if target_item is None:
|
|
2384
|
+
return (None, None)
|
|
2385
|
+
properties = target_item.properties
|
|
2386
|
+
return (
|
|
2387
|
+
_user_login(self._list.context.web, properties.get("AuthorId"), self._login_cache),
|
|
2388
|
+
_user_login(self._list.context.web, properties.get("EditorId"), self._login_cache),
|
|
2389
|
+
)
|
|
2390
|
+
|
|
2391
|
+
def read_permissions(self, item: MigrationItem) -> list[PermissionEntry]:
|
|
2392
|
+
"""Read the written item's *unique* role assignments (for the permission diff)."""
|
|
2393
|
+
target_item = self._fresh_target_item(item)
|
|
2394
|
+
if target_item is None or not _has_unique_role_assignments(target_item, refresh=True):
|
|
2395
|
+
return []
|
|
2396
|
+
return _read_role_assignments(target_item, normalize=self._principals().normalize)
|
|
272
2397
|
|
|
273
2398
|
def close(self) -> None:
|
|
274
2399
|
pass
|
|
@@ -278,16 +2403,46 @@ class SharePointLibrarySource:
|
|
|
278
2403
|
"""Enumerates a document library's files and folders (recursively) for migration.
|
|
279
2404
|
|
|
280
2405
|
Folder entries (``item_type="folder"``, trailing ``/`` on the destination)
|
|
281
|
-
preserve the folder structure — including **empty** folders — on the target
|
|
2406
|
+
preserve the folder structure — including **empty** folders — on the target, and
|
|
2407
|
+
carry the folder's own column values (a document set's metadata, a managed
|
|
2408
|
+
folder's department) as their payload when the parent list is known.
|
|
282
2409
|
"""
|
|
283
2410
|
|
|
2411
|
+
def rebind(self) -> "SharePointLibrarySource":
|
|
2412
|
+
"""This source on its own cloned context (see :meth:`SharePointListSource.rebind`)."""
|
|
2413
|
+
context = _cloned_context(self._folder)
|
|
2414
|
+
sp_list = _cloned_list(self._sp_list, context) if self._sp_list is not None else None
|
|
2415
|
+
return SharePointLibrarySource(_cloned_folder(self._folder, context), self.on_bytes, sp_list=sp_list)
|
|
2416
|
+
|
|
284
2417
|
def __init__(
|
|
285
2418
|
self,
|
|
286
2419
|
library_folder: "Folder",
|
|
287
2420
|
on_bytes: Callable[[int, int | None], None] | None = None,
|
|
2421
|
+
sp_list: "SPList | None" = None,
|
|
288
2422
|
) -> None:
|
|
289
2423
|
self._folder = library_folder
|
|
2424
|
+
# The parent list, when known — author/editor ids (``AuthorId``/``EditorId``)
|
|
2425
|
+
# are not exposed on the ``File`` resource and must be read from its items.
|
|
2426
|
+
self._sp_list = sp_list
|
|
2427
|
+
# Folder column values come from the list's items and are projected exactly
|
|
2428
|
+
# like a record's (typed values normalized), so that code is reused here.
|
|
2429
|
+
self._list_source = SharePointListSource(sp_list) if sp_list is not None else None
|
|
2430
|
+
self._folder_payloads: dict[str, dict] | None = None
|
|
290
2431
|
self._files: dict[str, "File"] = {}
|
|
2432
|
+
# Folder resources by library-relative path, so their own system metadata
|
|
2433
|
+
# (``TimeCreated``/``TimeLastModified``) can be diffed after the run.
|
|
2434
|
+
self._folders: dict[str, "Folder"] = {}
|
|
2435
|
+
self._users: dict[int, "Principal | None"] = {}
|
|
2436
|
+
self._default_groups: _DefaultGroups | None = None
|
|
2437
|
+
self._item_authors: dict[str, tuple[int | None, int | None]] | None = None
|
|
2438
|
+
self._item_unique: dict[str, bool] | None = None
|
|
2439
|
+
# Content-approval state by file URL, read alongside the author ids (only
|
|
2440
|
+
# when the library moderates).
|
|
2441
|
+
self._item_moderation: dict[str, dict] | None = None
|
|
2442
|
+
# Server-relative URLs of checked-out files, read alongside the author ids.
|
|
2443
|
+
self._item_checkout: set[str] | None = None
|
|
2444
|
+
self._moderation_on: bool | None = None
|
|
2445
|
+
self.warnings: list[str] = []
|
|
291
2446
|
# Optional hook invoked while a file downloads: (bytes_read, file_size)
|
|
292
2447
|
self.on_bytes = on_bytes
|
|
293
2448
|
|
|
@@ -303,8 +2458,10 @@ class SharePointLibrarySource:
|
|
|
303
2458
|
self._folder.ensure_properties(["ServerRelativeUrl"]).execute_query()
|
|
304
2459
|
root = self._folder.server_relative_url or ""
|
|
305
2460
|
root = root.rstrip("/")
|
|
2461
|
+
extra = _site_assets_scaffolding(self._folder)
|
|
306
2462
|
loaded_folders = self._folder.get_folders(recursive=True).execute_query()
|
|
307
2463
|
loaded = self._folder.get_files(recursive=True).execute_query()
|
|
2464
|
+
authors = self._file_authors()
|
|
308
2465
|
|
|
309
2466
|
def _rel(url: str) -> str:
|
|
310
2467
|
return url[len(root) :].lstrip("/") if root else url.lstrip("/")
|
|
@@ -314,33 +2471,114 @@ class SharePointLibrarySource:
|
|
|
314
2471
|
for folder in loaded_folders:
|
|
315
2472
|
url = folder.server_relative_url or ""
|
|
316
2473
|
rel = _rel(url)
|
|
317
|
-
if not rel or rel in seen:
|
|
2474
|
+
if not rel or rel in seen or _is_system_path(rel, extra):
|
|
318
2475
|
continue
|
|
319
2476
|
seen.add(rel)
|
|
320
|
-
|
|
2477
|
+
self._folders[rel] = folder
|
|
2478
|
+
author_id, editor_id = authors.get(url, (None, None))
|
|
2479
|
+
folder_item = MigrationItem(
|
|
2480
|
+
source_path=url,
|
|
2481
|
+
dest_path=f"{rel}/",
|
|
2482
|
+
item_type="folder",
|
|
2483
|
+
modified=iso_or_none(folder.time_last_modified),
|
|
2484
|
+
created=iso_or_none(folder.time_created),
|
|
2485
|
+
author_id=author_id,
|
|
2486
|
+
editor_id=editor_id,
|
|
2487
|
+
)
|
|
2488
|
+
if author_id is not None:
|
|
2489
|
+
folder_item.author = self._principal_for(author_id)
|
|
2490
|
+
if editor_id is not None:
|
|
2491
|
+
folder_item.editor = self._principal_for(editor_id)
|
|
2492
|
+
result.append(folder_item)
|
|
321
2493
|
emit_progress(progress, done=len(result), stage="planning", items=[folder])
|
|
322
2494
|
for file in loaded:
|
|
323
2495
|
url = file.server_relative_url or ""
|
|
324
2496
|
rel = _rel(url)
|
|
2497
|
+
if _is_system_path(rel, extra):
|
|
2498
|
+
continue
|
|
325
2499
|
self._files[rel] = file
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
)
|
|
2500
|
+
author_id = parse_int(file.author.id)
|
|
2501
|
+
editor_id = parse_int(file.modified_by.id)
|
|
2502
|
+
if author_id is None and editor_id is None:
|
|
2503
|
+
author_id, editor_id = authors.get(url, (None, None))
|
|
2504
|
+
item = MigrationItem(
|
|
2505
|
+
source_path=url,
|
|
2506
|
+
dest_path=rel,
|
|
2507
|
+
size_bytes=file.length or 0,
|
|
2508
|
+
item_type="file",
|
|
2509
|
+
modified=iso_or_none(file.time_last_modified),
|
|
2510
|
+
created=iso_or_none(file.time_created),
|
|
2511
|
+
author_id=author_id,
|
|
2512
|
+
editor_id=editor_id,
|
|
2513
|
+
source_id=str(file.unique_id) if file.unique_id else None,
|
|
2514
|
+
version_label=file.ui_version_label,
|
|
337
2515
|
)
|
|
2516
|
+
if author_id is not None:
|
|
2517
|
+
item.author = self._principal_for(author_id)
|
|
2518
|
+
if editor_id is not None:
|
|
2519
|
+
item.editor = self._principal_for(editor_id)
|
|
2520
|
+
result.append(item)
|
|
338
2521
|
emit_progress(progress, done=len(result), stage="planning", items=[file])
|
|
2522
|
+
self._report_checkouts(result)
|
|
2523
|
+
self._report_drafts(result)
|
|
339
2524
|
return result
|
|
340
2525
|
|
|
341
|
-
def
|
|
2526
|
+
def _report_checkouts(self, items: list[MigrationItem]) -> None:
|
|
2527
|
+
"""Warn about files whose in-progress edits REST cannot see.
|
|
2528
|
+
|
|
2529
|
+
A checked-out file exposes its last **checked-in** content, so the copy is
|
|
2530
|
+
faithful to what the source returns while the author's pending edits stay
|
|
2531
|
+
behind. Verification cannot catch that — it compares the same read — so the
|
|
2532
|
+
loss is reported here instead of passing silently.
|
|
2533
|
+
"""
|
|
2534
|
+
checkout = self._item_checkout or set()
|
|
2535
|
+
checked = sorted(item.dest_path for item in items if item.item_type != "folder" and item.source_path in checkout)
|
|
2536
|
+
if not checked:
|
|
2537
|
+
return
|
|
2538
|
+
shown = ", ".join(checked[:_WARNING_SAMPLE])
|
|
2539
|
+
if len(checked) > _WARNING_SAMPLE:
|
|
2540
|
+
shown = f"{shown} and {len(checked) - _WARNING_SAMPLE} more"
|
|
2541
|
+
self.warnings.append(
|
|
2542
|
+
f"{len(checked)} checked-out file(s) were copied from their last checked-in version "
|
|
2543
|
+
f"(in-progress edits are not visible over REST): {shown}"
|
|
2544
|
+
)
|
|
2545
|
+
|
|
2546
|
+
def _report_drafts(self, items: list[MigrationItem]) -> None:
|
|
2547
|
+
"""Warn about files whose current version is an un-published (minor) draft.
|
|
2548
|
+
|
|
2549
|
+
REST returns the *draft* content, so the copy is faithful to the version the
|
|
2550
|
+
source reports — but version history is not preserved, so the published
|
|
2551
|
+
version a draft supersedes never reaches the target and the copy keeps the
|
|
2552
|
+
draft alone. Verification compares the same read and cannot see the
|
|
2553
|
+
difference, so the loss is reported here instead of passing silently.
|
|
2554
|
+
|
|
2555
|
+
The signal rides the file listing (``MinorVersion``), so this costs no extra
|
|
2556
|
+
request: a published file is ``x.0``, a draft ``x.n`` with ``n > 0``.
|
|
2557
|
+
"""
|
|
2558
|
+
drafts: list[str] = []
|
|
2559
|
+
for item in items:
|
|
2560
|
+
if item.item_type == "folder":
|
|
2561
|
+
continue
|
|
2562
|
+
file = self._files.get(item.dest_path)
|
|
2563
|
+
if file is None or not file.minor_version:
|
|
2564
|
+
continue
|
|
2565
|
+
drafts.append(item.dest_path)
|
|
2566
|
+
if not drafts:
|
|
2567
|
+
return
|
|
2568
|
+
drafts.sort()
|
|
2569
|
+
shown = ", ".join(drafts[:_WARNING_SAMPLE])
|
|
2570
|
+
if len(drafts) > _WARNING_SAMPLE:
|
|
2571
|
+
shown = f"{shown} and {len(drafts) - _WARNING_SAMPLE} more"
|
|
2572
|
+
self.warnings.append(
|
|
2573
|
+
f"{len(drafts)} file(s) are on an un-published (minor) version: the draft content "
|
|
2574
|
+
f"is copied, the published version it supersedes is not (version history is not "
|
|
2575
|
+
f"preserved): {shown}"
|
|
2576
|
+
)
|
|
2577
|
+
|
|
2578
|
+
def read(self, item: MigrationItem) -> object:
|
|
342
2579
|
if item.item_type == "folder":
|
|
343
|
-
|
|
2580
|
+
# A folder's payload is its column values (empty when it carries none).
|
|
2581
|
+
return self._folder_values(item) or b""
|
|
344
2582
|
file = self._files.get(item.dest_path)
|
|
345
2583
|
if file is None:
|
|
346
2584
|
raise FileNotFoundError(item.source_path)
|
|
@@ -354,10 +2592,55 @@ class SharePointLibrarySource:
|
|
|
354
2592
|
file.download_session(buffer, chunk_downloaded=_chunk_downloaded).execute_query()
|
|
355
2593
|
return buffer.getvalue()
|
|
356
2594
|
|
|
2595
|
+
def read_version_history(self, item: MigrationItem) -> tuple[str | None, list[tuple[str, bytes]]]:
|
|
2596
|
+
"""Read the file's current version label and its older versions.
|
|
2597
|
+
|
|
2598
|
+
Returns ``(current_label, older)`` where ``older`` is oldest-first and
|
|
2599
|
+
excludes the current version (which :meth:`read` already supplies). The
|
|
2600
|
+
current label comes from ``UIVersionLabel``; older content is streamed
|
|
2601
|
+
from each ``File.Versions`` entry. Folders have no version history.
|
|
2602
|
+
"""
|
|
2603
|
+
if item.item_type == "folder":
|
|
2604
|
+
return None, []
|
|
2605
|
+
file = self._files.get(item.dest_path)
|
|
2606
|
+
if file is None:
|
|
2607
|
+
raise FileNotFoundError(item.source_path)
|
|
2608
|
+
if file.ui_version_label is None:
|
|
2609
|
+
file.ensure_property("UIVersionLabel").execute_query()
|
|
2610
|
+
older: list[tuple[str, bytes]] = []
|
|
2611
|
+
for version in file.versions.get().execute_query():
|
|
2612
|
+
label = version.version_label
|
|
2613
|
+
if not label:
|
|
2614
|
+
continue
|
|
2615
|
+
data = version.open_binary_stream().execute_query().value
|
|
2616
|
+
older.append((label, data or b""))
|
|
2617
|
+
older.sort(key=lambda row: _version_sort_key(row[0]))
|
|
2618
|
+
return file.ui_version_label, older
|
|
2619
|
+
|
|
2620
|
+
@property
|
|
2621
|
+
def supports_folder_records(self) -> bool:
|
|
2622
|
+
"""Whether a folder's column values are migrated *and* comparable."""
|
|
2623
|
+
return self._list_source is not None
|
|
2624
|
+
|
|
357
2625
|
def checksum(self, item: MigrationItem) -> str:
|
|
358
2626
|
if item.item_type == "folder":
|
|
359
|
-
return
|
|
360
|
-
return hashlib.md5(self.read(item)).hexdigest()
|
|
2627
|
+
return _content_checksum(_canonicalize_taxonomy_record(self._folder_values(item)))
|
|
2628
|
+
return hashlib.md5(cast(bytes, self.read(item))).hexdigest()
|
|
2629
|
+
|
|
2630
|
+
def _folder_values(self, item: MigrationItem) -> dict:
|
|
2631
|
+
"""The source folder's column values (empty when it carries none)."""
|
|
2632
|
+
if self._folder_payloads is None:
|
|
2633
|
+
payloads: dict[str, dict] = {}
|
|
2634
|
+
if self._sp_list is None or self._list_source is None:
|
|
2635
|
+
self.warnings.append(
|
|
2636
|
+
"folder column values were not read: the parent library is unknown "
|
|
2637
|
+
"(pass sp_list to SharePointLibrarySource)"
|
|
2638
|
+
)
|
|
2639
|
+
else:
|
|
2640
|
+
payloads.update(self._list_source.folder_records())
|
|
2641
|
+
self.warnings.extend(self._list_source.warnings)
|
|
2642
|
+
self._folder_payloads = payloads
|
|
2643
|
+
return self._folder_payloads.get(str(item.source_path).rstrip("/"), {})
|
|
361
2644
|
|
|
362
2645
|
def read_permissions(self, item: MigrationItem) -> list[PermissionEntry]:
|
|
363
2646
|
"""Read the item's role assignments (for best-effort ACL preservation).
|
|
@@ -368,16 +2651,56 @@ class SharePointLibrarySource:
|
|
|
368
2651
|
securable = self._securable(item)
|
|
369
2652
|
if securable is None:
|
|
370
2653
|
return []
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
2654
|
+
# Files: reuse the unique-ACL flag read with the authorship pass (the
|
|
2655
|
+
# ``File``/``ListItem`` resources do not expose it without an extra GET).
|
|
2656
|
+
if item.item_type != "folder" and self._item_unique is not None:
|
|
2657
|
+
unique = self._item_unique.get(item.source_path)
|
|
2658
|
+
if unique is not None:
|
|
2659
|
+
securable.properties["HasUniqueRoleAssignments"] = unique
|
|
2660
|
+
if not _has_unique_role_assignments(securable):
|
|
2661
|
+
return []
|
|
2662
|
+
return _read_role_assignments(securable, resolve=self._principal_for, normalize=self._normalize_name)
|
|
2663
|
+
|
|
2664
|
+
def _normalize_name(self, name: str) -> str:
|
|
2665
|
+
"""Canonicalize a principal name (default groups -> ``default:<Kind>``)."""
|
|
2666
|
+
context = getattr(self._folder, "context", None)
|
|
2667
|
+
web = getattr(context, "web", None) if context is not None else None
|
|
2668
|
+
if web is None:
|
|
2669
|
+
return name
|
|
2670
|
+
if self._default_groups is None:
|
|
2671
|
+
self._default_groups = _DefaultGroups(web)
|
|
2672
|
+
return self._default_groups.normalize(name)
|
|
2673
|
+
|
|
2674
|
+
def read_metadata(self, item: MigrationItem) -> dict | None:
|
|
2675
|
+
"""Comparable attributes of the source item (for the post-run metadata diff)."""
|
|
2676
|
+
if item.item_type == "folder":
|
|
2677
|
+
folder = self._folders.get(str(item.dest_path).rstrip("/"))
|
|
2678
|
+
if folder is None:
|
|
2679
|
+
return None
|
|
2680
|
+
return {
|
|
2681
|
+
"created": iso_or_none(folder.time_created),
|
|
2682
|
+
"modified": iso_or_none(folder.time_last_modified),
|
|
2683
|
+
}
|
|
2684
|
+
file = self._files.get(item.dest_path)
|
|
2685
|
+
if file is None:
|
|
2686
|
+
return None
|
|
2687
|
+
metadata = {
|
|
2688
|
+
"created": iso_or_none(file.time_created),
|
|
2689
|
+
"modified": iso_or_none(file.time_last_modified),
|
|
2690
|
+
"size_bytes": file.length or 0,
|
|
2691
|
+
}
|
|
2692
|
+
self._file_authors()
|
|
2693
|
+
state = (self._item_moderation or {}).get(item.source_path)
|
|
2694
|
+
if state:
|
|
2695
|
+
metadata["moderation_status"] = state.get("_ModerationStatus")
|
|
2696
|
+
return metadata
|
|
2697
|
+
|
|
2698
|
+
def read_authorship(self, item: MigrationItem) -> tuple[str | None, str | None]:
|
|
2699
|
+
"""The source author/editor login names (for the authorship diff)."""
|
|
2700
|
+
return (
|
|
2701
|
+
item.author.login_name if item.author else None,
|
|
2702
|
+
item.editor.login_name if item.editor else None,
|
|
2703
|
+
)
|
|
381
2704
|
|
|
382
2705
|
def _securable(self, item: MigrationItem):
|
|
383
2706
|
if item.item_type == "folder":
|
|
@@ -385,6 +2708,122 @@ class SharePointLibrarySource:
|
|
|
385
2708
|
file = self._files.get(item.dest_path)
|
|
386
2709
|
return file.listItemAllFields if file is not None else None
|
|
387
2710
|
|
|
2711
|
+
def _principal_for(self, user_id: Any) -> "Principal | None":
|
|
2712
|
+
"""Resolve a site-user id to a :class:`Principal` (login + PUID), cached."""
|
|
2713
|
+
key = parse_int(user_id)
|
|
2714
|
+
if key is None:
|
|
2715
|
+
return None
|
|
2716
|
+
if key in self._users:
|
|
2717
|
+
return self._users[key]
|
|
2718
|
+
principal: "Principal | None" = None
|
|
2719
|
+
try:
|
|
2720
|
+
user = self._folder.context.web.site_users.get_by_id(cast("str", key)).get().execute_query()
|
|
2721
|
+
user_id_info = user.properties.get("UserId")
|
|
2722
|
+
raw_id = getattr(user_id_info, "NameId", None)
|
|
2723
|
+
system_id = normalize_puid(raw_id)
|
|
2724
|
+
if raw_id and system_id is None:
|
|
2725
|
+
# e.g. the app principal, whose ``UserId.NameId`` is not a PUID.
|
|
2726
|
+
# Emitting it would fail the whole ingestion job, so drop it.
|
|
2727
|
+
self.warnings.append(
|
|
2728
|
+
f"user {key} ({user.login_name!r}) has no usable PUID ({raw_id!r}); "
|
|
2729
|
+
"it is omitted from the server-side user map"
|
|
2730
|
+
)
|
|
2731
|
+
principal = Principal(
|
|
2732
|
+
login_name=user.login_name or "",
|
|
2733
|
+
system_id=system_id,
|
|
2734
|
+
display_name=user.title,
|
|
2735
|
+
email=user.properties.get("Email"),
|
|
2736
|
+
)
|
|
2737
|
+
except Exception as e: # noqa: BLE001 — a missing user degrades to no authorship
|
|
2738
|
+
self.warnings.append(f"user {key} could not be resolved: {e}")
|
|
2739
|
+
self._users[key] = principal
|
|
2740
|
+
return principal
|
|
2741
|
+
|
|
2742
|
+
def _file_authors(self) -> dict[str, tuple[int | None, int | None]]:
|
|
2743
|
+
"""Author/editor ids by server-relative file URL (one list-items read).
|
|
2744
|
+
|
|
2745
|
+
``File.Author``/``ModifiedBy`` is a non-expandable navigation on the File
|
|
2746
|
+
resource, so the ids come from the parent library's list items.
|
|
2747
|
+
"""
|
|
2748
|
+
if self._item_authors is not None:
|
|
2749
|
+
return self._item_authors
|
|
2750
|
+
authors: dict[str, tuple[int | None, int | None]] = {}
|
|
2751
|
+
unique: dict[str, bool] = {}
|
|
2752
|
+
moderation: dict[str, dict] = {}
|
|
2753
|
+
checkout: set[str] = set()
|
|
2754
|
+
moderates = self._moderation_enabled()
|
|
2755
|
+
if self._sp_list is not None:
|
|
2756
|
+
try:
|
|
2757
|
+
# ``CheckoutUser`` is a user field: it only projects when its
|
|
2758
|
+
# sub-properties are selected *and* the field is expanded — a bare
|
|
2759
|
+
# ``$select=CheckoutUser`` is rejected by the server.
|
|
2760
|
+
select = [
|
|
2761
|
+
"FileRef",
|
|
2762
|
+
"AuthorId",
|
|
2763
|
+
"EditorId",
|
|
2764
|
+
"HasUniqueRoleAssignments",
|
|
2765
|
+
"CheckoutUser/Id",
|
|
2766
|
+
"CheckoutUser/Title",
|
|
2767
|
+
]
|
|
2768
|
+
if moderates:
|
|
2769
|
+
select.extend(_MODERATION_FIELDS)
|
|
2770
|
+
items = self._sp_list.items.select(select).expand(["CheckoutUser"]).get_all().execute_query()
|
|
2771
|
+
for item in items:
|
|
2772
|
+
ref = item.properties.get("FileRef")
|
|
2773
|
+
if ref:
|
|
2774
|
+
authors[ref] = (
|
|
2775
|
+
parse_int(item.properties.get("AuthorId")),
|
|
2776
|
+
parse_int(item.properties.get("EditorId")),
|
|
2777
|
+
)
|
|
2778
|
+
unique[ref] = bool(item.properties.get("HasUniqueRoleAssignments"))
|
|
2779
|
+
if item.properties.get("CheckoutUser"):
|
|
2780
|
+
checkout.add(ref)
|
|
2781
|
+
if moderates:
|
|
2782
|
+
state = _moderation_state(item.properties)
|
|
2783
|
+
if state:
|
|
2784
|
+
moderation[ref] = state
|
|
2785
|
+
except Exception as e: # noqa: BLE001 — authorship is best-effort
|
|
2786
|
+
self.warnings.append(f"author/editor read failed: {e}")
|
|
2787
|
+
self._item_authors = authors
|
|
2788
|
+
self._item_unique = unique
|
|
2789
|
+
self._item_moderation = moderation
|
|
2790
|
+
self._item_checkout = checkout
|
|
2791
|
+
return authors
|
|
2792
|
+
|
|
2793
|
+
def _moderation_enabled(self) -> bool:
|
|
2794
|
+
"""Whether the parent library enables content approval (read once)."""
|
|
2795
|
+
if self._moderation_on is None:
|
|
2796
|
+
self._moderation_on = False
|
|
2797
|
+
if self._sp_list is not None:
|
|
2798
|
+
try:
|
|
2799
|
+
self._sp_list.ensure_properties(["EnableModeration"]).get().execute_query()
|
|
2800
|
+
self._moderation_on = bool(self._sp_list.properties.get("EnableModeration"))
|
|
2801
|
+
except Exception as e: # noqa: BLE001 — approval state is best-effort
|
|
2802
|
+
self.warnings.append(f"moderation setting read failed: {e}")
|
|
2803
|
+
return self._moderation_on
|
|
2804
|
+
|
|
2805
|
+
@property
|
|
2806
|
+
def supports_item_state(self) -> bool:
|
|
2807
|
+
"""Whether approval state can be read (needs the parent library)."""
|
|
2808
|
+
return self._list_source is not None
|
|
2809
|
+
|
|
2810
|
+
def read_item_state(self, item: MigrationItem) -> dict | None:
|
|
2811
|
+
"""Approval state that a fresh upload would not reproduce.
|
|
2812
|
+
|
|
2813
|
+
An uploaded document starts out *approved*, so only a draft/pending or
|
|
2814
|
+
rejected source file carries state, together with its approval comments.
|
|
2815
|
+
"""
|
|
2816
|
+
if item.item_type == "folder" or self._list_source is None:
|
|
2817
|
+
return None
|
|
2818
|
+
self._file_authors()
|
|
2819
|
+
state = (self._item_moderation or {}).get(item.source_path)
|
|
2820
|
+
if not state:
|
|
2821
|
+
return None
|
|
2822
|
+
status = state.get("_ModerationStatus")
|
|
2823
|
+
if status is None or status == _APPROVED_STATUS:
|
|
2824
|
+
return None
|
|
2825
|
+
return state
|
|
2826
|
+
|
|
388
2827
|
def close(self) -> None:
|
|
389
2828
|
pass
|
|
390
2829
|
|
|
@@ -394,30 +2833,177 @@ class SharePointLibraryTarget:
|
|
|
394
2833
|
|
|
395
2834
|
Uses the simple upload for files up to ~4MB; ``write_many`` (parallel,
|
|
396
2835
|
``concurrency > 1``) uses ``create_upload_session`` for larger files.
|
|
2836
|
+
|
|
2837
|
+
Pass ``sp_list`` (the target library) to also replay a folder's column values: a
|
|
2838
|
+
folder is a list item, so a document set's metadata or a managed folder's
|
|
2839
|
+
department is written onto the created folder's item. ``content_type_map``
|
|
2840
|
+
rewrites the folder's content type id to its target counterpart, exactly as it
|
|
2841
|
+
does for an item record.
|
|
397
2842
|
"""
|
|
398
2843
|
|
|
399
|
-
|
|
2844
|
+
#: Files carry content-approval state (``apply_item_state``). Documents refuse a
|
|
2845
|
+
#: moderation change combined with other property writes, so it is a write of its
|
|
2846
|
+
#: own, applied after the timestamps that would otherwise re-approve the file.
|
|
2847
|
+
supports_item_state = True
|
|
2848
|
+
|
|
2849
|
+
def rebind(self) -> "SharePointLibraryTarget":
|
|
2850
|
+
"""This target on its own cloned context (see :meth:`SharePointListSource.rebind`)."""
|
|
2851
|
+
context = _cloned_context(self._folder)
|
|
2852
|
+
sp_list = _cloned_list(self._sp_list, context) if self._sp_list is not None else None
|
|
2853
|
+
return SharePointLibraryTarget(
|
|
2854
|
+
_cloned_folder(self._folder, context),
|
|
2855
|
+
concurrency=self._concurrency,
|
|
2856
|
+
sp_list=sp_list,
|
|
2857
|
+
content_type_map=self._content_type_map,
|
|
2858
|
+
)
|
|
2859
|
+
|
|
2860
|
+
def __init__(
|
|
2861
|
+
self,
|
|
2862
|
+
library_folder: "Folder",
|
|
2863
|
+
concurrency: int = 1,
|
|
2864
|
+
sp_list: "SPList | None" = None,
|
|
2865
|
+
content_type_map: "ContentTypeMap | None" = None,
|
|
2866
|
+
) -> None:
|
|
400
2867
|
self._folder = library_folder
|
|
401
2868
|
self._concurrency = concurrency
|
|
402
|
-
|
|
2869
|
+
# The folder-record mapping is the record mapping: reuse the list target so a
|
|
2870
|
+
# folder's Lookup/Person/taxonomy/choice values resolve the same way.
|
|
2871
|
+
self._sp_list = sp_list
|
|
2872
|
+
self._list_target = (
|
|
2873
|
+
SharePointListTarget(sp_list, content_type_map=content_type_map) if sp_list is not None else None
|
|
2874
|
+
)
|
|
2875
|
+
self._content_type_map = content_type_map
|
|
2876
|
+
self.warnings: list[str] = []
|
|
2877
|
+
self._login_cache: dict[int, str | None] = {}
|
|
2878
|
+
self._principals_cache: _TargetPrincipals | None = None
|
|
2879
|
+
# Author/Editor values written by ``apply_authorship`` and folded into the
|
|
2880
|
+
# final timestamp write so ``Editor`` isn't reset by the latter.
|
|
2881
|
+
self._pending_authorship: dict[str, dict] = {}
|
|
2882
|
+
# Target web time zone (lazily loaded) and a UTC->local conversion cache.
|
|
2883
|
+
self._time_zone = None
|
|
2884
|
+
self._local_cache: dict[str, str] = {}
|
|
2885
|
+
self._field_scan: SharePointListSource | None = None
|
|
2886
|
+
|
|
2887
|
+
def _utc_to_local(self, *values: str | None) -> list[str | None]:
|
|
2888
|
+
"""Convert UTC timestamp strings to web-local wall-clock (one round trip).
|
|
2889
|
+
|
|
2890
|
+
Document items parse form dates in the web's locale, so the desired UTC
|
|
2891
|
+
instant must be converted to local wall-clock first. The conversion runs
|
|
2892
|
+
server-side (``TimeZone.UTCToLocalTime``) so DST is handled correctly.
|
|
2893
|
+
Results are cached by the exact UTC value.
|
|
2894
|
+
"""
|
|
2895
|
+
if self._time_zone is None:
|
|
2896
|
+
self._time_zone = self._folder.context.web.regional_settings.time_zone
|
|
2897
|
+
outputs: list[str | None] = [None] * len(values)
|
|
2898
|
+
pending: dict[str, object] = {}
|
|
2899
|
+
assignments: list[tuple[int, str]] = []
|
|
2900
|
+
for index, value in enumerate(values):
|
|
2901
|
+
if not value:
|
|
2902
|
+
continue
|
|
2903
|
+
utc = _sp_utc_timestamp(value)
|
|
2904
|
+
if utc is None:
|
|
2905
|
+
continue
|
|
2906
|
+
if not utc.endswith("Z"):
|
|
2907
|
+
# Unparseable value: pass it through unchanged so a rejection is
|
|
2908
|
+
# surfaced by ``_report_rejected_fields`` rather than silently lost.
|
|
2909
|
+
outputs[index] = str(value)
|
|
2910
|
+
continue
|
|
2911
|
+
cached = self._local_cache.get(utc)
|
|
2912
|
+
if cached is not None:
|
|
2913
|
+
outputs[index] = cached
|
|
2914
|
+
continue
|
|
2915
|
+
instant = parse_datetime(utc)
|
|
2916
|
+
if instant is None:
|
|
2917
|
+
outputs[index] = str(value)
|
|
2918
|
+
continue
|
|
2919
|
+
if utc not in pending:
|
|
2920
|
+
pending[utc] = self._time_zone.utc_to_local_time(instant)
|
|
2921
|
+
assignments.append((index, utc))
|
|
2922
|
+
if pending:
|
|
2923
|
+
self._folder.context.execute_query()
|
|
2924
|
+
for utc, result in pending.items():
|
|
2925
|
+
local = _sp_local_timestamp(getattr(result, "value", None))
|
|
2926
|
+
if local is not None:
|
|
2927
|
+
self._local_cache[utc] = local
|
|
2928
|
+
for index, utc in assignments:
|
|
2929
|
+
outputs[index] = self._local_cache.get(utc)
|
|
2930
|
+
return outputs
|
|
2931
|
+
|
|
2932
|
+
def _principals(self) -> _TargetPrincipals:
|
|
2933
|
+
"""The (lazily built) target principal/role resolver."""
|
|
2934
|
+
if self._principals_cache is None:
|
|
2935
|
+
self._principals_cache = _TargetPrincipals(self._folder.context.web, self.warnings)
|
|
2936
|
+
return self._principals_cache
|
|
403
2937
|
|
|
404
2938
|
def label(self) -> str:
|
|
405
2939
|
return f"library:{self._folder.server_relative_url}"
|
|
406
2940
|
|
|
407
2941
|
def _url(self, item: MigrationItem) -> str:
|
|
408
|
-
return f"{(self._folder.server_relative_url or '').rstrip('/')}/{item.dest_path}"
|
|
2942
|
+
return f"{(self._folder.server_relative_url or '').rstrip('/')}/{str(item.dest_path).strip('/')}"
|
|
409
2943
|
|
|
410
2944
|
def exists(self, item: MigrationItem) -> bool:
|
|
2945
|
+
url = self._url(item)
|
|
411
2946
|
try:
|
|
412
|
-
|
|
2947
|
+
if item.item_type == "folder":
|
|
2948
|
+
self._folder.context.web.get_folder_by_server_relative_url(url).get().execute_query()
|
|
2949
|
+
else:
|
|
2950
|
+
self._folder.context.web.get_file_by_server_relative_url(url).get().execute_query()
|
|
413
2951
|
return True
|
|
414
|
-
except Exception: # noqa: BLE001 — a missing
|
|
2952
|
+
except Exception: # noqa: BLE001 — a missing item surfaces as a request error
|
|
415
2953
|
return False
|
|
416
2954
|
|
|
2955
|
+
def _ensure_folders(self, items: list[MigrationItem]) -> None:
|
|
2956
|
+
"""Create folder items (deduplicated to deepest paths) in one round-trip."""
|
|
2957
|
+
paths = [str(item.dest_path).strip("/") for item in items]
|
|
2958
|
+
paths = [path for path in paths if path]
|
|
2959
|
+
if not paths:
|
|
2960
|
+
return
|
|
2961
|
+
self._folder.ensure_folders(paths)
|
|
2962
|
+
self._folder.context.execute_query()
|
|
2963
|
+
|
|
417
2964
|
def write(self, item: MigrationItem, payload: object) -> None:
|
|
2965
|
+
if item.item_type == "folder":
|
|
2966
|
+
self._ensure_folders([item])
|
|
2967
|
+
if isinstance(payload, dict):
|
|
2968
|
+
self._apply_folder_values(item, payload)
|
|
2969
|
+
return
|
|
418
2970
|
content = payload if isinstance(payload, bytes) else str(payload).encode("utf-8")
|
|
419
2971
|
self._folder.upload_file(item.dest_path, content).execute_query()
|
|
420
2972
|
|
|
2973
|
+
@property
|
|
2974
|
+
def supports_folder_records(self) -> bool:
|
|
2975
|
+
"""Whether a folder's column values are migrated *and* comparable."""
|
|
2976
|
+
return self._list_target is not None
|
|
2977
|
+
|
|
2978
|
+
def _apply_folder_values(self, item: MigrationItem, record: dict) -> None:
|
|
2979
|
+
"""Write a folder's column values onto the folder's underlying list item."""
|
|
2980
|
+
assert self._list_target is not None
|
|
2981
|
+
values = _folder_record_values(record)
|
|
2982
|
+
if not values:
|
|
2983
|
+
return
|
|
2984
|
+
values = self._list_target.translate_record(values)
|
|
2985
|
+
folder_item = self._securable(item)
|
|
2986
|
+
for key, value in values.items():
|
|
2987
|
+
folder_item.set_property(key, value)
|
|
2988
|
+
try:
|
|
2989
|
+
folder_item.update().execute_query()
|
|
2990
|
+
except Exception as e: # noqa: BLE001 — a folder's structure is worth more than its values
|
|
2991
|
+
self.warnings.append(f"folder column values for {item.dest_path!r} could not be written: {e}")
|
|
2992
|
+
|
|
2993
|
+
def _folder_projection(self) -> list[str]:
|
|
2994
|
+
"""The target's folder columns (read once) — the set the source projected."""
|
|
2995
|
+
if self._sp_list is None:
|
|
2996
|
+
return []
|
|
2997
|
+
if self._field_scan is None:
|
|
2998
|
+
# Field definitions are read by the same code on both sides: structure
|
|
2999
|
+
# provisioning keeps the internal names, so the projections line up.
|
|
3000
|
+
self._field_scan = SharePointListSource(self._sp_list)
|
|
3001
|
+
try:
|
|
3002
|
+
return self._field_scan.folder_projection()
|
|
3003
|
+
except Exception as e: # noqa: BLE001 — without a projection no folder values move
|
|
3004
|
+
self.warnings.append(f"could not read the target columns for folder values: {e}")
|
|
3005
|
+
return []
|
|
3006
|
+
|
|
421
3007
|
def write_many(
|
|
422
3008
|
self,
|
|
423
3009
|
items: list[MigrationItem],
|
|
@@ -431,90 +3017,273 @@ class SharePointLibraryTarget:
|
|
|
431
3017
|
"""
|
|
432
3018
|
from office365.migration.sharepoint.transfer import _transfer_files_parallel
|
|
433
3019
|
|
|
3020
|
+
failures: list[Failure] = []
|
|
3021
|
+
folders = [item for item in items if item.item_type == "folder"]
|
|
3022
|
+
if folders:
|
|
3023
|
+
try:
|
|
3024
|
+
self._ensure_folders(folders)
|
|
3025
|
+
except Exception: # noqa: BLE001 — fall back to per-folder to attribute errors
|
|
3026
|
+
for folder in folders:
|
|
3027
|
+
try:
|
|
3028
|
+
self._ensure_folders([folder])
|
|
3029
|
+
except Exception as e: # noqa: BLE001
|
|
3030
|
+
failures.append((folder.dest_path, str(e)))
|
|
3031
|
+
# A folder's column values can only be written once its item exists, so they
|
|
3032
|
+
# follow the (batched) folder creation.
|
|
3033
|
+
for item, payload in zip(items, payloads):
|
|
3034
|
+
if item.item_type == "folder" and isinstance(payload, dict):
|
|
3035
|
+
self._apply_folder_values(item, payload)
|
|
434
3036
|
files = [
|
|
435
3037
|
(item.dest_path, payload if isinstance(payload, bytes) else str(payload).encode("utf-8"))
|
|
436
3038
|
for item, payload in zip(items, payloads)
|
|
3039
|
+
if item.item_type != "folder"
|
|
437
3040
|
]
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
3041
|
+
if files:
|
|
3042
|
+
failures.extend(
|
|
3043
|
+
_transfer_files_parallel(
|
|
3044
|
+
self._folder,
|
|
3045
|
+
files,
|
|
3046
|
+
concurrency=concurrency or self._concurrency or 1,
|
|
3047
|
+
)
|
|
3048
|
+
)
|
|
3049
|
+
return failures
|
|
443
3050
|
|
|
444
3051
|
def list_paths(self) -> list[str]:
|
|
445
3052
|
root = (self._folder.server_relative_url or "").rstrip("/")
|
|
446
|
-
|
|
447
|
-
|
|
3053
|
+
extra = _site_assets_scaffolding(self._folder)
|
|
3054
|
+
loaded_files = self._folder.get_files(recursive=True).execute_query()
|
|
3055
|
+
loaded_folders = self._folder.get_folders(recursive=True).execute_query()
|
|
3056
|
+
paths = [(f.server_relative_url or "")[len(root) :].lstrip("/") for f in loaded_files]
|
|
3057
|
+
paths += [f"{(f.server_relative_url or '')[len(root) :].lstrip('/')}/" for f in loaded_folders]
|
|
3058
|
+
# Mirror the source projection: SharePoint-owned scaffolding (``Forms/``,
|
|
3059
|
+
# ``desktop.ini``, and on SiteAssets the site icon + auto-provisioned
|
|
3060
|
+
# notebook) is recreated server-side, never migrated, so it must not be
|
|
3061
|
+
# reported as target-only content during verification. Source folder paths
|
|
3062
|
+
# carry a trailing slash; target folder paths mirror that.
|
|
3063
|
+
return [p for p in paths if p != "/" and not _is_system_path(p, extra)]
|
|
448
3064
|
|
|
449
|
-
def
|
|
3065
|
+
def read(self, item: MigrationItem) -> bytes:
|
|
3066
|
+
"""Download the written item's bytes (used by verification for Office files)."""
|
|
3067
|
+
if item.item_type == "folder":
|
|
3068
|
+
return b""
|
|
450
3069
|
file = self._folder.context.web.get_file_by_server_relative_url(self._url(item)).get().execute_query()
|
|
451
3070
|
buffer = io.BytesIO()
|
|
452
3071
|
file.download(buffer).execute_query()
|
|
453
|
-
return
|
|
3072
|
+
return buffer.getvalue()
|
|
3073
|
+
|
|
3074
|
+
def checksum(self, item: MigrationItem) -> str:
|
|
3075
|
+
if item.item_type == "folder":
|
|
3076
|
+
values = self._target_folder_values(item)
|
|
3077
|
+
if values is None or self._list_target is None:
|
|
3078
|
+
return ""
|
|
3079
|
+
return self._list_target.content_checksum(values)
|
|
3080
|
+
return hashlib.md5(self.read(item)).hexdigest()
|
|
3081
|
+
|
|
3082
|
+
def _target_folder_values(self, item: MigrationItem) -> dict | None:
|
|
3083
|
+
"""The target folder's column values as they read back (``None`` if unknown)."""
|
|
3084
|
+
columns = self._folder_projection()
|
|
3085
|
+
if not columns:
|
|
3086
|
+
return None
|
|
3087
|
+
list_item = self._securable(item).select(columns).get().execute_query()
|
|
3088
|
+
return _folder_record_values(dict(list_item.properties))
|
|
454
3089
|
|
|
455
3090
|
def modified(self, item: MigrationItem) -> str:
|
|
456
3091
|
"""Last-modified of the target file (for incremental migration)."""
|
|
3092
|
+
if item.item_type == "folder":
|
|
3093
|
+
return ""
|
|
457
3094
|
file = self._folder.context.web.get_file_by_server_relative_url(self._url(item)).get().execute_query()
|
|
458
3095
|
return iso_or_none(file.time_last_modified) or ""
|
|
459
3096
|
|
|
460
3097
|
def apply_timestamps(self, item: MigrationItem) -> None:
|
|
461
3098
|
"""Best-effort: restore ``Created``/``Modified`` on the written item.
|
|
462
3099
|
|
|
463
|
-
Uses ``ValidateUpdateListItem
|
|
464
|
-
|
|
465
|
-
|
|
3100
|
+
Uses ``ValidateUpdateListItem``, the only REST path that can set these
|
|
3101
|
+
system fields. For files it runs as a system update
|
|
3102
|
+
(``bNewDocumentUpdate=True``, the CSOM ``UpdateOverwriteVersion`` path) so
|
|
3103
|
+
the metadata lands without creating a new version; folders have no version
|
|
3104
|
+
history. ``preserve_versions`` still needs the server-side API. Authorship
|
|
3105
|
+
values resolved for the same item ride along in this write.
|
|
466
3106
|
"""
|
|
467
|
-
form_values: dict
|
|
468
|
-
created =
|
|
469
|
-
modified = _sp_timestamp(item.modified)
|
|
3107
|
+
form_values: dict = {}
|
|
3108
|
+
created, modified = self._utc_to_local(item.created, item.modified)
|
|
470
3109
|
if created:
|
|
471
3110
|
form_values["Created"] = created
|
|
472
3111
|
if modified:
|
|
473
3112
|
form_values["Modified"] = modified
|
|
3113
|
+
form_values.update(self._pending_authorship.pop(item.dest_path, {}))
|
|
474
3114
|
if not form_values:
|
|
475
3115
|
return
|
|
476
3116
|
securable = self._securable(item)
|
|
477
3117
|
if securable is None:
|
|
478
3118
|
return
|
|
479
|
-
securable.validate_update_list_item(
|
|
3119
|
+
result = securable.validate_update_list_item(
|
|
480
3120
|
form_values,
|
|
481
3121
|
dates_in_utc=True,
|
|
482
3122
|
new_document_update=item.item_type != "folder",
|
|
483
3123
|
).execute_query()
|
|
3124
|
+
_report_rejected_fields(result, self.warnings, f"timestamps {item.dest_path}")
|
|
3125
|
+
|
|
3126
|
+
def apply_authorship(self, item: MigrationItem) -> None:
|
|
3127
|
+
"""Best-effort: resolve ``Author``/``Editor`` and cache them for one write.
|
|
3128
|
+
|
|
3129
|
+
A principal is applied only when its login name resolves to a target user
|
|
3130
|
+
(``EnsureUser``); unresolved principals are skipped (same-tenant only).
|
|
3131
|
+
|
|
3132
|
+
Nothing is sent here: a separate write per item doubled the cost of a
|
|
3133
|
+
preserved run, and a system update re-approves a document, so the cached
|
|
3134
|
+
values are better folded into the timestamps write (or in
|
|
3135
|
+
:meth:`finalize_fidelity` when timestamps are not preserved).
|
|
3136
|
+
"""
|
|
3137
|
+
securable = self._securable(item)
|
|
3138
|
+
if securable is None:
|
|
3139
|
+
return
|
|
3140
|
+
author = self._principals().user(item.author.login_name if item.author else None)
|
|
3141
|
+
editor = self._principals().user(item.editor.login_name if item.editor else None)
|
|
3142
|
+
if author is not None and _is_system_principal(author):
|
|
3143
|
+
author = None
|
|
3144
|
+
if editor is not None and _is_system_principal(editor):
|
|
3145
|
+
editor = None
|
|
3146
|
+
if author is None and editor is None:
|
|
3147
|
+
return
|
|
3148
|
+
form_values: dict[str, FieldUserValue] = {}
|
|
3149
|
+
if author is not None:
|
|
3150
|
+
form_values["Author"] = FieldUserValue(author.id, author.login_name)
|
|
3151
|
+
if editor is not None:
|
|
3152
|
+
form_values["Editor"] = FieldUserValue(editor.id, editor.login_name)
|
|
3153
|
+
self._pending_authorship[item.dest_path] = dict(form_values)
|
|
3154
|
+
|
|
3155
|
+
def finalize_fidelity(self, item: MigrationItem) -> None:
|
|
3156
|
+
"""Write the fidelity values that were only cached for this item.
|
|
3157
|
+
|
|
3158
|
+
The last hook wins the write: with timestamps preserved that is
|
|
3159
|
+
:meth:`apply_timestamps`; without them it is this method, so an item costs
|
|
3160
|
+
one system update instead of one per property group. Runs before the
|
|
3161
|
+
approval write, which must stay last.
|
|
3162
|
+
"""
|
|
3163
|
+
form_values = self._pending_authorship.pop(item.dest_path, {})
|
|
3164
|
+
if not form_values:
|
|
3165
|
+
return
|
|
3166
|
+
securable = self._securable(item)
|
|
3167
|
+
if securable is None:
|
|
3168
|
+
return
|
|
3169
|
+
result = securable.validate_update_list_item(
|
|
3170
|
+
form_values,
|
|
3171
|
+
dates_in_utc=True,
|
|
3172
|
+
new_document_update=item.item_type != "folder",
|
|
3173
|
+
).execute_query()
|
|
3174
|
+
_report_rejected_fields(result, self.warnings, f"authorship {item.dest_path}")
|
|
484
3175
|
|
|
485
3176
|
def apply_permissions(self, item: MigrationItem, permissions: list[PermissionEntry]) -> None:
|
|
486
|
-
"""Best-effort:
|
|
3177
|
+
"""Best-effort: replay a file's or folder's unique ACLs onto the target item.
|
|
487
3178
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
3179
|
+
Same-tenant only — cross-tenant identity mapping is out of scope. See
|
|
3180
|
+
:func:`_replay_role_assignments` for what happens to principals, roles and
|
|
3181
|
+
inheritance that cannot be resolved.
|
|
491
3182
|
"""
|
|
492
3183
|
securable = self._securable(item)
|
|
493
3184
|
if securable is None:
|
|
494
3185
|
return
|
|
495
|
-
securable.
|
|
496
|
-
for entry in permissions:
|
|
497
|
-
for role_name in entry.roles:
|
|
498
|
-
role = self._role_definition(role_name)
|
|
499
|
-
if role is not None:
|
|
500
|
-
securable.add_role_assignment(entry.principal_name, role)
|
|
3186
|
+
_replay_role_assignments(securable, self._principals(), permissions, self.warnings, item.dest_path)
|
|
501
3187
|
securable.context.execute_query()
|
|
502
3188
|
|
|
3189
|
+
def apply_item_state(self, item: MigrationItem, state: dict) -> None:
|
|
3190
|
+
"""Best-effort: restore a migrated file's content-approval state.
|
|
3191
|
+
|
|
3192
|
+
An uploaded file starts out approved, and the timestamp/authorship system
|
|
3193
|
+
updates approve it again, so a source draft or rejected file would
|
|
3194
|
+
otherwise silently become visible. Documents reject a moderation change
|
|
3195
|
+
combined with other properties (``You cannot change moderation status and
|
|
3196
|
+
set other item properties at that same time.``), hence a separate write,
|
|
3197
|
+
applied last. A library without moderation enabled has no such fields: the
|
|
3198
|
+
write fails and is reported, never fatal.
|
|
3199
|
+
"""
|
|
3200
|
+
if item.item_type == "folder" or not state:
|
|
3201
|
+
return
|
|
3202
|
+
securable = self._securable(item)
|
|
3203
|
+
if securable is None:
|
|
3204
|
+
return
|
|
3205
|
+
try:
|
|
3206
|
+
result = securable.validate_update_list_item(
|
|
3207
|
+
dict(state),
|
|
3208
|
+
dates_in_utc=True,
|
|
3209
|
+
new_document_update=True,
|
|
3210
|
+
).execute_query()
|
|
3211
|
+
except Exception as e: # noqa: BLE001 — approval state is best-effort
|
|
3212
|
+
self.warnings.append(f"approval state for {item.dest_path!r} could not be restored: {e}")
|
|
3213
|
+
return
|
|
3214
|
+
_report_rejected_fields(result, self.warnings, f"approval state {item.dest_path}")
|
|
3215
|
+
|
|
3216
|
+
def read_metadata(self, item: MigrationItem) -> dict | None:
|
|
3217
|
+
"""Comparable attributes of the written item (for the metadata diff)."""
|
|
3218
|
+
if item.item_type == "folder":
|
|
3219
|
+
# A folder is an item too: the timestamp system update lands on its list
|
|
3220
|
+
# item, and the folder resource reflects it (same read as for a file).
|
|
3221
|
+
folder = (
|
|
3222
|
+
self._folder.context.web.get_folder_by_server_relative_path(self._url(item).rstrip("/"))
|
|
3223
|
+
.get()
|
|
3224
|
+
.execute_query()
|
|
3225
|
+
)
|
|
3226
|
+
return {
|
|
3227
|
+
"created": iso_or_none(folder.time_created),
|
|
3228
|
+
"modified": iso_or_none(folder.time_last_modified),
|
|
3229
|
+
}
|
|
3230
|
+
file = self._folder.context.web.get_file_by_server_relative_url(self._url(item)).get().execute_query()
|
|
3231
|
+
metadata = {
|
|
3232
|
+
"created": iso_or_none(file.time_created),
|
|
3233
|
+
"modified": iso_or_none(file.time_last_modified),
|
|
3234
|
+
"size_bytes": file.length or 0,
|
|
3235
|
+
}
|
|
3236
|
+
status = self._target_moderation_status(item)
|
|
3237
|
+
if status is not None:
|
|
3238
|
+
metadata["moderation_status"] = status
|
|
3239
|
+
return metadata
|
|
3240
|
+
|
|
3241
|
+
def _target_moderation_status(self, item: MigrationItem) -> str | None:
|
|
3242
|
+
"""The written file's approval status, or ``None`` when not readable."""
|
|
3243
|
+
securable = self._securable(item)
|
|
3244
|
+
if securable is None:
|
|
3245
|
+
return None
|
|
3246
|
+
try:
|
|
3247
|
+
securable.select(["OData__ModerationStatus"]).get().execute_query()
|
|
3248
|
+
except Exception: # noqa: BLE001 — a library without moderation has no such column
|
|
3249
|
+
return None
|
|
3250
|
+
status = securable.properties.get("OData__ModerationStatus")
|
|
3251
|
+
return None if status is None else str(status)
|
|
3252
|
+
|
|
3253
|
+
def read_permissions(self, item: MigrationItem) -> list[PermissionEntry]:
|
|
3254
|
+
"""Read the written item's *unique* role assignments (for the permission diff)."""
|
|
3255
|
+
securable = self._securable(item)
|
|
3256
|
+
if securable is None or not _has_unique_role_assignments(securable, refresh=True):
|
|
3257
|
+
return []
|
|
3258
|
+
return _read_role_assignments(securable, normalize=self._principals().normalize)
|
|
3259
|
+
|
|
3260
|
+
def _fresh_securable(self, item: MigrationItem):
|
|
3261
|
+
"""Re-read the written item so its properties reflect what was applied."""
|
|
3262
|
+
securable = self._securable(item)
|
|
3263
|
+
if securable is not None:
|
|
3264
|
+
securable.get().execute_query()
|
|
3265
|
+
return securable
|
|
3266
|
+
|
|
3267
|
+
def read_authorship(self, item: MigrationItem) -> tuple[str | None, str | None]:
|
|
3268
|
+
"""The written item's author/editor login names (for the authorship diff)."""
|
|
3269
|
+
securable = self._fresh_securable(item)
|
|
3270
|
+
if securable is None:
|
|
3271
|
+
return (None, None)
|
|
3272
|
+
properties = securable.properties
|
|
3273
|
+
return (
|
|
3274
|
+
_user_login(self._folder.context.web, properties.get("AuthorId"), self._login_cache),
|
|
3275
|
+
_user_login(self._folder.context.web, properties.get("EditorId"), self._login_cache),
|
|
3276
|
+
)
|
|
3277
|
+
|
|
503
3278
|
def _securable(self, item: MigrationItem):
|
|
504
3279
|
url = self._url(item).rstrip("/")
|
|
505
3280
|
if item.item_type == "folder":
|
|
506
3281
|
return self._folder.context.web.get_folder_by_server_relative_path(url).list_item_all_fields
|
|
507
3282
|
return self._folder.context.web.get_file_by_server_relative_url(url).listItemAllFields
|
|
508
3283
|
|
|
509
|
-
def _role_definition(self, name: str) -> "RoleDefinition | None":
|
|
510
|
-
"""Resolve (and cache) a target role definition by name."""
|
|
511
|
-
if self._role_definitions is None:
|
|
512
|
-
loaded = self._folder.context.web.role_definitions.get().execute_query()
|
|
513
|
-
self._role_definitions = {rd.name: rd for rd in loaded if rd.name}
|
|
514
|
-
return self._role_definitions.get(name)
|
|
515
|
-
|
|
516
3284
|
def commit(self, options=None) -> None:
|
|
517
|
-
|
|
3285
|
+
self._pending_authorship.clear()
|
|
3286
|
+
self._local_cache.clear()
|
|
518
3287
|
|
|
519
3288
|
def close(self) -> None:
|
|
520
3289
|
pass
|