)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"f0da3d658db297ca80f6b1b0d2071519e9a9a93a","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":7,"id":"a36d37ad_0ab9506a","updated":"2026-07-29 03:13:47.000000000","message":"I verified this against a real grafana/grafana:13.1.1 container and the\nchange is correct and necessary. The old routes are all gone:\n\n  GET    /api/datasources/1  -\u003e 404 Not found\n  PUT    /api/datasources/1  -\u003e 404 Not found\n  DELETE /api/datasources/1  -\u003e 404 Not found\n  GET    /api/datasources/uid/\u003cuid\u003e -\u003e 200\n\nDriving the real Datasource class end to end against 13.1.1,\ncreate -\u003e is_datasource -\u003e update -\u003e get -\u003e delete all work.\n\nTwo things I checked that turned out fine, noting so nobody redoes\nthem:\n\n- update() PUTs a body with no \"uid\" key (builder passes the raw\n  parsed data). Grafana 13 preserves the existing uid on PUT, so the\n  deterministic sha256 uid from create() survives updates and the\n  portability rationale in that comment block still holds.\n\n- is_datasource() changing its not-found return from 0 to \u0027\u0027 is safe;\n  both callers in builder.py guard with a plain truthiness check.\n\nOne real gap below on test coverage.","commit_id":"d5cef9b2d64382647a3133816c08826ef7f3a490"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"beaaa8630f9d28b5626b5216886d6302f5f3aa35","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":7,"id":"707a7e77_0a7e4f7d","updated":"2026-07-29 03:14:53.000000000","message":"Nothing here is a blocker; the test-coverage point is the only\none I would push for, and it is a one-line assert.","commit_id":"d5cef9b2d64382647a3133816c08826ef7f3a490"}],"grafana_dashboards/grafana/datasource.py":[{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"f0da3d658db297ca80f6b1b0d2071519e9a9a93a","unresolved":true,"context_lines":[{"line_number":84,"context_line":"        res.raise_for_status()"},{"line_number":85,"context_line":"        return res.json()"},{"line_number":86,"context_line":""},{"line_number":87,"context_line":"    def delete(self, datasource_uid):"},{"line_number":88,"context_line":"        \"\"\"Delete a datasource"},{"line_number":89,"context_line":""},{"line_number":90,"context_line":"        :param datasource_uid: uid value of datasource"},{"line_number":91,"context_line":"        :type datasource_uid: str"}],"source_content_type":"text/x-python","patch_set":7,"id":"faa639e1_d4320eec","line":88,"range":{"start_line":87,"start_character":4,"end_line":88,"end_character":30},"updated":"2026-07-29 03:13:47.000000000","message":"delete() and update() have no tests at all ... so their URL changes are\nthe untested part of this patch. The patch updates the mock URLs for\nget(); a matching test_delete/test_update asserting\n/api/datasources/uid/0123456abc would cover the rest cheaply.\n\nSeparately (pre-existing, take it or leave it): delete() never checks\nthe DELETE response, it only infers success from the following get()\nreturning None. Against 13.1.1:\n\n  DELETE /api/datasources/uid/doesnotexist -\u003e 404\n  grafyaml delete(\u0027doesnotexist\u0027)          -\u003e returns successfully\n\nThe verification is vacuous when the uid is absent for any reason. If\nGrafana later retires the uid DELETE route the way it just retired\nthe id ones, this would silently no-op instead of failing, which is\nthe exact failure mode this change is fixing. A res.raise_for_status()\non the delete would surface it.","commit_id":"d5cef9b2d64382647a3133816c08826ef7f3a490"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"4ec539889f2044f32b1f9330a9eb7deb26c1df18","unresolved":false,"context_lines":[{"line_number":84,"context_line":"        res.raise_for_status()"},{"line_number":85,"context_line":"        return res.json()"},{"line_number":86,"context_line":""},{"line_number":87,"context_line":"    def delete(self, datasource_uid):"},{"line_number":88,"context_line":"        \"\"\"Delete a datasource"},{"line_number":89,"context_line":""},{"line_number":90,"context_line":"        :param datasource_uid: uid value of datasource"},{"line_number":91,"context_line":"        :type datasource_uid: str"}],"source_content_type":"text/x-python","patch_set":7,"id":"f97dc790_b66fc09c","line":88,"range":{"start_line":87,"start_character":4,"end_line":88,"end_character":30},"in_reply_to":"faa639e1_d4320eec","updated":"2026-07-30 02:51:08.000000000","message":"Done","commit_id":"d5cef9b2d64382647a3133816c08826ef7f3a490"}],"tests/grafana/test_graphite_datasource.py":[{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"f0da3d658db297ca80f6b1b0d2071519e9a9a93a","unresolved":true,"context_lines":[{"line_number":95,"context_line":"        self.assertFalse(res)"},{"line_number":96,"context_line":""},{"line_number":97,"context_line":"    @requests_mock.Mocker()"},{"line_number":98,"context_line":"    def test_is_datasource_true(self, mock_requests):"},{"line_number":99,"context_line":"        mock_requests.get(\u0027/api/datasources/\u0027, json\u003d[DATASOURCE001])"},{"line_number":100,"context_line":"        res \u003d self.grafana.datasource.is_datasource(\u0027foobar\u0027)"},{"line_number":101,"context_line":"        self.assertTrue(res)"}],"source_content_type":"text/x-python","patch_set":7,"id":"1da4fae1_82adae09","line":101,"range":{"start_line":98,"start_character":5,"end_line":101,"end_character":28},"updated":"2026-07-29 03:13:47.000000000","message":"test_is_datasource_true only asserts assertTrue(res), so it does not\nactually cover the behaviour this change is about. I confirmed by\nmutation: revert this one line\n\n  -                return datasource[\u0027uid\u0027]\n  +                return datasource[\u0027id\u0027]\n\nand all 21 tests in tests/grafana/ + tests/test_builder.py still\npass, because DATASOURCE001 has both a truthy id (1) and a truthy\nuid.\n\nGiven the commit message notes this class of bug slipped through\nbefore, it would be good to pin it down. DATASOURCE001 already grows\na uid in this patch, so it is a one-liner:\n\n  self.assertEqual(res, \u00270123456abc\u0027)","commit_id":"d5cef9b2d64382647a3133816c08826ef7f3a490"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"4ec539889f2044f32b1f9330a9eb7deb26c1df18","unresolved":false,"context_lines":[{"line_number":95,"context_line":"        self.assertFalse(res)"},{"line_number":96,"context_line":""},{"line_number":97,"context_line":"    @requests_mock.Mocker()"},{"line_number":98,"context_line":"    def test_is_datasource_true(self, mock_requests):"},{"line_number":99,"context_line":"        mock_requests.get(\u0027/api/datasources/\u0027, json\u003d[DATASOURCE001])"},{"line_number":100,"context_line":"        res \u003d self.grafana.datasource.is_datasource(\u0027foobar\u0027)"},{"line_number":101,"context_line":"        self.assertTrue(res)"}],"source_content_type":"text/x-python","patch_set":7,"id":"959734a2_3a0bd51d","line":101,"range":{"start_line":98,"start_character":5,"end_line":101,"end_character":28},"in_reply_to":"1da4fae1_82adae09","updated":"2026-07-30 02:51:08.000000000","message":"Done","commit_id":"d5cef9b2d64382647a3133816c08826ef7f3a490"}],"tests/grafana/test_influxdb_datasource.py":[{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"f0da3d658db297ca80f6b1b0d2071519e9a9a93a","unresolved":true,"context_lines":[{"line_number":83,"context_line":"        self.assertEqual(res, [])"},{"line_number":84,"context_line":""},{"line_number":85,"context_line":"    @requests_mock.Mocker()"},{"line_number":86,"context_line":"    def test_is_datasource_empty(self, mock_requests):"},{"line_number":87,"context_line":"        mock_requests.get(\u0027/api/datasources/\u0027, json\u003d[])"},{"line_number":88,"context_line":"        res \u003d self.grafana.datasource.is_datasource(\u0027foobar\u0027)"},{"line_number":89,"context_line":"        self.assertFalse(res)"}],"source_content_type":"text/x-python","patch_set":7,"id":"7a2ef1c7_32a04fa9","line":86,"range":{"start_line":86,"start_character":8,"end_line":86,"end_character":32},"updated":"2026-07-29 03:13:47.000000000","message":"INLINE: tests/grafana/test_graphite_datasource.py:98\n  (same for test_influxdb_datasource.py:98)","commit_id":"d5cef9b2d64382647a3133816c08826ef7f3a490"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"4ec539889f2044f32b1f9330a9eb7deb26c1df18","unresolved":false,"context_lines":[{"line_number":83,"context_line":"        self.assertEqual(res, [])"},{"line_number":84,"context_line":""},{"line_number":85,"context_line":"    @requests_mock.Mocker()"},{"line_number":86,"context_line":"    def test_is_datasource_empty(self, mock_requests):"},{"line_number":87,"context_line":"        mock_requests.get(\u0027/api/datasources/\u0027, json\u003d[])"},{"line_number":88,"context_line":"        res \u003d self.grafana.datasource.is_datasource(\u0027foobar\u0027)"},{"line_number":89,"context_line":"        self.assertFalse(res)"}],"source_content_type":"text/x-python","patch_set":7,"id":"8099581c_6426c80e","line":86,"range":{"start_line":86,"start_character":8,"end_line":86,"end_character":32},"in_reply_to":"7a2ef1c7_32a04fa9","updated":"2026-07-30 02:51:08.000000000","message":"Done","commit_id":"d5cef9b2d64382647a3133816c08826ef7f3a490"}]}
