From 69566ee9287411f66e439d1f4ca2ba5bb9c5127a Mon Sep 17 00:00:00 2001 From: Jansen Price Date: Sat, 18 Apr 2026 17:43:59 -0500 Subject: [PATCH 1/4] Allow setting namespace from command line usage of uuid module - This applies to generating version 3 and 5 UUIDs - Will correctly bail when invalid UUID is supplied as namespace --- Lib/test/test_uuid.py | 46 +++++++++++++++++++ Lib/uuid.py | 12 ++++- ...-04-18-17-37-13.gh-issue-148740.sYnFi0.rst | 2 + 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-18-17-37-13.gh-issue-148740.sYnFi0.rst diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 5f9ab048cdeb6c..7bc7f310e67078 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1223,6 +1223,29 @@ def test_cli_uuid3_ouputted_with_valid_namespace_and_name(self): self.assertEqual(output, str(uuid_output)) self.assertEqual(uuid_output.version, 3) + @mock.patch.object(sys, "argv", ["", "-u", "uuid3", "-n", "any UUID", "-N", "python.org"]) + @mock.patch('sys.stderr', new_callable=io.StringIO) + def test_cli_uuid3_with_invalid_namespace(self, mock_err): + with self.assertRaises(SystemExit) as cm: + self.uuid.main() + # Check that exception code is the same as argparse.ArgumentParser.error + self.assertEqual(cm.exception.code, 2) + self.assertIn("error: badly formed hexadecimal UUID string", mock_err.getvalue()) + + @mock.patch.object(sys, "argv", + ["", "-u", "uuid3", "-n", "0d6a16cc-34a7-47d8-b660-214d0ae184d2", "-N", "some.user"]) + def test_cli_uuid3_ouputted_with_user_provided_namespace_and_name(self): + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + self.uuid.main() + + output = stdout.getvalue().strip() + uuid_output = self.uuid.UUID(output) + + # Output should be in the form of uuid5 + self.assertEqual(output, str(uuid_output)) + self.assertEqual(uuid_output.version, 3) + @mock.patch.object(sys, "argv", ["", "-u", "uuid5", "-n", "@dns", "-N", "python.org"]) def test_cli_uuid5_ouputted_with_valid_namespace_and_name(self): @@ -1237,6 +1260,29 @@ def test_cli_uuid5_ouputted_with_valid_namespace_and_name(self): self.assertEqual(output, str(uuid_output)) self.assertEqual(uuid_output.version, 5) + @mock.patch.object(sys, "argv", ["", "-u", "uuid5", "-n", "any UUID", "-N", "python.org"]) + @mock.patch('sys.stderr', new_callable=io.StringIO) + def test_cli_uuid5_with_invalid_namespace(self, mock_err): + with self.assertRaises(SystemExit) as cm: + self.uuid.main() + # Check that exception code is the same as argparse.ArgumentParser.error + self.assertEqual(cm.exception.code, 2) + self.assertIn("error: badly formed hexadecimal UUID string", mock_err.getvalue()) + + @mock.patch.object(sys, "argv", + ["", "-u", "uuid5", "-n", "0d6a16cc-34a7-47d8-b660-214d0ae184d2", "-N", "some.user"]) + def test_cli_uuid5_ouputted_with_user_provided_namespace_and_name(self): + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + self.uuid.main() + + output = stdout.getvalue().strip() + uuid_output = self.uuid.UUID(output) + + # Output should be in the form of uuid5 + self.assertEqual(output, str(uuid_output)) + self.assertEqual(uuid_output.version, 5) + @mock.patch.object(sys, "argv", ["", "-u", "uuid6"]) def test_cli_uuid6(self): self.do_test_standalone_uuid(6) diff --git a/Lib/uuid.py b/Lib/uuid.py index c0150a59d7cb9a..ced69f5fc2e7df 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -962,7 +962,7 @@ def main(): default="uuid4", help="function to generate the UUID") parser.add_argument("-n", "--namespace", - choices=["any UUID", *namespaces.keys()], + metavar=f"{{any UUID,{','.join(namespaces.keys())}}}", help="uuid3/uuid5 only: " "a UUID, or a well-known predefined UUID addressed " "by namespace name") @@ -984,7 +984,15 @@ def main(): f"{args.uuid} requires a namespace and a name. " "Run 'python -m uuid -h' for more information." ) - namespace = namespaces[namespace] if namespace in namespaces else UUID(namespace) + if namespace in namespaces: + namespace = namespaces[namespace] + else: + try: + namespace = UUID(namespace) + except ValueError as e: + parser.error( + f"{str(e)}: '{args.namespace}'." + ) for _ in range(args.count): print(uuid_func(namespace, name)) else: diff --git a/Misc/NEWS.d/next/Library/2026-04-18-17-37-13.gh-issue-148740.sYnFi0.rst b/Misc/NEWS.d/next/Library/2026-04-18-17-37-13.gh-issue-148740.sYnFi0.rst new file mode 100644 index 00000000000000..189f7443cab9e9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-18-17-37-13.gh-issue-148740.sYnFi0.rst @@ -0,0 +1,2 @@ +Fix usage for :mod:`uuid` command line interface to support a custom namespace be +provided for uuid3 and uuid5. From 3f6e530b5a799ab3ffe593cd5971238d4419c829 Mon Sep 17 00:00:00 2001 From: Jansen Price Date: Mon, 20 Apr 2026 12:14:17 -0500 Subject: [PATCH 2/4] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/uuid.py | 2 +- .../next/Library/2026-04-18-17-37-13.gh-issue-148740.sYnFi0.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index ced69f5fc2e7df..ef4894bdde024d 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -962,7 +962,7 @@ def main(): default="uuid4", help="function to generate the UUID") parser.add_argument("-n", "--namespace", - metavar=f"{{any UUID,{','.join(namespaces.keys())}}}", + metavar=f"{{any UUID,{','.join(namespaces)}}}", help="uuid3/uuid5 only: " "a UUID, or a well-known predefined UUID addressed " "by namespace name") diff --git a/Misc/NEWS.d/next/Library/2026-04-18-17-37-13.gh-issue-148740.sYnFi0.rst b/Misc/NEWS.d/next/Library/2026-04-18-17-37-13.gh-issue-148740.sYnFi0.rst index 189f7443cab9e9..7e49cedda7beb2 100644 --- a/Misc/NEWS.d/next/Library/2026-04-18-17-37-13.gh-issue-148740.sYnFi0.rst +++ b/Misc/NEWS.d/next/Library/2026-04-18-17-37-13.gh-issue-148740.sYnFi0.rst @@ -1,2 +1,2 @@ -Fix usage for :mod:`uuid` command line interface to support a custom namespace be +Fix usage for :mod:`uuid` command-line interface to support a custom namespace be provided for uuid3 and uuid5. From 0f01998ca92011b882c3aa0a6f164b4f1467a987 Mon Sep 17 00:00:00 2001 From: Jansen Price Date: Mon, 20 Apr 2026 12:16:12 -0500 Subject: [PATCH 3/4] Correct line lengths for style and fix typo in comment --- Lib/test/test_uuid.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 7bc7f310e67078..e85a8366ed92ae 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1223,18 +1223,22 @@ def test_cli_uuid3_ouputted_with_valid_namespace_and_name(self): self.assertEqual(output, str(uuid_output)) self.assertEqual(uuid_output.version, 3) - @mock.patch.object(sys, "argv", ["", "-u", "uuid3", "-n", "any UUID", "-N", "python.org"]) + @mock.patch.object(sys, "argv", + ["", "-u", "uuid3", "-n", "any UUID", "-N", "python.org"]) @mock.patch('sys.stderr', new_callable=io.StringIO) def test_cli_uuid3_with_invalid_namespace(self, mock_err): with self.assertRaises(SystemExit) as cm: self.uuid.main() # Check that exception code is the same as argparse.ArgumentParser.error self.assertEqual(cm.exception.code, 2) - self.assertIn("error: badly formed hexadecimal UUID string", mock_err.getvalue()) + self.assertIn("error: badly formed hexadecimal UUID string", + mock_err.getvalue()) @mock.patch.object(sys, "argv", - ["", "-u", "uuid3", "-n", "0d6a16cc-34a7-47d8-b660-214d0ae184d2", "-N", "some.user"]) - def test_cli_uuid3_ouputted_with_user_provided_namespace_and_name(self): + ["", "-u", "uuid3", "-n", + "0d6a16cc-34a7-47d8-b660-214d0ae184d2", + "-N", "some.user"]) + def test_cli_uuid3_ouputted_with_custom_namespace_and_name(self): stdout = io.StringIO() with contextlib.redirect_stdout(stdout): self.uuid.main() @@ -1242,7 +1246,7 @@ def test_cli_uuid3_ouputted_with_user_provided_namespace_and_name(self): output = stdout.getvalue().strip() uuid_output = self.uuid.UUID(output) - # Output should be in the form of uuid5 + # Output should be in the form of uuid3 self.assertEqual(output, str(uuid_output)) self.assertEqual(uuid_output.version, 3) @@ -1260,18 +1264,22 @@ def test_cli_uuid5_ouputted_with_valid_namespace_and_name(self): self.assertEqual(output, str(uuid_output)) self.assertEqual(uuid_output.version, 5) - @mock.patch.object(sys, "argv", ["", "-u", "uuid5", "-n", "any UUID", "-N", "python.org"]) + @mock.patch.object(sys, "argv", + ["", "-u", "uuid5", "-n", "any UUID", "-N", "python.org"]) @mock.patch('sys.stderr', new_callable=io.StringIO) def test_cli_uuid5_with_invalid_namespace(self, mock_err): with self.assertRaises(SystemExit) as cm: self.uuid.main() # Check that exception code is the same as argparse.ArgumentParser.error self.assertEqual(cm.exception.code, 2) - self.assertIn("error: badly formed hexadecimal UUID string", mock_err.getvalue()) + self.assertIn("error: badly formed hexadecimal UUID string", + mock_err.getvalue()) @mock.patch.object(sys, "argv", - ["", "-u", "uuid5", "-n", "0d6a16cc-34a7-47d8-b660-214d0ae184d2", "-N", "some.user"]) - def test_cli_uuid5_ouputted_with_user_provided_namespace_and_name(self): + ["", "-u", "uuid5", "-n", + "0d6a16cc-34a7-47d8-b660-214d0ae184d2", + "-N", "some.user"]) + def test_cli_uuid5_ouputted_with_custom_namespace_and_name(self): stdout = io.StringIO() with contextlib.redirect_stdout(stdout): self.uuid.main() From b84e504654f402592d2a2e6bc38f42221fdd2fbd Mon Sep 17 00:00:00 2001 From: Jansen Price Date: Tue, 21 Apr 2026 08:03:48 -0500 Subject: [PATCH 4/4] Reorder uuid cli tests and fix comment typo --- Lib/test/test_uuid.py | 62 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index e85a8366ed92ae..17f9abdee515e0 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1182,36 +1182,9 @@ def test_cli_name_required_for_uuid3(self, mock_err): self.assertEqual(cm.exception.code, 2) self.assertIn("error: Incorrect number of arguments", mock_err.getvalue()) - @mock.patch.object(sys, "argv", [""]) - def test_cli_uuid4_outputted_with_no_args(self): - stdout = io.StringIO() - with contextlib.redirect_stdout(stdout): - self.uuid.main() - - output = stdout.getvalue().strip() - uuid_output = self.uuid.UUID(output) - - # Output uuid should be in the format of uuid4 - self.assertEqual(output, str(uuid_output)) - self.assertEqual(uuid_output.version, 4) - - @mock.patch.object(sys, "argv", ["", "-C", "3"]) - def test_cli_uuid4_outputted_with_count(self): - stdout = io.StringIO() - with contextlib.redirect_stdout(stdout): - self.uuid.main() - - output = stdout.getvalue().strip().splitlines() - - # Check that 3 UUIDs in the format of uuid4 have been generated - self.assertEqual(len(output), 3) - for o in output: - uuid_output = self.uuid.UUID(o) - self.assertEqual(uuid_output.version, 4) - @mock.patch.object(sys, "argv", ["", "-u", "uuid3", "-n", "@dns", "-N", "python.org"]) - def test_cli_uuid3_ouputted_with_valid_namespace_and_name(self): + def test_cli_uuid3_outputted_with_valid_namespace_and_name(self): stdout = io.StringIO() with contextlib.redirect_stdout(stdout): self.uuid.main() @@ -1219,7 +1192,7 @@ def test_cli_uuid3_ouputted_with_valid_namespace_and_name(self): output = stdout.getvalue().strip() uuid_output = self.uuid.UUID(output) - # Output should be in the form of uuid5 + # Output should be in the form of uuid3 self.assertEqual(output, str(uuid_output)) self.assertEqual(uuid_output.version, 3) @@ -1238,7 +1211,7 @@ def test_cli_uuid3_with_invalid_namespace(self, mock_err): ["", "-u", "uuid3", "-n", "0d6a16cc-34a7-47d8-b660-214d0ae184d2", "-N", "some.user"]) - def test_cli_uuid3_ouputted_with_custom_namespace_and_name(self): + def test_cli_uuid3_outputted_with_custom_namespace_and_name(self): stdout = io.StringIO() with contextlib.redirect_stdout(stdout): self.uuid.main() @@ -1250,9 +1223,36 @@ def test_cli_uuid3_ouputted_with_custom_namespace_and_name(self): self.assertEqual(output, str(uuid_output)) self.assertEqual(uuid_output.version, 3) + @mock.patch.object(sys, "argv", [""]) + def test_cli_uuid4_outputted_with_no_args(self): + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + self.uuid.main() + + output = stdout.getvalue().strip() + uuid_output = self.uuid.UUID(output) + + # Output uuid should be in the format of uuid4 + self.assertEqual(output, str(uuid_output)) + self.assertEqual(uuid_output.version, 4) + + @mock.patch.object(sys, "argv", ["", "-C", "3"]) + def test_cli_uuid4_outputted_with_count(self): + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + self.uuid.main() + + output = stdout.getvalue().strip().splitlines() + + # Check that 3 UUIDs in the format of uuid4 have been generated + self.assertEqual(len(output), 3) + for o in output: + uuid_output = self.uuid.UUID(o) + self.assertEqual(uuid_output.version, 4) + @mock.patch.object(sys, "argv", ["", "-u", "uuid5", "-n", "@dns", "-N", "python.org"]) - def test_cli_uuid5_ouputted_with_valid_namespace_and_name(self): + def test_cli_uuid5_outputted_with_valid_namespace_and_name(self): stdout = io.StringIO() with contextlib.redirect_stdout(stdout): self.uuid.main()