)]}'
{"id":"openstack%2Fcliff~772854","triplet_id":"openstack%2Fcliff~master~I4803051a6dd05c143a15923254af97e32cd39693","project":"openstack/cliff","branch":"master","topic":"sort-dir","hashtags":[],"change_id":"I4803051a6dd05c143a15923254af97e32cd39693","subject":"Handle null values when sorting","status":"MERGED","created":"2021-01-28 11:03:01.000000000","updated":"2021-02-09 18:48:10.000000000","submitted":"2021-02-09 18:35:31.000000000","submitter":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"total_comment_count":5,"unresolved_comment_count":0,"has_review_started":true,"submission_id":"772854-sort-dir","meta_rev_id":"af27c7406e029a9edaf03e6ad0f8f0cf771b4d90","_number":772854,"virtual_id_number":772854,"owner":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"actions":{},"labels":{"Verified":{"approved":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"all":[{"value":0,"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},{"tag":"autogenerated:zuul:gate","value":2,"date":"2021-02-09 18:35:23.000000000","permitted_voting_range":{"min":2,"max":2},"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},{"value":0,"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"}],"values":{"-2":"Fails","-1":"Doesn\u0027t seem to work"," 0":"No score","+1":"Works for me","+2":"Verified"},"description":"","default_value":0,"optional":true},"Code-Review":{"approved":{"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},"all":[{"value":2,"date":"2021-01-29 15:59:16.000000000","permitted_voting_range":{"min":2,"max":2},"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},{"value":0,"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},{"value":2,"date":"2021-01-29 16:05:21.000000000","permitted_voting_range":{"min":2,"max":2},"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"}],"values":{"-2":"Do not merge","-1":"This patch needs further work before it can be merged"," 0":"No score","+1":"Looks good to me, but someone else must approve","+2":"Looks good to me (core reviewer)"},"description":"","default_value":0,"optional":true},"Workflow":{"approved":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"all":[{"value":0,"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},{"value":0,"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},{"value":1,"date":"2021-01-29 16:05:21.000000000","permitted_voting_range":{"min":1,"max":1},"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"}],"values":{"-1":"Work in progress"," 0":"Ready for reviews","+1":"Approved"},"description":"","default_value":0,"optional":true}},"removable_reviewers":[],"reviewers":{"REVIEWER":[{"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"}]},"pending_reviewers":{},"reviewer_updates":[{"updated":"2021-01-28 13:24:57.000000000","updated_by":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"reviewer":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"state":"REVIEWER"},{"updated":"2021-01-28 15:29:40.000000000","updated_by":{"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},"reviewer":{"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},"state":"CC"},{"updated":"2021-01-29 15:59:16.000000000","updated_by":{"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},"reviewer":{"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},"state":"REVIEWER"},{"updated":"2021-01-29 16:05:21.000000000","updated_by":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"reviewer":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"state":"REVIEWER"}],"messages":[{"id":"9cbcf8a38ad616932891b97667bbbdaa393a3345","tag":"autogenerated:gerrit:newPatchSet","author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"date":"2021-01-28 11:03:01.000000000","message":"Uploaded patch set 1.","accounts_in_message":[],"_revision_number":1},{"id":"c91aa7fb1549524202916d92e6294dcfdaa43db8","tag":"autogenerated:zuul:check","author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"date":"2021-01-28 13:24:57.000000000","message":"Patch Set 1: Verified+1\n\nBuild succeeded (check pipeline).\n\n- tempest-full-py3 https://zuul.opendev.org/t/openstack/build/d872680b1557469ea1e4dcd162ff42ab : SUCCESS in 1h 45m 25s\n- openstack-tox-pep8 https://zuul.opendev.org/t/openstack/build/c631c0748ce04d0cb3a97d28177fa4b5 : SUCCESS in 2m 58s\n- openstack-tox-py36 https://zuul.opendev.org/t/openstack/build/c615ee4aa95048cba70e6e88d1324349 : SUCCESS in 3m 12s\n- openstack-tox-py38 https://zuul.opendev.org/t/openstack/build/052bc977053f4a0f96d3ac53d78c2cb2 : SUCCESS in 3m 45s\n- openstack-tox-py39 https://zuul.opendev.org/t/openstack/build/26c940685969494284ab0ab37e986b82 : SUCCESS in 4m 05s (non-voting)\n- openstack-tox-docs https://zuul.opendev.org/t/openstack/build/5f46ff8529904a4f91b62dd610df5e34 : SUCCESS in 3m 25s\n- cliff-tox-py37-neutronclient-tip https://zuul.opendev.org/t/openstack/build/eded15e3fb6a4f42ace7645378073d03 : SUCCESS in 3m 54s\n- osc-tox-py36-tips https://zuul.opendev.org/t/openstack/build/c4e451281e0d4d95a2694a3b5f424b0f : SUCCESS in 5m 59s","accounts_in_message":[],"_revision_number":1},{"id":"8e454f8e36886e7164b3d87bc827b4b7fcabf4d2","author":{"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},"date":"2021-01-28 15:29:40.000000000","message":"Patch Set 1:\n\n(1 comment)","accounts_in_message":[],"_revision_number":1},{"id":"e97b5c0bc27633fcdcc9740a682c5287173b38d9","tag":"autogenerated:gerrit:newPatchSet","author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"date":"2021-01-28 16:14:06.000000000","message":"Uploaded patch set 2.","accounts_in_message":[],"_revision_number":2},{"id":"2bd029095cfe7171bb60771c96e2718d92401ce0","author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"date":"2021-01-28 16:18:25.000000000","message":"Patch Set 2:\n\n(1 comment)","accounts_in_message":[],"_revision_number":2},{"id":"5f0018ba729a57e874e3c964976b643a95145b01","author":{"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},"date":"2021-01-28 17:33:57.000000000","message":"Patch Set 2:\n\n(1 comment)","accounts_in_message":[],"_revision_number":2},{"id":"352581807a3465ef460b4fe41eb7b25416475416","tag":"autogenerated:zuul:check","author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"date":"2021-01-28 18:20:15.000000000","message":"Patch Set 2: Verified+1\n\nBuild succeeded (check pipeline).\n\n- tempest-full-py3 https://zuul.opendev.org/t/openstack/build/82b1d0204ad1426e9f9e360ba3d19ba2 : SUCCESS in 2h 03m 18s\n- openstack-tox-pep8 https://zuul.opendev.org/t/openstack/build/7edf3a44c70046b6a17997b9ce1c628a : SUCCESS in 3m 39s\n- openstack-tox-py36 https://zuul.opendev.org/t/openstack/build/fa0a603fe01d4f5a97fdb3d1fb4306fb : SUCCESS in 3m 51s\n- openstack-tox-py38 https://zuul.opendev.org/t/openstack/build/8039b5e22d7443a3a98ca11fdaf08c1f : SUCCESS in 3m 42s\n- openstack-tox-py39 https://zuul.opendev.org/t/openstack/build/8818dd766cf2426ea555f10e74d5aa30 : SUCCESS in 3m 48s (non-voting)\n- openstack-tox-docs https://zuul.opendev.org/t/openstack/build/f8c3497484a4439999a4072bb875e054 : SUCCESS in 3m 31s\n- cliff-tox-py37-neutronclient-tip https://zuul.opendev.org/t/openstack/build/4ab8326d19444b1c8b59ca2dd46d506f : SUCCESS in 3m 45s\n- osc-tox-py36-tips https://zuul.opendev.org/t/openstack/build/0e1b704683194b8eab7c777565bce8de : SUCCESS in 4m 49s","accounts_in_message":[],"_revision_number":2},{"id":"a976feb8ce9c91f707dc17660656ee9da7904089","tag":"autogenerated:gerrit:newPatchSet","author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"date":"2021-01-29 15:40:54.000000000","message":"Uploaded patch set 3.","accounts_in_message":[],"_revision_number":3},{"id":"fa4ecbf3be5c67c8df25091db018e19dad02e0f0","author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"date":"2021-01-29 15:53:24.000000000","message":"Patch Set 3:\n\n(1 comment)","accounts_in_message":[],"_revision_number":3},{"id":"816b4deed03cbf86f787079df3f466a208302ec9","author":{"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},"date":"2021-01-29 15:57:59.000000000","message":"Patch Set 3:\n\n(1 comment)","accounts_in_message":[],"_revision_number":3},{"id":"8df9cac4df563de18ca1322cfc7b1607d9fdebd1","author":{"_account_id":2472,"name":"Doug Hellmann","email":"dhellmann@redhat.com","username":"doug-hellmann"},"date":"2021-01-29 15:59:16.000000000","message":"Patch Set 3: Code-Review+2","accounts_in_message":[],"_revision_number":3},{"id":"91df33c1b34213af2f1bcf13832cce1ec908344e","author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"date":"2021-01-29 16:05:21.000000000","message":"Patch Set 3: Code-Review+2 Workflow+1","accounts_in_message":[],"_revision_number":3},{"id":"a4d3b460f5004096b532bd91fe5bfacf38230ffd","tag":"autogenerated:zuul:check","author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"date":"2021-01-29 17:10:08.000000000","message":"Patch Set 3: Verified+1\n\nBuild succeeded (check pipeline).\n\n- tempest-full-py3 https://zuul.opendev.org/t/openstack/build/703e362bcfff40efa294bb64306f620c : SUCCESS in 1h 24m 36s\n- openstack-tox-pep8 https://zuul.opendev.org/t/openstack/build/885d6b85e5e3414ead4fc38071d8d7fd : SUCCESS in 3m 27s\n- openstack-tox-py36 https://zuul.opendev.org/t/openstack/build/cbe167ac6a61486eb91efa5b53c6bac3 : SUCCESS in 3m 47s\n- openstack-tox-py38 https://zuul.opendev.org/t/openstack/build/1f43500cbc5a4bed8c130084a7c28bf9 : SUCCESS in 3m 47s\n- openstack-tox-py39 https://zuul.opendev.org/t/openstack/build/ecf16969611e4cf586b3f351eb1d3e61 : SUCCESS in 3m 36s (non-voting)\n- openstack-tox-docs https://zuul.opendev.org/t/openstack/build/c0027e6da5c046fd88f9d6f9e7740655 : SUCCESS in 3m 04s\n- cliff-tox-py37-neutronclient-tip https://zuul.opendev.org/t/openstack/build/c88b5999d0a242cba16cb8ce17de4683 : SUCCESS in 4m 07s\n- osc-tox-py36-tips https://zuul.opendev.org/t/openstack/build/ae9de6db3afd4465b90dc12ddfedd9b3 : SUCCESS in 4m 32s","accounts_in_message":[],"_revision_number":3},{"id":"1ab72895021a1ed27990574b8f39b4e3c43dfd85","tag":"autogenerated:zuul:gate","author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"date":"2021-02-09 16:41:35.000000000","message":"Patch Set 3: -Verified\n\nStarting gate jobs.","accounts_in_message":[],"_revision_number":3},{"id":"ace4d7a630732f24e4e001eca4f6b7d5846b025e","tag":"autogenerated:zuul:gate","author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"date":"2021-02-09 18:35:23.000000000","message":"Patch Set 3: Verified+2\n\nBuild succeeded (gate pipeline).\n\n- tempest-full-py3 https://zuul.opendev.org/t/openstack/build/49573b28290f4302bd5282b9e5b925c5 : SUCCESS in 1h 30m 10s\n- openstack-tox-pep8 https://zuul.opendev.org/t/openstack/build/066a9c2a67fe42a49d8d76368116aa4b : SUCCESS in 3m 11s\n- openstack-tox-py36 https://zuul.opendev.org/t/openstack/build/cafa8420a1c94e94bcc78c9d29263c6b : SUCCESS in 3m 33s\n- openstack-tox-py38 https://zuul.opendev.org/t/openstack/build/bf6eda93e49e4b31991408379efae769 : SUCCESS in 3m 46s\n- openstack-tox-docs https://zuul.opendev.org/t/openstack/build/37ad6e0be7924a56b167a5f547f8f39c : SUCCESS in 3m 36s\n- cliff-tox-py37-neutronclient-tip https://zuul.opendev.org/t/openstack/build/4e7c0ae64e6b46f889838246635c4a47 : SUCCESS in 4m 22s\n- osc-tox-py36-tips https://zuul.opendev.org/t/openstack/build/9f559e02c01d408296fd6e6433ddf1ce : SUCCESS in 5m 50s","accounts_in_message":[],"_revision_number":3},{"id":"b88ee2b0a9204d0bb8332e3d598e163376f82c54","tag":"autogenerated:gerrit:merged","author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"date":"2021-02-09 18:35:31.000000000","message":"Change has been successfully merged by Zuul","accounts_in_message":[],"_revision_number":3},{"id":"af27c7406e029a9edaf03e6ad0f8f0cf771b4d90","tag":"autogenerated:zuul:promote","author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"date":"2021-02-09 18:48:10.000000000","message":"Patch Set 3:\n\nBuild succeeded (promote pipeline).\n\n- promote-openstack-tox-docs https://zuul.opendev.org/t/openstack/build/88597f5403cf41fbb04111e5ac19c9ef : SUCCESS in 1m 34s","accounts_in_message":[],"_revision_number":3}],"current_revision_number":3,"current_revision":"4f45f9a30e657265c5b5ac119af3aaa0e5ec7184","revisions":{"609b2fc987664b77a51ab9f099461440a04202f9":{"kind":"REWORK","_number":1,"created":"2021-01-28 11:03:01.000000000","uploader":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"ref":"refs/changes/54/772854/1","fetch":{"anonymous http":{"url":"https://review.opendev.org/openstack/cliff","ref":"refs/changes/54/772854/1","commands":{"Checkout":"git fetch https://review.opendev.org/openstack/cliff refs/changes/54/772854/1 \u0026\u0026 git checkout FETCH_HEAD","Cherry Pick":"git fetch https://review.opendev.org/openstack/cliff refs/changes/54/772854/1 \u0026\u0026 git cherry-pick FETCH_HEAD","Format Patch":"git fetch https://review.opendev.org/openstack/cliff refs/changes/54/772854/1 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD","Pull":"git pull https://review.opendev.org/openstack/cliff refs/changes/54/772854/1"}}},"commit":{"parents":[{"commit":"36134739396213fa11d1c4ca1e9cacb8f40215eb","subject":"gitignore: Ignore reno artefacts","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/cliff/commit/36134739396213fa11d1c4ca1e9cacb8f40215eb"}]}],"author":{"name":"Stephen Finucane","email":"sfinucan@redhat.com","date":"2021-01-27 17:31:22.000000000","tz":0},"committer":{"name":"Stephen Finucane","email":"sfinucan@redhat.com","date":"2021-01-28 11:00:31.000000000","tz":0},"subject":"Handle null values when sorting","message":"Handle null values when sorting\n\nOne unfortunate change (or fortunate, depending on how you look at\ntypes) in Python 3 is the inability to sort iterables of different\ntypes. For example:\n\n  \u003e\u003e\u003e x \u003d [\u0027foo\u0027, \u0027bar\u0027, None, \u0027qux\u0027]\n  \u003e\u003e\u003e sorted(x)\n  Traceback (most recent call last):\n    File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\n  TypeError: \u0027\u003c\u0027 not supported between instances of \u0027NoneType\u0027 and \u0027str\u0027\n\nFortunately, we can take advantage of the fact that by providing a\nfunction for the \u0027key\u0027 that returns a tuple, we can sort on multiple\nconditions. In this case, \"when the first key returns that two elements\nare equal, the second key is used to compare.\" [1] We can use this to\nfirst separate the values by whether they are None or not, punting those\nthat are not to the end, before sorting the non-None values normally.\nFor example:\n\n  \u003e\u003e\u003e x \u003d [\u0027foo\u0027, \u0027bar\u0027, None, \u0027qux\u0027]\n  \u003e\u003e\u003e sorted(x, key\u003dlambda k: (k is None, k))\n  [\u0027bar\u0027, \u0027foo\u0027, \u0027qux\u0027, None]\n\nWe were already using this feature implicitly through our use of\n\u0027operator.itemgetter(*indexes)\u0027, which will return a tuple if there is\nmore than one item in \u0027indexes\u0027, and now we simply make that explicit,\nfixing the case where we\u0027re attempting to compare a comparable type\nwith None. For all other cases, such as comparing a value that isn\u0027t\ncomparable, we surround things with a try-catch and a debug logging\nstatement to allow things to continue.\n\nNote that we could optimize what we\u0027re done further by building a key\nvalue that covers all indexes, rather than using a for loop to do so.\nFor example:\n\n  \u003e\u003e\u003e x \u003d [(\u0027baz\u0027, 2), (None, 0), (\u0027bar\u0027, 3), (\u0027baz\u0027, 4), (\u0027qux\u0027, 0)]\n  \u003e\u003e\u003e sorted(x, key\u003dlambda k: list(\n  ...     itertools.chain((k[i] is None, k[i]) for i in (0, 1)))\n  ... )\n  [(\u0027bar\u0027, 3), (\u0027baz\u0027, 2), (\u0027baz\u0027, 4), (\u0027qux\u0027, 0), (None, 0)]\n\nHowever, this would be harder to grok and would also mean we\u0027re unable\nto handle exceptions on a single column where e.g. there are mixed types\nor types that are not comparable while still sorting on the other\ncolumns. Perhaps this would be desirable for some users, but sorting on\na best-effort basis does seem wiser and generally more user friendly.\nAnyone that wants to sort on such columns should ensure their types are\ncomparable or implement their own sorting implementation.\n\n[1] https://www.kite.com/python/answers/how-to-sort-by-two-keys-in-python\n\nChange-Id: I4803051a6dd05c143a15923254af97e32cd39693\nSigned-off-by: Stephen Finucane \u003csfinucan@redhat.com\u003e\nStory: 2008456\nTask: 41466\n","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/cliff/commit/609b2fc987664b77a51ab9f099461440a04202f9"}],"resolve_conflicts_web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/cliff/commit/609b2fc987664b77a51ab9f099461440a04202f9"}]},"branch":"refs/heads/master"},"e86f653b91c210ddcc9136d8fba61ae1c44b8e92":{"kind":"REWORK","_number":2,"created":"2021-01-28 16:14:06.000000000","uploader":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"ref":"refs/changes/54/772854/2","fetch":{"anonymous http":{"url":"https://review.opendev.org/openstack/cliff","ref":"refs/changes/54/772854/2","commands":{"Checkout":"git fetch https://review.opendev.org/openstack/cliff refs/changes/54/772854/2 \u0026\u0026 git checkout FETCH_HEAD","Cherry Pick":"git fetch https://review.opendev.org/openstack/cliff refs/changes/54/772854/2 \u0026\u0026 git cherry-pick FETCH_HEAD","Format Patch":"git fetch https://review.opendev.org/openstack/cliff refs/changes/54/772854/2 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD","Pull":"git pull https://review.opendev.org/openstack/cliff refs/changes/54/772854/2"}}},"commit":{"parents":[{"commit":"36134739396213fa11d1c4ca1e9cacb8f40215eb","subject":"gitignore: Ignore reno artefacts","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/cliff/commit/36134739396213fa11d1c4ca1e9cacb8f40215eb"}]}],"author":{"name":"Stephen Finucane","email":"sfinucan@redhat.com","date":"2021-01-27 17:31:22.000000000","tz":0},"committer":{"name":"Stephen Finucane","email":"sfinucan@redhat.com","date":"2021-01-28 16:13:47.000000000","tz":0},"subject":"Handle null values when sorting","message":"Handle null values when sorting\n\nOne unfortunate change (or fortunate, depending on how you look at\ntypes) in Python 3 is the inability to sort iterables of different\ntypes. For example:\n\n  \u003e\u003e\u003e x \u003d [\u0027foo\u0027, \u0027bar\u0027, None, \u0027qux\u0027]\n  \u003e\u003e\u003e sorted(x)\n  Traceback (most recent call last):\n    File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\n  TypeError: \u0027\u003c\u0027 not supported between instances of \u0027NoneType\u0027 and \u0027str\u0027\n\nFortunately, we can take advantage of the fact that by providing a\nfunction for the \u0027key\u0027 that returns a tuple, we can sort on multiple\nconditions. In this case, \"when the first key returns that two elements\nare equal, the second key is used to compare.\" [1] We can use this to\nfirst separate the values by whether they are None or not, punting those\nthat are not to the end, before sorting the non-None values normally.\nFor example:\n\n  \u003e\u003e\u003e x \u003d [\u0027foo\u0027, \u0027bar\u0027, None, \u0027qux\u0027]\n  \u003e\u003e\u003e sorted(x, key\u003dlambda k: (k is None, k))\n  [\u0027bar\u0027, \u0027foo\u0027, \u0027qux\u0027, None]\n\nWe were already using this feature implicitly through our use of\n\u0027operator.itemgetter(*indexes)\u0027, which will return a tuple if there is\nmore than one item in \u0027indexes\u0027, and now we simply make that explicit,\nfixing the case where we\u0027re attempting to compare a comparable type\nwith None. For all other cases, such as comparing a value that isn\u0027t\ncomparable, we surround things with a try-catch and a debug logging\nstatement to allow things to continue.\n\nNote that we could optimize what we\u0027re done further by building a key\nvalue that covers all indexes, rather than using a for loop to do so.\nFor example:\n\n  \u003e\u003e\u003e x \u003d [(\u0027baz\u0027, 2), (None, 0), (\u0027bar\u0027, 3), (\u0027baz\u0027, 4), (\u0027qux\u0027, 0)]\n  \u003e\u003e\u003e sorted(x, key\u003dlambda k: list(\n  ...     itertools.chain((k[i] is None, k[i]) for i in (0, 1)))\n  ... )\n  [(\u0027bar\u0027, 3), (\u0027baz\u0027, 2), (\u0027baz\u0027, 4), (\u0027qux\u0027, 0), (None, 0)]\n\nHowever, this would be harder to grok and would also mean we\u0027re unable\nto handle exceptions on a single column where e.g. there are mixed types\nor types that are not comparable while still sorting on the other\ncolumns. Perhaps this would be desirable for some users, but sorting on\na best-effort basis does seem wiser and generally more user friendly.\nAnyone that wants to sort on such columns should ensure their types are\ncomparable or implement their own sorting implementation.\n\n[1] https://www.kite.com/python/answers/how-to-sort-by-two-keys-in-python\n\nChange-Id: I4803051a6dd05c143a15923254af97e32cd39693\nSigned-off-by: Stephen Finucane \u003csfinucan@redhat.com\u003e\nStory: 2008456\nTask: 41466\n","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/cliff/commit/e86f653b91c210ddcc9136d8fba61ae1c44b8e92"}],"resolve_conflicts_web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/cliff/commit/e86f653b91c210ddcc9136d8fba61ae1c44b8e92"}]},"branch":"refs/heads/master"},"4f45f9a30e657265c5b5ac119af3aaa0e5ec7184":{"kind":"REWORK","_number":3,"created":"2021-01-29 15:40:54.000000000","uploader":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"ref":"refs/changes/54/772854/3","fetch":{"anonymous http":{"url":"https://review.opendev.org/openstack/cliff","ref":"refs/changes/54/772854/3","commands":{"Checkout":"git fetch https://review.opendev.org/openstack/cliff refs/changes/54/772854/3 \u0026\u0026 git checkout FETCH_HEAD","Cherry Pick":"git fetch https://review.opendev.org/openstack/cliff refs/changes/54/772854/3 \u0026\u0026 git cherry-pick FETCH_HEAD","Format Patch":"git fetch https://review.opendev.org/openstack/cliff refs/changes/54/772854/3 \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD","Pull":"git pull https://review.opendev.org/openstack/cliff refs/changes/54/772854/3"}}},"commit":{"parents":[{"commit":"36134739396213fa11d1c4ca1e9cacb8f40215eb","subject":"gitignore: Ignore reno artefacts","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/cliff/commit/36134739396213fa11d1c4ca1e9cacb8f40215eb"}]}],"author":{"name":"Stephen Finucane","email":"sfinucan@redhat.com","date":"2021-01-27 17:31:22.000000000","tz":0},"committer":{"name":"Stephen Finucane","email":"sfinucan@redhat.com","date":"2021-01-29 15:40:31.000000000","tz":0},"subject":"Handle null values when sorting","message":"Handle null values when sorting\n\nOne unfortunate change (or fortunate, depending on how you look at\ntypes) in Python 3 is the inability to sort iterables of different\ntypes. For example:\n\n  \u003e\u003e\u003e x \u003d [\u0027foo\u0027, \u0027bar\u0027, None, \u0027qux\u0027]\n  \u003e\u003e\u003e sorted(x)\n  Traceback (most recent call last):\n    File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\n  TypeError: \u0027\u003c\u0027 not supported between instances of \u0027NoneType\u0027 and \u0027str\u0027\n\nFortunately, we can take advantage of the fact that by providing a\nfunction for the \u0027key\u0027 that returns a tuple, we can sort on multiple\nconditions. In this case, \"when the first key returns that two elements\nare equal, the second key is used to compare.\" [1] We can use this to\nfirst separate the values by whether they are None or not, punting those\nthat are not to the end, before sorting the non-None values normally.\nFor example:\n\n  \u003e\u003e\u003e x \u003d [\u0027foo\u0027, \u0027bar\u0027, None, \u0027qux\u0027]\n  \u003e\u003e\u003e sorted(x, key\u003dlambda k: (k is None, k))\n  [\u0027bar\u0027, \u0027foo\u0027, \u0027qux\u0027, None]\n\nWe were already using this feature implicitly through our use of\n\u0027operator.itemgetter(*indexes)\u0027, which will return a tuple if there is\nmore than one item in \u0027indexes\u0027, and now we simply make that explicit,\nfixing the case where we\u0027re attempting to compare a comparable type\nwith None. For all other cases, such as comparing a value that isn\u0027t\ncomparable, we surround things with a try-catch and a debug logging\nstatement to allow things to continue.\n\nNote that we could optimize what we\u0027re done further by building a key\nvalue that covers all indexes, rather than using a for loop to do so.\nFor example:\n\n  \u003e\u003e\u003e x \u003d [(\u0027baz\u0027, 2), (None, 0), (\u0027bar\u0027, 3), (\u0027baz\u0027, 4), (\u0027qux\u0027, 0)]\n  \u003e\u003e\u003e sorted(x, key\u003dlambda k: list(\n  ...     itertools.chain((k[i] is None, k[i]) for i in (0, 1)))\n  ... )\n  [(\u0027bar\u0027, 3), (\u0027baz\u0027, 2), (\u0027baz\u0027, 4), (\u0027qux\u0027, 0), (None, 0)]\n\nHowever, this would be harder to grok and would also mean we\u0027re unable\nto handle exceptions on a single column where e.g. there are mixed types\nor types that are not comparable while still sorting on the other\ncolumns. Perhaps this would be desirable for some users, but sorting on\na best-effort basis does seem wiser and generally more user friendly.\nAnyone that wants to sort on such columns should ensure their types are\ncomparable or implement their own sorting implementation.\n\n[1] https://www.kite.com/python/answers/how-to-sort-by-two-keys-in-python\n\nChange-Id: I4803051a6dd05c143a15923254af97e32cd39693\nSigned-off-by: Stephen Finucane \u003csfinucan@redhat.com\u003e\nStory: 2008456\nTask: 41466\n","web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/cliff/commit/4f45f9a30e657265c5b5ac119af3aaa0e5ec7184"}],"resolve_conflicts_web_links":[{"name":"gitea","tooltip":"Open in GitWeb","url":"https://opendev.org/openstack/cliff/commit/4f45f9a30e657265c5b5ac119af3aaa0e5ec7184"}]},"branch":"refs/heads/master"}},"requirements":[],"submit_records":[{"status":"CLOSED","labels":[{"label":"Verified","status":"OK","applied_by":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]}},{"label":"Code-Review","status":"OK","applied_by":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"}},{"label":"Workflow","status":"OK","applied_by":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"}}]}],"submit_requirements":[{"name":"Verified","status":"SATISFIED","is_legacy":true,"submittability_expression_result":{"expression":"label:Verified\u003dCustom-Rule","fulfilled":true,"status":"PASS","passing_atoms":["label:Verified\u003dCustom-Rule"],"failing_atoms":[],"atom_explanations":{}}},{"name":"Workflow","status":"SATISFIED","is_legacy":true,"submittability_expression_result":{"expression":"label:Workflow\u003dCustom-Rule","fulfilled":true,"status":"PASS","passing_atoms":["label:Workflow\u003dCustom-Rule"],"failing_atoms":[],"atom_explanations":{}}},{"name":"Code-Review","status":"SATISFIED","is_legacy":true,"submittability_expression_result":{"expression":"label:Code-Review\u003dCustom-Rule","fulfilled":true,"status":"PASS","passing_atoms":["label:Code-Review\u003dCustom-Rule"],"failing_atoms":[],"atom_explanations":{}}}]}
