microsoftgraph / msgraph-sdk-python

MIT License
359 stars 49 forks source link

Cannot upload PDF file to OneDrive, unclear API Error #869

Open Mirciulica15 opened 3 weeks ago

Mirciulica15 commented 3 weeks ago

Describe the bug

I am trying to upload a PDF file to OneDrive, but I receive a 400 APIError with an Invalid Request.

Expected behavior

Successful upload of the file.

How to reproduce


files = [item.name for item in await self._get_onedrive_items(
    query_filter="file ne null and file/mimeType eq 'application/pdf'"
)]

folder_id_map = await self._get_folder_ids(source_folders)

for folder in source_folders:
    folder_path = os.path.join('documents', folder)
    if not os.path.exists(folder_path):
        continue

    for file_name in os.listdir(folder_path):
        if file_name in files:
            continue

        file_path = os.path.join(folder_path, file_name)

        with open(file_path, 'rb') as file:
            file_content = file.read()

        folder_id = folder_id_map.get(folder)
        if not folder_id:
            print(f"Folder ID for '{folder}' not found.")
            continue

        parent_reference = ItemReference(
            id=folder_id
        )

        drive_item = DriveItem(
            name=file_name,
            file=File(),
            parent_reference=parent_reference,
            content=file_content
        )

        try:
            response = await self._user_client.drives.by_drive_id(
                os.getenv('ONEDRIVE_DEV_DRIVE_ID')
            ).items.post(drive_item)

            print("Response", response)
        except Exception as e:
            print(f"Failed to upload file '{file_name}': {e}")

SDK Version

msgraph-core~=1.1.2 msgraph-sdk~=1.5.3

Latest version known to work for scenario above?

No response

Known Workarounds

No response

Debug output

Click to expand log ``` APIError Code: 400 message: None error: MainError(additional_data={}, code='invalidRequest', details=None, inner_error=InnerError(additional_data={}, client_request_id='13230f64-2c2e-4083-93c1-f7325291333c', date=DateTime(2024, 8, 22, 10, 12, 34, tzinfo=Timezone('UTC')), odata_type=None, request_id='f1815677-f808-46dd-af6d-7882803e975b'), message='Invalid request', target=None) ```

Configuration

Other information

The 400 APIError could be more robust than just "Invalid request"

shemogumbe commented 3 weeks ago

Hello @Mirciulica15 thanks for using the SDK and for reporting this,

How large is the file you want to upload, do you want to do it in chunks:

If so, use the LargeFileUploadTask from https://github.com/microsoftgraph/msgraph-sdk-python-core/


destination_path = "path/to/your_file.txt"
file_path = "path/to/your_file.txt"

async def upload_large_file():
    try:
        file = open(file_path, 'rb')
        uploadable_properties = DriveItemUploadableProperties(
            additional_data={'@microsoft.graph.conflictBehavior': 'replace'}
        )
        upload_session_request_body = CreateUploadSessionPostRequestBody(item=uploadable_properties)
        print(f"Uploadable Properties: {uploadable_properties.additional_data}")
        # can be used for normal drive uploads
        try:

            upload_session = await user_client.drives.by_drive_id(
                "b!WtUPKhiPm0a6Bj6Z_J97a53XIv_KoNNAkdLRUrSF06lh-qW6JpABSoW62oPNb03R"
            ).items.by_drive_item_id('root:/my_docs/test_upload.txt:'
                                     ).create_upload_session.post(upload_session_request_body)

        except APIError as ex:
            print(f"Error creating upload session: {ex}")

        # to be used for large file uploads
        large_file_upload_session = LargeFileUploadSession(
            upload_url=upload_session.upload_url,
            expiration_date_time=datetime.now() + timedelta(days=1),
            additional_data=upload_session.additional_data,
            is_cancelled=False,
            next_expected_ranges=upload_session.next_expected_ranges
        )

        # max_slice_size = 320 * 1024 - remove to use default
        task = LargeFileUploadTask(large_file_upload_session, user_client.request_adapter, file)
        total_length = os.path.getsize(file_path)

        # Upload the file
        # The callback
        def progress_callback(uploaded_byte_range: tuple[int, int]):
            print(f"Uploaded {uploaded_byte_range[0]} bytes of {total_length} bytes\n\n")

        try:
            upload_result = await task.upload(progress_callback)
            print(f"Upload complete {upload_result}")
        except APIError as ex:
            print(f"Error uploading: {ex.message} - {ex.response_status_code}")
    except APIError as e:
        print(f"Error: {e}")

asyncio.run(upload_large_file())

Optionally, on your snippet, you can use https://learn.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0

Mirciulica15 commented 3 weeks ago

Thank you for the reply, Shem! I managed to do it using the large file upload method 😀

Do you know whether it is possible to place the files in a specific folder within the drive? I tried to do that with an ItemReference, but it did not work out.

I would like to make a PR with my implementation in the examples directory later if that's ok, in case other people have the same use case and need help.