[{"id":36591,"web_url":"https://patchwork.libcamera.org/comment/36591/","msgid":"<ksuersjm7pq36ykgb3ist5upiitfk4ucxfb725c5loorpetiga@tv67rges7uhg>","date":"2025-11-01T16:24:23","subject":"Re: [RFC PATCH v2 2/2] treewide: Use `argparse.FileType` in more\n\tplaces","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Barnabás\n\nOn Fri, Sep 12, 2025 at 02:55:28PM +0200, Barnabás Pőcze wrote:\n> Convert some scripts to use `argparse.FileType` where the change is relatively\n> easily doable. This allows better error messages as e.g. missing input files\n> will be detected during argument parsing. And it also makes writing to stdout\n> in absence of an explicit argument simpler.\n>\n> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nI won't pretend I know anything about this, I'm just saying that if\nthe two of you are fine, you can push these two patches even with a\nsingle tag\n\n> ---\n> changes in v2:\n>   * split open() mode change\n>   * rename `lines` -> `file` in `DRMFourCC` constructor\n>\n> v1: https://patchwork.libcamera.org/patch/24328/\n> ---\n>  Documentation/gen-doxyfile.py       | 16 +++++-----------\n>  src/py/libcamera/gen-py-controls.py | 19 ++++++++-----------\n>  src/py/libcamera/gen-py-formats.py  | 26 +++++++-------------------\n>  utils/codegen/gen-controls.py       | 27 +++++++++++----------------\n>  utils/codegen/gen-formats.py        | 27 ++++++++++-----------------\n>  utils/codegen/gen-gst-controls.py   | 18 ++++++++----------\n>  6 files changed, 49 insertions(+), 84 deletions(-)\n>\n> diff --git a/Documentation/gen-doxyfile.py b/Documentation/gen-doxyfile.py\n> index c265bc2f3..af9b24aa0 100755\n> --- a/Documentation/gen-doxyfile.py\n> +++ b/Documentation/gen-doxyfile.py\n> @@ -12,15 +12,6 @@ import string\n>  import sys\n>\n>\n> -def fill_template(template, data):\n> -\n> -    template = open(template, 'rb').read()\n> -    template = template.decode('utf-8')\n> -    template = string.Template(template)\n> -\n> -    return template.substitute(data)\n> -\n> -\n>  def main(argv):\n>\n>      parser = argparse.ArgumentParser()\n> @@ -28,7 +19,8 @@ def main(argv):\n>                          type=argparse.FileType('w', encoding='utf-8'),\n>                          default=sys.stdout,\n>                          help='Output file name (default: standard output)')\n> -    parser.add_argument('template', metavar='doxyfile.tmpl', type=str,\n> +    parser.add_argument('template', metavar='doxyfile.tmpl',\n> +                        type=argparse.FileType('r', encoding='utf-8'),\n>                          help='Doxyfile template')\n>      parser.add_argument('inputs', type=str, nargs='*',\n>                          help='Input files')\n> @@ -36,7 +28,9 @@ def main(argv):\n>      args = parser.parse_args(argv[1:])\n>\n>      inputs = [f'\"{os.path.realpath(input)}\"' for input in args.inputs]\n> -    data = fill_template(args.template, {'inputs': (' \\\\\\n' + ' ' * 25).join(inputs)})\n> +    data = string.Template(args.template.read()).substitute({\n> +        'inputs': (' \\\\\\n' + ' ' * 25).join(inputs),\n> +    })\n>      args.output.write(data)\n>\n>      return 0\n> diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py\n> index d43a7c1c7..97849eb34 100755\n> --- a/src/py/libcamera/gen-py-controls.py\n> +++ b/src/py/libcamera/gen-py-controls.py\n> @@ -60,11 +60,13 @@ def main(argv):\n>      parser = argparse.ArgumentParser()\n>      parser.add_argument('--mode', '-m', type=str, required=True,\n>                          help='Mode is either \"controls\" or \"properties\"')\n> -    parser.add_argument('--output', '-o', metavar='file', type=str,\n> +    parser.add_argument('--output', '-o', metavar='file', default=sys.stdout,\n> +                        type=argparse.FileType('w', encoding='utf-8'),\n>                          help='Output file name. Defaults to standard output if not specified.')\n> -    parser.add_argument('--template', '-t', type=str, required=True,\n> +    parser.add_argument('--template', '-t', required=True,\n> +                        type=argparse.FileType('r', encoding='utf-8'),\n>                          help='Template file name.')\n> -    parser.add_argument('input', type=str, nargs='+',\n> +    parser.add_argument('input', type=argparse.FileType('rb'), nargs='+',\n>                          help='Input file name.')\n>      args = parser.parse_args(argv[1:])\n>\n> @@ -76,7 +78,7 @@ def main(argv):\n>      vendors = []\n>\n>      for input in args.input:\n> -        data = yaml.safe_load(open(input, 'rb').read())\n> +        data = yaml.safe_load(input)\n>\n>          vendor = data['vendor']\n>          if vendor != 'libcamera':\n> @@ -94,15 +96,10 @@ def main(argv):\n>      }\n>\n>      env = jinja2.Environment()\n> -    template = env.from_string(open(args.template, 'r', encoding='utf-8').read())\n> +    template = env.from_string(args.template.read())\n>      string = template.render(data)\n>\n> -    if args.output:\n> -        output = open(args.output, 'w', encoding='utf-8')\n> -        output.write(string)\n> -        output.close()\n> -    else:\n> -        sys.stdout.write(string)\n> +    args.output.write(string)\n>\n>      return 0\n>\n> diff --git a/src/py/libcamera/gen-py-formats.py b/src/py/libcamera/gen-py-formats.py\n> index 6323e237f..4d1963333 100755\n> --- a/src/py/libcamera/gen-py-formats.py\n> +++ b/src/py/libcamera/gen-py-formats.py\n> @@ -19,35 +19,23 @@ def generate(formats):\n>      return {'formats': '\\n'.join(fmts)}\n>\n>\n> -def fill_template(template, data):\n> -    with open(template, encoding='utf-8') as f:\n> -        template = f.read()\n> -\n> -    template = string.Template(template)\n> -    return template.substitute(data)\n> -\n> -\n>  def main(argv):\n>      parser = argparse.ArgumentParser()\n> -    parser.add_argument('-o', dest='output', metavar='file', type=str,\n> +    parser.add_argument('-o', dest='output', metavar='file', default=sys.stdout,\n> +                        type=argparse.FileType('w', encoding='utf-8'),\n>                          help='Output file name. Defaults to standard output if not specified.')\n> -    parser.add_argument('input', type=str,\n> +    parser.add_argument('input', type=argparse.FileType('rb'),\n>                          help='Input file name.')\n> -    parser.add_argument('template', type=str,\n> +    parser.add_argument('template', type=argparse.FileType('r', encoding='utf-8'),\n>                          help='Template file name.')\n>      args = parser.parse_args(argv[1:])\n>\n> -    with open(args.input, 'rb') as f:\n> -        formats = yaml.safe_load(f)['formats']\n> +    formats = yaml.safe_load(args.input)['formats']\n>\n>      data = generate(formats)\n> -    data = fill_template(args.template, data)\n> +    data = string.Template(args.template.read()).substitute(data)\n>\n> -    if args.output:\n> -        with open(args.output, 'w', encoding='utf-8') as f:\n> -            f.write(data)\n> -    else:\n> -        sys.stdout.write(data)\n> +    args.output.write(data)\n>\n>      return 0\n>\n> diff --git a/utils/codegen/gen-controls.py b/utils/codegen/gen-controls.py\n> index 59b716c1c..90aba344b 100755\n> --- a/utils/codegen/gen-controls.py\n> +++ b/utils/codegen/gen-controls.py\n> @@ -44,25 +44,25 @@ def main(argv):\n>      parser = argparse.ArgumentParser()\n>      parser.add_argument('--mode', '-m', type=str, required=True, choices=['controls', 'properties'],\n>                          help='Mode of operation')\n> -    parser.add_argument('--output', '-o', metavar='file', type=str,\n> +    parser.add_argument('--output', '-o', metavar='file', default=sys.stdout,\n> +                        type=argparse.FileType('w', encoding='utf-8'),\n>                          help='Output file name. Defaults to standard output if not specified.')\n> -    parser.add_argument('--ranges', '-r', type=str, required=True,\n> +    parser.add_argument('--ranges', '-r', required=True, type=argparse.FileType('rb'),\n>                          help='Control id range reservation file.')\n> -    parser.add_argument('--template', '-t', dest='template', type=str, required=True,\n> +    parser.add_argument('--template', '-t', dest='template', required=True,\n> +                        type=argparse.FileType('r', encoding='utf-8'),\n>                          help='Template file name.')\n> -    parser.add_argument('input', type=str, nargs='+',\n> +    parser.add_argument('input', nargs='+',\n> +                        type=argparse.FileType('rb'),\n>                          help='Input file name.')\n>\n>      args = parser.parse_args(argv[1:])\n>\n> -    ranges = {}\n> -    with open(args.ranges, 'rb') as f:\n> -        data = open(args.ranges, 'rb').read()\n> -        ranges = yaml.safe_load(data)['ranges']\n> +    ranges = yaml.safe_load(args.ranges)['ranges']\n>\n>      controls = {}\n>      for input in args.input:\n> -        data = yaml.safe_load(open(input, 'rb').read())\n> +        data = yaml.safe_load(input)\n>\n>          vendor = data['vendor']\n>          if vendor not in ranges.keys():\n> @@ -92,15 +92,10 @@ def main(argv):\n>      env = jinja2.Environment()\n>      env.filters['format_description'] = format_description\n>      env.filters['snake_case'] = snake_case\n> -    template = env.from_string(open(args.template, 'r', encoding='utf-8').read())\n> +    template = env.from_string(args.template.read())\n>      string = template.render(data)\n>\n> -    if args.output:\n> -        output = open(args.output, 'w', encoding='utf-8')\n> -        output.write(string)\n> -        output.close()\n> -    else:\n> -        sys.stdout.write(string)\n> +    args.output.write(string)\n>\n>      return 0\n>\n> diff --git a/utils/codegen/gen-formats.py b/utils/codegen/gen-formats.py\n> index 7542d8841..740790e8f 100755\n> --- a/utils/codegen/gen-formats.py\n> +++ b/utils/codegen/gen-formats.py\n> @@ -19,14 +19,12 @@ class DRMFourCC(object):\n>      mod_vendor_regex = re.compile(r\"#define DRM_FORMAT_MOD_VENDOR_([A-Z0-9_]+)[ \\t]+([0-9a-fA-Fx]+)\")\n>      mod_regex = re.compile(r\"#define ([A-Za-z0-9_]+)[ \\t]+fourcc_mod_code\\(([A-Z0-9_]+), ([0-9a-fA-Fx]+)\\)\")\n>\n> -    def __init__(self, filename):\n> +    def __init__(self, file):\n>          self.formats = {}\n>          self.vendors = {}\n>          self.mods = {}\n>\n> -        for line in open(filename, 'rb').readlines():\n> -            line = line.decode('utf-8')\n> -\n> +        for line in file:\n>              match = DRMFourCC.format_regex.match(line)\n>              if match:\n>                  format, fourcc = match.groups()\n> @@ -80,32 +78,27 @@ def main(argv):\n>\n>      # Parse command line arguments\n>      parser = argparse.ArgumentParser()\n> -    parser.add_argument('-o', dest='output', metavar='file', type=str,\n> +    parser.add_argument('-o', dest='output', metavar='file', default=sys.stdout,\n> +                        type=argparse.FileType('w', encoding='utf-8'),\n>                          help='Output file name. Defaults to standard output if not specified.')\n> -    parser.add_argument('input', type=str,\n> +    parser.add_argument('input', type=argparse.FileType('rb'),\n>                          help='Input file name.')\n> -    parser.add_argument('template', type=str,\n> +    parser.add_argument('template', type=argparse.FileType('r', encoding='utf-8'),\n>                          help='Template file name.')\n> -    parser.add_argument('drm_fourcc', type=str,\n> +    parser.add_argument('drm_fourcc', type=argparse.FileType('r', encoding='utf-8'),\n>                          help='Path to drm_fourcc.h.')\n>      args = parser.parse_args(argv[1:])\n>\n> -    data = open(args.input, 'rb').read()\n> -    formats = yaml.safe_load(data)['formats']\n> +    formats = yaml.safe_load(args.input)['formats']\n>      drm_fourcc = DRMFourCC(args.drm_fourcc)\n>\n>      env = jinja2.Environment()\n> -    template = env.from_string(open(args.template, 'r', encoding='utf-8').read())\n> +    template = env.from_string(args.template.read())\n>      string = template.render({\n>          'formats': generate_formats(formats, drm_fourcc),\n>      })\n>\n> -    if args.output:\n> -        output = open(args.output, 'wb')\n> -        output.write(string.encode('utf-8'))\n> -        output.close()\n> -    else:\n> -        sys.stdout.write(string)\n> +    args.output.write(string)\n>\n>      return 0\n>\n> diff --git a/utils/codegen/gen-gst-controls.py b/utils/codegen/gen-gst-controls.py\n> index 4ca76049e..31f18625f 100755\n> --- a/utils/codegen/gen-gst-controls.py\n> +++ b/utils/codegen/gen-gst-controls.py\n> @@ -138,18 +138,20 @@ def extend_control(ctrl):\n>  def main(argv):\n>      # Parse command line arguments\n>      parser = argparse.ArgumentParser()\n> -    parser.add_argument('--output', '-o', metavar='file', type=str,\n> +    parser.add_argument('--output', '-o', metavar='file', default=sys.stdout,\n> +                        type=argparse.FileType('w', encoding='utf-8'),\n>                          help='Output file name. Defaults to standard output if not specified.')\n> -    parser.add_argument('--template', '-t', dest='template', type=str, required=True,\n> +    parser.add_argument('--template', '-t', dest='template', required=True,\n> +                        type=argparse.FileType('r', encoding='utf-8'),\n>                          help='Template file name.')\n> -    parser.add_argument('input', type=str, nargs='+',\n> +    parser.add_argument('input', nargs='+', type=argparse.FileType('rb'),\n>                          help='Input file name.')\n>\n>      args = parser.parse_args(argv[1:])\n>\n>      controls = {}\n>      for input in args.input:\n> -        data = yaml.safe_load(open(input, 'rb').read())\n> +        data = yaml.safe_load(input)\n>\n>          vendor = data['vendor']\n>          ctrls = controls.setdefault(vendor, [])\n> @@ -167,14 +169,10 @@ def main(argv):\n>      env.filters['indent_str'] = indent_str\n>      env.filters['snake_case'] = snake_case\n>      env.filters['kebab_case'] = kebab_case\n> -    template = env.from_string(open(args.template, 'r', encoding='utf-8').read())\n> +    template = env.from_string(args.template.read())\n>      string = template.render(data)\n>\n> -    if args.output:\n> -        with open(args.output, 'w', encoding='utf-8') as output:\n> -            output.write(string)\n> -    else:\n> -        sys.stdout.write(string)\n> +    args.output.write(string)\n>\n>      return 0\n>\n> --\n> 2.51.0","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 8D124C3241\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSat,  1 Nov 2025 16:24:28 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E917A60A7B;\n\tSat,  1 Nov 2025 17:24:27 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id CC919606D5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat,  1 Nov 2025 17:24:26 +0100 (CET)","from ideasonboard.com (unknown\n\t[IPv6:2001:b07:6462:5de2:153:f9b8:5024:faa2])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 18DA5BB;\n\tSat,  1 Nov 2025 17:22:35 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"QCeH2hql\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1762014155;\n\tbh=92MOWlSmhALqRRvVZD5hgUmMAWCxhveKxonznmNcHXo=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=QCeH2hqlMPah97H8bhlfbJXqSYg/+B58aSeYpCVSOB1UsJ3OwzB6uQ7mxBznagw3z\n\tJZxUd2Me55ehDpfg8EfrTyD6MpZikZrN40MrZncNI6FNmFzIyNnhpxDcddyX/wXgSm\n\tBNig95AAKu6moPGD1xvRokn9s5MjfwXGCtpcI80Y=","Date":"Sat, 1 Nov 2025 17:24:23 +0100","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org, \n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>","Subject":"Re: [RFC PATCH v2 2/2] treewide: Use `argparse.FileType` in more\n\tplaces","Message-ID":"<ksuersjm7pq36ykgb3ist5upiitfk4ucxfb725c5loorpetiga@tv67rges7uhg>","References":"<20250912125528.1963619-1-barnabas.pocze@ideasonboard.com>\n\t<20250912125528.1963619-2-barnabas.pocze@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20250912125528.1963619-2-barnabas.pocze@ideasonboard.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":36771,"web_url":"https://patchwork.libcamera.org/comment/36771/","msgid":"<f4aba1c7-f8bb-4830-ae76-0309caac605b@ideasonboard.com>","date":"2025-11-11T12:25:53","subject":"Re: [RFC PATCH v2 2/2] treewide: Use `argparse.FileType` in more\n\tplaces","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"2025. 11. 01. 17:24 keltezéssel, Jacopo Mondi írta:\n> Hi Barnabás\n> \n> On Fri, Sep 12, 2025 at 02:55:28PM +0200, Barnabás Pőcze wrote:\n>> Convert some scripts to use `argparse.FileType` where the change is relatively\n>> easily doable. This allows better error messages as e.g. missing input files\n>> will be detected during argument parsing. And it also makes writing to stdout\n>> in absence of an explicit argument simpler.\n>>\n>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n>> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> \n> I won't pretend I know anything about this, I'm just saying that if\n> the two of you are fine, you can push these two patches even with a\n> single tag\n\nOkay, then I'll go ahead unless someone stops me in the next couple hours.\n\n\n> \n>> ---\n>> changes in v2:\n>>    * split open() mode change\n>>    * rename `lines` -> `file` in `DRMFourCC` constructor\n>>\n>> v1: https://patchwork.libcamera.org/patch/24328/\n>> ---\n>>   Documentation/gen-doxyfile.py       | 16 +++++-----------\n>>   src/py/libcamera/gen-py-controls.py | 19 ++++++++-----------\n>>   src/py/libcamera/gen-py-formats.py  | 26 +++++++-------------------\n>>   utils/codegen/gen-controls.py       | 27 +++++++++++----------------\n>>   utils/codegen/gen-formats.py        | 27 ++++++++++-----------------\n>>   utils/codegen/gen-gst-controls.py   | 18 ++++++++----------\n>>   6 files changed, 49 insertions(+), 84 deletions(-)\n>>\n>> diff --git a/Documentation/gen-doxyfile.py b/Documentation/gen-doxyfile.py\n>> index c265bc2f3..af9b24aa0 100755\n>> --- a/Documentation/gen-doxyfile.py\n>> +++ b/Documentation/gen-doxyfile.py\n>> @@ -12,15 +12,6 @@ import string\n>>   import sys\n>>\n>>\n>> -def fill_template(template, data):\n>> -\n>> -    template = open(template, 'rb').read()\n>> -    template = template.decode('utf-8')\n>> -    template = string.Template(template)\n>> -\n>> -    return template.substitute(data)\n>> -\n>> -\n>>   def main(argv):\n>>\n>>       parser = argparse.ArgumentParser()\n>> @@ -28,7 +19,8 @@ def main(argv):\n>>                           type=argparse.FileType('w', encoding='utf-8'),\n>>                           default=sys.stdout,\n>>                           help='Output file name (default: standard output)')\n>> -    parser.add_argument('template', metavar='doxyfile.tmpl', type=str,\n>> +    parser.add_argument('template', metavar='doxyfile.tmpl',\n>> +                        type=argparse.FileType('r', encoding='utf-8'),\n>>                           help='Doxyfile template')\n>>       parser.add_argument('inputs', type=str, nargs='*',\n>>                           help='Input files')\n>> @@ -36,7 +28,9 @@ def main(argv):\n>>       args = parser.parse_args(argv[1:])\n>>\n>>       inputs = [f'\"{os.path.realpath(input)}\"' for input in args.inputs]\n>> -    data = fill_template(args.template, {'inputs': (' \\\\\\n' + ' ' * 25).join(inputs)})\n>> +    data = string.Template(args.template.read()).substitute({\n>> +        'inputs': (' \\\\\\n' + ' ' * 25).join(inputs),\n>> +    })\n>>       args.output.write(data)\n>>\n>>       return 0\n>> diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py\n>> index d43a7c1c7..97849eb34 100755\n>> --- a/src/py/libcamera/gen-py-controls.py\n>> +++ b/src/py/libcamera/gen-py-controls.py\n>> @@ -60,11 +60,13 @@ def main(argv):\n>>       parser = argparse.ArgumentParser()\n>>       parser.add_argument('--mode', '-m', type=str, required=True,\n>>                           help='Mode is either \"controls\" or \"properties\"')\n>> -    parser.add_argument('--output', '-o', metavar='file', type=str,\n>> +    parser.add_argument('--output', '-o', metavar='file', default=sys.stdout,\n>> +                        type=argparse.FileType('w', encoding='utf-8'),\n>>                           help='Output file name. Defaults to standard output if not specified.')\n>> -    parser.add_argument('--template', '-t', type=str, required=True,\n>> +    parser.add_argument('--template', '-t', required=True,\n>> +                        type=argparse.FileType('r', encoding='utf-8'),\n>>                           help='Template file name.')\n>> -    parser.add_argument('input', type=str, nargs='+',\n>> +    parser.add_argument('input', type=argparse.FileType('rb'), nargs='+',\n>>                           help='Input file name.')\n>>       args = parser.parse_args(argv[1:])\n>>\n>> @@ -76,7 +78,7 @@ def main(argv):\n>>       vendors = []\n>>\n>>       for input in args.input:\n>> -        data = yaml.safe_load(open(input, 'rb').read())\n>> +        data = yaml.safe_load(input)\n>>\n>>           vendor = data['vendor']\n>>           if vendor != 'libcamera':\n>> @@ -94,15 +96,10 @@ def main(argv):\n>>       }\n>>\n>>       env = jinja2.Environment()\n>> -    template = env.from_string(open(args.template, 'r', encoding='utf-8').read())\n>> +    template = env.from_string(args.template.read())\n>>       string = template.render(data)\n>>\n>> -    if args.output:\n>> -        output = open(args.output, 'w', encoding='utf-8')\n>> -        output.write(string)\n>> -        output.close()\n>> -    else:\n>> -        sys.stdout.write(string)\n>> +    args.output.write(string)\n>>\n>>       return 0\n>>\n>> diff --git a/src/py/libcamera/gen-py-formats.py b/src/py/libcamera/gen-py-formats.py\n>> index 6323e237f..4d1963333 100755\n>> --- a/src/py/libcamera/gen-py-formats.py\n>> +++ b/src/py/libcamera/gen-py-formats.py\n>> @@ -19,35 +19,23 @@ def generate(formats):\n>>       return {'formats': '\\n'.join(fmts)}\n>>\n>>\n>> -def fill_template(template, data):\n>> -    with open(template, encoding='utf-8') as f:\n>> -        template = f.read()\n>> -\n>> -    template = string.Template(template)\n>> -    return template.substitute(data)\n>> -\n>> -\n>>   def main(argv):\n>>       parser = argparse.ArgumentParser()\n>> -    parser.add_argument('-o', dest='output', metavar='file', type=str,\n>> +    parser.add_argument('-o', dest='output', metavar='file', default=sys.stdout,\n>> +                        type=argparse.FileType('w', encoding='utf-8'),\n>>                           help='Output file name. Defaults to standard output if not specified.')\n>> -    parser.add_argument('input', type=str,\n>> +    parser.add_argument('input', type=argparse.FileType('rb'),\n>>                           help='Input file name.')\n>> -    parser.add_argument('template', type=str,\n>> +    parser.add_argument('template', type=argparse.FileType('r', encoding='utf-8'),\n>>                           help='Template file name.')\n>>       args = parser.parse_args(argv[1:])\n>>\n>> -    with open(args.input, 'rb') as f:\n>> -        formats = yaml.safe_load(f)['formats']\n>> +    formats = yaml.safe_load(args.input)['formats']\n>>\n>>       data = generate(formats)\n>> -    data = fill_template(args.template, data)\n>> +    data = string.Template(args.template.read()).substitute(data)\n>>\n>> -    if args.output:\n>> -        with open(args.output, 'w', encoding='utf-8') as f:\n>> -            f.write(data)\n>> -    else:\n>> -        sys.stdout.write(data)\n>> +    args.output.write(data)\n>>\n>>       return 0\n>>\n>> diff --git a/utils/codegen/gen-controls.py b/utils/codegen/gen-controls.py\n>> index 59b716c1c..90aba344b 100755\n>> --- a/utils/codegen/gen-controls.py\n>> +++ b/utils/codegen/gen-controls.py\n>> @@ -44,25 +44,25 @@ def main(argv):\n>>       parser = argparse.ArgumentParser()\n>>       parser.add_argument('--mode', '-m', type=str, required=True, choices=['controls', 'properties'],\n>>                           help='Mode of operation')\n>> -    parser.add_argument('--output', '-o', metavar='file', type=str,\n>> +    parser.add_argument('--output', '-o', metavar='file', default=sys.stdout,\n>> +                        type=argparse.FileType('w', encoding='utf-8'),\n>>                           help='Output file name. Defaults to standard output if not specified.')\n>> -    parser.add_argument('--ranges', '-r', type=str, required=True,\n>> +    parser.add_argument('--ranges', '-r', required=True, type=argparse.FileType('rb'),\n>>                           help='Control id range reservation file.')\n>> -    parser.add_argument('--template', '-t', dest='template', type=str, required=True,\n>> +    parser.add_argument('--template', '-t', dest='template', required=True,\n>> +                        type=argparse.FileType('r', encoding='utf-8'),\n>>                           help='Template file name.')\n>> -    parser.add_argument('input', type=str, nargs='+',\n>> +    parser.add_argument('input', nargs='+',\n>> +                        type=argparse.FileType('rb'),\n>>                           help='Input file name.')\n>>\n>>       args = parser.parse_args(argv[1:])\n>>\n>> -    ranges = {}\n>> -    with open(args.ranges, 'rb') as f:\n>> -        data = open(args.ranges, 'rb').read()\n>> -        ranges = yaml.safe_load(data)['ranges']\n>> +    ranges = yaml.safe_load(args.ranges)['ranges']\n>>\n>>       controls = {}\n>>       for input in args.input:\n>> -        data = yaml.safe_load(open(input, 'rb').read())\n>> +        data = yaml.safe_load(input)\n>>\n>>           vendor = data['vendor']\n>>           if vendor not in ranges.keys():\n>> @@ -92,15 +92,10 @@ def main(argv):\n>>       env = jinja2.Environment()\n>>       env.filters['format_description'] = format_description\n>>       env.filters['snake_case'] = snake_case\n>> -    template = env.from_string(open(args.template, 'r', encoding='utf-8').read())\n>> +    template = env.from_string(args.template.read())\n>>       string = template.render(data)\n>>\n>> -    if args.output:\n>> -        output = open(args.output, 'w', encoding='utf-8')\n>> -        output.write(string)\n>> -        output.close()\n>> -    else:\n>> -        sys.stdout.write(string)\n>> +    args.output.write(string)\n>>\n>>       return 0\n>>\n>> diff --git a/utils/codegen/gen-formats.py b/utils/codegen/gen-formats.py\n>> index 7542d8841..740790e8f 100755\n>> --- a/utils/codegen/gen-formats.py\n>> +++ b/utils/codegen/gen-formats.py\n>> @@ -19,14 +19,12 @@ class DRMFourCC(object):\n>>       mod_vendor_regex = re.compile(r\"#define DRM_FORMAT_MOD_VENDOR_([A-Z0-9_]+)[ \\t]+([0-9a-fA-Fx]+)\")\n>>       mod_regex = re.compile(r\"#define ([A-Za-z0-9_]+)[ \\t]+fourcc_mod_code\\(([A-Z0-9_]+), ([0-9a-fA-Fx]+)\\)\")\n>>\n>> -    def __init__(self, filename):\n>> +    def __init__(self, file):\n>>           self.formats = {}\n>>           self.vendors = {}\n>>           self.mods = {}\n>>\n>> -        for line in open(filename, 'rb').readlines():\n>> -            line = line.decode('utf-8')\n>> -\n>> +        for line in file:\n>>               match = DRMFourCC.format_regex.match(line)\n>>               if match:\n>>                   format, fourcc = match.groups()\n>> @@ -80,32 +78,27 @@ def main(argv):\n>>\n>>       # Parse command line arguments\n>>       parser = argparse.ArgumentParser()\n>> -    parser.add_argument('-o', dest='output', metavar='file', type=str,\n>> +    parser.add_argument('-o', dest='output', metavar='file', default=sys.stdout,\n>> +                        type=argparse.FileType('w', encoding='utf-8'),\n>>                           help='Output file name. Defaults to standard output if not specified.')\n>> -    parser.add_argument('input', type=str,\n>> +    parser.add_argument('input', type=argparse.FileType('rb'),\n>>                           help='Input file name.')\n>> -    parser.add_argument('template', type=str,\n>> +    parser.add_argument('template', type=argparse.FileType('r', encoding='utf-8'),\n>>                           help='Template file name.')\n>> -    parser.add_argument('drm_fourcc', type=str,\n>> +    parser.add_argument('drm_fourcc', type=argparse.FileType('r', encoding='utf-8'),\n>>                           help='Path to drm_fourcc.h.')\n>>       args = parser.parse_args(argv[1:])\n>>\n>> -    data = open(args.input, 'rb').read()\n>> -    formats = yaml.safe_load(data)['formats']\n>> +    formats = yaml.safe_load(args.input)['formats']\n>>       drm_fourcc = DRMFourCC(args.drm_fourcc)\n>>\n>>       env = jinja2.Environment()\n>> -    template = env.from_string(open(args.template, 'r', encoding='utf-8').read())\n>> +    template = env.from_string(args.template.read())\n>>       string = template.render({\n>>           'formats': generate_formats(formats, drm_fourcc),\n>>       })\n>>\n>> -    if args.output:\n>> -        output = open(args.output, 'wb')\n>> -        output.write(string.encode('utf-8'))\n>> -        output.close()\n>> -    else:\n>> -        sys.stdout.write(string)\n>> +    args.output.write(string)\n>>\n>>       return 0\n>>\n>> diff --git a/utils/codegen/gen-gst-controls.py b/utils/codegen/gen-gst-controls.py\n>> index 4ca76049e..31f18625f 100755\n>> --- a/utils/codegen/gen-gst-controls.py\n>> +++ b/utils/codegen/gen-gst-controls.py\n>> @@ -138,18 +138,20 @@ def extend_control(ctrl):\n>>   def main(argv):\n>>       # Parse command line arguments\n>>       parser = argparse.ArgumentParser()\n>> -    parser.add_argument('--output', '-o', metavar='file', type=str,\n>> +    parser.add_argument('--output', '-o', metavar='file', default=sys.stdout,\n>> +                        type=argparse.FileType('w', encoding='utf-8'),\n>>                           help='Output file name. Defaults to standard output if not specified.')\n>> -    parser.add_argument('--template', '-t', dest='template', type=str, required=True,\n>> +    parser.add_argument('--template', '-t', dest='template', required=True,\n>> +                        type=argparse.FileType('r', encoding='utf-8'),\n>>                           help='Template file name.')\n>> -    parser.add_argument('input', type=str, nargs='+',\n>> +    parser.add_argument('input', nargs='+', type=argparse.FileType('rb'),\n>>                           help='Input file name.')\n>>\n>>       args = parser.parse_args(argv[1:])\n>>\n>>       controls = {}\n>>       for input in args.input:\n>> -        data = yaml.safe_load(open(input, 'rb').read())\n>> +        data = yaml.safe_load(input)\n>>\n>>           vendor = data['vendor']\n>>           ctrls = controls.setdefault(vendor, [])\n>> @@ -167,14 +169,10 @@ def main(argv):\n>>       env.filters['indent_str'] = indent_str\n>>       env.filters['snake_case'] = snake_case\n>>       env.filters['kebab_case'] = kebab_case\n>> -    template = env.from_string(open(args.template, 'r', encoding='utf-8').read())\n>> +    template = env.from_string(args.template.read())\n>>       string = template.render(data)\n>>\n>> -    if args.output:\n>> -        with open(args.output, 'w', encoding='utf-8') as output:\n>> -            output.write(string)\n>> -    else:\n>> -        sys.stdout.write(string)\n>> +    args.output.write(string)\n>>\n>>       return 0\n>>\n>> --\n>> 2.51.0","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 41D54C3263\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 11 Nov 2025 12:25:59 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id DC4A860A80;\n\tTue, 11 Nov 2025 13:25:58 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 39071606A0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 11 Nov 2025 13:25:58 +0100 (CET)","from [192.168.33.25] (185.221.140.239.nat.pool.zt.hu\n\t[185.221.140.239])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 0C57D741;\n\tTue, 11 Nov 2025 13:23:58 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"BCuuVmmX\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1762863839;\n\tbh=CHlerKeUnERr+QqYjgKkTWowx9vdHfaUA1734kBK+0M=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=BCuuVmmXwDfMqI7goG2LxQEf5k2US4ddnMGNDMbMFJPXH9z3JAKbmAWU6ftqajjSa\n\t3CfIurvMor8ntQyl7b1J5LPt141K3ptHFxZnwKAXl39GNw7AeKOSGc0Rg/Yl4YcRRb\n\tOgvhNy6Gat+qAXX7DJMcFMNGpDStotRFoxKRkKMs=","Message-ID":"<f4aba1c7-f8bb-4830-ae76-0309caac605b@ideasonboard.com>","Date":"Tue, 11 Nov 2025 13:25:53 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [RFC PATCH v2 2/2] treewide: Use `argparse.FileType` in more\n\tplaces","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>","References":"<20250912125528.1963619-1-barnabas.pocze@ideasonboard.com>\n\t<20250912125528.1963619-2-barnabas.pocze@ideasonboard.com>\n\t<ksuersjm7pq36ykgb3ist5upiitfk4ucxfb725c5loorpetiga@tv67rges7uhg>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<ksuersjm7pq36ykgb3ist5upiitfk4ucxfb725c5loorpetiga@tv67rges7uhg>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]