[Python-checkins] GH-88597: Added command line interface to UUID module. (#99463)

gpshead webhook-mailer at python.org
Sun Jan 22 01:59:58 EST 2023


https://github.com/python/cpython/commit/95f5b05a8cad61b296807c14e50896075c4dc1de
commit: 95f5b05a8cad61b296807c14e50896075c4dc1de
branch: main
author: achhina <aman.s.chhina at gmail.com>
committer: gpshead <greg at krypto.org>
date: 2023-01-21T22:59:31-08:00
summary:

GH-88597: Added command line interface to UUID module. (#99463)

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]
...
```

files:
A Misc/NEWS.d/next/Library/2022-11-14-03-06-03.gh-issue-88597.EYJA-Q.rst
M Doc/library/uuid.rst
M Doc/whatsnew/3.12.rst
M Lib/test/test_uuid.py
M Lib/uuid.py
M Misc/ACKS

diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst
index a71fe7abf5b5..804884dee0a2 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>
+               --uuid <uuid>
+
+   Specify the function name to use to generate the uuid. By default :func:`uuid4`
+   is used.
+
+.. cmdoption:: -ns <namespace>
+               --namespace <namespace>
+
+   The namespace used as part of generating the uuid. Only required for
+   :func:`uuid3` / :func:`uuid5` functions.
+
+.. cmdoption:: -n <name>
+               --name <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 4d6d4669d0c7..851105f004ba 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 <uuid-cli>`.
+  (Contributed by Adam Chhina in :gh:`88597`.)
+
 tempfile
 --------
 
diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py
index 411eec0f4062..61ae2567c90d 100755
--- a/Lib/test/test_uuid.py
+++ b/Lib/test/test_uuid.py
@@ -675,6 +675,64 @@ def test_uuid_weakref(self):
         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 e863b631877f..2904b9c4af64 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 768034516d32..74abcebe21ea 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 000000000000..a98e1ab4d157
--- /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``.



More information about the Python-checkins mailing list