)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b0f97e9b8a18976dd4fc054c9f77e82bed78929e","unresolved":true,"context_lines":[{"line_number":4,"context_line":"Commit:     Jianjian Huo \u003cjhuo@nvidia.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2026-01-21 21:29:15 -0800"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"WIP: s3api: Support for object expiration time"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"S3 and Swift both support expiring objects in some form, but the"},{"line_number":10,"context_line":"semantics are entirely different."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":4,"id":"46904ecc_e5dcfb35","line":7,"range":{"start_line":7,"start_character":0,"end_line":7,"end_character":5},"updated":"2026-01-22 12:53:18.000000000","message":"WIP can be deleted 😊","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"b40ebdbfd77ed3d6d4bb25aa420022bbcb840307","unresolved":false,"context_lines":[{"line_number":4,"context_line":"Commit:     Jianjian Huo \u003cjhuo@nvidia.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2026-01-21 21:29:15 -0800"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"WIP: s3api: Support for object expiration time"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"S3 and Swift both support expiring objects in some form, but the"},{"line_number":10,"context_line":"semantics are entirely different."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":4,"id":"88856e0d_7ac0db32","line":7,"range":{"start_line":7,"start_character":0,"end_line":7,"end_character":5},"in_reply_to":"46904ecc_e5dcfb35","updated":"2026-01-28 21:13:15.000000000","message":"Done","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b0f97e9b8a18976dd4fc054c9f77e82bed78929e","unresolved":true,"context_lines":[{"line_number":27,"context_line":"    x-amz-expiration: expiry-date\u003d\"Fri, 16 Jan 2026 00:00:00 GMT\","},{"line_number":28,"context_line":"                      rule-id\u003d\"logs-expiration\""},{"line_number":29,"context_line":""},{"line_number":30,"context_line":"This patch adds this support into Swift’s S3API for expiring objects."},{"line_number":31,"context_line":""},{"line_number":32,"context_line":"Adds Sam, who came up with this approach, as co-author."},{"line_number":33,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":4,"id":"61a98f3a_824986b7","line":30,"updated":"2026-01-22 12:53:18.000000000","message":"for future us, could we add:\n\n```\nSwift\u0027s S3API does not currently support expiration via bucket lifecycle policies, so the x-delete-at header is the only possible source of expiration time to be returned in the x-amz-expiration header. However, if bucket lifecycle expiration were to be implemented then the x-amz-expiration header could return the lowest of the bucket expiration time or the x-delete-at time.\n```","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"b40ebdbfd77ed3d6d4bb25aa420022bbcb840307","unresolved":false,"context_lines":[{"line_number":27,"context_line":"    x-amz-expiration: expiry-date\u003d\"Fri, 16 Jan 2026 00:00:00 GMT\","},{"line_number":28,"context_line":"                      rule-id\u003d\"logs-expiration\""},{"line_number":29,"context_line":""},{"line_number":30,"context_line":"This patch adds this support into Swift’s S3API for expiring objects."},{"line_number":31,"context_line":""},{"line_number":32,"context_line":"Adds Sam, who came up with this approach, as co-author."},{"line_number":33,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":4,"id":"15f9ab8d_373545bf","line":30,"in_reply_to":"61a98f3a_824986b7","updated":"2026-01-28 21:13:15.000000000","message":"Done","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2e545629adc39c37fb9e5a5d7af93463b1276831","unresolved":true,"context_lines":[{"line_number":32,"context_line":"policies, so the x-delete-at header is the only possible source of"},{"line_number":33,"context_line":"expiration time to be returned in the x-amz-expiration header. However,"},{"line_number":34,"context_line":"if bucket lifecycle expiration were to be implemented then the"},{"line_number":35,"context_line":"x-amz-expiration header could return the lowest of the bucket expiration"},{"line_number":36,"context_line":"time or the x-delete-at time."},{"line_number":37,"context_line":""},{"line_number":38,"context_line":"Adds Sam, who came up with this approach, as co-author."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":6,"id":"532f78e3_e2987da8","line":35,"updated":"2026-01-30 15:20:04.000000000","message":"nice call out - KUDOS","commit_id":"7ddc6ef4d92d390cb9696aedc3c183254ef72704"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"61139873be61bd9518eb32c94ea3e0ac6d93dadc","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"1566ecf8_852414ba","updated":"2026-01-20 17:36:18.000000000","message":"This might be a good place to add/extend a functional test: test.functional.s3api.test_object.TestS3ApiObject.test_put_object_valid_delete_headers\n\nIt looks like existing header translation is covered in unit tests in test_obj.py e.g. test.unit.common.middleware.s3api.test_obj.TestS3ApiObj.test_cors_headers","commit_id":"b8e44c8ab1844d7b87ada8ae1733ebcab9cd60c0"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"f72aa4fb67d46703bf80f2d93cd537cbb6fd0087","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"1cd145ac_c17f743b","updated":"2026-01-22 00:49:05.000000000","message":"I tested this implementation in my vsaio, s3api was able to receive the ``\"x-amz-expiration\"`` header and display it correctly under ``\"Expiration\"``.\n\n```\nvagrant@saio:~$ s3cmd info s3://s3test/test\ns3://s3test/test (object):\n   File size: 6\n   Last mod:  Wed, 14 Jan 2026 05:14:14 GMT\n   MIME type: text/plain\n   Storage:   STANDARD\n   MD5 sum:   cc9a6246ff4b5f0ba9b376f8b13513fa\n   SSE:       none\n   Policy:    none\n   CORS:      none\n   ACL:       test:tester: FULL_CONTROL\n   x-amz-meta-s3cmd-attrs: atime:1768367635/ctime:1756148782/gid:1000/gname:vagrant/md5:cc9a6246ff4b5f0ba9b376f8b13513fa/mode:33204/mtime:1756148782/uid:1000/uname:vagrant\nvagrant@saio:~$ swift post s3test test -H \u0027x-delete-at: 1769114566\u0027\nvagrant@saio:~$ swift stat s3test test\n               Account: AUTH_test\n             Container: s3test\n                Object: test\n          Content Type: text/plain\n        Content Length: 6\n         Last Modified: Wed, 21 Jan 2026 16:57:05 GMT\n                  ETag: \"cc9a6246ff4b5f0ba9b376f8b13513fa\"\n           X-Delete-At: 1769114566\n           X-Timestamp: 1769014624.39593\n         Accept-Ranges: bytes\n            X-Trans-Id: txda6a273c1ea548eda46c0-00697108b2\nX-Openstack-Request-Id: txda6a273c1ea548eda46c0-00697108b2\nvagrant@saio:~$ aws --endpoint-url http://localhost:8080 s3api head-object --bucket s3test --key test\n{\n    \"AcceptRanges\": \"bytes\",\n    \"Expiration\": \"expiry-date\u003d\\\"Thu, 22 Jan 2026 20:42:46 GMT\\\", rule-id\u003d\\\"swift-object-expiration\\\"\",\n    \"LastModified\": \"Wed, 21 Jan 2026 16:57:05 GMT\",\n    \"ContentLength\": 6,\n    \"ETag\": \"\\\"cc9a6246ff4b5f0ba9b376f8b13513fa\\\"\",\n    \"ContentType\": \"text/plain\",\n    \"Metadata\": {}\n}\n```\n\nhowever it seems s3cmd (I am using version 2.4.0) was able to receive it but not able to display it.\n\n```\nvagrant@saio:~$ s3cmd info s3://s3test/test\ns3://s3test/test (object):\n   File size: 6\n   Last mod:  Wed, 21 Jan 2026 16:57:05 GMT\n   MIME type: text/plain\n   Storage:   STANDARD\n   MD5 sum:   cc9a6246ff4b5f0ba9b376f8b13513fa\n   SSE:       none\n   Policy:    none\n   CORS:      none\n   ACL:       test:tester: FULL_CONTROL\nvagrant@saio:~$ s3cmd info s3://s3test/test --debug 2\u003e\u00261 | grep \"x-amz-expiration\"\n             \u0027x-amz-expiration\u0027: \u0027expiry-date\u003d\"Thu, 22 Jan 2026 20:42:46 GMT\", \u0027\nvagrant@saio:~$ s3cmd get s3://s3test/test --debug 2\u003e\u00261 | grep \"x-amz-expiration\"\n             \u0027x-amz-expiration\u0027: \u0027expiry-date\u003d\"Thu, 22 Jan 2026 20:42:46 GMT\", \u0027\n```\n\nbut probably s3api is the expected usage for @spam+launchpad@andcheese.org.","commit_id":"b64c8f7a4433974e9e188f54f895967dc6b391ab"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"b40ebdbfd77ed3d6d4bb25aa420022bbcb840307","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":3,"id":"33410438_87483410","in_reply_to":"1cd145ac_c17f743b","updated":"2026-01-28 21:13:15.000000000","message":"I am still not sure why ``s3cmd info s3://s3test/test`` doesn\u0027t show the value of ``x-amz-meta-s3cmd-attrs`` anymore with this patch","commit_id":"b64c8f7a4433974e9e188f54f895967dc6b391ab"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"011fd62e1efcf4df8340fc3b89d0ee8fb0d6b3cf","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":3,"id":"ea96de9c_d7d4cfe7","in_reply_to":"33410438_87483410","updated":"2026-01-30 14:05:57.000000000","message":"@jhuo@nvidia.com \n\n\u003e I am still not sure why s3cmd info s3://s3test/test doesn\u0027t show the value of x-amz-meta-s3cmd-attrs anymore with this patch\n\ns3cmd sets some attrs on the object with the put command. These are stored as object metadata:\n\n```\nvagrant@saio:~/swift$ s3cmd put LICENSE s3://test\nupload: \u0027LICENSE\u0027 -\u003e \u0027s3://test/LICENSE\u0027  [1 of 1]\n 11358 of 11358   100% in    0s   505.16 KB/s  done\nvagrant@saio:~/swift$ swift stat test LICENSE\n               Account: AUTH_test\n             Container: test\n                Object: LICENSE\n          Content Type: text/plain\n        Content Length: 11358\n         Last Modified: Fri, 30 Jan 2026 12:53:02 GMT\n                  ETag: \"3b83ef96387f14655fc854ddc3c6bd57\"\n      Meta S3Cmd-Attrs: atime:1769777475/ctime:1769777474/gid:1000/gname:vagrant/md5:3b83ef96387f14655fc854ddc3c6bd57/mode:33188/mtime:1769701145/uid:1000/uname:vagrant\n           X-Timestamp: 1769777581.89078\n         Accept-Ranges: bytes\n            X-Trans-Id: txc3eb23d7c88747f8a2a18-00697ca9b3\nX-Openstack-Request-Id: txc3eb23d7c88747f8a2a18-00697ca9b3\nvagrant@saio:~/swift$ s3cmd info s3://test/LICENSE\ns3://test/LICENSE (object):\n   File size: 11358\n   Last mod:  Fri, 30 Jan 2026 12:53:02 GMT\n   MIME type: text/plain\n   Storage:   STANDARD\n   MD5 sum:   3b83ef96387f14655fc854ddc3c6bd57\n   SSE:       none\n   Policy:    none\n   CORS:      none\n   ACL:       test:tester: FULL_CONTROL\n   x-amz-meta-s3cmd-attrs: atime:1769777475/ctime:1769777474/gid:1000/gname:vagrant/md5:3b83ef96387f14655fc854ddc3c6bd57/mode:33188/mtime:1769701145/uid:1000/uname:vagrant\n```\n\nwhen the x-delete-at is POST\u0027d to the object it is stored as object metadata, replacing *all* existing object metadata (this is usual swift API behavior - x-delete-at is considered an element of \"user\" object metadata which is replaced as a whole on each POST).\n\n```\nvagrant@saio:~/swift$ swift post test LICENSE -H \u0027x-delete-at: 1789114566\u0027\nvagrant@saio:~/swift$ swift stat test LICENSE\n               Account: AUTH_test\n             Container: test\n                Object: LICENSE\n          Content Type: text/plain\n        Content Length: 11358\n         Last Modified: Fri, 30 Jan 2026 12:54:56 GMT\n                  ETag: \"3b83ef96387f14655fc854ddc3c6bd57\"\n           X-Delete-At: 1789114566\n           X-Timestamp: 1769777695.62440\n         Accept-Ranges: bytes\n            X-Trans-Id: tx6585fffd68d44a43884e1-00697caa22\nX-Openstack-Request-Id: tx6585fffd68d44a43884e1-00697caa22\nvagrant@saio:~/swift$ s3cmd info s3://test/LICENSE\ns3://test/LICENSE (object):\n   File size: 11358\n   Last mod:  Fri, 30 Jan 2026 12:54:56 GMT\n   MIME type: text/plain\n   Storage:   STANDARD\n   MD5 sum:   3b83ef96387f14655fc854ddc3c6bd57\n   SSE:       none\n   Policy:    none\n   CORS:      none\n   ACL:       test:tester: FULL_CONTROL\n```\n\nThe examples above were made using master branch. This patch does not change the behaviour w.r.t. POST of x-delete-at over-writing existing s3cm object metadata.","commit_id":"b64c8f7a4433974e9e188f54f895967dc6b391ab"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2e545629adc39c37fb9e5a5d7af93463b1276831","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"8b563934_0e904f56","in_reply_to":"ea96de9c_d7d4cfe7","updated":"2026-01-30 15:20:04.000000000","message":"FWIW `Policy:    none` does NOT seem related to aws s3 bucket life-cycle policy","commit_id":"b64c8f7a4433974e9e188f54f895967dc6b391ab"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b0f97e9b8a18976dd4fc054c9f77e82bed78929e","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"d3dab0dc_0c0ccf0c","updated":"2026-01-22 12:53:18.000000000","message":"WFM\n\n```\nvagrant@saio:~/swift$ aws s3api put-object --bucket test --key foo\n{\n    \"ETag\": \"\\\"d41d8cd98f00b204e9800998ecf8427e\\\"\"\n}\nvagrant@saio:~/swift$ aws s3api head-object --bucket test --key foo\n{\n    \"AcceptRanges\": \"bytes\",\n    \"LastModified\": \"Thu, 22 Jan 2026 12:51:12 GMT\",\n    \"ContentLength\": 0,\n    \"ETag\": \"\\\"d41d8cd98f00b204e9800998ecf8427e\\\"\",\n    \"ContentType\": \"binary/octet-stream\",\n    \"Metadata\": {}\n}\nvagrant@saio:~/swift$ swift post test foo -H \u0027x-delete-at: 1769091000\u0027\nvagrant@saio:~/swift$ aws s3api head-object --bucket test --key foo\n{\n    \"AcceptRanges\": \"bytes\",\n    \"Expiration\": \"expiry-date\u003d\\\"Thu, 22 Jan 2026 14:10:00 GMT\\\", rule-id\u003d\\\"swift-object-expiration\\\"\",\n    \"LastModified\": \"Thu, 22 Jan 2026 12:51:29 GMT\",\n    \"ContentLength\": 0,\n    \"ETag\": \"\\\"d41d8cd98f00b204e9800998ecf8427e\\\"\",\n    \"ContentType\": \"binary/octet-stream\",\n    \"Metadata\": {}\n}\nvagrant@saio:~/swift$ aws s3api get-object --bucket test --key foo /dev/null\n{\n    \"AcceptRanges\": \"bytes\",\n    \"Expiration\": \"expiry-date\u003d\\\"Thu, 22 Jan 2026 14:10:00 GMT\\\", rule-id\u003d\\\"swift-object-expiration\\\"\",\n    \"LastModified\": \"Thu, 22 Jan 2026 12:51:29 GMT\",\n    \"ContentLength\": 0,\n    \"ETag\": \"\\\"d41d8cd98f00b204e9800998ecf8427e\\\"\",\n    \"ContentType\": \"binary/octet-stream\",\n    \"Metadata\": {}\n}\n```\n\n\n``s3cmd info`` doesn\u0027t display x-amz-expiration https://github.com/s3tools/s3cmd/blob/master/s3cmd#L1057 but if you run the command with ``--debug`` you can see the header is returned and logged.\n\n+1 because I\u0027d like to check this works for our use case befor emerging","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"7931e2bb33bf1fba50df0566ab682db06fc2d92f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"e2889115_aed48f73","updated":"2026-01-22 12:53:52.000000000","message":"also, some test fixup suggestions:\n\n```\ndiff --git a/test/functional/s3api/test_object.py b/test/functional/s3api/test_object.py\nindex 547414e8c..6735a7464 100644\n--- a/test/functional/s3api/test_object.py\n+++ b/test/functional/s3api/test_object.py\n@@ -21,7 +21,7 @@ from email.utils import formatdate, parsedate\n from time import mktime\n \n import test.functional as tf\n-from swift.common import utils\n+from swift.common import utils, swob\n \n from swift.common.middleware.s3api.etree import fromstring\n from swift.common.middleware.s3api.utils import S3Timestamp\n@@ -482,12 +482,24 @@ class TestS3ApiObject(S3ApiBase):\n         # Test that X-Delete-At translates to x-amz-expiration.\n         obj \u003d \u0027expiring-object\u0027\n         content \u003d b\u0027test content\u0027\n-        ts \u003d utils.Timestamp.now()\n-        delete_at_time \u003d int(ts) + 3600  # 1 hour from now\n-        headers \u003d {\u0027X-Delete-At\u0027: str(delete_at_time)}\n+        status, headers, body \u003d \\\n+            self.conn.make_request(\u0027PUT\u0027, self.bucket, obj, {}, content)\n+        self.assertEqual(status, 200)\n \n         status, headers, body \u003d \\\n-            self.conn.make_request(\u0027PUT\u0027, self.bucket, obj, headers, content)\n+            self.conn.make_request(\u0027HEAD\u0027, self.bucket, obj)\n+        self.assertEqual(status, 200)\n+        self.assertNotIn(\u0027x-amz-expiration\u0027, headers)\n+        status, headers, body \u003d \\\n+            self.conn.make_request(\u0027GET\u0027, self.bucket, obj)\n+        self.assertEqual(status, 200)\n+        self.assertNotIn(\u0027x-amz-expiration\u0027, headers)\n+\n+        # now set x-delete-at\n+        delete_at_ts \u003d utils.Timestamp.now(delta\u003d3600 * 1e5)\n+        headers \u003d {\u0027X-Delete-At\u0027: str(delete_at_ts.ceil())}\n+        status, headers, body \u003d \\\n+            self.conn.make_request(\u0027PUT\u0027, self.bucket, obj, headers)\n         self.assertEqual(status, 200)\n \n         # HEAD should return x-amz-expiration\n@@ -495,15 +507,18 @@ class TestS3ApiObject(S3ApiBase):\n             self.conn.make_request(\u0027HEAD\u0027, self.bucket, obj)\n         self.assertEqual(status, 200)\n         self.assertIn(\u0027x-amz-expiration\u0027, headers)\n-        expiration \u003d headers[\u0027x-amz-expiration\u0027]\n-        self.assertIn(\u0027rule-id\u003d\"swift-object-expiration\"\u0027, expiration)\n-        self.assertIn(\u0027expiry-date\u003d\"\u0027, expiration)\n+        self.assertEqual(\u0027expiry-date\u003d\"%s\", rule-id\u003d\"swift-object-expiration\"\u0027\n+                         % swob.date_header_format(delete_at_ts),\n+                         headers[\u0027x-amz-expiration\u0027])\n \n         # GET should also return x-amz-expiration\n         status, headers, body \u003d \\\n             self.conn.make_request(\u0027GET\u0027, self.bucket, obj)\n         self.assertEqual(status, 200)\n         self.assertIn(\u0027x-amz-expiration\u0027, headers)\n+        self.assertEqual(\u0027expiry-date\u003d\"%s\", rule-id\u003d\"swift-object-expiration\"\u0027\n+                         % swob.date_header_format(delete_at_ts),\n+                         headers[\u0027x-amz-expiration\u0027])\n \n     def test_put_object_invalid_x_delete_at(self):\n         obj \u003d \u0027object\u0027\n\n```","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2e545629adc39c37fb9e5a5d7af93463b1276831","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"8588bc80_15401136","in_reply_to":"d3dab0dc_0c0ccf0c","updated":"2026-01-30 15:20:04.000000000","message":"helpful link - kudos!","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"011fd62e1efcf4df8340fc3b89d0ee8fb0d6b3cf","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"bc7a0327_c000f857","updated":"2026-01-30 14:05:57.000000000","message":"LGTM","commit_id":"7ddc6ef4d92d390cb9696aedc3c183254ef72704"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2e545629adc39c37fb9e5a5d7af93463b1276831","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"3436f83f_93b2f0fa","updated":"2026-01-30 15:20:04.000000000","message":"LGTM!","commit_id":"7ddc6ef4d92d390cb9696aedc3c183254ef72704"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"b40ebdbfd77ed3d6d4bb25aa420022bbcb840307","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"3f88bcba_dcc36394","updated":"2026-01-28 21:13:15.000000000","message":"thanks for the reviews!","commit_id":"7ddc6ef4d92d390cb9696aedc3c183254ef72704"}],"swift/common/middleware/s3api/s3response.py":[{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"8e6165d814291ab2a7ad434bc1a007ba8c445c08","unresolved":true,"context_lines":[{"line_number":100,"context_line":"            delete_at \u003d int(val)"},{"line_number":101,"context_line":"            # Format as RFC 1123 (e.g., \"Fri, 16 Jan 2026 00:00:00 GMT\")"},{"line_number":102,"context_line":"            expiry_date \u003d email.utils.formatdate(delete_at, usegmt\u003dTrue)"},{"line_number":103,"context_line":"            rule_id \u003d quote(\u0027swift-object-expiration\u0027, safe\u003d\u0027\u0027)"},{"line_number":104,"context_line":"            return \u0027x-amz-expiration\u0027, \\"},{"line_number":105,"context_line":"                \u0027expiry-date\u003d\"%s\", rule-id\u003d\"%s\"\u0027 % (expiry_date, rule_id)"},{"line_number":106,"context_line":"        except (ValueError, TypeError):"}],"source_content_type":"text/x-python","patch_set":1,"id":"67d2cb65_2512834a","line":103,"range":{"start_line":103,"start_character":12,"end_line":103,"end_character":19},"updated":"2026-01-16 22:22:37.000000000","message":"Micro-optimization: this is just the literal string `\"swift-object-expiration\"`, so you can skip the call to quote()","commit_id":"7bd2194ef40931888941b9996c8fcb6143974f26"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"f8851937948bac13540ed5671942774e2ae73263","unresolved":false,"context_lines":[{"line_number":100,"context_line":"            delete_at \u003d int(val)"},{"line_number":101,"context_line":"            # Format as RFC 1123 (e.g., \"Fri, 16 Jan 2026 00:00:00 GMT\")"},{"line_number":102,"context_line":"            expiry_date \u003d email.utils.formatdate(delete_at, usegmt\u003dTrue)"},{"line_number":103,"context_line":"            rule_id \u003d quote(\u0027swift-object-expiration\u0027, safe\u003d\u0027\u0027)"},{"line_number":104,"context_line":"            return \u0027x-amz-expiration\u0027, \\"},{"line_number":105,"context_line":"                \u0027expiry-date\u003d\"%s\", rule-id\u003d\"%s\"\u0027 % (expiry_date, rule_id)"},{"line_number":106,"context_line":"        except (ValueError, TypeError):"}],"source_content_type":"text/x-python","patch_set":1,"id":"e4791bc7_bd55428d","line":103,"range":{"start_line":103,"start_character":12,"end_line":103,"end_character":19},"in_reply_to":"67d2cb65_2512834a","updated":"2026-01-19 02:14:33.000000000","message":"Done","commit_id":"7bd2194ef40931888941b9996c8fcb6143974f26"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"61139873be61bd9518eb32c94ea3e0ac6d93dadc","unresolved":true,"context_lines":[{"line_number":97,"context_line":"    elif _key \u003d\u003d \u0027x-delete-at\u0027:"},{"line_number":98,"context_line":"        try:"},{"line_number":99,"context_line":"            delete_at \u003d int(val)"},{"line_number":100,"context_line":"            # Format as RFC 1123 (e.g., \"Fri, 16 Jan 2026 00:00:00 GMT\")"},{"line_number":101,"context_line":"            expiry_date \u003d email.utils.formatdate(delete_at, usegmt\u003dTrue)"},{"line_number":102,"context_line":"            rule_id \u003d \u0027swift-object-expiration\u0027"},{"line_number":103,"context_line":"            return \u0027x-amz-expiration\u0027, \\"}],"source_content_type":"text/x-python","patch_set":2,"id":"7077066c_c17a7351","line":100,"updated":"2026-01-20 17:36:18.000000000","message":"there is a helper method for this swift.common.swob.date_header_format (recently added)","commit_id":"b8e44c8ab1844d7b87ada8ae1733ebcab9cd60c0"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"f72aa4fb67d46703bf80f2d93cd537cbb6fd0087","unresolved":false,"context_lines":[{"line_number":97,"context_line":"    elif _key \u003d\u003d \u0027x-delete-at\u0027:"},{"line_number":98,"context_line":"        try:"},{"line_number":99,"context_line":"            delete_at \u003d int(val)"},{"line_number":100,"context_line":"            # Format as RFC 1123 (e.g., \"Fri, 16 Jan 2026 00:00:00 GMT\")"},{"line_number":101,"context_line":"            expiry_date \u003d email.utils.formatdate(delete_at, usegmt\u003dTrue)"},{"line_number":102,"context_line":"            rule_id \u003d \u0027swift-object-expiration\u0027"},{"line_number":103,"context_line":"            return \u0027x-amz-expiration\u0027, \\"}],"source_content_type":"text/x-python","patch_set":2,"id":"f98de47f_e3a4af07","line":100,"in_reply_to":"7077066c_c17a7351","updated":"2026-01-22 00:49:05.000000000","message":"yes, it\u0027s the same format, thanks.","commit_id":"b8e44c8ab1844d7b87ada8ae1733ebcab9cd60c0"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1e5a946e4a9cd12a19b51f1ca4e4f39692518acf","unresolved":true,"context_lines":[{"line_number":98,"context_line":"        try:"},{"line_number":99,"context_line":"            delete_at \u003d int(val)"},{"line_number":100,"context_line":"            # Format as RFC 1123 (e.g., \"Fri, 16 Jan 2026 00:00:00 GMT\")"},{"line_number":101,"context_line":"            expiry_date \u003d email.utils.formatdate(delete_at, usegmt\u003dTrue)"},{"line_number":102,"context_line":"            rule_id \u003d \u0027swift-object-expiration\u0027"},{"line_number":103,"context_line":"            return \u0027x-amz-expiration\u0027, \\"},{"line_number":104,"context_line":"                \u0027expiry-date\u003d\"%s\", rule-id\u003d\"%s\"\u0027 % (expiry_date, rule_id)"}],"source_content_type":"text/x-python","patch_set":2,"id":"62b0a058_787b65b7","line":101,"updated":"2026-01-20 17:27:50.000000000","message":"I think S3Timestamp might have a helper that does this in a consistent way...","commit_id":"b8e44c8ab1844d7b87ada8ae1733ebcab9cd60c0"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"f72aa4fb67d46703bf80f2d93cd537cbb6fd0087","unresolved":false,"context_lines":[{"line_number":98,"context_line":"        try:"},{"line_number":99,"context_line":"            delete_at \u003d int(val)"},{"line_number":100,"context_line":"            # Format as RFC 1123 (e.g., \"Fri, 16 Jan 2026 00:00:00 GMT\")"},{"line_number":101,"context_line":"            expiry_date \u003d email.utils.formatdate(delete_at, usegmt\u003dTrue)"},{"line_number":102,"context_line":"            rule_id \u003d \u0027swift-object-expiration\u0027"},{"line_number":103,"context_line":"            return \u0027x-amz-expiration\u0027, \\"},{"line_number":104,"context_line":"                \u0027expiry-date\u003d\"%s\", rule-id\u003d\"%s\"\u0027 % (expiry_date, rule_id)"}],"source_content_type":"text/x-python","patch_set":2,"id":"860dacf4_9616188c","line":101,"in_reply_to":"62b0a058_787b65b7","updated":"2026-01-22 00:49:05.000000000","message":"Done","commit_id":"b8e44c8ab1844d7b87ada8ae1733ebcab9cd60c0"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"2e545629adc39c37fb9e5a5d7af93463b1276831","unresolved":true,"context_lines":[{"line_number":97,"context_line":"        try:"},{"line_number":98,"context_line":"            delete_at \u003d int(val)"},{"line_number":99,"context_line":"            expiry_date \u003d swob.date_header_format(delete_at)"},{"line_number":100,"context_line":"            rule_id \u003d \u0027swift-object-expiration\u0027"},{"line_number":101,"context_line":"            return \u0027x-amz-expiration\u0027, \\"},{"line_number":102,"context_line":"                \u0027expiry-date\u003d\"%s\", rule-id\u003d\"%s\"\u0027 % (expiry_date, rule_id)"},{"line_number":103,"context_line":"        except (ValueError, TypeError):"}],"source_content_type":"text/x-python","patch_set":6,"id":"e369a072_25da53b6","line":100,"updated":"2026-01-30 15:20:04.000000000","message":"IIUC the choice of rule_id \"swift-object-expiration\" is arbitrary, we wouldn\u0027t expect aws to namespace the \"swift-\" prefix when naming bucket-lifecycle policies - although we could enforce it in s3api if we ever add them.","commit_id":"7ddc6ef4d92d390cb9696aedc3c183254ef72704"}],"test/functional/s3api/test_object.py":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b0f97e9b8a18976dd4fc054c9f77e82bed78929e","unresolved":true,"context_lines":[{"line_number":494,"context_line":"        status, headers, body \u003d \\"},{"line_number":495,"context_line":"            self.conn.make_request(\u0027HEAD\u0027, self.bucket, obj)"},{"line_number":496,"context_line":"        self.assertEqual(status, 200)"},{"line_number":497,"context_line":"        self.assertIn(\u0027x-amz-expiration\u0027, headers)"},{"line_number":498,"context_line":"        expiration \u003d headers[\u0027x-amz-expiration\u0027]"},{"line_number":499,"context_line":"        self.assertIn(\u0027rule-id\u003d\"swift-object-expiration\"\u0027, expiration)"},{"line_number":500,"context_line":"        self.assertIn(\u0027expiry-date\u003d\"\u0027, expiration)"}],"source_content_type":"text/x-python","patch_set":4,"id":"f8b2e44b_472af975","line":497,"updated":"2026-01-22 12:53:18.000000000","message":"let\u0027s add a negative ``self.assertNotIn(\u0027x-amz-expiration\u0027, headers)`` assertion:\n\nPUT the object with no x-delete-at, assert no expiration header, then PUT with x-delete-at, assert expiration header","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"b40ebdbfd77ed3d6d4bb25aa420022bbcb840307","unresolved":false,"context_lines":[{"line_number":494,"context_line":"        status, headers, body \u003d \\"},{"line_number":495,"context_line":"            self.conn.make_request(\u0027HEAD\u0027, self.bucket, obj)"},{"line_number":496,"context_line":"        self.assertEqual(status, 200)"},{"line_number":497,"context_line":"        self.assertIn(\u0027x-amz-expiration\u0027, headers)"},{"line_number":498,"context_line":"        expiration \u003d headers[\u0027x-amz-expiration\u0027]"},{"line_number":499,"context_line":"        self.assertIn(\u0027rule-id\u003d\"swift-object-expiration\"\u0027, expiration)"},{"line_number":500,"context_line":"        self.assertIn(\u0027expiry-date\u003d\"\u0027, expiration)"}],"source_content_type":"text/x-python","patch_set":4,"id":"29a2f2c0_7909c351","line":497,"in_reply_to":"f8b2e44b_472af975","updated":"2026-01-28 21:13:15.000000000","message":"Done","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b0f97e9b8a18976dd4fc054c9f77e82bed78929e","unresolved":true,"context_lines":[{"line_number":497,"context_line":"        self.assertIn(\u0027x-amz-expiration\u0027, headers)"},{"line_number":498,"context_line":"        expiration \u003d headers[\u0027x-amz-expiration\u0027]"},{"line_number":499,"context_line":"        self.assertIn(\u0027rule-id\u003d\"swift-object-expiration\"\u0027, expiration)"},{"line_number":500,"context_line":"        self.assertIn(\u0027expiry-date\u003d\"\u0027, expiration)"},{"line_number":501,"context_line":""},{"line_number":502,"context_line":"        # GET should also return x-amz-expiration"},{"line_number":503,"context_line":"        status, headers, body \u003d \\"}],"source_content_type":"text/x-python","patch_set":4,"id":"574afc8a_9ce5e5b0","line":500,"updated":"2026-01-22 12:53:18.000000000","message":"the exact header value can be asserted - we know the expiry-date will be\ndata_header_format(delete_at_time)","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"b40ebdbfd77ed3d6d4bb25aa420022bbcb840307","unresolved":false,"context_lines":[{"line_number":497,"context_line":"        self.assertIn(\u0027x-amz-expiration\u0027, headers)"},{"line_number":498,"context_line":"        expiration \u003d headers[\u0027x-amz-expiration\u0027]"},{"line_number":499,"context_line":"        self.assertIn(\u0027rule-id\u003d\"swift-object-expiration\"\u0027, expiration)"},{"line_number":500,"context_line":"        self.assertIn(\u0027expiry-date\u003d\"\u0027, expiration)"},{"line_number":501,"context_line":""},{"line_number":502,"context_line":"        # GET should also return x-amz-expiration"},{"line_number":503,"context_line":"        status, headers, body \u003d \\"}],"source_content_type":"text/x-python","patch_set":4,"id":"e2d26cb6_7af131e0","line":500,"in_reply_to":"574afc8a_9ce5e5b0","updated":"2026-01-28 21:13:15.000000000","message":"Done","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b0f97e9b8a18976dd4fc054c9f77e82bed78929e","unresolved":true,"context_lines":[{"line_number":503,"context_line":"        status, headers, body \u003d \\"},{"line_number":504,"context_line":"            self.conn.make_request(\u0027GET\u0027, self.bucket, obj)"},{"line_number":505,"context_line":"        self.assertEqual(status, 200)"},{"line_number":506,"context_line":"        self.assertIn(\u0027x-amz-expiration\u0027, headers)"},{"line_number":507,"context_line":""},{"line_number":508,"context_line":"    def test_put_object_invalid_x_delete_at(self):"},{"line_number":509,"context_line":"        obj \u003d \u0027object\u0027"}],"source_content_type":"text/x-python","patch_set":4,"id":"af47083b_2dc094a8","line":506,"updated":"2026-01-22 12:53:18.000000000","message":"for completeness, also assert the value again","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"b40ebdbfd77ed3d6d4bb25aa420022bbcb840307","unresolved":false,"context_lines":[{"line_number":503,"context_line":"        status, headers, body \u003d \\"},{"line_number":504,"context_line":"            self.conn.make_request(\u0027GET\u0027, self.bucket, obj)"},{"line_number":505,"context_line":"        self.assertEqual(status, 200)"},{"line_number":506,"context_line":"        self.assertIn(\u0027x-amz-expiration\u0027, headers)"},{"line_number":507,"context_line":""},{"line_number":508,"context_line":"    def test_put_object_invalid_x_delete_at(self):"},{"line_number":509,"context_line":"        obj \u003d \u0027object\u0027"}],"source_content_type":"text/x-python","patch_set":4,"id":"32c577f2_787d910d","line":506,"in_reply_to":"af47083b_2dc094a8","updated":"2026-01-28 21:13:15.000000000","message":"Done","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"}],"test/unit/common/middleware/s3api/test_s3response.py":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b0f97e9b8a18976dd4fc054c9f77e82bed78929e","unresolved":true,"context_lines":[{"line_number":132,"context_line":"        })"},{"line_number":133,"context_line":"        s3resp \u003d S3Response.from_swift_resp(resp)"},{"line_number":134,"context_line":"        self.assertNotIn(\u0027x-amz-expiration\u0027, s3resp.headers)"},{"line_number":135,"context_line":"        self.assertEqual(\u0027\"theetag\"\u0027, s3resp.headers[\u0027ETag\u0027])"},{"line_number":136,"context_line":""},{"line_number":137,"context_line":"    def test_response_x_delete_at_empty_value(self):"},{"line_number":138,"context_line":"        resp \u003d Response(headers\u003d{"}],"source_content_type":"text/x-python","patch_set":4,"id":"f77f039f_a4ea4d2c","line":135,"updated":"2026-01-22 12:53:18.000000000","message":"nice, so we check that a bad x-delete-at doesn\u0027t mess up other header translation","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"b40ebdbfd77ed3d6d4bb25aa420022bbcb840307","unresolved":false,"context_lines":[{"line_number":132,"context_line":"        })"},{"line_number":133,"context_line":"        s3resp \u003d S3Response.from_swift_resp(resp)"},{"line_number":134,"context_line":"        self.assertNotIn(\u0027x-amz-expiration\u0027, s3resp.headers)"},{"line_number":135,"context_line":"        self.assertEqual(\u0027\"theetag\"\u0027, s3resp.headers[\u0027ETag\u0027])"},{"line_number":136,"context_line":""},{"line_number":137,"context_line":"    def test_response_x_delete_at_empty_value(self):"},{"line_number":138,"context_line":"        resp \u003d Response(headers\u003d{"}],"source_content_type":"text/x-python","patch_set":4,"id":"d51bcedc_a0cd887e","line":135,"in_reply_to":"f77f039f_a4ea4d2c","updated":"2026-01-28 21:13:15.000000000","message":"Acknowledged","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"b0f97e9b8a18976dd4fc054c9f77e82bed78929e","unresolved":true,"context_lines":[{"line_number":141,"context_line":"        })"},{"line_number":142,"context_line":"        s3resp \u003d S3Response.from_swift_resp(resp)"},{"line_number":143,"context_line":"        self.assertNotIn(\u0027x-amz-expiration\u0027, s3resp.headers)"},{"line_number":144,"context_line":""},{"line_number":145,"context_line":""},{"line_number":146,"context_line":"class DummyErrorResponse(ErrorResponse):"},{"line_number":147,"context_line":"    _status \u003d \"418 I\u0027m a teapot\""}],"source_content_type":"text/x-python","patch_set":4,"id":"d8134ba4_f4d38d36","line":144,"updated":"2026-01-22 12:53:18.000000000","message":"can we have this here too ;-)\n```\nself.assertEqual(\u0027\"theetag\"\u0027, s3resp.headers[\u0027ETag\u0027])\n```","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"b40ebdbfd77ed3d6d4bb25aa420022bbcb840307","unresolved":false,"context_lines":[{"line_number":141,"context_line":"        })"},{"line_number":142,"context_line":"        s3resp \u003d S3Response.from_swift_resp(resp)"},{"line_number":143,"context_line":"        self.assertNotIn(\u0027x-amz-expiration\u0027, s3resp.headers)"},{"line_number":144,"context_line":""},{"line_number":145,"context_line":""},{"line_number":146,"context_line":"class DummyErrorResponse(ErrorResponse):"},{"line_number":147,"context_line":"    _status \u003d \"418 I\u0027m a teapot\""}],"source_content_type":"text/x-python","patch_set":4,"id":"bd706ca5_19468119","line":144,"in_reply_to":"d8134ba4_f4d38d36","updated":"2026-01-28 21:13:15.000000000","message":"Done","commit_id":"8f7561e10fa987cd20c182fb757748598f517b46"}]}
