)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"5d5e0bb4fe2bb541804133aeab7de8b25735ae6d","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"3d240f55_6cc31e05","updated":"2026-05-08 16:13:40.000000000","message":"This has been here long enough and has the requisite 2 +2s","commit_id":"fa22c39de3e73c8c771edbc8eb2848d779fd8f8c"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"3ff291a6758208220f8a82c7d100f87aa3588ea7","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":3,"id":"ce85f2c2_b8754f4a","updated":"2026-03-12 18:08:33.000000000","message":"im ok with this but i want to hear what other think about the typeing in genreal.\n\ni think its good at catch letent bug so i generally suprpot typed python","commit_id":"fa22c39de3e73c8c771edbc8eb2848d779fd8f8c"}],"hacking/config.py":[{"author":{"_account_id":13252,"name":"Dr. Jens Harbott","display_name":"Jens Harbott (frickler)","email":"frickler@offenerstapel.de","username":"jrosenboom"},"change_message_id":"9ae48fc63741dc4634d11adacd6c696f732b9896","unresolved":true,"context_lines":[{"line_number":17,"context_line":""},{"line_number":18,"context_line":"class Config:"},{"line_number":19,"context_line":"    def __init__("},{"line_number":20,"context_line":"        self, default_section: str, tox_file: str \u003d \u0027tox.ini\u0027"},{"line_number":21,"context_line":"    ) -\u003e None:"},{"line_number":22,"context_line":"        conf \u003d configparser.RawConfigParser()"},{"line_number":23,"context_line":"        conf.read(tox_file)"}],"source_content_type":"text/x-python","patch_set":2,"id":"0b3b4fa0_9ad50d26","line":20,"updated":"2026-03-11 18:41:05.000000000","message":"why are you dropping the `None` default?","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"239d9381596d311038839186a03d2a9323620db1","unresolved":false,"context_lines":[{"line_number":17,"context_line":""},{"line_number":18,"context_line":"class Config:"},{"line_number":19,"context_line":"    def __init__("},{"line_number":20,"context_line":"        self, default_section: str, tox_file: str \u003d \u0027tox.ini\u0027"},{"line_number":21,"context_line":"    ) -\u003e None:"},{"line_number":22,"context_line":"        conf \u003d configparser.RawConfigParser()"},{"line_number":23,"context_line":"        conf.read(tox_file)"}],"source_content_type":"text/x-python","patch_set":2,"id":"04fda1b2_705219cf","line":20,"in_reply_to":"04c792c5_2c1077b7","updated":"2026-03-11 21:33:28.000000000","message":"Done","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"3ff291a6758208220f8a82c7d100f87aa3588ea7","unresolved":false,"context_lines":[{"line_number":17,"context_line":""},{"line_number":18,"context_line":"class Config:"},{"line_number":19,"context_line":"    def __init__("},{"line_number":20,"context_line":"        self, default_section: str, tox_file: str \u003d \u0027tox.ini\u0027"},{"line_number":21,"context_line":"    ) -\u003e None:"},{"line_number":22,"context_line":"        conf \u003d configparser.RawConfigParser()"},{"line_number":23,"context_line":"        conf.read(tox_file)"}],"source_content_type":"text/x-python","patch_set":2,"id":"ff6ca20a_0324de82","line":20,"in_reply_to":"04fda1b2_705219cf","updated":"2026-03-12 18:08:33.000000000","message":"ack its noted in the commti so im good with that.\nwhen we relesase the next version im not sure if we will need to do a major version bump to refect that change but given it woudl not work before it proably fine","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"ff8ed013d17eb3034afd9a08e30296071ff9043c","unresolved":true,"context_lines":[{"line_number":17,"context_line":""},{"line_number":18,"context_line":"class Config:"},{"line_number":19,"context_line":"    def __init__("},{"line_number":20,"context_line":"        self, default_section: str, tox_file: str \u003d \u0027tox.ini\u0027"},{"line_number":21,"context_line":"    ) -\u003e None:"},{"line_number":22,"context_line":"        conf \u003d configparser.RawConfigParser()"},{"line_number":23,"context_line":"        conf.read(tox_file)"}],"source_content_type":"text/x-python","patch_set":2,"id":"dae59d2c_81214706","line":20,"in_reply_to":"0b3b4fa0_9ad50d26","updated":"2026-03-11 19:22:48.000000000","message":"no th9is is changign form a keyword arg to a postional and mkaing the default requried but later in get its till optional so this looks like a mistake to me","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"046f21f28fc431d1b7c9c72fa2b4441d26e32d4e","unresolved":true,"context_lines":[{"line_number":17,"context_line":""},{"line_number":18,"context_line":"class Config:"},{"line_number":19,"context_line":"    def __init__("},{"line_number":20,"context_line":"        self, default_section: str, tox_file: str \u003d \u0027tox.ini\u0027"},{"line_number":21,"context_line":"    ) -\u003e None:"},{"line_number":22,"context_line":"        conf \u003d configparser.RawConfigParser()"},{"line_number":23,"context_line":"        conf.read(tox_file)"}],"source_content_type":"text/x-python","patch_set":2,"id":"04c792c5_2c1077b7","line":20,"in_reply_to":"dae59d2c_81214706","updated":"2026-03-11 21:30:34.000000000","message":"You can\u0027t call `RawConfigParser.has_section` with `None`. This is used below in `get`. As a result, either we must insist that `default_section` is non-null, or the `section` argument to `get` and `get_multiple` is. Given we always call this with `default_section\u003d\u0027hacking\u0027` I figured this was the better approach.","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"3ff291a6758208220f8a82c7d100f87aa3588ea7","unresolved":true,"context_lines":[{"line_number":45,"context_line":"    def get_multiple("},{"line_number":46,"context_line":"        self, option: str, section: str | None \u003d None, default: list[str] \u003d ..."},{"line_number":47,"context_line":"    ) -\u003e list[str]:"},{"line_number":48,"context_line":"        ..."},{"line_number":49,"context_line":""},{"line_number":50,"context_line":"    @overload"},{"line_number":51,"context_line":"    def get_multiple("}],"source_content_type":"text/x-python","patch_set":3,"id":"5296c9bf_e07b4458","line":48,"range":{"start_line":48,"start_character":8,"end_line":48,"end_character":11},"updated":"2026-03-12 18:08:33.000000000","message":"ah useing elipse like this is neat\n\nthe one time i tried to use overload nova said no but its nice that we can type\nthings liek this more stictly in some cases.\n\ni.e. if a defualt is specified then the return type of the default is the return tip of the fucntion instead of optially None","commit_id":"fa22c39de3e73c8c771edbc8eb2848d779fd8f8c"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"3b49ec61654c147bfa232ad196aeed69b8252836","unresolved":false,"context_lines":[{"line_number":45,"context_line":"    def get_multiple("},{"line_number":46,"context_line":"        self, option: str, section: str | None \u003d None, default: list[str] \u003d ..."},{"line_number":47,"context_line":"    ) -\u003e list[str]:"},{"line_number":48,"context_line":"        ..."},{"line_number":49,"context_line":""},{"line_number":50,"context_line":"    @overload"},{"line_number":51,"context_line":"    def get_multiple("}],"source_content_type":"text/x-python","patch_set":3,"id":"4abb37ba_4e810177","line":48,"range":{"start_line":48,"start_character":8,"end_line":48,"end_character":11},"in_reply_to":"5296c9bf_e07b4458","updated":"2026-03-12 19:44:25.000000000","message":"Indeed. We make extensive use of them in SDK (out of necessity). [For example](https://github.com/openstack/openstacksdk/blob/e2c457c9fcfac201d14c0efb676d3e3cf0f20a40/openstack/resource.py#L2249-L2304)","commit_id":"fa22c39de3e73c8c771edbc8eb2848d779fd8f8c"}],"hacking/core.py":[{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"777faef3833e00dca39297ded136e38640b1641e","unresolved":true,"context_lines":[{"line_number":29,"context_line":"gettext.install(\u0027hacking\u0027)"},{"line_number":30,"context_line":""},{"line_number":31,"context_line":""},{"line_number":32,"context_line":"F \u003d TypeVar(\u0027F\u0027, bound\u003dCallable[..., Any])"},{"line_number":33,"context_line":""},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"def flake8ext(f: F) -\u003e F:"}],"source_content_type":"text/x-python","patch_set":2,"id":"1f089504_dd11ddd1","line":32,"updated":"2026-03-11 19:30:07.000000000","message":"by the way coudl we avodi setattr if we defeind a protocol and specifeid that in typevar so that the type system know what fields shoudl exist  or something liek that\n\ngemini say this shoudl work for older python\n```\nfrom typing import Any, Callable, TypeVar, Protocol, cast\n\n# 1. Define the structure you expect\nclass Flake8Extension(Protocol):\n    name: str\n    version: str\n    skip_on_py3: bool\n    off_by_default: bool\n    def __call__(self, *args: Any, **kwargs: Any) -\u003e Any: ...\n\nF \u003d TypeVar(\u0027F\u0027, bound\u003dCallable[..., Any])\n\ndef flake8ext(f: F) -\u003e F \u0026 Flake8Extension:  # Note: Intersection types aren\u0027t fully native yet\n    # We cast \u0027f\u0027 to our Protocol so we can use dot notation safely\n    plugin \u003d cast(Flake8Extension, f)\n    \n    plugin.name \u003d __name__\n    plugin.version \u003d \u00270.0.1\u0027\n    plugin.skip_on_py3 \u003d False\n    \n    if not hasattr(plugin, \u0027off_by_default\u0027):\n        plugin.off_by_default \u003d False\n        \n    return cast(F, plugin)\n\n```\nand \n```\nfrom typing import Any, Callable, TypeVar, Protocol, cast, TypeAlias\n\nclass Flake8Plugin(Protocol):\n    name: str\n    version: str\n    skip_on_py3: bool\n    off_by_default: bool\n    def __call__(self, *args: Any, **kwargs: Any) -\u003e Any: ...\n\nF \u003d TypeVar(\u0027F\u0027, bound\u003dCallable[..., Any])\n\ndef flake8ext(f: F) -\u003e F:\n    # Cast to our protocol to use dot notation safely\n    plugin \u003d cast(Flake8Plugin, f)\n    \n    plugin.name \u003d __name__\n    plugin.version \u003d \u00270.0.1\u0027\n    plugin.skip_on_py3 \u003d False\n    \n    # Standard hasattr check works perfectly with Protocols\n    if not hasattr(plugin, \u0027off_by_default\u0027):\n        plugin.off_by_default \u003d False\n        \n    return f\n```\nor \n```\nfrom typing import Any, Callable, Generic, TypeVar, ParamSpec\n\nP \u003d ParamSpec(\"P\")\nR \u003d TypeVar(\"R\")\n\nclass flake8ext(Generic[P, R]):\n    def __init__(self, func: Callable[P, R]):\n        self.func \u003d func\n        self.name \u003d __name__\n        self.version \u003d \u00270.0.1\u0027\n        self.skip_on_py3 \u003d False\n        self.off_by_default \u003d getattr(func, \u0027off_by_default\u0027, False)\n\n    def __call__(self, *args: P, **kwargs: P) -\u003e R:\n        return self.func(*args, **kwargs)\n\n# Usage\n@flake8ext\ndef my_check(tree: Any) -\u003e None:\n    ...\n\n# Mypy now knows exactly what these are:\nprint(my_check.name)\n```\nwork on 3.10+\n\nthe Protocol + Cast is proably the better approch as it doesnt have any real runtime overhead.","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"046f21f28fc431d1b7c9c72fa2b4441d26e32d4e","unresolved":true,"context_lines":[{"line_number":29,"context_line":"gettext.install(\u0027hacking\u0027)"},{"line_number":30,"context_line":""},{"line_number":31,"context_line":""},{"line_number":32,"context_line":"F \u003d TypeVar(\u0027F\u0027, bound\u003dCallable[..., Any])"},{"line_number":33,"context_line":""},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"def flake8ext(f: F) -\u003e F:"}],"source_content_type":"text/x-python","patch_set":2,"id":"5e27024e_f8da2ab3","line":32,"in_reply_to":"1f089504_dd11ddd1","updated":"2026-03-11 21:30:34.000000000","message":"yeah, we\u0027ve a few users of Protocol around but I don\u0027t think this is a correct usage. To me, this would imply the function already had these attributes set when passed, which is clearly not the case. I think `setattr` is the better approach here.","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"239d9381596d311038839186a03d2a9323620db1","unresolved":false,"context_lines":[{"line_number":29,"context_line":"gettext.install(\u0027hacking\u0027)"},{"line_number":30,"context_line":""},{"line_number":31,"context_line":""},{"line_number":32,"context_line":"F \u003d TypeVar(\u0027F\u0027, bound\u003dCallable[..., Any])"},{"line_number":33,"context_line":""},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"def flake8ext(f: F) -\u003e F:"}],"source_content_type":"text/x-python","patch_set":2,"id":"b3dca611_62970ee4","line":32,"in_reply_to":"5e27024e_f8da2ab3","updated":"2026-03-11 21:33:28.000000000","message":"Done","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"3ff291a6758208220f8a82c7d100f87aa3588ea7","unresolved":false,"context_lines":[{"line_number":29,"context_line":"gettext.install(\u0027hacking\u0027)"},{"line_number":30,"context_line":""},{"line_number":31,"context_line":""},{"line_number":32,"context_line":"F \u003d TypeVar(\u0027F\u0027, bound\u003dCallable[..., Any])"},{"line_number":33,"context_line":""},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"def flake8ext(f: F) -\u003e F:"}],"source_content_type":"text/x-python","patch_set":2,"id":"e4729151_0fa5d2fd","line":32,"in_reply_to":"b3dca611_62970ee4","updated":"2026-03-12 18:08:33.000000000","message":"that fair. we generally shoudl avoid stashing thing on random obejct like this but this is ligitamte valid use of setattr","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"ff8ed013d17eb3034afd9a08e30296071ff9043c","unresolved":true,"context_lines":[{"line_number":37,"context_line":"    setattr(f, \u0027version\u0027, \u00270.0.1\u0027)"},{"line_number":38,"context_line":"    setattr(f, \u0027skip_on_py3\u0027, False)"},{"line_number":39,"context_line":"    if not hasattr(f, \u0027off_by_default\u0027):"},{"line_number":40,"context_line":"        setattr(f, \u0027off_by_default\u0027, False)"},{"line_number":41,"context_line":"    return f"},{"line_number":42,"context_line":""},{"line_number":43,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"e02ef307_4f77efd9","line":40,"updated":"2026-03-11 19:22:48.000000000","message":"This is unfortunet but it does make it clear that this is takeing a generic function f and extending/modifying its fileds.\nso as much as i hate using setattr this is actully a valid usage.","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"046f21f28fc431d1b7c9c72fa2b4441d26e32d4e","unresolved":false,"context_lines":[{"line_number":37,"context_line":"    setattr(f, \u0027version\u0027, \u00270.0.1\u0027)"},{"line_number":38,"context_line":"    setattr(f, \u0027skip_on_py3\u0027, False)"},{"line_number":39,"context_line":"    if not hasattr(f, \u0027off_by_default\u0027):"},{"line_number":40,"context_line":"        setattr(f, \u0027off_by_default\u0027, False)"},{"line_number":41,"context_line":"    return f"},{"line_number":42,"context_line":""},{"line_number":43,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"3092a956_92a2358e","line":40,"in_reply_to":"e02ef307_4f77efd9","updated":"2026-03-11 21:30:34.000000000","message":"Agreed.","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":13252,"name":"Dr. Jens Harbott","display_name":"Jens Harbott (frickler)","email":"frickler@offenerstapel.de","username":"jrosenboom"},"change_message_id":"9ae48fc63741dc4634d11adacd6c696f732b9896","unresolved":true,"context_lines":[{"line_number":50,"context_line":"    flake8 documentation:"},{"line_number":51,"context_line":"    http://flake8.readthedocs.org/en/latest/extensions.html."},{"line_number":52,"context_line":"    \"\"\""},{"line_number":53,"context_line":"    setattr(f, \u0027off_by_default\u0027, False)"},{"line_number":54,"context_line":"    return f"},{"line_number":55,"context_line":""},{"line_number":56,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"85acf4ad_e1d17e2b","line":53,"range":{"start_line":53,"start_character":33,"end_line":53,"end_character":38},"updated":"2026-03-11 18:41:05.000000000","message":"```suggestion\n    setattr(f, \u0027off_by_default\u0027, True)\n```\nor am I misunderstanding something here?","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":11604,"name":"sean mooney","email":"smooney@redhat.com","username":"sean-k-mooney"},"change_message_id":"ff8ed013d17eb3034afd9a08e30296071ff9043c","unresolved":true,"context_lines":[{"line_number":50,"context_line":"    flake8 documentation:"},{"line_number":51,"context_line":"    http://flake8.readthedocs.org/en/latest/extensions.html."},{"line_number":52,"context_line":"    \"\"\""},{"line_number":53,"context_line":"    setattr(f, \u0027off_by_default\u0027, False)"},{"line_number":54,"context_line":"    return f"},{"line_number":55,"context_line":""},{"line_number":56,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"9759839c_10b01c34","line":53,"range":{"start_line":53,"start_character":33,"end_line":53,"end_character":38},"in_reply_to":"85acf4ad_e1d17e2b","updated":"2026-03-11 19:22:48.000000000","message":"no i think Stephen just got confused with the double negation \ni.e. off_by_default \u003d True meaning it off\n\nthis is changing the behvior","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"239d9381596d311038839186a03d2a9323620db1","unresolved":false,"context_lines":[{"line_number":50,"context_line":"    flake8 documentation:"},{"line_number":51,"context_line":"    http://flake8.readthedocs.org/en/latest/extensions.html."},{"line_number":52,"context_line":"    \"\"\""},{"line_number":53,"context_line":"    setattr(f, \u0027off_by_default\u0027, False)"},{"line_number":54,"context_line":"    return f"},{"line_number":55,"context_line":""},{"line_number":56,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"19ad0c3e_119559a1","line":53,"range":{"start_line":53,"start_character":33,"end_line":53,"end_character":38},"in_reply_to":"8e30de4b_06526a55","updated":"2026-03-11 21:33:28.000000000","message":"Done","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"046f21f28fc431d1b7c9c72fa2b4441d26e32d4e","unresolved":true,"context_lines":[{"line_number":50,"context_line":"    flake8 documentation:"},{"line_number":51,"context_line":"    http://flake8.readthedocs.org/en/latest/extensions.html."},{"line_number":52,"context_line":"    \"\"\""},{"line_number":53,"context_line":"    setattr(f, \u0027off_by_default\u0027, False)"},{"line_number":54,"context_line":"    return f"},{"line_number":55,"context_line":""},{"line_number":56,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"8e30de4b_06526a55","line":53,"range":{"start_line":53,"start_character":33,"end_line":53,"end_character":38},"in_reply_to":"9759839c_10b01c34","updated":"2026-03-11 21:30:34.000000000","message":"Nah, I think this is copy-paste mistake (I must have copied the `setattr` call from below up here)","commit_id":"957f7d6060ba02a3a2c39571b0143db17897db63"}]}
