)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ea4dad3fd7894df372874dc79bf4ea5c25176610","unresolved":true,"context_lines":[{"line_number":7,"context_line":"WIP s3api: enforce configured max part num"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"AWS has a limit of 10000 parts but swift\u0027s limit is configurable and"},{"line_number":10,"context_line":"defaults to 1000."},{"line_number":11,"context_line":""},{"line_number":12,"context_line":"Change-Id: I4305e5a1853ce96dd99d10061f551a5c073387cc"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":1,"id":"afa2717f_ab6100b7","line":10,"updated":"2023-12-21 17:06:18.000000000","message":"oh god, that\u0027s embarassing:\n\nhttps://github.com/NVIDIA/swift/blob/master/swift/common/middleware/s3api/s3api.py#L279\n\nI\u0027d have to guess that default has something to do with:\n\nhttps://github.com/NVIDIA/swift/blob/master/swift/common/middleware/slo.py#L367\n\nI verified swiftstack clusters get configured with both of these to 10K by default; I think we should increase both upstream defaults.","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ea4dad3fd7894df372874dc79bf4ea5c25176610","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"ae01f083_36af06ec","updated":"2023-12-21 17:06:18.000000000","message":"IIUC this is exactly what we want:\n\n\tvagrant@saio:~$ aws s3api get-object --bucket s3api-test-test-mpu-891ca8fa9b1347539238a6d18a95b474 --key s3api-test-part-copy-dest-86738cd8c20f4988b664fbbf89d72bfb --part-number 5 deleteme\n\n\tAn error occurred (InvalidArgument) when calling the GetObject operation: Part number must be an integer between 1 and 2, inclusive\n\tvagrant@saio:~$ aws s3api get-object --bucket s3api-test-test-mpu-891ca8fa9b1347539238a6d18a95b474 --key s3api-test-part-copy-dest-86738cd8c20f4988b664fbbf89d72bfb --part-number 4 deleteme\n\t{\n\t    \"AcceptRanges\": \"bytes\",\n\t    \"LastModified\": \"Thu, 21 Dec 2023 16:31:13 GMT\",\n\t    \"ContentLength\": 5242880,\n\t    \"ETag\": \"\\\"a524545003083f3171719a691253cc00-4\\\"\",\n\t    \"ContentRange\": \"bytes 15728640-20971519/20971520\",\n\t    \"ContentType\": \"application/octet-stream\",\n\t    \"Metadata\": {},\n\t    \"PartsCount\": 4\n\t}\n\nWe should call it out in an explict unittest:\n\n\n\tdiff --git a/test/unit/common/middleware/s3api/test_multi_get.py b/test/unit/common/middleware/s3api/test_multi_get.py\n\tindex bdc626ef8..4f43fcce4 100644\n\t--- a/test/unit/common/middleware/s3api/test_multi_get.py\n\t+++ b/test/unit/common/middleware/s3api/test_multi_get.py\n\t@@ -16,6 +16,7 @@\n\t import binascii\n\t import string\n\t import json\n\t+import mock\n\t \n\t from swift.common import swob, utils\n\t from swift.common.request_helpers import get_reserved_name\n\t@@ -199,6 +200,25 @@ class TestMpuGETorHEAD(S3ApiTestCase):\n\t\t self._do_test_mpu_GET_out_of_range_part_num(4)\n\t\t self._do_test_mpu_GET_out_of_range_part_num(10000)\n\t \n\t+    def test_existing_part_number_greater_than_max_parts_allowed(self):\n\t+        part_number \u003d 3\n\t+        max_parts \u003d 2\n\t+        req \u003d swob.Request.blank(\u0027/bucket/mpu\u0027, params\u003d{\n\t+            \u0027partNumber\u0027: str(part_number),\n\t+        }, headers\u003d{\n\t+            \u0027Authorization\u0027: \u0027AWS test:tester:hmac\u0027,\n\t+            \u0027Date\u0027: self.get_date_header()\n\t+        })\n\t+        with mock.patch.object(self.s3api.conf,\n\t+                               \u0027max_upload_part_num\u0027, max_parts):\n\t+            status, headers, body \u003d self.call_s3api(req)\n\t+            self.assertEqual(status.split()[0], \u0027206\u0027)\n\t+            # sanity\n\t+            req.params \u003d {\u0027partNumber\u0027: str(part_number + 1)}\n\t+            status, headers, body \u003d self.call_s3api(req)\n\t+            self.assertIn(\u0027must be an integer between 1 and 2, inclusive\u0027,\n\t+                          self._get_error_message(body))\n\t+\n\t     def test_mpu_GET_huge_part_num(self):\n\t\t req \u003d swob.Request.blank(\u0027/bucket/mpu\u0027, params\u003d{\n\t\t     \u0027partNumber\u0027: \u002710001\u0027,","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"eb53eb9aef8a9778273fdd4f93b429f9a0dca8a7","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"f163f19c_5086d938","updated":"2023-12-21 15:46:07.000000000","message":"TODO: whatever the configured max part num is, if a 416 response tells us that there are more part than the configured max, then the error message should state the actual number of parts as the max.","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"09f600dac71d8e9c3a985c158df8d2bc5e075011","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"fba8e683_44be3f40","updated":"2023-12-21 17:45:34.000000000","message":"i think this can all squash too","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"}],"swift/common/middleware/s3api/controllers/obj.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ea4dad3fd7894df372874dc79bf4ea5c25176610","unresolved":true,"context_lines":[{"line_number":111,"context_line":"            # SLO ignores part_number for non-slo objects, but s3api only"},{"line_number":112,"context_line":"            # allows the query param for non-MPU if it\u0027s exactly 1."},{"line_number":113,"context_line":"            part_number \u003d req.validate_part_number("},{"line_number":114,"context_line":"                max_parts\u003dself.conf.max_upload_part_num, num_parts\u003d1)"},{"line_number":115,"context_line":"            if part_number:"},{"line_number":116,"context_line":"                # When the query param *is* exactly 1 the response status code"},{"line_number":117,"context_line":"                # and headers are updated."}],"source_content_type":"text/x-python","patch_set":1,"id":"65e1d019_61c15306","line":114,"updated":"2023-12-21 17:06:18.000000000","message":"this seems more consistent with the validation when upooading parts\n\nsince it\u0027s a non-slo the max_part check doesn\u0027t really have to worrk about num_parts \u003e max_parts","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"09f600dac71d8e9c3a985c158df8d2bc5e075011","unresolved":false,"context_lines":[{"line_number":111,"context_line":"            # SLO ignores part_number for non-slo objects, but s3api only"},{"line_number":112,"context_line":"            # allows the query param for non-MPU if it\u0027s exactly 1."},{"line_number":113,"context_line":"            part_number \u003d req.validate_part_number("},{"line_number":114,"context_line":"                max_parts\u003dself.conf.max_upload_part_num, num_parts\u003d1)"},{"line_number":115,"context_line":"            if part_number:"},{"line_number":116,"context_line":"                # When the query param *is* exactly 1 the response status code"},{"line_number":117,"context_line":"                # and headers are updated."}],"source_content_type":"text/x-python","patch_set":1,"id":"16a5a14c_363f0cc5","line":114,"in_reply_to":"65e1d019_61c15306","updated":"2023-12-21 17:45:34.000000000","message":"Acknowledged","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"}],"swift/common/middleware/s3api/s3request.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ea4dad3fd7894df372874dc79bf4ea5c25176610","unresolved":true,"context_lines":[{"line_number":1454,"context_line":"                raise InvalidArgument(\u0027X-Delete-After\u0027,"},{"line_number":1455,"context_line":"                                      self.headers[\u0027X-Delete-After\u0027],"},{"line_number":1456,"context_line":"                                      err_str)"},{"line_number":1457,"context_line":"            elif \u0027Part number must be an integer\u0027 in err_str:"},{"line_number":1458,"context_line":"                self.validate_part_number("},{"line_number":1459,"context_line":"                    max_parts\u003dself.conf.max_upload_part_num)"},{"line_number":1460,"context_line":"            else:"}],"source_content_type":"text/x-python","patch_set":1,"id":"c6877507_0f6b3403","line":1457,"updated":"2023-12-21 17:06:18.000000000","message":"I think on a HEAD responsewe don\u0027t get an err_str - but it also doesn\u0027t matter what kind of 400 we raise because our response won\u0027t have a body either","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"09f600dac71d8e9c3a985c158df8d2bc5e075011","unresolved":false,"context_lines":[{"line_number":1454,"context_line":"                raise InvalidArgument(\u0027X-Delete-After\u0027,"},{"line_number":1455,"context_line":"                                      self.headers[\u0027X-Delete-After\u0027],"},{"line_number":1456,"context_line":"                                      err_str)"},{"line_number":1457,"context_line":"            elif \u0027Part number must be an integer\u0027 in err_str:"},{"line_number":1458,"context_line":"                self.validate_part_number("},{"line_number":1459,"context_line":"                    max_parts\u003dself.conf.max_upload_part_num)"},{"line_number":1460,"context_line":"            else:"}],"source_content_type":"text/x-python","patch_set":1,"id":"2bef728b_4a95aa92","line":1457,"in_reply_to":"c6877507_0f6b3403","updated":"2023-12-21 17:45:34.000000000","message":"Acknowledged","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ea4dad3fd7894df372874dc79bf4ea5c25176610","unresolved":true,"context_lines":[{"line_number":1456,"context_line":"                                      err_str)"},{"line_number":1457,"context_line":"            elif \u0027Part number must be an integer\u0027 in err_str:"},{"line_number":1458,"context_line":"                self.validate_part_number("},{"line_number":1459,"context_line":"                    max_parts\u003dself.conf.max_upload_part_num)"},{"line_number":1460,"context_line":"            else:"},{"line_number":1461,"context_line":"                raise InvalidRequest(msg\u003derr_str)"},{"line_number":1462,"context_line":"        if status \u003d\u003d HTTP_UNAUTHORIZED:"}],"source_content_type":"text/x-python","patch_set":1,"id":"cd8ed469_18e814cd","line":1459,"updated":"2023-12-21 17:06:18.000000000","message":"this reads kind of strange to me because it\u0027s not immediately obvoius that validate_part_number *must* raise a 400 error; and if it doesn\u0027t I think we end up trying this \"part number must be an integer\" 400 response into a 500 error; which is maybe appropriate because if slo doesn\u0027t like the part-number qs why wouldn\u0027t our validate_part_number raise a 400 too?","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"09f600dac71d8e9c3a985c158df8d2bc5e075011","unresolved":false,"context_lines":[{"line_number":1456,"context_line":"                                      err_str)"},{"line_number":1457,"context_line":"            elif \u0027Part number must be an integer\u0027 in err_str:"},{"line_number":1458,"context_line":"                self.validate_part_number("},{"line_number":1459,"context_line":"                    max_parts\u003dself.conf.max_upload_part_num)"},{"line_number":1460,"context_line":"            else:"},{"line_number":1461,"context_line":"                raise InvalidRequest(msg\u003derr_str)"},{"line_number":1462,"context_line":"        if status \u003d\u003d HTTP_UNAUTHORIZED:"}],"source_content_type":"text/x-python","patch_set":1,"id":"d6e3be52_dd0c5815","line":1459,"in_reply_to":"cd8ed469_18e814cd","updated":"2023-12-21 17:45:34.000000000","message":"Done","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ea4dad3fd7894df372874dc79bf4ea5c25176610","unresolved":true,"context_lines":[{"line_number":1458,"context_line":"                self.validate_part_number("},{"line_number":1459,"context_line":"                    max_parts\u003dself.conf.max_upload_part_num)"},{"line_number":1460,"context_line":"            else:"},{"line_number":1461,"context_line":"                raise InvalidRequest(msg\u003derr_str)"},{"line_number":1462,"context_line":"        if status \u003d\u003d HTTP_UNAUTHORIZED:"},{"line_number":1463,"context_line":"            raise SignatureDoesNotMatch("},{"line_number":1464,"context_line":"                **self.signature_does_not_match_kwargs())"}],"source_content_type":"text/x-python","patch_set":1,"id":"38828d7c_23d91d44","line":1461,"updated":"2023-12-21 17:06:18.000000000","message":"maybe it would be a little more \"obviously correct\" if we de-dented this; such that status \u003d\u003d HTTP_BAD_REQUEST *might* raise a more specific InvalidArgument response; but it will *definately* raise a generic InvalidRequest.","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"09f600dac71d8e9c3a985c158df8d2bc5e075011","unresolved":false,"context_lines":[{"line_number":1458,"context_line":"                self.validate_part_number("},{"line_number":1459,"context_line":"                    max_parts\u003dself.conf.max_upload_part_num)"},{"line_number":1460,"context_line":"            else:"},{"line_number":1461,"context_line":"                raise InvalidRequest(msg\u003derr_str)"},{"line_number":1462,"context_line":"        if status \u003d\u003d HTTP_UNAUTHORIZED:"},{"line_number":1463,"context_line":"            raise SignatureDoesNotMatch("},{"line_number":1464,"context_line":"                **self.signature_does_not_match_kwargs())"}],"source_content_type":"text/x-python","patch_set":1,"id":"e7428a17_d8a13b68","line":1461,"in_reply_to":"38828d7c_23d91d44","updated":"2023-12-21 17:45:34.000000000","message":"Done","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ea4dad3fd7894df372874dc79bf4ea5c25176610","unresolved":true,"context_lines":[{"line_number":1466,"context_line":"            raise AccessDenied(reason\u003d\u0027forbidden\u0027)"},{"line_number":1467,"context_line":"        if status \u003d\u003d HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:"},{"line_number":1468,"context_line":"            self.validate_part_number("},{"line_number":1469,"context_line":"                max_parts\u003dself.conf.max_upload_part_num,"},{"line_number":1470,"context_line":"                num_parts\u003dresp.headers.get(\u0027x-amz-mp-parts-count\u0027))"},{"line_number":1471,"context_line":"            raise InvalidRange()"},{"line_number":1472,"context_line":"        if status \u003d\u003d HTTP_SERVICE_UNAVAILABLE:"}],"source_content_type":"text/x-python","patch_set":1,"id":"90d2b5a6_fce8486b","line":1469,"updated":"2023-12-21 17:06:18.000000000","message":"ok, so in theory SLO would have responded 2XX if \n\nmax_parts \u003c partNumber \u003c num_parts - so this is only about converting 416 to 400 when num_parts \u003c max_parts \u003c partNumber \n\nand while the error message might say \"integer between 1 and 10000\" if the client managed to previously get a 14K segment MPU uploaded they could still request ?partNumber \u003e max_parts","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"09f600dac71d8e9c3a985c158df8d2bc5e075011","unresolved":false,"context_lines":[{"line_number":1466,"context_line":"            raise AccessDenied(reason\u003d\u0027forbidden\u0027)"},{"line_number":1467,"context_line":"        if status \u003d\u003d HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:"},{"line_number":1468,"context_line":"            self.validate_part_number("},{"line_number":1469,"context_line":"                max_parts\u003dself.conf.max_upload_part_num,"},{"line_number":1470,"context_line":"                num_parts\u003dresp.headers.get(\u0027x-amz-mp-parts-count\u0027))"},{"line_number":1471,"context_line":"            raise InvalidRange()"},{"line_number":1472,"context_line":"        if status \u003d\u003d HTTP_SERVICE_UNAVAILABLE:"}],"source_content_type":"text/x-python","patch_set":1,"id":"0d9f966e_0ac5d1a7","line":1469,"in_reply_to":"90d2b5a6_fce8486b","updated":"2023-12-21 17:45:34.000000000","message":"that\u0027s exactly how it works and I think that\u0027s what we want","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"}],"test/s3api/__init__.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ea4dad3fd7894df372874dc79bf4ea5c25176610","unresolved":true,"context_lines":[{"line_number":223,"context_line":""},{"line_number":224,"context_line":"    @classmethod"},{"line_number":225,"context_line":"    def create_name(cls, slug):"},{"line_number":226,"context_line":"        return \u0027%s%s-%s\u0027 % (TEST_PREFIX, slug, uuid.uuid4().hex)"},{"line_number":227,"context_line":""},{"line_number":228,"context_line":"    @classmethod"},{"line_number":229,"context_line":"    def clear_account(cls, client):"}],"source_content_type":"text/x-python","patch_set":1,"id":"1e73603a_61e991b8","line":226,"updated":"2023-12-21 17:06:18.000000000","message":"interesting\n\nthis doesn\u0027t obviously need to be a method or classmethod - it\u0027s a pure function","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"09f600dac71d8e9c3a985c158df8d2bc5e075011","unresolved":false,"context_lines":[{"line_number":223,"context_line":""},{"line_number":224,"context_line":"    @classmethod"},{"line_number":225,"context_line":"    def create_name(cls, slug):"},{"line_number":226,"context_line":"        return \u0027%s%s-%s\u0027 % (TEST_PREFIX, slug, uuid.uuid4().hex)"},{"line_number":227,"context_line":""},{"line_number":228,"context_line":"    @classmethod"},{"line_number":229,"context_line":"    def clear_account(cls, client):"}],"source_content_type":"text/x-python","patch_set":1,"id":"7f4e8199_0a4d3d00","line":226,"in_reply_to":"1e73603a_61e991b8","updated":"2023-12-21 17:45:34.000000000","message":"I guess all the other methods in this class are classmethods; but I have no idea why - it doesn\u0027t appear to be needed for any of them.\n\nmaybe it was to highlight in get_s3_client that the signature_version is class attribute; but there\u0027s pleanty of prior art where we access class attributes off self; any time you look at an instance member you have to remember it might be a class attribute.","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"86741300c86790ad7e79d8b559861c16455056b7","unresolved":false,"context_lines":[{"line_number":223,"context_line":""},{"line_number":224,"context_line":"    @classmethod"},{"line_number":225,"context_line":"    def create_name(cls, slug):"},{"line_number":226,"context_line":"        return \u0027%s%s-%s\u0027 % (TEST_PREFIX, slug, uuid.uuid4().hex)"},{"line_number":227,"context_line":""},{"line_number":228,"context_line":"    @classmethod"},{"line_number":229,"context_line":"    def clear_account(cls, client):"}],"source_content_type":"text/x-python","patch_set":1,"id":"721affb3_d3dfb384","line":226,"in_reply_to":"7f4e8199_0a4d3d00","updated":"2024-01-02 11:40:32.000000000","message":"hmmm, I think the @classmethod crept in at one point I was trying to get the discover_max_part_num thing in the subclass to be executed in setUpClass. But I ended up abandoning that approach.","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"}],"test/s3api/test_mpu.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ea4dad3fd7894df372874dc79bf4ea5c25176610","unresolved":true,"context_lines":[{"line_number":219,"context_line":""},{"line_number":220,"context_line":"    def _check_part_num_zero_exc(self, exc, is_head\u003dFalse):"},{"line_number":221,"context_line":"        self._check_part_num_invalid_exc(exc, 0, is_head\u003dis_head)"},{"line_number":222,"context_line":""},{"line_number":223,"context_line":"    def _check_part_num_out_of_range_exc(self, exc, is_head\u003dFalse):"},{"line_number":224,"context_line":"        err_resp \u003d exc.response"},{"line_number":225,"context_line":"        self.assertEqual(416, err_resp[\u0027ResponseMetadata\u0027][\u0027HTTPStatusCode\u0027])"}],"source_content_type":"text/x-python","patch_set":1,"id":"581e084a_c34bfd8f","side":"PARENT","line":222,"updated":"2023-12-21 17:06:18.000000000","message":"that\u0027s fine; i wasn\u0027t in love with this helper or anything\n\nmaybe having only\n\n_check_part_num_invalid_exc and _check_part_num_out_of_range_exc make it more obvious there\u0027s only 400 and 416 - and then you can infer that partNumber\u003d0 is a 400 by reading tests; rather than seeing this helper","commit_id":"5232ee2e04e86fc6afef403cb6d813f62cc9aae1"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"09f600dac71d8e9c3a985c158df8d2bc5e075011","unresolved":false,"context_lines":[{"line_number":219,"context_line":""},{"line_number":220,"context_line":"    def _check_part_num_zero_exc(self, exc, is_head\u003dFalse):"},{"line_number":221,"context_line":"        self._check_part_num_invalid_exc(exc, 0, is_head\u003dis_head)"},{"line_number":222,"context_line":""},{"line_number":223,"context_line":"    def _check_part_num_out_of_range_exc(self, exc, is_head\u003dFalse):"},{"line_number":224,"context_line":"        err_resp \u003d exc.response"},{"line_number":225,"context_line":"        self.assertEqual(416, err_resp[\u0027ResponseMetadata\u0027][\u0027HTTPStatusCode\u0027])"}],"source_content_type":"text/x-python","patch_set":1,"id":"639d5ede_c6e1593d","side":"PARENT","line":222,"in_reply_to":"581e084a_c34bfd8f","updated":"2023-12-21 17:45:34.000000000","message":"Acknowledged","commit_id":"5232ee2e04e86fc6afef403cb6d813f62cc9aae1"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"ea4dad3fd7894df372874dc79bf4ea5c25176610","unresolved":true,"context_lines":[{"line_number":176,"context_line":"        err_msg \u003d err_resp[\u0027Error\u0027][\u0027Message\u0027]"},{"line_number":177,"context_line":"        preamble \u003d \u0027Part number must be an integer between 1 and \u0027"},{"line_number":178,"context_line":"        self.assertIn(preamble, err_msg)"},{"line_number":179,"context_line":"        return int(err_msg[len(preamble):].split(\u0027,\u0027)[0])"},{"line_number":180,"context_line":""},{"line_number":181,"context_line":"    def test_basic_upload(self):"},{"line_number":182,"context_line":"        key_name \u003d self.create_name(\u0027key\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"96e5b56f_48756360","line":179,"updated":"2023-12-21 17:06:18.000000000","message":"heh, that\u0027s one way to do it!\n\nI reconfigured my s3api mw with max_upload_part_num \u003d 1000 and tests pass\n\nif i take it down to only \"2\" I get an error like:\n\nbotocore.exceptions.ClientError: An error occurred (InvalidArgument) when calling the UploadPart operation: Part number must be an integer between 1 and 2, inclusive\n\nin the test_upload_part_copy test (which uses self.num_parts \u003d 4)\n\nso I think this is basically working.","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"09f600dac71d8e9c3a985c158df8d2bc5e075011","unresolved":false,"context_lines":[{"line_number":176,"context_line":"        err_msg \u003d err_resp[\u0027Error\u0027][\u0027Message\u0027]"},{"line_number":177,"context_line":"        preamble \u003d \u0027Part number must be an integer between 1 and \u0027"},{"line_number":178,"context_line":"        self.assertIn(preamble, err_msg)"},{"line_number":179,"context_line":"        return int(err_msg[len(preamble):].split(\u0027,\u0027)[0])"},{"line_number":180,"context_line":""},{"line_number":181,"context_line":"    def test_basic_upload(self):"},{"line_number":182,"context_line":"        key_name \u003d self.create_name(\u0027key\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"6ad1136d_d9b84248","line":179,"in_reply_to":"96e5b56f_48756360","updated":"2023-12-21 17:45:34.000000000","message":"Acknowledged","commit_id":"162224ed1e424e05dfa913756e9497d94fcf2102"}]}
