{"id":24350,"url":"https://patchwork.libcamera.org/api/1.1/patches/24350/?format=json","web_url":"https://patchwork.libcamera.org/patch/24350/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20250912125528.1963619-2-barnabas.pocze@ideasonboard.com>","date":"2025-09-12T12:55:28","name":"[RFC,v2,2/2] treewide: Use `argparse.FileType` in more places","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"44663b2251ce0684354d0f9988888f2f57ca9a4a","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/1.1/people/216/?format=json","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/24350/mbox/","series":[{"id":5440,"url":"https://patchwork.libcamera.org/api/1.1/series/5440/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5440","date":"2025-09-12T12:55:27","name":"[RFC,v2,1/2] py: gen-py-formats.py: Open input file in binary mode","version":2,"mbox":"https://patchwork.libcamera.org/series/5440/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/24350/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/24350/checks/","tags":{},"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 B7A8BC328C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 12 Sep 2025 12:55:38 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 98FA069375;\n\tFri, 12 Sep 2025 14:55:37 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 19FA46936A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 12 Sep 2025 14:55:35 +0200 (CEST)","from pb-laptop.local (185.221.142.115.nat.pool.zt.hu\n\t[185.221.142.115])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 721B1C7B;\n\tFri, 12 Sep 2025 14:54:18 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"a28XWJ11\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1757681658;\n\tbh=MrA6/RngMUVvmgzzsSKjXr+4ZWgz5BAZI2RpNW8DLRs=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=a28XWJ11Bv5JQRyCnUU6huLO5DPHfiPfbrmXzJMjb45A7zcvNn0b8HnX5bIgSQlyP\n\toViMqE1x+MSQg/Y7BDodcplEVMmjyUhtQzX2H9HhmmacdNv8ueDXuECrmtHObPxNeB\n\t/sZAGSAX0SZKjN6KKE/mpVPNUfX12bvYwxjvT+k8=","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>","Subject":"[RFC PATCH v2 2/2] treewide: Use `argparse.FileType` in more places","Date":"Fri, 12 Sep 2025 14:55:28 +0200","Message-ID":"<20250912125528.1963619-2-barnabas.pocze@ideasonboard.com>","X-Mailer":"git-send-email 2.51.0","In-Reply-To":"<20250912125528.1963619-1-barnabas.pocze@ideasonboard.com>","References":"<20250912125528.1963619-1-barnabas.pocze@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","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>"},"content":"Convert some scripts to use `argparse.FileType` where the change is relatively\neasily doable. This allows better error messages as e.g. missing input files\nwill be detected during argument parsing. And it also makes writing to stdout\nin absence of an explicit argument simpler.\n\nSigned-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n---\nchanges in v2:\n  * split open() mode change\n  * rename `lines` -> `file` in `DRMFourCC` constructor\n\nv1: 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--\n2.51.0","diff":"diff --git a/Documentation/gen-doxyfile.py b/Documentation/gen-doxyfile.py\nindex 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\ndiff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py\nindex 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\ndiff --git a/src/py/libcamera/gen-py-formats.py b/src/py/libcamera/gen-py-formats.py\nindex 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\ndiff --git a/utils/codegen/gen-controls.py b/utils/codegen/gen-controls.py\nindex 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\ndiff --git a/utils/codegen/gen-formats.py b/utils/codegen/gen-formats.py\nindex 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\ndiff --git a/utils/codegen/gen-gst-controls.py b/utils/codegen/gen-gst-controls.py\nindex 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","prefixes":["RFC","v2","2/2"]}