Repository: softlayer/softlayer-python

Issue:
unable to pipe out the `slcli server list commands`
Running `slcli server list` works fine, but it throws an error when piping to another file (or grep)

`slcli vs list` can pipe to another unix commands fine.

`slcli server list | tee /tmp/a`

    An unexpected error has occured:
    Traceback (most recent call last):
      File "/usr/local/lib/python3.4/site-packages/SoftLayer-4.0.1-py3.4.egg/SoftLayer/CLI/core.py", line 180, in main
        cli.main()
      File "/usr/local/lib/python3.4/site-packages/click/core.py", line 644, in main
        rv = self.invoke(ctx)
      File "/usr/local/lib/python3.4/site-packages/click/core.py", line 991, in invoke
        return _process_result(sub_ctx.command.invoke(sub_ctx))
      File "/usr/local/lib/python3.4/site-packages/click/core.py", line 959, in _process_result
        **ctx.params)
      File "/usr/local/lib/python3.4/site-packages/click/core.py", line 464, in invoke
        return callback(*args, **kwargs)
      File "/usr/local/lib/python3.4/site-packages/SoftLayer-4.0.1-py3.4.egg/SoftLayer/CLI/core.py", line 162, in output_result
        output = env.fmt(result)
      File "/usr/local/lib/python3.4/site-packages/SoftLayer-4.0.1-py3.4.egg/SoftLayer/CLI/environment.py", line 45, in fmt
        return formatting.format_output(output, fmt=self.format)
      File "/usr/local/lib/python3.4/site-packages/SoftLayer-4.0.1-py3.4.egg/SoftLayer/CLI/formatting.py", line 38, in format_output
        return str(format_no_tty(data))
      File "/usr/local/lib/python3.4/site-packages/prettytable.py", line 237, in __str__
        return self.__unicode__()
      File "/usr/local/lib/python3.4/site-packages/prettytable.py", line 243, in __unicode__
        return self.get_string()
      File "/usr/local/lib/python3.4/site-packages/prettytable.py", line 984, in get_string
        rows = self._get_rows(options)
      File "/usr/local/lib/python3.4/site-packages/prettytable.py", line 933, in _get_rows
        rows.sort(reverse=options["reversesort"], key=options["sort_key"])
    TypeError: unorderable types: FormattedItem() < str()
    
    Feel free to report this error as it is likely a bug:
        https://github.com/softlayer/softlayer-python/issues





Discussion:

Patch:
diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py
index 138e5318..41556a9a 100644
--- a/SoftLayer/CLI/formatting.py
+++ b/SoftLayer/CLI/formatting.py
@@ -73,6 +73,7 @@ def format_prettytable(table):
     for i, row in enumerate(table.rows):
         for j, item in enumerate(row):
             table.rows[i][j] = format_output(item)
+
     ptable = table.prettytable()
     ptable.hrules = prettytable.FRAME
     ptable.horizontal_char = '.'
@@ -83,12 +84,15 @@ def format_prettytable(table):
 
 def format_no_tty(table):
     """Converts SoftLayer.CLI.formatting.Table instance to a prettytable."""
+
     for i, row in enumerate(table.rows):
         for j, item in enumerate(row):
             table.rows[i][j] = format_output(item, fmt='raw')
     ptable = table.prettytable()
+
     for col in table.columns:
         ptable.align[col] = 'l'
+
     ptable.hrules = prettytable.NONE
     ptable.border = False
     ptable.header = False
@@ -304,6 +308,30 @@ def __str__(self):
 
     __repr__ = __str__
 
+    # Implement sorting methods.
+    # NOTE(kmcdonald): functools.total_ordering could be used once support for
+    # Python 2.6 is dropped
+    def __eq__(self, other):
+        return self.original == getattr(other, 'original', other)
+
+    def __lt__(self, other):
+        if self.original is None:
+            return True
+
+        other_val = getattr(other, 'original', other)
+        if other_val is None:
+            return False
+        return self.original < other_val
+
+    def __gt__(self, other):
+        return not (self < other or self == other)
+
+    def __le__(self, other):
+        return self < other or self == other
+
+    def __ge__(self, other):
+        return not self < other
+
 
 def _format_python_value(value):
     """If the value has to_python() defined then return that."""
diff --git a/SoftLayer/CLI/server/list.py b/SoftLayer/CLI/server/list.py
index 03ba456e..d6f45622 100644
--- a/SoftLayer/CLI/server/list.py
+++ b/SoftLayer/CLI/server/list.py
@@ -14,7 +14,7 @@
 @click.command()
 @click.option('--sortby',
               help='Column to sort by',
-              type=click.Choice(['guid',
+              type=click.Choice(['id',
                                  'hostname',
                                  'primary_ip',
                                  'backend_ip',


Test patch:
diff --git a/SoftLayer/testing/fixtures/SoftLayer_Account.py b/SoftLayer/testing/fixtures/SoftLayer_Account.py
index 994d5fb6..9e1a43da 100644
--- a/SoftLayer/testing/fixtures/SoftLayer_Account.py
+++ b/SoftLayer/testing/fixtures/SoftLayer_Account.py
@@ -136,7 +136,7 @@
             'friendlyName': 'Friendly Transaction Name',
             'id': 6660
         }
-    }
+    },
 }, {
     'id': 1001,
     'globalIdentifier': '1a2b3c-1702',
@@ -224,6 +224,8 @@
             'id': 19082
         },
     ]
+}, {
+    'id': 1003,
 }]
 getDomains = [{'name': 'example.com',
                'id': 12345,
diff --git a/SoftLayer/tests/CLI/helper_tests.py b/SoftLayer/tests/CLI/helper_tests.py
index 1dea9ab4..e693535a 100644
--- a/SoftLayer/tests/CLI/helper_tests.py
+++ b/SoftLayer/tests/CLI/helper_tests.py
@@ -131,6 +131,27 @@ def test_blank(self):
         self.assertEqual('-', item.formatted)
         self.assertEqual('NULL', str(item))
 
+    def test_sort_mixed(self):
+        blank = formatting.blank()
+        items = [10, blank]
+        sorted_items = sorted(items)
+        self.assertEqual(sorted_items, [blank, 10])
+
+        items = [blank, 10]
+        sorted_items = sorted(items)
+        self.assertEqual(sorted_items, [blank, 10])
+
+        items = [blank, "10"]
+        sorted_items = sorted(items)
+        self.assertEqual(sorted_items, [blank, "10"])
+
+    def test_sort(self):
+        items = [10, formatting.FormattedItem(20), formatting.FormattedItem(5)]
+        sorted_items = sorted(items)
+        self.assertEqual(sorted_items, [formatting.FormattedItem(5),
+                                        10,
+                                        formatting.FormattedItem(20)])
+
 
 class FormattedListTests(testing.TestCase):
     def test_init(self):
diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py
index a65538cf..85df2f42 100644
--- a/SoftLayer/tests/CLI/modules/server_tests.py
+++ b/SoftLayer/tests/CLI/modules/server_tests.py
@@ -84,11 +84,19 @@ def test_list_servers(self):
                 'id': 1002,
                 'backend_ip': '10.1.0.4',
                 'action': None,
-            }
+            },
+            {
+                'action': None,
+                'backend_ip': None,
+                'datacenter': None,
+                'hostname': None,
+                'id': 1003,
+                'primary_ip': None,
+            },
         ]
 
         self.assertEqual(result.exit_code, 0)
-        self.assertEqual(json.loads(result.output), expected)
+        self.assertEqual(expected, json.loads(result.output))
 
     @mock.patch('SoftLayer.CLI.formatting.no_going_back')
     @mock.patch('SoftLayer.HardwareManager.reload')
diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py
index 79aa5025..efec2591 100644
--- a/SoftLayer/tests/managers/hardware_tests.py
+++ b/SoftLayer/tests/managers/hardware_tests.py
@@ -78,7 +78,7 @@ def test_list_hardware_with_filters(self):
 
     def test_resolve_ids_ip(self):
         _id = self.hardware._get_ids_from_ip('172.16.1.100')
-        self.assertEqual(_id, [1000, 1001, 1002])
+        self.assertEqual(_id, [1000, 1001, 1002, 1003])
 
         _id = self.hardware._get_ids_from_ip('nope')
         self.assertEqual(_id, [])
@@ -93,7 +93,7 @@ def test_resolve_ids_ip(self):
 
     def test_resolve_ids_hostname(self):
         _id = self.hardware._get_ids_from_hostname('hardware-test1')
-        self.assertEqual(_id, [1000, 1001, 1002])
+        self.assertEqual(_id, [1000, 1001, 1002, 1003])
 
     def test_get_hardware(self):
         result = self.hardware.get_hardware(1000)