)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":14228,"name":"Clif Houck","email":"me@clifhouck.com","username":"clif_h"},"change_message_id":"0dcf3ad5ce747b30e4bc6e66a59c1c8d97cc8cd0","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"2531efd3_73105f22","updated":"2026-01-23 18:20:20.000000000","message":"LGTM if tests pass","commit_id":"32b405b06f48d1d0225b7f9e7923daca2cba5d86"},{"author":{"_account_id":14228,"name":"Clif Houck","email":"me@clifhouck.com","username":"clif_h"},"change_message_id":"6dcefc39fc9917120e91cb2127424d59998205af","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"cd263251_dbde8157","updated":"2026-02-17 19:54:33.000000000","message":"-1 mainly for keeping the `TraitAction` keys constants with the class definition.","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"ce18aaa8373230367206846d0c8a41c1a5d280cb","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"f2e46f5b_a2a8803d","updated":"2026-02-16 23:19:28.000000000","message":"Recheck","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"e80c4e860e5c952c685a126fea080bf20620618e","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"d0ec6eb5_6afa25c4","updated":"2026-02-13 18:26:37.000000000","message":"Recheck metal3 issues","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"0c71738c5bd671c3020234443ef2bf2458f27779","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"5ff7526a_b391ac74","in_reply_to":"cd263251_dbde8157","updated":"2026-02-18 15:21:13.000000000","message":"They are. They\u0027re actually more tied to the class definition now than free form strings.","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":14228,"name":"Clif Houck","email":"me@clifhouck.com","username":"clif_h"},"change_message_id":"4b5e33da24cc980f3299f2561fee1cf6cd0b7d6d","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"0310906e_f9dfdb2a","updated":"2026-02-23 16:02:41.000000000","message":"LGTM, thank you!","commit_id":"eed81c73af6e3c2faef8f50b137b4652e7a9ca13"}],"ironic/common/trait_based_networking/base.py":[{"author":{"_account_id":14228,"name":"Clif Houck","email":"me@clifhouck.com","username":"clif_h"},"change_message_id":"6dcefc39fc9917120e91cb2127424d59998205af","unresolved":true,"context_lines":[{"line_number":251,"context_line":"    \"\"\""},{"line_number":252,"context_line":"    trait_name: str"},{"line_number":253,"context_line":"    action: Actions"},{"line_number":254,"context_line":"    filter: \u0027FilterExpression\u0027"},{"line_number":255,"context_line":"    min_count: int | None \u003d None"},{"line_number":256,"context_line":"    max_count: int | None \u003d None"},{"line_number":257,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"bfdaf15f_e209ee69","line":254,"updated":"2026-02-17 19:54:33.000000000","message":"Why is this a string and not just the bare type name?","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"38244a4a44278cadc5a74f3d4ff355fb8c3c92ac","unresolved":true,"context_lines":[{"line_number":251,"context_line":"    \"\"\""},{"line_number":252,"context_line":"    trait_name: str"},{"line_number":253,"context_line":"    action: Actions"},{"line_number":254,"context_line":"    filter: \u0027FilterExpression\u0027"},{"line_number":255,"context_line":"    min_count: int | None \u003d None"},{"line_number":256,"context_line":"    max_count: int | None \u003d None"},{"line_number":257,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"c0771724_99c83f38","line":254,"in_reply_to":"bfdaf15f_e209ee69","updated":"2026-02-18 15:20:11.000000000","message":"That\u0027s how you do forward declarations of types in Python. The type is defined below this one.","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":14228,"name":"Clif Houck","email":"me@clifhouck.com","username":"clif_h"},"change_message_id":"4fc21eca1c3bfef20be49263e7fc7396d289ebf5","unresolved":false,"context_lines":[{"line_number":251,"context_line":"    \"\"\""},{"line_number":252,"context_line":"    trait_name: str"},{"line_number":253,"context_line":"    action: Actions"},{"line_number":254,"context_line":"    filter: \u0027FilterExpression\u0027"},{"line_number":255,"context_line":"    min_count: int | None \u003d None"},{"line_number":256,"context_line":"    max_count: int | None \u003d None"},{"line_number":257,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"0ea55e38_be7b09fa","line":254,"in_reply_to":"c0771724_99c83f38","updated":"2026-02-18 15:36:08.000000000","message":"Done","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"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":"263009082e4c4096a6647007eba37d39722bbf15","unresolved":true,"context_lines":[{"line_number":379,"context_line":""},{"line_number":380,"context_line":"    @abstractmethod"},{"line_number":381,"context_line":"    def portlike_uuid(self):"},{"line_number":382,"context_line":"        ..."},{"line_number":383,"context_line":""},{"line_number":384,"context_line":"    @abstractmethod"},{"line_number":385,"context_line":"    def get_portlike_object(self, task):"}],"source_content_type":"text/x-python","patch_set":2,"id":"0bd883ef_a089a89d","line":382,"range":{"start_line":382,"start_character":0,"end_line":382,"end_character":11},"updated":"2026-02-16 23:08:42.000000000","message":"This is cool. Is it the same as using `pass` in this context?","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"c13ee27b3e9b1d3fb80ade4e5bfda65728c151cb","unresolved":true,"context_lines":[{"line_number":379,"context_line":""},{"line_number":380,"context_line":"    @abstractmethod"},{"line_number":381,"context_line":"    def portlike_uuid(self):"},{"line_number":382,"context_line":"        ..."},{"line_number":383,"context_line":""},{"line_number":384,"context_line":"    @abstractmethod"},{"line_number":385,"context_line":"    def get_portlike_object(self, task):"}],"source_content_type":"text/x-python","patch_set":2,"id":"c012087d_7055a4fb","line":382,"range":{"start_line":382,"start_character":0,"end_line":382,"end_character":11},"in_reply_to":"0bd883ef_a089a89d","updated":"2026-02-16 23:19:08.000000000","message":"I don’t think so. I think it’s just for abstract methods.","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"38244a4a44278cadc5a74f3d4ff355fb8c3c92ac","unresolved":true,"context_lines":[{"line_number":379,"context_line":""},{"line_number":380,"context_line":"    @abstractmethod"},{"line_number":381,"context_line":"    def portlike_uuid(self):"},{"line_number":382,"context_line":"        ..."},{"line_number":383,"context_line":""},{"line_number":384,"context_line":"    @abstractmethod"},{"line_number":385,"context_line":"    def get_portlike_object(self, task):"}],"source_content_type":"text/x-python","patch_set":2,"id":"a5938408_ed564e86","line":382,"range":{"start_line":382,"start_character":0,"end_line":382,"end_character":11},"in_reply_to":"5ac912d2_ebce1606","updated":"2026-02-18 15:20:11.000000000","message":"https://docs.python.org/3/library/abc.html#abc.abstractmethod `...` is used in their examples.","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":14228,"name":"Clif Houck","email":"me@clifhouck.com","username":"clif_h"},"change_message_id":"4fc21eca1c3bfef20be49263e7fc7396d289ebf5","unresolved":false,"context_lines":[{"line_number":379,"context_line":""},{"line_number":380,"context_line":"    @abstractmethod"},{"line_number":381,"context_line":"    def portlike_uuid(self):"},{"line_number":382,"context_line":"        ..."},{"line_number":383,"context_line":""},{"line_number":384,"context_line":"    @abstractmethod"},{"line_number":385,"context_line":"    def get_portlike_object(self, task):"}],"source_content_type":"text/x-python","patch_set":2,"id":"9b60d434_50643050","line":382,"range":{"start_line":382,"start_character":0,"end_line":382,"end_character":11},"in_reply_to":"a5938408_ed564e86","updated":"2026-02-18 15:36:08.000000000","message":"Done","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":14228,"name":"Clif Houck","email":"me@clifhouck.com","username":"clif_h"},"change_message_id":"6dcefc39fc9917120e91cb2127424d59998205af","unresolved":true,"context_lines":[{"line_number":379,"context_line":""},{"line_number":380,"context_line":"    @abstractmethod"},{"line_number":381,"context_line":"    def portlike_uuid(self):"},{"line_number":382,"context_line":"        ..."},{"line_number":383,"context_line":""},{"line_number":384,"context_line":"    @abstractmethod"},{"line_number":385,"context_line":"    def get_portlike_object(self, task):"}],"source_content_type":"text/x-python","patch_set":2,"id":"5ac912d2_ebce1606","line":382,"range":{"start_line":382,"start_character":0,"end_line":382,"end_character":11},"in_reply_to":"c012087d_7055a4fb","updated":"2026-02-17 19:54:33.000000000","message":"https://docs.python.org/3/library/constants.html#Ellipsis \"an object frequently used to indicate that something is omitted\". It\u0027s not entirely clear to me what the best practice is for the body of an abstractmethod. I\u0027ve seen things like `pass` or `raise NotImplemented` but `Ellipsis` or `...` is new to me.","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"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":"3cf78f65161a7bdd6605f04c80ec4568a641d639","unresolved":true,"context_lines":[{"line_number":382,"context_line":"    @classmethod"},{"line_number":383,"context_line":"    def from_vif_info(cls, vif_info):"},{"line_number":384,"context_line":"        \"\"\"Helper method to create Networks from vif_info dictionaries\"\"\""},{"line_number":385,"context_line":"        return cls(vif_info[\u0027id\u0027], # vif_info is guaranteed to have \u0027id\u0027."},{"line_number":386,"context_line":"                       vif_info.get(\u0027name\u0027),"},{"line_number":387,"context_line":"                       vif_info.get(\u0027tags\u0027))"},{"line_number":388,"context_line":""},{"line_number":389,"context_line":""},{"line_number":390,"context_line":"@dataclass(frozen\u003dTrue)"},{"line_number":391,"context_line":"class RenderedAction:"}],"source_content_type":"text/x-python","patch_set":3,"id":"2aa0ff63_df6509d8","line":388,"range":{"start_line":385,"start_character":15,"end_line":388,"end_character":0},"updated":"2026-02-23 16:44:11.000000000","message":"nit: I\u0027m surprised this did not raise linter error because of the identation.","commit_id":"eed81c73af6e3c2faef8f50b137b4652e7a9ca13"},{"author":{"_account_id":11655,"name":"Julia Kreger","email":"juliaashleykreger@gmail.com","username":"jkreger","status":"Flying to the moon with a Jetpack!"},"change_message_id":"107eecb8ece3780276c409fe589dd8e95adffdcf","unresolved":true,"context_lines":[{"line_number":382,"context_line":"    @classmethod"},{"line_number":383,"context_line":"    def from_vif_info(cls, vif_info):"},{"line_number":384,"context_line":"        \"\"\"Helper method to create Networks from vif_info dictionaries\"\"\""},{"line_number":385,"context_line":"        return cls(vif_info[\u0027id\u0027], # vif_info is guaranteed to have \u0027id\u0027."},{"line_number":386,"context_line":"                       vif_info.get(\u0027name\u0027),"},{"line_number":387,"context_line":"                       vif_info.get(\u0027tags\u0027))"},{"line_number":388,"context_line":""},{"line_number":389,"context_line":""},{"line_number":390,"context_line":"@dataclass(frozen\u003dTrue)"},{"line_number":391,"context_line":"class RenderedAction:"}],"source_content_type":"text/x-python","patch_set":3,"id":"22d01c59_27605344","line":388,"range":{"start_line":385,"start_character":15,"end_line":388,"end_character":0},"in_reply_to":"2aa0ff63_df6509d8","updated":"2026-02-24 18:09:08.000000000","message":"Wow, yeah. I guess technically it is still valid as well.","commit_id":"eed81c73af6e3c2faef8f50b137b4652e7a9ca13"}],"ironic/common/trait_based_networking/config_file.py":[{"author":{"_account_id":14228,"name":"Clif Houck","email":"me@clifhouck.com","username":"clif_h"},"change_message_id":"6dcefc39fc9917120e91cb2127424d59998205af","unresolved":true,"context_lines":[{"line_number":64,"context_line":"                       \"actions\"))"},{"line_number":65,"context_line":"                valid \u003d False"},{"line_number":66,"context_line":"                continue"},{"line_number":67,"context_line":"            ALL_KEYS \u003d ["},{"line_number":68,"context_line":"                x.name for x in dataclasses.fields(base.TraitAction)"},{"line_number":69,"context_line":"                if x.name !\u003d \"trait_name\""},{"line_number":70,"context_line":"            ]"},{"line_number":71,"context_line":"            NECESSARY_KEYS \u003d ["},{"line_number":72,"context_line":"                x.name for x in dataclasses.fields(base.TraitAction)"},{"line_number":73,"context_line":"                if x.default \u003d\u003d dataclasses.MISSING and x.name !\u003d \"trait_name\""},{"line_number":74,"context_line":"            ]"},{"line_number":75,"context_line":"            for trait_action in trait_members[\u0027actions\u0027]:"},{"line_number":76,"context_line":"                # Check necessary keys are present."},{"line_number":77,"context_line":"                for n in NECESSARY_KEYS:"}],"source_content_type":"text/x-python","patch_set":2,"id":"fa2eb8e3_de366e61","line":74,"range":{"start_line":67,"start_character":1,"end_line":74,"end_character":13},"updated":"2026-02-17 19:54:33.000000000","message":"IMO these constants should stick with the class definition. I realize they\u0027re only used here but they are tightly coupled with the class, even in this form, as they are wholly derived from the class definition.","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"a3da7fd2636451d9191cb035c22f7213ade1502d","unresolved":false,"context_lines":[{"line_number":64,"context_line":"                       \"actions\"))"},{"line_number":65,"context_line":"                valid \u003d False"},{"line_number":66,"context_line":"                continue"},{"line_number":67,"context_line":"            ALL_KEYS \u003d ["},{"line_number":68,"context_line":"                x.name for x in dataclasses.fields(base.TraitAction)"},{"line_number":69,"context_line":"                if x.name !\u003d \"trait_name\""},{"line_number":70,"context_line":"            ]"},{"line_number":71,"context_line":"            NECESSARY_KEYS \u003d ["},{"line_number":72,"context_line":"                x.name for x in dataclasses.fields(base.TraitAction)"},{"line_number":73,"context_line":"                if x.default \u003d\u003d dataclasses.MISSING and x.name !\u003d \"trait_name\""},{"line_number":74,"context_line":"            ]"},{"line_number":75,"context_line":"            for trait_action in trait_members[\u0027actions\u0027]:"},{"line_number":76,"context_line":"                # Check necessary keys are present."},{"line_number":77,"context_line":"                for n in NECESSARY_KEYS:"}],"source_content_type":"text/x-python","patch_set":2,"id":"2f603e51_9d199c0b","line":74,"range":{"start_line":67,"start_character":1,"end_line":74,"end_character":13},"in_reply_to":"0fa32acd_2f55665c","updated":"2026-02-21 04:20:54.000000000","message":"Done","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":14228,"name":"Clif Houck","email":"me@clifhouck.com","username":"clif_h"},"change_message_id":"4fc21eca1c3bfef20be49263e7fc7396d289ebf5","unresolved":true,"context_lines":[{"line_number":64,"context_line":"                       \"actions\"))"},{"line_number":65,"context_line":"                valid \u003d False"},{"line_number":66,"context_line":"                continue"},{"line_number":67,"context_line":"            ALL_KEYS \u003d ["},{"line_number":68,"context_line":"                x.name for x in dataclasses.fields(base.TraitAction)"},{"line_number":69,"context_line":"                if x.name !\u003d \"trait_name\""},{"line_number":70,"context_line":"            ]"},{"line_number":71,"context_line":"            NECESSARY_KEYS \u003d ["},{"line_number":72,"context_line":"                x.name for x in dataclasses.fields(base.TraitAction)"},{"line_number":73,"context_line":"                if x.default \u003d\u003d dataclasses.MISSING and x.name !\u003d \"trait_name\""},{"line_number":74,"context_line":"            ]"},{"line_number":75,"context_line":"            for trait_action in trait_members[\u0027actions\u0027]:"},{"line_number":76,"context_line":"                # Check necessary keys are present."},{"line_number":77,"context_line":"                for n in NECESSARY_KEYS:"}],"source_content_type":"text/x-python","patch_set":2,"id":"0fa32acd_2f55665c","line":74,"range":{"start_line":67,"start_character":1,"end_line":74,"end_character":13},"in_reply_to":"56317dfe_0cbba6f2","updated":"2026-02-18 15:36:08.000000000","message":"I\u0027m fine with derivation, but I still believe `ALL_KEYS` and `NECESSARY_KEYS` should live under `TraitAction`. Other code may be interested in knowing or checking which keys are necessary and which are not.","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"38244a4a44278cadc5a74f3d4ff355fb8c3c92ac","unresolved":true,"context_lines":[{"line_number":64,"context_line":"                       \"actions\"))"},{"line_number":65,"context_line":"                valid \u003d False"},{"line_number":66,"context_line":"                continue"},{"line_number":67,"context_line":"            ALL_KEYS \u003d ["},{"line_number":68,"context_line":"                x.name for x in dataclasses.fields(base.TraitAction)"},{"line_number":69,"context_line":"                if x.name !\u003d \"trait_name\""},{"line_number":70,"context_line":"            ]"},{"line_number":71,"context_line":"            NECESSARY_KEYS \u003d ["},{"line_number":72,"context_line":"                x.name for x in dataclasses.fields(base.TraitAction)"},{"line_number":73,"context_line":"                if x.default \u003d\u003d dataclasses.MISSING and x.name !\u003d \"trait_name\""},{"line_number":74,"context_line":"            ]"},{"line_number":75,"context_line":"            for trait_action in trait_members[\u0027actions\u0027]:"},{"line_number":76,"context_line":"                # Check necessary keys are present."},{"line_number":77,"context_line":"                for n in NECESSARY_KEYS:"}],"source_content_type":"text/x-python","patch_set":2,"id":"56317dfe_0cbba6f2","line":74,"range":{"start_line":67,"start_character":1,"end_line":74,"end_character":13},"in_reply_to":"fa2eb8e3_de366e61","updated":"2026-02-18 15:20:11.000000000","message":"They\u0027re duplicated from the field definitions and easy to get wrong. So my change is to make them come automatically from the definition of the class instead of free form strings which don\u0027t even match the `__init__()` params. Felt like that was a better approach no?","commit_id":"e7516d759ad509ccba5b5449838b0f06f7d3a43b"}]}
