)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"edc677fb43b7ac13b4d25c196a5e52ae0a69e817","unresolved":true,"context_lines":[{"line_number":7,"context_line":"Pre-test typing cleanup"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"- Fix TODO tag to use the correct username."},{"line_number":10,"context_line":"- Drop mypy (and types-only deps) from test-requirements.txt; install them in"},{"line_number":11,"context_line":"  the tox mypy env instead"},{"line_number":12,"context_line":"- Consolidate from typing imports onto one line per file where flake8 allows."},{"line_number":13,"context_line":"- Rework pyproject.toml: strict mypy, check all of ironicclient/, overrides"},{"line_number":14,"context_line":"  for tests (relaxed defs + disabled noisy error codes); incremental on."},{"line_number":15,"context_line":"- Adjust production code for the stricter checker (generics, list() returns,"},{"line_number":16,"context_line":"  casts / ignores as needed)."},{"line_number":17,"context_line":"- Remove the pre-commit mypy hook. Running mypy there duplicates dependency"},{"line_number":18,"context_line":"  pins and cannot use upper-constraints the way tox does; tox keeps one"},{"line_number":19,"context_line":"  dependency graph for the project and ensures mypy runs in the same gate as"},{"line_number":20,"context_line":"  the rest of CI (see Stephen\u0027s rationale on running mypy from tox)."},{"line_number":21,"context_line":"- Fix codespell typos in release notes and node docstring"},{"line_number":22,"context_line":""},{"line_number":23,"context_line":"Related: https://review.opendev.org/c/openstack/python-ironicclient/+/981228"},{"line_number":24,"context_line":"Change-Id: I723d8c826f4e7e4e96b8ac44cda3a4143c314a11"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":4,"id":"62a89b23_df53e57d","line":21,"range":{"start_line":10,"start_character":0,"end_line":21,"end_character":57},"updated":"2026-04-06 11:05:09.000000000","message":"Can you wrap these at \u003c\u003d 72 chars?","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"e9c6bd7328b8bec14af4c263e4b89ca1425c56f3","unresolved":false,"context_lines":[{"line_number":7,"context_line":"Pre-test typing cleanup"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"- Fix TODO tag to use the correct username."},{"line_number":10,"context_line":"- Drop mypy (and types-only deps) from test-requirements.txt; install them in"},{"line_number":11,"context_line":"  the tox mypy env instead"},{"line_number":12,"context_line":"- Consolidate from typing imports onto one line per file where flake8 allows."},{"line_number":13,"context_line":"- Rework pyproject.toml: strict mypy, check all of ironicclient/, overrides"},{"line_number":14,"context_line":"  for tests (relaxed defs + disabled noisy error codes); incremental on."},{"line_number":15,"context_line":"- Adjust production code for the stricter checker (generics, list() returns,"},{"line_number":16,"context_line":"  casts / ignores as needed)."},{"line_number":17,"context_line":"- Remove the pre-commit mypy hook. Running mypy there duplicates dependency"},{"line_number":18,"context_line":"  pins and cannot use upper-constraints the way tox does; tox keeps one"},{"line_number":19,"context_line":"  dependency graph for the project and ensures mypy runs in the same gate as"},{"line_number":20,"context_line":"  the rest of CI (see Stephen\u0027s rationale on running mypy from tox)."},{"line_number":21,"context_line":"- Fix codespell typos in release notes and node docstring"},{"line_number":22,"context_line":""},{"line_number":23,"context_line":"Related: https://review.opendev.org/c/openstack/python-ironicclient/+/981228"},{"line_number":24,"context_line":"Change-Id: I723d8c826f4e7e4e96b8ac44cda3a4143c314a11"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":4,"id":"948ab159_d192c20d","line":21,"range":{"start_line":10,"start_character":0,"end_line":21,"end_character":57},"in_reply_to":"62a89b23_df53e57d","updated":"2026-04-08 12:05:57.000000000","message":"Done","commit_id":"108406920f9900df8a822490aab555c51bf48525"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"b8fc244229aa443993155fe7dd212480d2b008bc","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":4,"id":"9cf4f644_f5d51b22","updated":"2026-04-06 00:58:02.000000000","message":"Hey @stephenfin@redhat.com\nI implemented a couple of your suggestions here after annotating the complete production level code. Can you please confirm if this looks right, and then I can start type annotating the test code? Thank you!","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"edc677fb43b7ac13b4d25c196a5e52ae0a69e817","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"b6366dff_d794be27","updated":"2026-04-06 11:05:09.000000000","message":"This is looking great. Could of comments inline but they\u0027re all minor improvements to what you\u0027ve already done.","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"d5ded015541a2f4cc9c00571963c796aca45a088","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"97fb86c2_59733fe2","in_reply_to":"9cf4f644_f5d51b22","updated":"2026-04-08 12:07:56.000000000","message":"Done","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"d66c41a89d35eccd4aea103d064680033e68f7aa","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"28baaa4a_a48d862e","updated":"2026-04-08 09:54:43.000000000","message":"-1 just to draw attention to the outstanding question on on where to place deps.\n\n@anandkarancompsci@gmail.com Can you also go through and mark all the comments you think you\u0027ve addressed as `Resolved`, please?","commit_id":"a1adb4cfbe880707fdabd338c54355ff219b15c4"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"8d54fe5616f23bc68aa233338856396db4ff5d17","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"8454ca7b_3bfc6008","updated":"2026-04-08 06:16:18.000000000","message":"Hey @stephenfin@redhat.com, thanks for the review! I have addressed all of your comments here in the latest patch.\n\ncc: @jay@jvf.cc, @cid@gr-oss.io","commit_id":"a1adb4cfbe880707fdabd338c54355ff219b15c4"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"d66c41a89d35eccd4aea103d064680033e68f7aa","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"33e7c92a_36bd7586","in_reply_to":"8454ca7b_3bfc6008","updated":"2026-04-08 09:54:43.000000000","message":"Ack","commit_id":"a1adb4cfbe880707fdabd338c54355ff219b15c4"},{"author":{"_account_id":36770,"name":"cid","display_name":"cid","email":"cid@gr-oss.io","username":"cidelight","status":"@gr-oss upstream: Doing good IRONIC things..."},"change_message_id":"cd9c47a13041f5f2ecf29cf9d3e588058b7ce3eb","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"597ea1a0_5a4740ca","updated":"2026-04-08 13:08:23.000000000","message":"nit: I can see how the commit title could be confusing to someone that did not know that \u0027pre-test\u0027 is alduding to a future work.","commit_id":"fd0e8ee172ad0aadb4d2bbecc3ac27a0129f7faa"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"0bb46e7b9b7d87a23dd30afbf923901ace67874c","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"210936d9_0e3a7c77","in_reply_to":"597ea1a0_5a4740ca","updated":"2026-04-08 13:15:03.000000000","message":"How about this \"Clean up typing setup before annotating tests\"?","commit_id":"fd0e8ee172ad0aadb4d2bbecc3ac27a0129f7faa"}],"ironicclient/common/apiclient/base.py":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"d66c41a89d35eccd4aea103d064680033e68f7aa","unresolved":true,"context_lines":[{"line_number":91,"context_line":"class ManagerProtocol(Protocol):"},{"line_number":92,"context_line":"    \"\"\"Minimal protocol for Resource\u0027s manager dependency.\"\"\""},{"line_number":93,"context_line":""},{"line_number":94,"context_line":"    client: Any"},{"line_number":95,"context_line":""},{"line_number":96,"context_line":""},{"line_number":97,"context_line":"class BaseManager(HookableMixin):"}],"source_content_type":"text/x-python","patch_set":5,"id":"e8ce40db_1d568c22","line":94,"updated":"2026-04-08 09:54:43.000000000","message":"I would have expected mypy to insist on the following here:\n\n```suggestion\n    client: Any\n\n    def get(self, *args: Any, **kwargs: Any) -\u003e Resource: ...\n```\n\nDue to the `self.manager.get` call later on in this file.\n\nYou might want to investigate this and ensure `self.manager` is getting typed correctly (hint: you can use `reveal_type`)","commit_id":"a1adb4cfbe880707fdabd338c54355ff219b15c4"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"a1c898fdda5269374ac66f7eca50de75d05c8915","unresolved":false,"context_lines":[{"line_number":91,"context_line":"class ManagerProtocol(Protocol):"},{"line_number":92,"context_line":"    \"\"\"Minimal protocol for Resource\u0027s manager dependency.\"\"\""},{"line_number":93,"context_line":""},{"line_number":94,"context_line":"    client: Any"},{"line_number":95,"context_line":""},{"line_number":96,"context_line":""},{"line_number":97,"context_line":"class BaseManager(HookableMixin):"}],"source_content_type":"text/x-python","patch_set":5,"id":"63b24471_8a7aecb0","line":94,"in_reply_to":"1e44e0ea_34bca35b","updated":"2026-04-08 13:12:28.000000000","message":"Yeah, your approach is a reasonable one, IMO.","commit_id":"a1adb4cfbe880707fdabd338c54355ff219b15c4"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"e9c6bd7328b8bec14af4c263e4b89ca1425c56f3","unresolved":true,"context_lines":[{"line_number":91,"context_line":"class ManagerProtocol(Protocol):"},{"line_number":92,"context_line":"    \"\"\"Minimal protocol for Resource\u0027s manager dependency.\"\"\""},{"line_number":93,"context_line":""},{"line_number":94,"context_line":"    client: Any"},{"line_number":95,"context_line":""},{"line_number":96,"context_line":""},{"line_number":97,"context_line":"class BaseManager(HookableMixin):"}],"source_content_type":"text/x-python","patch_set":5,"id":"1e44e0ea_34bca35b","line":94,"in_reply_to":"e8ce40db_1d568c22","updated":"2026-04-08 12:05:57.000000000","message":"Investigated adding get() to ManagerProtocol as suggested, but that would require 11 # type: ignore[arg-type] comments across BaseManager and Manager since they don\u0027t implement get. Only their concrete subclasses do. Instead, replaced the existing hasattr guard in Resource.get() with a typed getattr(..., None) annotated as Callable[..., Resource] | None. This gives mypy full visibility at the one call site without forcing the protocol to declare a method that not every manager actually has, and avoids any type: ignore comments, if that sounds good?","commit_id":"a1adb4cfbe880707fdabd338c54355ff219b15c4"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"82224071c4a4e038be6e13ff90e300889fb750b6","unresolved":true,"context_lines":[{"line_number":565,"context_line":"        \"\"\""},{"line_number":566,"context_line":"        # set_loaded() first ... so if we have to bail, we know we tried."},{"line_number":567,"context_line":"        self.set_loaded(True)"},{"line_number":568,"context_line":"        get_fn: Callable[..., Resource] | None \u003d getattr("},{"line_number":569,"context_line":"            self.manager, \"get\", None,"},{"line_number":570,"context_line":"        )"},{"line_number":571,"context_line":"        if get_fn is None:"}],"source_content_type":"text/x-python","patch_set":6,"id":"943f0ae8_fd9f4c75","line":568,"updated":"2026-04-08 12:09:30.000000000","message":"This is what was changed based on the comment above.\n\ncc: @stephenfin@redhat.com\n\nThanks again for reviewing!","commit_id":"fd0e8ee172ad0aadb4d2bbecc3ac27a0129f7faa"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"ab5c01bb07d1530d16dc470714cfee993480de83","unresolved":false,"context_lines":[{"line_number":565,"context_line":"        \"\"\""},{"line_number":566,"context_line":"        # set_loaded() first ... so if we have to bail, we know we tried."},{"line_number":567,"context_line":"        self.set_loaded(True)"},{"line_number":568,"context_line":"        get_fn: Callable[..., Resource] | None \u003d getattr("},{"line_number":569,"context_line":"            self.manager, \"get\", None,"},{"line_number":570,"context_line":"        )"},{"line_number":571,"context_line":"        if get_fn is None:"}],"source_content_type":"text/x-python","patch_set":6,"id":"03ffee4e_244d8a89","line":568,"in_reply_to":"28c12592_23d55563","updated":"2026-04-08 13:16:56.000000000","message":"Thanks boss!","commit_id":"fd0e8ee172ad0aadb4d2bbecc3ac27a0129f7faa"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"a1c898fdda5269374ac66f7eca50de75d05c8915","unresolved":false,"context_lines":[{"line_number":565,"context_line":"        \"\"\""},{"line_number":566,"context_line":"        # set_loaded() first ... so if we have to bail, we know we tried."},{"line_number":567,"context_line":"        self.set_loaded(True)"},{"line_number":568,"context_line":"        get_fn: Callable[..., Resource] | None \u003d getattr("},{"line_number":569,"context_line":"            self.manager, \"get\", None,"},{"line_number":570,"context_line":"        )"},{"line_number":571,"context_line":"        if get_fn is None:"}],"source_content_type":"text/x-python","patch_set":6,"id":"28c12592_23d55563","line":568,"in_reply_to":"943f0ae8_fd9f4c75","updated":"2026-04-08 13:12:28.000000000","message":"looks good to me 👌","commit_id":"fd0e8ee172ad0aadb4d2bbecc3ac27a0129f7faa"}],"ironicclient/common/base.py":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"edc677fb43b7ac13b4d25c196a5e52ae0a69e817","unresolved":true,"context_lines":[{"line_number":39,"context_line":"        return copy.deepcopy(self._info)"},{"line_number":40,"context_line":""},{"line_number":41,"context_line":""},{"line_number":42,"context_line":"ResourceT \u003d TypeVar(\u0027ResourceT\u0027, bound\u003dResource)"},{"line_number":43,"context_line":""},{"line_number":44,"context_line":""},{"line_number":45,"context_line":"def getid(obj: str | Resource) -\u003e str:"}],"source_content_type":"text/x-python","patch_set":4,"id":"dfba6e66_9455de9f","line":42,"updated":"2026-04-06 11:05:09.000000000","message":"```suggestion\nResourceT \u003d TypeVar(\u0027ResourceT\u0027, bound\u003dResource)\nResourceAltT \u003d TypeVar(\u0027ResourceAltT\u0027, bound\u003dResource)\n```","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"904a790e3bf3527e17f0d4d040e610cf38bddb60","unresolved":false,"context_lines":[{"line_number":39,"context_line":"        return copy.deepcopy(self._info)"},{"line_number":40,"context_line":""},{"line_number":41,"context_line":""},{"line_number":42,"context_line":"ResourceT \u003d TypeVar(\u0027ResourceT\u0027, bound\u003dResource)"},{"line_number":43,"context_line":""},{"line_number":44,"context_line":""},{"line_number":45,"context_line":"def getid(obj: str | Resource) -\u003e str:"}],"source_content_type":"text/x-python","patch_set":4,"id":"2dc3ba8c_c3589af7","line":42,"in_reply_to":"dfba6e66_9455de9f","updated":"2026-04-08 12:07:05.000000000","message":"Done","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"edc677fb43b7ac13b4d25c196a5e52ae0a69e817","unresolved":true,"context_lines":[{"line_number":159,"context_line":"            return [data]"},{"line_number":160,"context_line":""},{"line_number":161,"context_line":"        return data"},{"line_number":162,"context_line":""},{"line_number":163,"context_line":"    def _list_pagination("},{"line_number":164,"context_line":"        self,"},{"line_number":165,"context_line":"        url: str,"},{"line_number":166,"context_line":"        response_key: str | None \u003d None,"},{"line_number":167,"context_line":"        obj_class: type[ResourceT] | None \u003d None,"},{"line_number":168,"context_line":"        limit: int | None \u003d None,"},{"line_number":169,"context_line":"        os_ironic_api_version: str | None \u003d None,"},{"line_number":170,"context_line":"        global_request_id: str | None \u003d None,"},{"line_number":171,"context_line":"    ) -\u003e list[ResourceT]:"},{"line_number":172,"context_line":"        \"\"\"Retrieve a list of items."},{"line_number":173,"context_line":""},{"line_number":174,"context_line":"        The Ironic API is configured to return a maximum number of"}],"source_content_type":"text/x-python","patch_set":4,"id":"ae7ec777_7d402983","line":171,"range":{"start_line":162,"start_character":1,"end_line":171,"end_character":25},"updated":"2026-04-06 11:05:09.000000000","message":"I believe if you use `override` you can resolve the issues you are seeing at e.g. https://review.opendev.org/c/openstack/python-ironicclient/+/983360/4/ironicclient/v1/node.py#217\n\n```suggestion\n\n    @override\n    def _list_pagination(\n        self,\n        url: str,\n        response_key: str | None \u003d None,\n        obj_class: None \u003d None,\n        limit: int | None \u003d None,\n        os_ironic_api_version: str | None \u003d None,\n        global_request_id: str | None \u003d None,\n    ) -\u003e list[ResourceT]: ...\n    \n    @override\n    def _list_pagination(\n        self,\n        url: str,\n        response_key: str | None \u003d None,\n        obj_class: type[ResourceT] \u003d ...,\n        limit: int | None \u003d None,\n        os_ironic_api_version: str | None \u003d None,\n        global_request_id: str | None \u003d None,\n    ) -\u003e list[ResourceAltT]: ...\n\n    def _list_pagination(\n        self,\n        url: str,\n        response_key: str | None \u003d None,\n        obj_class: type[ResourceT] | None \u003d None,\n        limit: int | None \u003d None,\n        os_ironic_api_version: str | None \u003d None,\n        global_request_id: str | None \u003d None,\n    ) -\u003e list[ResourceT | ResourceAltT]:\n```\n\nThat says if we don\u0027t give `obj_class` or it\u0027s None, return the bound generic type, else return the type of `obj_class`. I think the second `TypeVar` (`ResourceAltT`) is necessary since the first will already be bound to the generic here.\n\nDitto for `_list` and anything else that takes an `obj_class` parameter.","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"904a790e3bf3527e17f0d4d040e610cf38bddb60","unresolved":false,"context_lines":[{"line_number":159,"context_line":"            return [data]"},{"line_number":160,"context_line":""},{"line_number":161,"context_line":"        return data"},{"line_number":162,"context_line":""},{"line_number":163,"context_line":"    def _list_pagination("},{"line_number":164,"context_line":"        self,"},{"line_number":165,"context_line":"        url: str,"},{"line_number":166,"context_line":"        response_key: str | None \u003d None,"},{"line_number":167,"context_line":"        obj_class: type[ResourceT] | None \u003d None,"},{"line_number":168,"context_line":"        limit: int | None \u003d None,"},{"line_number":169,"context_line":"        os_ironic_api_version: str | None \u003d None,"},{"line_number":170,"context_line":"        global_request_id: str | None \u003d None,"},{"line_number":171,"context_line":"    ) -\u003e list[ResourceT]:"},{"line_number":172,"context_line":"        \"\"\"Retrieve a list of items."},{"line_number":173,"context_line":""},{"line_number":174,"context_line":"        The Ironic API is configured to return a maximum number of"}],"source_content_type":"text/x-python","patch_set":4,"id":"d60fd08f_41a51bcb","line":171,"range":{"start_line":162,"start_character":1,"end_line":171,"end_character":25},"in_reply_to":"ae7ec777_7d402983","updated":"2026-04-08 12:07:05.000000000","message":"Done","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"edc677fb43b7ac13b4d25c196a5e52ae0a69e817","unresolved":true,"context_lines":[{"line_number":226,"context_line":"                    \u0027API response body must be a JSON object; got %s\u0027 %"},{"line_number":227,"context_line":"                    type(body).__name__)"},{"line_number":228,"context_line":"            data \u003d self._format_body_data(body, response_key)"},{"line_number":229,"context_line":"            for obj in data:"},{"line_number":230,"context_line":"                # NOTE(anandkaranubc): Manager does not inherit from"},{"line_number":231,"context_line":"                # apiclient.base.BaseManager, so passing self where"},{"line_number":232,"context_line":"                # Resource.__init__ expects BaseManager is a structural"},{"line_number":233,"context_line":"                # mismatch between the two class hierarchies."},{"line_number":234,"context_line":"                item \u003d obj_class("},{"line_number":235,"context_line":"                    self,  # type: ignore[arg-type]"},{"line_number":236,"context_line":"                    obj,"},{"line_number":237,"context_line":"                    loaded\u003dTrue,"},{"line_number":238,"context_line":"                )"},{"line_number":239,"context_line":"                object_list.append(item)"},{"line_number":240,"context_line":"                object_count +\u003d 1"},{"line_number":241,"context_line":"                if limit and object_count \u003e\u003d limit:"}],"source_content_type":"text/x-python","patch_set":4,"id":"94342dd3_4708fd91","line":238,"range":{"start_line":229,"start_character":28,"end_line":238,"end_character":17},"updated":"2026-04-06 11:05:09.000000000","message":"You could resolve this by creating a Manager protocol that only implements enough to satisfy what we need here. From a quick read, that would be a `.get` method and `.client` attribute (both needed for `Resource.get`)?","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"904a790e3bf3527e17f0d4d040e610cf38bddb60","unresolved":false,"context_lines":[{"line_number":226,"context_line":"                    \u0027API response body must be a JSON object; got %s\u0027 %"},{"line_number":227,"context_line":"                    type(body).__name__)"},{"line_number":228,"context_line":"            data \u003d self._format_body_data(body, response_key)"},{"line_number":229,"context_line":"            for obj in data:"},{"line_number":230,"context_line":"                # NOTE(anandkaranubc): Manager does not inherit from"},{"line_number":231,"context_line":"                # apiclient.base.BaseManager, so passing self where"},{"line_number":232,"context_line":"                # Resource.__init__ expects BaseManager is a structural"},{"line_number":233,"context_line":"                # mismatch between the two class hierarchies."},{"line_number":234,"context_line":"                item \u003d obj_class("},{"line_number":235,"context_line":"                    self,  # type: ignore[arg-type]"},{"line_number":236,"context_line":"                    obj,"},{"line_number":237,"context_line":"                    loaded\u003dTrue,"},{"line_number":238,"context_line":"                )"},{"line_number":239,"context_line":"                object_list.append(item)"},{"line_number":240,"context_line":"                object_count +\u003d 1"},{"line_number":241,"context_line":"                if limit and object_count \u003e\u003d limit:"}],"source_content_type":"text/x-python","patch_set":4,"id":"19d753ac_a60b4d2f","line":238,"range":{"start_line":229,"start_character":28,"end_line":238,"end_character":17},"in_reply_to":"94342dd3_4708fd91","updated":"2026-04-08 12:07:05.000000000","message":"Done","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"edc677fb43b7ac13b4d25c196a5e52ae0a69e817","unresolved":true,"context_lines":[{"line_number":360,"context_line":"        # PATCH/PUT requests may not return a body"},{"line_number":361,"context_line":"        if body:"},{"line_number":362,"context_line":"            # NOTE(anandkaranubc): Manager/BaseManager mismatch (self) and"},{"line_number":363,"context_line":"            # json_request returns dict | list | bytes, not just dict."},{"line_number":364,"context_line":"            return self.resource_class(self, body)  # type: ignore[arg-type]"},{"line_number":365,"context_line":"        return None"},{"line_number":366,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"4d1861fc_ac314c75","line":363,"updated":"2026-04-06 11:05:09.000000000","message":"As above. To resolve the other issue, maybe just use a `cast` (or an isinstance check if you want it to fail early at runtime)?","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"904a790e3bf3527e17f0d4d040e610cf38bddb60","unresolved":false,"context_lines":[{"line_number":360,"context_line":"        # PATCH/PUT requests may not return a body"},{"line_number":361,"context_line":"        if body:"},{"line_number":362,"context_line":"            # NOTE(anandkaranubc): Manager/BaseManager mismatch (self) and"},{"line_number":363,"context_line":"            # json_request returns dict | list | bytes, not just dict."},{"line_number":364,"context_line":"            return self.resource_class(self, body)  # type: ignore[arg-type]"},{"line_number":365,"context_line":"        return None"},{"line_number":366,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"dbc35fc2_c92e7b12","line":363,"in_reply_to":"4d1861fc_ac314c75","updated":"2026-04-08 12:07:05.000000000","message":"Done","commit_id":"108406920f9900df8a822490aab555c51bf48525"}],"ironicclient/common/utils.py":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"edc677fb43b7ac13b4d25c196a5e52ae0a69e817","unresolved":true,"context_lines":[{"line_number":243,"context_line":"        filters.append(\u0027detail\u003dTrue\u0027)"},{"line_number":244,"context_line":"    return filters"},{"line_number":245,"context_line":""},{"line_number":246,"context_line":""},{"line_number":247,"context_line":"# NOTE(anandkaranubc): *args/**kwargs can be narrowed to match"},{"line_number":248,"context_line":"# tempfile.mkdtemp: use keyword-only params (suffix: str | None,"},{"line_number":249,"context_line":"# prefix: str | None, dir: str | None) and call"},{"line_number":250,"context_line":"# mkdtemp(suffix\u003d..., prefix\u003d..., dir\u003d...) for stricter typing."},{"line_number":251,"context_line":"@contextlib.contextmanager"},{"line_number":252,"context_line":"def tempdir("},{"line_number":253,"context_line":"    *args: Any,"},{"line_number":254,"context_line":"    **kwargs: Any,"},{"line_number":255,"context_line":") -\u003e Generator[str, None, None]:"},{"line_number":256,"context_line":"    dirname \u003d tempfile.mkdtemp(*args, **kwargs)"},{"line_number":257,"context_line":"    try:"},{"line_number":258,"context_line":"        yield dirname"}],"source_content_type":"text/x-python","patch_set":4,"id":"9cf56c56_19899df7","line":255,"range":{"start_line":246,"start_character":1,"end_line":255,"end_character":32},"updated":"2026-04-06 11:05:09.000000000","message":"Can we not fix this here by replacing `*args, **kwargs` with the actual parameters expected by `tempfile.mkdtemp`.\n\nAlternatively, we could just drop this in favour of [`tempfile.TemporaryDirectory`](https://docs.python.org/3/library/tempfile.html#tempfile.TemporaryDirectory) which already supports use as a context manager? If so, this should be done in a separate change. Maybe this function needs to be kept for a while also for API stability purposes (though I don\u0027t know if we consider this our public API) but we can definitely deprecate it and replace our own use of it if so.","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"904a790e3bf3527e17f0d4d040e610cf38bddb60","unresolved":false,"context_lines":[{"line_number":243,"context_line":"        filters.append(\u0027detail\u003dTrue\u0027)"},{"line_number":244,"context_line":"    return filters"},{"line_number":245,"context_line":""},{"line_number":246,"context_line":""},{"line_number":247,"context_line":"# NOTE(anandkaranubc): *args/**kwargs can be narrowed to match"},{"line_number":248,"context_line":"# tempfile.mkdtemp: use keyword-only params (suffix: str | None,"},{"line_number":249,"context_line":"# prefix: str | None, dir: str | None) and call"},{"line_number":250,"context_line":"# mkdtemp(suffix\u003d..., prefix\u003d..., dir\u003d...) for stricter typing."},{"line_number":251,"context_line":"@contextlib.contextmanager"},{"line_number":252,"context_line":"def tempdir("},{"line_number":253,"context_line":"    *args: Any,"},{"line_number":254,"context_line":"    **kwargs: Any,"},{"line_number":255,"context_line":") -\u003e Generator[str, None, None]:"},{"line_number":256,"context_line":"    dirname \u003d tempfile.mkdtemp(*args, **kwargs)"},{"line_number":257,"context_line":"    try:"},{"line_number":258,"context_line":"        yield dirname"}],"source_content_type":"text/x-python","patch_set":4,"id":"3a93b225_0a587a11","line":255,"range":{"start_line":246,"start_character":1,"end_line":255,"end_character":32},"in_reply_to":"9cf56c56_19899df7","updated":"2026-04-08 12:07:05.000000000","message":"Done","commit_id":"108406920f9900df8a822490aab555c51bf48525"}],"ironicclient/osc/command.py":[{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"a5ef9df08805533f441d0be2d95926d75b7bd801","unresolved":true,"context_lines":[{"line_number":35,"context_line":"# Return type of cliff/osc-lib Command.get_parser (extends stdlib"},{"line_number":36,"context_line":"# ArgumentParser). Exposed for annotations so get_parser overrides satisfy"},{"line_number":37,"context_line":"# mypy without ignore[override]. cliff._argparse is private; import only here."},{"line_number":38,"context_line":"# TODO(anandkaranubc): Remove this alias once osc-lib publicly exports the"},{"line_number":39,"context_line":"# parser type"},{"line_number":40,"context_line":"CliffArgumentParser \u003d _cliff_argparse.ArgumentParser"},{"line_number":41,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"5ae2e0c1_de620293","line":38,"updated":"2026-04-08 15:56:33.000000000","message":"It has been added here.\n\ncc: @jay@jvf.cc","commit_id":"8a8a0a984985dae0274c9e23e8f4f55815c52e2b"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"599355e86b081e5f60e63e7b7d9295be88f18dcb","unresolved":true,"context_lines":[{"line_number":35,"context_line":"# Return type of cliff/osc-lib Command.get_parser (extends stdlib"},{"line_number":36,"context_line":"# ArgumentParser). Exposed for annotations so get_parser overrides satisfy"},{"line_number":37,"context_line":"# mypy without ignore[override]. cliff._argparse is private; import only here."},{"line_number":38,"context_line":"# TODO(anandkaranubc): Remove this alias once osc-lib publicly exports the"},{"line_number":39,"context_line":"# parser type"},{"line_number":40,"context_line":"CliffArgumentParser \u003d _cliff_argparse.ArgumentParser"},{"line_number":41,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"1d6549e2_b0630b55","line":38,"in_reply_to":"5ae2e0c1_de620293","updated":"2026-04-08 16:11:21.000000000","message":"Per https://review.opendev.org/c/openstack/python-ironicclient/+/983351/comment/5d83ad72_5df94146/, this isn\u0027t entirely accurate but maybe it\u0027s close enough 🤷‍♂️","commit_id":"8a8a0a984985dae0274c9e23e8f4f55815c52e2b"}],"ironicclient/osc/v1/baremetal_volume_target.py":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"edc677fb43b7ac13b4d25c196a5e52ae0a69e817","unresolved":true,"context_lines":[{"line_number":156,"context_line":"        volume_target \u003d baremetal_client.volume_target.get("},{"line_number":157,"context_line":"            parsed_args.volume_target, fields\u003dfields)._info"},{"line_number":158,"context_line":""},{"line_number":159,"context_line":"        volume_target.pop(\"links\", None)"},{"line_number":160,"context_line":"        return cast("},{"line_number":161,"context_line":"            tuple[tuple[str, ...], tuple[Any, ...]],"},{"line_number":162,"context_line":"            tuple(zip(*sorted(volume_target.items()))),"},{"line_number":163,"context_line":"        )"},{"line_number":164,"context_line":""},{"line_number":165,"context_line":""},{"line_number":166,"context_line":"class ListBaremetalVolumeTarget(command.Lister):"}],"source_content_type":"text/x-python","patch_set":4,"id":"e9c1cf1c_ed387127","line":163,"range":{"start_line":159,"start_character":40,"end_line":163,"end_character":9},"updated":"2026-04-06 11:05:09.000000000","message":"I believe you can resolve this like by exploding the returned zip:\n\n```suggestion\n        volume_target.pop(\"links\", None)\n        columns, data \u003d zip(*sorted(volume_target.items()))\n        return columns, data\n```","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"904a790e3bf3527e17f0d4d040e610cf38bddb60","unresolved":false,"context_lines":[{"line_number":156,"context_line":"        volume_target \u003d baremetal_client.volume_target.get("},{"line_number":157,"context_line":"            parsed_args.volume_target, fields\u003dfields)._info"},{"line_number":158,"context_line":""},{"line_number":159,"context_line":"        volume_target.pop(\"links\", None)"},{"line_number":160,"context_line":"        return cast("},{"line_number":161,"context_line":"            tuple[tuple[str, ...], tuple[Any, ...]],"},{"line_number":162,"context_line":"            tuple(zip(*sorted(volume_target.items()))),"},{"line_number":163,"context_line":"        )"},{"line_number":164,"context_line":""},{"line_number":165,"context_line":""},{"line_number":166,"context_line":"class ListBaremetalVolumeTarget(command.Lister):"}],"source_content_type":"text/x-python","patch_set":4,"id":"ac6e2539_f5b3afd5","line":163,"range":{"start_line":159,"start_character":40,"end_line":163,"end_character":9},"in_reply_to":"e9c1cf1c_ed387127","updated":"2026-04-08 12:07:05.000000000","message":"Done","commit_id":"108406920f9900df8a822490aab555c51bf48525"}],"ironicclient/v1/node.py":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"edc677fb43b7ac13b4d25c196a5e52ae0a69e817","unresolved":true,"context_lines":[{"line_number":203,"context_line":""},{"line_number":204,"context_line":"        path \u003d \"%s/volume/connectors\" % node_id"},{"line_number":205,"context_line":"        if filters:"},{"line_number":206,"context_line":"            path +\u003d \u0027?\u0027 + \u0027\u0026\u0027.join(filters)"},{"line_number":207,"context_line":"        # NOTE(anandkaranubc): obj_class is VolumeConnector, not Node;"},{"line_number":208,"context_line":"        # Manager[Node]._list expects type[Node] for obj_class and"},{"line_number":209,"context_line":"        # returns list[Node], but at runtime produces VolumeConnector"},{"line_number":210,"context_line":"        # objects."},{"line_number":211,"context_line":"        vc \u003d volume_connector.VolumeConnector"},{"line_number":212,"context_line":"        if limit is None:"},{"line_number":213,"context_line":"            return self._list(  # type: ignore[return-value]"},{"line_number":214,"context_line":"                self._path(path), response_key\u003d\"connectors\","},{"line_number":215,"context_line":"                obj_class\u003dvc,  # type: ignore[arg-type]"},{"line_number":216,"context_line":"                os_ironic_api_version\u003dos_ironic_api_version,"},{"line_number":217,"context_line":"                global_request_id\u003dglobal_request_id,"},{"line_number":218,"context_line":"            )"},{"line_number":219,"context_line":"        else:"},{"line_number":220,"context_line":"            return self._list_pagination(  # type: ignore[return-value]"}],"source_content_type":"text/x-python","patch_set":4,"id":"5004790f_f6c51263","line":217,"range":{"start_line":206,"start_character":43,"end_line":217,"end_character":52},"updated":"2026-04-06 11:05:09.000000000","message":"You can fix this with an overload. Have left a comment on the class.","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"904a790e3bf3527e17f0d4d040e610cf38bddb60","unresolved":false,"context_lines":[{"line_number":203,"context_line":""},{"line_number":204,"context_line":"        path \u003d \"%s/volume/connectors\" % node_id"},{"line_number":205,"context_line":"        if filters:"},{"line_number":206,"context_line":"            path +\u003d \u0027?\u0027 + \u0027\u0026\u0027.join(filters)"},{"line_number":207,"context_line":"        # NOTE(anandkaranubc): obj_class is VolumeConnector, not Node;"},{"line_number":208,"context_line":"        # Manager[Node]._list expects type[Node] for obj_class and"},{"line_number":209,"context_line":"        # returns list[Node], but at runtime produces VolumeConnector"},{"line_number":210,"context_line":"        # objects."},{"line_number":211,"context_line":"        vc \u003d volume_connector.VolumeConnector"},{"line_number":212,"context_line":"        if limit is None:"},{"line_number":213,"context_line":"            return self._list(  # type: ignore[return-value]"},{"line_number":214,"context_line":"                self._path(path), response_key\u003d\"connectors\","},{"line_number":215,"context_line":"                obj_class\u003dvc,  # type: ignore[arg-type]"},{"line_number":216,"context_line":"                os_ironic_api_version\u003dos_ironic_api_version,"},{"line_number":217,"context_line":"                global_request_id\u003dglobal_request_id,"},{"line_number":218,"context_line":"            )"},{"line_number":219,"context_line":"        else:"},{"line_number":220,"context_line":"            return self._list_pagination(  # type: ignore[return-value]"}],"source_content_type":"text/x-python","patch_set":4,"id":"0256e479_6d74d098","line":217,"range":{"start_line":206,"start_character":43,"end_line":217,"end_character":52},"in_reply_to":"5004790f_f6c51263","updated":"2026-04-08 12:07:05.000000000","message":"Acknowledged","commit_id":"108406920f9900df8a822490aab555c51bf48525"}],"test-requirements.txt":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"edc677fb43b7ac13b4d25c196a5e52ae0a69e817","unresolved":true,"context_lines":[{"line_number":8,"context_line":"ddt\u003e\u003d1.0.1 # MIT"},{"line_number":9,"context_line":"python-openstackclient\u003e\u003d3.12.0 # Apache-2.0"},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"# Type checking"},{"line_number":12,"context_line":"mypy\u003e\u003d1.0.0 # MIT"},{"line_number":13,"context_line":"types-PyYAML # Apache-2.0"},{"line_number":14,"context_line":"types-requests # Apache-2.0"}],"source_content_type":"text/plain","patch_set":4,"id":"e28a2572_22903f19","line":14,"range":{"start_line":11,"start_character":0,"end_line":14,"end_character":27},"updated":"2026-04-06 11:05:09.000000000","message":"The commit message said you\u0027d moved these to `tox.ini`? (which is the correct thing to do, IMO). Could you do that?","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"d66c41a89d35eccd4aea103d064680033e68f7aa","unresolved":true,"context_lines":[{"line_number":8,"context_line":"ddt\u003e\u003d1.0.1 # MIT"},{"line_number":9,"context_line":"python-openstackclient\u003e\u003d3.12.0 # Apache-2.0"},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"# Type checking"},{"line_number":12,"context_line":"mypy\u003e\u003d1.0.0 # MIT"},{"line_number":13,"context_line":"types-PyYAML # Apache-2.0"},{"line_number":14,"context_line":"types-requests # Apache-2.0"}],"source_content_type":"text/plain","patch_set":4,"id":"fd1b5876_10bd5e61","line":14,"range":{"start_line":11,"start_character":0,"end_line":14,"end_character":27},"in_reply_to":"a5e6ae96_7a028443","updated":"2026-04-08 09:54:43.000000000","message":"Just to be clear, you\u0027ll either need to drop the requirements from here or you need to drop this line from the commit message:\n\n```\n- Drop mypy (and types-only deps) from\n  test-requirements.txt; install them in the tox\n  mypy env instead.\n```\n\nOne or the other, because currently they say the opposing things.","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"cea368ec2f4cdb5f3c43d2ecbacb52056e5bedf7","unresolved":true,"context_lines":[{"line_number":8,"context_line":"ddt\u003e\u003d1.0.1 # MIT"},{"line_number":9,"context_line":"python-openstackclient\u003e\u003d3.12.0 # Apache-2.0"},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"# Type checking"},{"line_number":12,"context_line":"mypy\u003e\u003d1.0.0 # MIT"},{"line_number":13,"context_line":"types-PyYAML # Apache-2.0"},{"line_number":14,"context_line":"types-requests # Apache-2.0"}],"source_content_type":"text/plain","patch_set":4,"id":"a5e6ae96_7a028443","line":14,"range":{"start_line":11,"start_character":0,"end_line":14,"end_character":27},"in_reply_to":"e28a2572_22903f19","updated":"2026-04-08 06:14:37.000000000","message":"I guess @jay@jvf.cc will be able to better answer this. Thank you!","commit_id":"108406920f9900df8a822490aab555c51bf48525"},{"author":{"_account_id":38828,"name":"Karan Anand","display_name":"Karan","email":"anandkarancompsci@gmail.com","username":"anandkaranubc"},"change_message_id":"904a790e3bf3527e17f0d4d040e610cf38bddb60","unresolved":false,"context_lines":[{"line_number":8,"context_line":"ddt\u003e\u003d1.0.1 # MIT"},{"line_number":9,"context_line":"python-openstackclient\u003e\u003d3.12.0 # Apache-2.0"},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"# Type checking"},{"line_number":12,"context_line":"mypy\u003e\u003d1.0.0 # MIT"},{"line_number":13,"context_line":"types-PyYAML # Apache-2.0"},{"line_number":14,"context_line":"types-requests # Apache-2.0"}],"source_content_type":"text/plain","patch_set":4,"id":"3b021e42_bff12ca0","line":14,"range":{"start_line":11,"start_character":0,"end_line":14,"end_character":27},"in_reply_to":"fd1b5876_10bd5e61","updated":"2026-04-08 12:07:05.000000000","message":"Done","commit_id":"108406920f9900df8a822490aab555c51bf48525"}]}
