From 95f5b05a8cad61b296807c14e50896075c4dc1de Mon Sep 17 00:00:00 2001 From: achhina Date: Sun, 22 Jan 2023 01:59:31 -0500 Subject: GH-88597: Added command line interface to UUID module. (#99463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `uuid` module now supports command line usage. ```python ❯ ./python.exe -m uuid 5f2d57b1-90e8-417c-ba5d-69b9b6f74289 ❯ ./python.exe -m uuid -h usage: uuid.py [-h] [-u {uuid1,uuid3,uuid4,uuid5}] [-ns NAMESPACE] [-n NAME] ... ``` --- Doc/library/uuid.rst | 59 ++++++++++++++++++++++ Doc/whatsnew/3.12.rst | 6 +++ Lib/test/test_uuid.py | 58 +++++++++++++++++++++ Lib/uuid.py | 49 ++++++++++++++++++ Misc/ACKS | 1 + .../2022-11-14-03-06-03.gh-issue-88597.EYJA-Q.rst | 1 + 6 files changed, 174 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2022-11-14-03-06-03.gh-issue-88597.EYJA-Q.rst diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index a71fe7a..804884d 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -261,6 +261,46 @@ of the :attr:`variant` attribute: internal format of UUIDs, and methods of generating UUIDs. +.. _uuid-cli: + +Command-Line Usage +------------------ + +.. versionadded:: 3.12 + +The :mod:`uuid` module can be executed as a script from the command line. + +.. code-block:: sh + + python -m uuid [-h] [-u {uuid1,uuid3,uuid4,uuid5}] [-ns NAMESPACE] [-n NAME] + +The following options are accepted: + +.. program:: uuid + +.. cmdoption:: -h, --help + + Show the help message and exit. + +.. cmdoption:: -u + --uuid + + Specify the function name to use to generate the uuid. By default :func:`uuid4` + is used. + +.. cmdoption:: -ns + --namespace + + The namespace used as part of generating the uuid. Only required for + :func:`uuid3` / :func:`uuid5` functions. + +.. cmdoption:: -n + --name + + The name used as part of generating the uuid. Only required for + :func:`uuid3` / :func:`uuid5` functions. + + .. _uuid-example: Example @@ -301,3 +341,22 @@ Here are some examples of typical usage of the :mod:`uuid` module:: >>> uuid.UUID(bytes=x.bytes) UUID('00010203-0405-0607-0809-0a0b0c0d0e0f') + +.. _uuid-cli-example: + +Command-Line Example +-------------------- + +Here are some examples of typical usage of the :mod:`uuid` command line interface: + +.. code-block:: shell + + # generate a random uuid - by default uuid4() is used + $ python -m uuid + + # generate a uuid using uuid1() + $ python -m uuid -u uuid1 + + # generate a uuid using uuid5 + $ python -m uuid -u uuid5 -ns NAMESPACE_URL -n example.com + diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 4d6d466..851105f 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -327,6 +327,12 @@ unicodedata * The Unicode database has been updated to version 15.0.0. (Contributed by Benjamin Peterson in :gh:`96734`). +uuid +---- + +* Add a :ref:`command-line interface `. + (Contributed by Adam Chhina in :gh:`88597`.) + tempfile -------- diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 411eec0..61ae256 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -675,6 +675,64 @@ class BaseTestUUID: weak = weakref.ref(strong) self.assertIs(strong, weak()) + @mock.patch.object(sys, "argv", ["", "-u", "uuid3", "-ns", "NAMESPACE_DNS"]) + def test_cli_namespace_required_for_uuid3(self): + 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) + + @mock.patch.object(sys, "argv", ["", "-u", "uuid3", "-n", "python.org"]) + def test_cli_name_required_for_uuid3(self): + 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) + + @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", + ["", "-u", "uuid3", "-ns", "NAMESPACE_DNS", "-n", "python.org"]) + def test_cli_uuid3_ouputted_with_valid_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", "-ns", "NAMESPACE_DNS", "-n", "python.org"]) + def test_cli_uuid5_ouputted_with_valid_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) + + class TestUUIDWithoutExtModule(BaseTestUUID, unittest.TestCase): uuid = py_uuid diff --git a/Lib/uuid.py b/Lib/uuid.py index e863b63..2904b9c 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -728,9 +728,58 @@ def uuid5(namespace, name): hash = sha1(namespace.bytes + bytes(name, "utf-8")).digest() return UUID(bytes=hash[:16], version=5) + +def main(): + """Run the uuid command line interface.""" + uuid_funcs = {"uuid1": uuid1, + "uuid3": uuid3, + "uuid4": uuid4, + "uuid5": uuid5} + uuid_namespace_funcs = ("uuid3", "uuid5") + namespaces = { + "NAMESPACE_DNS": NAMESPACE_DNS, + "NAMESPACE_URL": NAMESPACE_URL, + "NAMESPACE_OID": NAMESPACE_OID, + "NAMESPACE_X500": NAMESPACE_X500 + } + + import argparse + parser = argparse.ArgumentParser( + description="Generates a uuid using the selected uuid function.") + parser.add_argument("-u", "--uuid", choices=uuid_funcs.keys(), default="uuid4", + help="The function to use to generate the uuid. " + "By default uuid4 function is used.") + parser.add_argument("-ns", "--namespace", + help="The namespace used as part of generating the uuid. " + "Only required for uuid3/uuid5 functions.") + parser.add_argument("-n", "--name", + help="The name used as part of generating the uuid. " + "Only required for uuid3/uuid5 functions.") + + args = parser.parse_args() + uuid_func = uuid_funcs[args.uuid] + namespace = args.namespace + name = args.name + + if args.uuid in uuid_namespace_funcs: + if not namespace or not name: + parser.error( + "Incorrect number of arguments. " + 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) + print(uuid_func(namespace, name)) + else: + print(uuid_func()) + + # The following standard UUIDs are for use with uuid3() or uuid5(). NAMESPACE_DNS = UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8') NAMESPACE_URL = UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8') NAMESPACE_OID = UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8') NAMESPACE_X500 = UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8') + +if __name__ == "__main__": + main() diff --git a/Misc/ACKS b/Misc/ACKS index 7680345..74abceb 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -315,6 +315,7 @@ Nicolas Chauvat Jerry Chen Michael Chermside Ingrid Cheung +Adam Chhina Terry Chia Albert Chin-A-Young Adal Chiriliuc diff --git a/Misc/NEWS.d/next/Library/2022-11-14-03-06-03.gh-issue-88597.EYJA-Q.rst b/Misc/NEWS.d/next/Library/2022-11-14-03-06-03.gh-issue-88597.EYJA-Q.rst new file mode 100644 index 0000000..a98e1ab --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-11-14-03-06-03.gh-issue-88597.EYJA-Q.rst @@ -0,0 +1 @@ +:mod:`uuid` now has a command line interface. Try ``python -m uuid -h``. -- cgit v0.12