Visitar URL original
BatchHttpRequest Fails When Mixing files.modifyLabels and files.update Methods · Issue #2655 · googleapis/google-api-python-client · GitHub
Skip to content

BatchHttpRequest Fails When Mixing files.modifyLabels and files.update Methods #2655

Description

@mfcarroll

Environment details

  • OS type and version: macOS 15.6.1
  • Python version: 3.13.7
  • pip version: 25.2
  • google-api-python-client version: 2.181.0

Description

When attempting to batch API calls using service.new_batch_http_request(), the batch fails if it contains both a files().modifyLabels() request and a files().update() request. The overall batch request returns a 200 OK, but the individual responses for each sub-request show a 400 Bad Request.

This issue occurs regardless of whether the operations target the same file ID or different file IDs.

The batching mechanism works as expected in the following scenarios:

  1. When the modifyLabels and update calls are placed in two separate batch requests.
  2. When multiple modifyLabels calls are placed in a single batch request.
  3. When multiple update calls are placed in a single batch request.

There is either an incompatibility between these two methods within the batching feature, or a general problem with batching differing request types.

Note, I did test this out building the requests manually and the behaviour is the same, so it appears to be an underlying bug in the API itself, rather than specific to the python client.

Steps to reproduce

The following script provides a minimal, reproducible example.

Prerequisites:

  1. A Google Cloud Project with the Drive API enabled.
  2. Authenticated Application Default Credentials (gcloud auth application-default login).
  3. Two Google Docs with their file IDs.
  4. A Google Drive Label created with a Selection field.
  • DRIVE_LABEL_ID: The ID of the label itself.
  • LABEL_FIELD_ID: The ID of the selection field within the label.
  • FIELD_CHOICE_ID: The ID of one of the choices for that selection field.

Code example

import json
from datetime import datetime

import google.auth
import httplib2
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

httplib2.debuglevel = 4

# --- Configuration (NEEDS TO BE SET BY USER) ---
# To reproduce, create two Google Docs and a Drive Label with a selection field.
FILE1_ID = "17XMCxQSTXfOAcn5k9CdboRKjLqeUzhb4a3Z7f0f7rWg"
FILE2_ID = "1Hrxhm1pl9h9OFVgt3Ribj28EgMYRB4nS7HnJr0A5mLM"
DRIVE_LABEL_ID = "Ox6e6sJsPu2BHkC0Q6flhbI8sSx7Di7om27RNNEbbFcb"
LABEL_FIELD_ID = "789D8ADF26"
FIELD_CHOICE_ID = "844694D02D"


def batch_callback(request_id: str, response: dict, exception: HttpError):
    """Callback function to print the result of each API call."""
    if exception:
        print(f"\n[FAILED] Request ID: {request_id}\nException: {exception}\n")
    else:
        print(f"\n[SUCCESS] Request ID: {request_id}\n")
        print(f"Response: {json.dumps(response, indent=2)}")


def run_batch_request(drive_service, requests: list, description: str):
    """Helper to build and execute a batch request."""
    print(f"\n--- {description} ---\n")
    try:
        batch = drive_service.new_batch_http_request(callback=batch_callback)
        for req in requests:
            batch.add(req)
        batch.execute()
    except Exception as e:
        print(f"An unexpected error occurred: {e}")


def main():
    """Demonstrates the Google Drive API batching bug with detailed logging."""
    credentials, _ = google.auth.default(
        scopes=["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/drive.labels"]
    )
    credentials.refresh(Request())
    drive_service = build("drive", "v3", credentials=credentials)

    # Define the request bodies, which are proven to work in single calls
    modify_labels_body = {
        "kind": "drive#modifyLabelsRequest",
        "labelModifications": [
            {
                "kind": "drive#labelModification",
                "labelId": DRIVE_LABEL_ID,
                "fieldModifications": [{"fieldId": LABEL_FIELD_ID, "setSelectionValues": [FIELD_CHOICE_ID]}],
            }
        ],
    }
    update_body = {"properties": {"testSource": f"batch-bug-report-{datetime.now().isoformat()}"}}

    # --- Test Case 1: Separate Batches (Success) ---
    run_batch_request(
        drive_service,
        [drive_service.files().modifyLabels(fileId=FILE1_ID, body=modify_labels_body)],
        "Test Case 1a: Separate Batch - Single modifyLabels Request",
    )

    run_batch_request(
        drive_service,
        [drive_service.files().update(fileId=FILE2_ID, body=update_body, supportsAllDrives=True)],
        "Test Case 1b: Separate Batch - Single update Request",
    )

    # --- Test Case 2: Batch of Same Method (Success) ---
    run_batch_request(
        drive_service,
        [
            drive_service.files().modifyLabels(fileId=FILE1_ID, body=modify_labels_body),
            drive_service.files().modifyLabels(fileId=FILE2_ID, body=modify_labels_body),
        ],
        "Test Case 2a: Single Batch - Two modifyLabels Requests",
    )

    run_batch_request(
        drive_service,
        [
            drive_service.files().update(fileId=FILE1_ID, body=update_body, supportsAllDrives=True),
            drive_service.files().update(fileId=FILE2_ID, body=update_body, supportsAllDrives=True),
        ],
        "Test Case 2b: Single Batch - Two update Requests",
    )

    # --- Test Case 3: Mixed Batch (Failure) ---
    run_batch_request(
        drive_service,
        [
            drive_service.files().modifyLabels(fileId=FILE1_ID, body=modify_labels_body),
            drive_service.files().update(fileId=FILE2_ID, body=update_body, supportsAllDrives=True),
        ],
        "Test Case 3: Single Batch - Mixed Batch (Failure)",
    )


if __name__ == "__main__":
    main()

Expected Result:

  • All three test cases in the script should execute successfully, with each API call within the batches returning a 200 OK status in its individual response.

Actual Result:

  • Test Case 1 (Separate Batches) succeeds.
  • Test Case 2 (Batch of Same Method) succeeds.
  • Test Case 3 (Mixed Batch) fails. The batch request returns 200 OK, but the internal responses for both the modifyLabels and update calls are 400 Bad Request.

Stack trace

--- Test Case 1a: Separate Batch - Single modifyLabels Request ---

connect: (www.googleapis.com, 443)
send: b'POST /batch/drive/v3 HTTP/1.1\r\nHost: www.googleapis.com\r\nContent-Length: 1101\r\ncontent-type: multipart/mixed; boundary="===============6087181464682891008=="\r\nx-goog-api-client: cred-type/u\r\nauthorization: Bearer ***\r\nx-goog-user-project: ***\r\nuser-agent: Python-httplib2/0.30.0 (gzip)\r\naccept-encoding: gzip, deflate\r\n\r\n'
send: b'--===============6087181464682891008==\nContent-Type: application/http\nMIME-Version: 1.0\nContent-Transfer-Encoding: binary\nContent-ID: <42ec34b7-21cc-4347-9e4e-b22bccea35ac + 1>\n\nPOST /drive/v3/files/17XMCxQSTXfOAcn5k9CdboRKjLqeUzhb4a3Z7f0f7rWg/modifyLabels?alt=json HTTP/1.1\nContent-Type: application/json\nMIME-Version: 1.0\naccept: application/json\naccept-encoding: gzip, deflate\nuser-agent: (gzip)\nx-goog-api-client: gdcl/2.181.0 gl-python/3.13.7\nauthorization: Bearer ***\nx-goog-user-project: ***\nHost: www.googleapis.com\ncontent-length: 246\n\n{"kind": "drive#modifyLabelsRequest", "labelModifications": [{"kind": "drive#labelModification", "labelId": "Ox6e6sJsPu2BHkC0Q6flhbI8sSx7Di7om27RNNEbbFcb", "fieldModifications": [{"fieldId": "789D8ADF26", "setSelectionValues": ["844694D02D"]}]}]}\n--===============6087181464682891008==--\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: content-type: multipart/mixed; boundary=batch_d5mL9UOa4ZIp58SsbGt0qiTTMT1BR9oI
header: Expires: Mon, 01 Jan 1990 00:00:00 GMT
header: Pragma: no-cache
header: Date: Thu, 11 Sep 2025 01:08:04 GMT
header: Vary: Origin, X-Origin
header: Cache-Control: no-cache, no-store, max-age=0, must-revalidate
header: Content-Encoding: gzip
header: Server: ESF
header: X-XSS-Protection: 0
header: X-Frame-Options: SAMEORIGIN
header: X-Content-Type-Options: nosniff
header: Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
header: Transfer-Encoding: chunked

[SUCCESS] Request ID: 1

Response: {
  "modifiedLabels": [
    {
      "fields": {
        "789D8ADF26": {
          "selection": [
            "844694D02D"
          ],
          "kind": "drive#labelField",
          "id": "789D8ADF26",
          "valueType": "selection"
        }
      },
      "kind": "drive#label",
      "id": "Ox6e6sJsPu2BHkC0Q6flhbI8sSx7Di7om27RNNEbbFcb",
      "revisionId": "59"
    }
  ],
  "kind": "drive#modifyLabelsResponse"
}

--- Test Case 1b: Separate Batch - Single update Request ---

send: b'POST /batch/drive/v3 HTTP/1.1\r\nHost: www.googleapis.com\r\nContent-Length: 942\r\ncontent-type: multipart/mixed; boundary="===============0571320967349974227=="\r\nx-goog-api-client: cred-type/u\r\nauthorization: Bearer ***\r\nx-goog-user-project: ***\r\nuser-agent: Python-httplib2/0.30.0 (gzip)\r\naccept-encoding: gzip, deflate\r\n\r\n'
send: b'--===============0571320967349974227==\nContent-Type: application/http\nMIME-Version: 1.0\nContent-Transfer-Encoding: binary\nContent-ID: <4ac4afcf-d99e-4c78-884d-f22dd0b637c8 + 1>\n\nPATCH /drive/v3/files/1Hrxhm1pl9h9OFVgt3Ribj28EgMYRB4nS7HnJr0A5mLM?supportsAllDrives=true&alt=json HTTP/1.1\nContent-Type: application/json\nMIME-Version: 1.0\naccept: application/json\naccept-encoding: gzip, deflate\nuser-agent: (gzip)\nx-goog-api-client: gdcl/2.181.0 gl-python/3.13.7\nauthorization: Bearer ***\nx-goog-user-project: ***\nHost: www.googleapis.com\ncontent-length: 77\n\n{"properties": {"testSource": "batch-bug-report-2025-09-10T18:08:03.770234"}}\n--===============0571320967349974227==--\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: content-type: multipart/mixed; boundary=batch_1tdnt60ZX3EQHMkXBBIoTxsn7jV4TN1G
header: Pragma: no-cache
header: Date: Thu, 11 Sep 2025 01:08:04 GMT
header: Cache-Control: no-cache, no-store, max-age=0, must-revalidate
header: Expires: Mon, 01 Jan 1990 00:00:00 GMT
header: Vary: Origin, X-Origin
header: Content-Encoding: gzip
header: Server: ESF
header: X-XSS-Protection: 0
header: X-Frame-Options: SAMEORIGIN
header: X-Content-Type-Options: nosniff
header: Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
header: Transfer-Encoding: chunked

[SUCCESS] Request ID: 1

Response: {
  "kind": "drive#file",
  "id": "1Hrxhm1pl9h9OFVgt3Ribj28EgMYRB4nS7HnJr0A5mLM",
  "name": "Test doc 2",
  "mimeType": "application/vnd.google-apps.document",
  "teamDriveId": "0AGJpjlgQ1LAkUk9PVA",
  "driveId": "0AGJpjlgQ1LAkUk9PVA"
}

--- Test Case 2a: Single Batch - Two modifyLabels Requests ---

send: b'POST /batch/drive/v3 HTTP/1.1\r\nHost: www.googleapis.com\r\nContent-Length: 2161\r\ncontent-type: multipart/mixed; boundary="===============5731707871754303950=="\r\nx-goog-api-client: cred-type/u\r\nauthorization: Bearer ***\r\nx-goog-user-project: ***\r\nuser-agent: Python-httplib2/0.30.0 (gzip)\r\naccept-encoding: gzip, deflate\r\n\r\n'
send: b'--===============5731707871754303950==\nContent-Type: application/http\nMIME-Version: 1.0\nContent-Transfer-Encoding: binary\nContent-ID: <58b02175-2a77-48b5-abc5-324c9ece6a0b + 1>\n\nPOST /drive/v3/files/17XMCxQSTXfOAcn5k9CdboRKjLqeUzhb4a3Z7f0f7rWg/modifyLabels?alt=json HTTP/1.1\nContent-Type: application/json\nMIME-Version: 1.0\naccept: application/json\naccept-encoding: gzip, deflate\nuser-agent: (gzip)\nx-goog-api-client: gdcl/2.181.0 gl-python/3.13.7\nauthorization: Bearer ***\nx-goog-user-project: ***\nHost: www.googleapis.com\ncontent-length: 246\n\n{"kind": "drive#modifyLabelsRequest", "labelModifications": [{"kind": "drive#labelModification", "labelId": "Ox6e6sJsPu2BHkC0Q6flhbI8sSx7Di7om27RNNEbbFcb", "fieldModifications": [{"fieldId": "789D8ADF26", "setSelectionValues": ["844694D02D"]}]}]}\n--===============5731707871754303950==\nContent-Type: application/http\nMIME-Version: 1.0\nContent-Transfer-Encoding: binary\nContent-ID: <58b02175-2a77-48b5-abc5-324c9ece6a0b + 2>\n\nPOST /drive/v3/files/1Hrxhm1pl9h9OFVgt3Ribj28EgMYRB4nS7HnJr0A5mLM/modifyLabels?alt=json HTTP/1.1\nContent-Type: application/json\nMIME-Version: 1.0\naccept: application/json\naccept-encoding: gzip, deflate\nuser-agent: (gzip)\nx-goog-api-client: gdcl/2.181.0 gl-python/3.13.7\nauthorization: Bearer ***\nx-goog-user-project: ***\nHost: www.googleapis.com\ncontent-length: 246\n\n{"kind": "drive#modifyLabelsRequest", "labelModifications": [{"kind": "drive#labelModification", "labelId": "Ox6e6sJsPu2BHkC0Q6flhbI8sSx7Di7om27RNNEbbFcb", "fieldModifications": [{"fieldId": "789D8ADF26", "setSelectionValues": ["844694D02D"]}]}]}\n--===============5731707871754303950==--\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: content-type: multipart/mixed; boundary=batch_CiOxUm0lV6_9OlQFDlKFj8wZ9uo7aHif
header: Expires: Mon, 01 Jan 1990 00:00:00 GMT
header: Pragma: no-cache
header: Date: Thu, 11 Sep 2025 01:08:05 GMT
header: Cache-Control: no-cache, no-store, max-age=0, must-revalidate
header: Vary: Origin, X-Origin
header: Content-Encoding: gzip
header: Server: ESF
header: X-XSS-Protection: 0
header: X-Frame-Options: SAMEORIGIN
header: X-Content-Type-Options: nosniff
header: Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
header: Transfer-Encoding: chunked

[SUCCESS] Request ID: 1

Response: {
  "modifiedLabels": [
    {
      "fields": {
        "789D8ADF26": {
          "selection": [
            "844694D02D"
          ],
          "kind": "drive#labelField",
          "id": "789D8ADF26",
          "valueType": "selection"
        }
      },
      "kind": "drive#label",
      "id": "Ox6e6sJsPu2BHkC0Q6flhbI8sSx7Di7om27RNNEbbFcb",
      "revisionId": "59"
    }
  ],
  "kind": "drive#modifyLabelsResponse"
}

[SUCCESS] Request ID: 2

Response: {
  "modifiedLabels": [
    {
      "fields": {
        "789D8ADF26": {
          "selection": [
            "844694D02D"
          ],
          "kind": "drive#labelField",
          "id": "789D8ADF26",
          "valueType": "selection"
        }
      },
      "kind": "drive#label",
      "id": "Ox6e6sJsPu2BHkC0Q6flhbI8sSx7Di7om27RNNEbbFcb",
      "revisionId": "59"
    }
  ],
  "kind": "drive#modifyLabelsResponse"
}

--- Test Case 2b: Single Batch - Two update Requests ---

send: b'POST /batch/drive/v3 HTTP/1.1\r\nHost: www.googleapis.com\r\nContent-Length: 1843\r\ncontent-type: multipart/mixed; boundary="===============8192210390717026875=="\r\nx-goog-api-client: cred-type/u\r\nauthorization: Bearer ***\r\nx-goog-user-project: ***\r\nuser-agent: Python-httplib2/0.30.0 (gzip)\r\naccept-encoding: gzip, deflate\r\n\r\n'
send: b'--===============8192210390717026875==\nContent-Type: application/http\nMIME-Version: 1.0\nContent-Transfer-Encoding: binary\nContent-ID: <83471f88-19c1-4a58-ad14-ba712c82e9aa + 1>\n\nPATCH /drive/v3/files/17XMCxQSTXfOAcn5k9CdboRKjLqeUzhb4a3Z7f0f7rWg?supportsAllDrives=true&alt=json HTTP/1.1\nContent-Type: application/json\nMIME-Version: 1.0\naccept: application/json\naccept-encoding: gzip, deflate\nuser-agent: (gzip)\nx-goog-api-client: gdcl/2.181.0 gl-python/3.13.7\nauthorization: Bearer ***\nx-goog-user-project: ***\nHost: www.googleapis.com\ncontent-length: 77\n\n{"properties": {"testSource": "batch-bug-report-2025-09-10T18:08:03.770234"}}\n--===============8192210390717026875==\nContent-Type: application/http\nMIME-Version: 1.0\nContent-Transfer-Encoding: binary\nContent-ID: <83471f88-19c1-4a58-ad14-ba712c82e9aa + 2>\n\nPATCH /drive/v3/files/1Hrxhm1pl9h9OFVgt3Ribj28EgMYRB4nS7HnJr0A5mLM?supportsAllDrives=true&alt=json HTTP/1.1\nContent-Type: application/json\nMIME-Version: 1.0\naccept: application/json\naccept-encoding: gzip, deflate\nuser-agent: (gzip)\nx-goog-api-client: gdcl/2.181.0 gl-python/3.13.7\nauthorization: Bearer ***\nx-goog-user-project: ***\nHost: www.googleapis.com\ncontent-length: 77\n\n{"properties": {"testSource": "batch-bug-report-2025-09-10T18:08:03.770234"}}\n--===============8192210390717026875==--\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: content-type: multipart/mixed; boundary=batch_aHiY_4m6254KP-3UgtdqMZuLO1AdQGvI
header: Cache-Control: no-cache, no-store, max-age=0, must-revalidate
header: Date: Thu, 11 Sep 2025 01:08:06 GMT
header: Vary: Origin, X-Origin
header: Expires: Mon, 01 Jan 1990 00:00:00 GMT
header: Pragma: no-cache
header: Content-Encoding: gzip
header: Server: ESF
header: X-XSS-Protection: 0
header: X-Frame-Options: SAMEORIGIN
header: X-Content-Type-Options: nosniff
header: Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
header: Transfer-Encoding: chunked

[SUCCESS] Request ID: 1

Response: {
  "kind": "drive#file",
  "id": "17XMCxQSTXfOAcn5k9CdboRKjLqeUzhb4a3Z7f0f7rWg",
  "name": "Test doc",
  "mimeType": "application/vnd.google-apps.document",
  "teamDriveId": "0AGJpjlgQ1LAkUk9PVA",
  "driveId": "0AGJpjlgQ1LAkUk9PVA"
}

[SUCCESS] Request ID: 2

Response: {
  "kind": "drive#file",
  "id": "1Hrxhm1pl9h9OFVgt3Ribj28EgMYRB4nS7HnJr0A5mLM",
  "name": "Test doc 2",
  "mimeType": "application/vnd.google-apps.document",
  "teamDriveId": "0AGJpjlgQ1LAkUk9PVA",
  "driveId": "0AGJpjlgQ1LAkUk9PVA"
}

--- Test Case 3: Single Batch - Mixed Batch (Failure) ---

send: b'POST /batch/drive/v3 HTTP/1.1\r\nHost: www.googleapis.com\r\nContent-Length: 2002\r\ncontent-type: multipart/mixed; boundary="===============6157900518903741992=="\r\nx-goog-api-client: cred-type/u\r\nauthorization: Bearer ***\r\nx-goog-user-project: ***\r\nuser-agent: Python-httplib2/0.30.0 (gzip)\r\naccept-encoding: gzip, deflate\r\n\r\n'
send: b'--===============6157900518903741992==\nContent-Type: application/http\nMIME-Version: 1.0\nContent-Transfer-Encoding: binary\nContent-ID: <18367dd2-d452-4e28-b363-a6c76704bfaa + 1>\n\nPOST /drive/v3/files/17XMCxQSTXfOAcn5k9CdboRKjLqeUzhb4a3Z7f0f7rWg/modifyLabels?alt=json HTTP/1.1\nContent-Type: application/json\nMIME-Version: 1.0\naccept: application/json\naccept-encoding: gzip, deflate\nuser-agent: (gzip)\nx-goog-api-client: gdcl/2.181.0 gl-python/3.13.7\nauthorization: Bearer ***\nx-goog-user-project: ***\nHost: www.googleapis.com\ncontent-length: 246\n\n{"kind": "drive#modifyLabelsRequest", "labelModifications": [{"kind": "drive#labelModification", "labelId": "Ox6e6sJsPu2BHkC0Q6flhbI8sSx7Di7om27RNNEbbFcb", "fieldModifications": [{"fieldId": "789D8ADF26", "setSelectionValues": ["844694D02D"]}]}]}\n--===============6157900518903741992==\nContent-Type: application/http\nMIME-Version: 1.0\nContent-Transfer-Encoding: binary\nContent-ID: <18367dd2-d452-4e28-b363-a6c76704bfaa + 2>\n\nPATCH /drive/v3/files/1Hrxhm1pl9h9OFVgt3Ribj28EgMYRB4nS7HnJr0A5mLM?supportsAllDrives=true&alt=json HTTP/1.1\nContent-Type: application/json\nMIME-Version: 1.0\naccept: application/json\naccept-encoding: gzip, deflate\nuser-agent: (gzip)\nx-goog-api-client: gdcl/2.181.0 gl-python/3.13.7\nauthorization: Bearer ***\nx-goog-user-project: ***\nHost: www.googleapis.com\ncontent-length: 77\n\n{"properties": {"testSource": "batch-bug-report-2025-09-10T18:08:03.770234"}}\n--===============6157900518903741992==--\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: content-type: multipart/mixed; boundary=batch_1Xu3WVRptpQ8frvTXujiHeDkN2sKTlfz
header: Vary: Origin, X-Origin
header: Expires: Mon, 01 Jan 1990 00:00:00 GMT
header: Pragma: no-cache
header: Cache-Control: no-cache, no-store, max-age=0, must-revalidate
header: Date: Thu, 11 Sep 2025 01:08:07 GMT
header: Content-Encoding: gzip
header: Server: ESF
header: X-XSS-Protection: 0
header: X-Frame-Options: SAMEORIGIN
header: X-Content-Type-Options: nosniff
header: Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
header: Transfer-Encoding: chunked

[FAILED] Request ID: 1
Exception: <HttpError 400 when requesting https://www.googleapis.com/drive/v3/files/17XMCxQSTXfOAcn5k9CdboRKjLqeUzhb4a3Z7f0f7rWg/modifyLabels?alt=json returned "Bad Request". Details: "[{'message': 'Bad Request', 'domain': 'global', 'reason': 'badRequest'}]">


[FAILED] Request ID: 2
Exception: <HttpError 400 when requesting https://www.googleapis.com/drive/v3/files/1Hrxhm1pl9h9OFVgt3Ribj28EgMYRB4nS7HnJr0A5mLM?supportsAllDrives=true&alt=json returned "Bad Request". Details: "[{'message': 'Bad Request', 'domain': 'global', 'reason': 'badRequest'}]">

Activity

  1. changed the title [-]BatchHttpRequest Fails When Mixing files().modifyLabels() and files().update() Methods[/-] [+]BatchHttpRequest Fails When Mixing files.modifyLabels and files.update Methods[/+] on Sep 11, 2025
  2. removed their assignment
    on Oct 9, 2025
  3. added
    api: driveIssues related to the Drive API API.
    type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.
    priority: p3Desirable enhancement or fix. May not be included in next release.
    on Oct 15, 2025
  4. namanguptagit commented on Nov 3, 2025

    @namanguptagit

    Hi maintainers 👋,

    I’d like to work on this issue and submit a potential fix.

    I’ve reproduced the behavior on my local environment:

    1. OS: Windows 11 (64-bit)
    2. Python: 3.13.7
    3. pip: 25.2
    4. google-api-python-client: 2.181.0

    The problem appears consistently when batching a files().modifyLabels() (POST) and files().update() (PATCH) request within the same new_batch_http_request().
    The mixed batch returns overall 200 OK, but the individual sub-responses show 400 Bad Request.
    Homogeneous batches (all modifyLabels or all update) work correctly.

    Based on initial inspection, the issue likely stems from how BatchHttpRequest constructs multipart requests in apiclient/http.py — particularly when combining different HTTP verbs within a single batch.
    The Drive API batch endpoint may not be handling mixed request types consistently.

    Plan to investigate:

    1. Trace multipart construction inside BatchHttpRequest._execute() and confirm whether POST / PATCH requests are encoded identically.
    2. Compare Drive’s /batch/drive/v3 behavior with other Google APIs (e.g., Gmail) to see if mixed-verb batching works elsewhere.
    3. If it’s client-side, propose a fix in http.py ensuring consistent handling of mixed verbs.
    4. If it’s a backend issue, document a reproducible test and note a workaround (splitting into separate batches).

    Please let me know if this direction sounds good — once confirmed, I’ll proceed with a small PR or at least a reproducible test case for discussion.

    Thanks!

  5. daniel-sanche commented on May 28, 2026

    @daniel-sanche
    Contributor

    This issue is being bulk closed due to inactivity. Please re-open if you think this still requires attention, and we'll take another look. Thanks!

  6. mfcarroll commented on May 28, 2026

    @mfcarroll
    Author

    @daniel-sanche this issue is still unresolved. I don't have permissions to reopen. There is a PR above that addresses the problem. Please re-open and look at integrating that patch if possible. Thanks.

  7. daniel-sanche commented on May 28, 2026

    @daniel-sanche
    Contributor

    Sorry, I'm not sure why the permissions don't allow you to re-open

    The discovery-based google-api-python-client has mostly been replaced with the grpc-based clients in google-cloud-python. I'll keep this on our backlog, but we may not realistically be able to get to this in the near term

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

api: driveIssues related to the Drive API API.priority: p3Desirable enhancement or fix. May not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions