diff mbox series

[ovs-dev] python: Add uuid/convert references to uuid for Row.__str__.

Message ID 20250606214915.1240248-1-twilson@redhat.com
State Changes Requested
Delegated to: Ilya Maximets
Headers show
Series [ovs-dev] python: Add uuid/convert references to uuid for Row.__str__. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/cirrus-robot success cirrus build: passed
ovsrobot/github-robot-_Build_and_Test fail github build: failed

Commit Message

Terry Wilson June 6, 2025, 9:49 p.m. UTC
Row stringification happens a lot in client logs and it is far
more useful to have the logged Row's uuid printed. This also
adds converting referenced Row objects, and references within
set and map columns to UUIDs.

Signed-off-by: Terry Wilson <twilson@redhat.com>
---
 python/ovs/db/idl.py | 32 ++++++++++++++++++++++++++++----
 tests/test-ovsdb.py  |  3 +++
 2 files changed, 31 insertions(+), 4 deletions(-)

Comments

Ilya Maximets June 18, 2025, 2:21 p.m. UTC | #1
On 6/6/25 11:49 PM, Terry Wilson via dev wrote:
> Row stringification happens a lot in client logs and it is far
> more useful to have the logged Row's uuid printed. This also
> adds converting referenced Row objects, and references within
> set and map columns to UUIDs.
> 
> Signed-off-by: Terry Wilson <twilson@redhat.com>
> ---
>  python/ovs/db/idl.py | 32 ++++++++++++++++++++++++++++----
>  tests/test-ovsdb.py  |  3 +++
>  2 files changed, 31 insertions(+), 4 deletions(-)

Hi, Terry.  Thanks for the patch!

This looks like a goos improvement indeed, printing out pointers
instead of map content is indeed not user friendly.

I think, there is some part missing though, I tried to print out
the _Server database row and got this:

Database(uuid=<1> cid=[] connected=true index=[] leader=true model=standalone name=_Server schema=[{"cksum":"3009684573 744""name":"_Server""tables":{"Database":{"columns":{"cid":{"type":{"key":"uuid""min":0}}"connected":{"type":"boolean"}"index":{"type":{"key":"integer""min":0}}"leader":{"type":"boolean"}"model":{"type":{"key":{"enum":["set"["clustered""relay""standalone"]]"type":"string"}}}"name":{"type":"string"}"schema":{"type":{"key":"string""min":0}}"sid":{"type":{"key":"uuid""min":0}}}}}"version":"1.2.0"}] sid=[])

It all looks good, but there is no delimiter between map elements,
e.g. "cksum":"3009684573 744""name":"_Server".  I'd expect a space
or a comma between the entries.  Can this be done?

A couple nits below.

> 
> diff --git a/python/ovs/db/idl.py b/python/ovs/db/idl.py
> index f01e21b5e..4188d1842 100644
> --- a/python/ovs/db/idl.py
> +++ b/python/ovs/db/idl.py
> @@ -1229,6 +1229,29 @@ def _row_to_uuid(value):
>          return value
>  
>  
> +def _rows_to_uuid_str(value):
> +    if isinstance(value, collections.abc.Mapping):
> +        try:
> +            k, v = next(iter(value.items()))
> +            # Pass through early without iterating if not Rows

Period at the end of a comment.

> +            if isinstance(k, Row) or isinstance(v, Row):
> +                return {str(_row_to_uuid(x)): str(_row_to_uuid(y))
> +                        for x, y in value.items()}
> +        except StopIteration:
> +            # Empty, return default

ditto.

> +            pass
> +    elif (isinstance(value, collections.abc.Iterable)
> +        and not isinstance(value, str)):
> +        try:
> +            # Pass through early without iterating if not Rows

ditto.

> +            if value and isinstance(value[0], Row):
> +                return type(value)(str(_row_to_uuid(x)) for x in value)
> +        except TypeError:
> +            # Weird Iterable, pass through

ditto.

> +            pass
> +    return str(_row_to_uuid(value))
> +
> +
>  @functools.total_ordering
>  class Row(object):
>      """A row within an IDL.
> @@ -1334,11 +1357,12 @@ class Row(object):
>          return int(self.__dict__['uuid'])
>  
>      def __str__(self):
> -        return "{table}({data})".format(
> +        return "{table}(uuid={uuid}, {data})".format(
>              table=self._table.name,
> -            data=", ".join("{col}={val}".format(col=c, val=getattr(self, c))
> -                           for c in sorted(self._table.columns)
> -                           if hasattr(self, c)))
> +            uuid=self.uuid,
> +            data=", ".join("{col}={val}".format(
> +                col=c, val=_rows_to_uuid_str(getattr(self, c)))
> +                for c in sorted(self._table.columns) if hasattr(self, c)))
>  
>      def _uuid_to_row(self, atom, base):
>          if base.ref_table:
> diff --git a/tests/test-ovsdb.py b/tests/test-ovsdb.py
> index b690b4f07..32e1894e1 100644
> --- a/tests/test-ovsdb.py
> +++ b/tests/test-ovsdb.py
> @@ -214,6 +214,9 @@ def do_parse_schema(schema_string):
>  
>  
>  def get_simple_printable_row_string(row, columns):
> +    # NOTE(twilson):This turns out to be a particularly good place to test that
> +    # Row object stringification doesn't crash on a large variety of row types
> +    assert(str(row))

flake8 complains that this should be 'assert str(row)' instead.

It would be nice if we could just use the result here instead of
table-specific printing functions, but that will require a large
amount of test changes, so it may be good to do sometime later.

Best regards, Ilya Maximets.
Ilya Maximets June 18, 2025, 7:59 p.m. UTC | #2
On 6/18/25 4:21 PM, Ilya Maximets wrote:
> On 6/6/25 11:49 PM, Terry Wilson via dev wrote:
>> Row stringification happens a lot in client logs and it is far
>> more useful to have the logged Row's uuid printed. This also
>> adds converting referenced Row objects, and references within
>> set and map columns to UUIDs.
>>
>> Signed-off-by: Terry Wilson <twilson@redhat.com>
>> ---
>>  python/ovs/db/idl.py | 32 ++++++++++++++++++++++++++++----
>>  tests/test-ovsdb.py  |  3 +++
>>  2 files changed, 31 insertions(+), 4 deletions(-)
> 
> Hi, Terry.  Thanks for the patch!
> 
> This looks like a goos improvement indeed, printing out pointers
> instead of map content is indeed not user friendly.
> 
> I think, there is some part missing though, I tried to print out
> the _Server database row and got this:
> 
> Database(uuid=<1> cid=[] connected=true index=[] leader=true model=standalone name=_Server schema=[{"cksum":"3009684573 744""name":"_Server""tables":{"Database":{"columns":{"cid":{"type":{"key":"uuid""min":0}}"connected":{"type":"boolean"}"index":{"type":{"key":"integer""min":0}}"leader":{"type":"boolean"}"model":{"type":{"key":{"enum":["set"["clustered""relay""standalone"]]"type":"string"}}}"name":{"type":"string"}"schema":{"type":{"key":"string""min":0}}"sid":{"type":{"key":"uuid""min":0}}}}}"version":"1.2.0"}] sid=[])
> 
> It all looks good, but there is no delimiter between map elements,
> e.g. "cksum":"3009684573 744""name":"_Server".  I'd expect a space
> or a comma between the entries.  Can this be done?

Uff, sorry, I messed up while printing out things and passed the
text through regexes in the get_simple_printable_row_string() that's
why commas disappeared.  So, ignore this part.

> 
> A couple nits below.
> 
>>
>> diff --git a/python/ovs/db/idl.py b/python/ovs/db/idl.py
>> index f01e21b5e..4188d1842 100644
>> --- a/python/ovs/db/idl.py
>> +++ b/python/ovs/db/idl.py
>> @@ -1229,6 +1229,29 @@ def _row_to_uuid(value):
>>          return value
>>  
>>  
>> +def _rows_to_uuid_str(value):
>> +    if isinstance(value, collections.abc.Mapping):
>> +        try:
>> +            k, v = next(iter(value.items()))
>> +            # Pass through early without iterating if not Rows
> 
> Period at the end of a comment.
> 
>> +            if isinstance(k, Row) or isinstance(v, Row):
>> +                return {str(_row_to_uuid(x)): str(_row_to_uuid(y))
>> +                        for x, y in value.items()}
>> +        except StopIteration:
>> +            # Empty, return default
> 
> ditto.
> 
>> +            pass
>> +    elif (isinstance(value, collections.abc.Iterable)
>> +        and not isinstance(value, str)):
>> +        try:
>> +            # Pass through early without iterating if not Rows
> 
> ditto.
> 
>> +            if value and isinstance(value[0], Row):
>> +                return type(value)(str(_row_to_uuid(x)) for x in value)
>> +        except TypeError:
>> +            # Weird Iterable, pass through
> 
> ditto.
> 
>> +            pass
>> +    return str(_row_to_uuid(value))
>> +
>> +
>>  @functools.total_ordering
>>  class Row(object):
>>      """A row within an IDL.
>> @@ -1334,11 +1357,12 @@ class Row(object):
>>          return int(self.__dict__['uuid'])
>>  
>>      def __str__(self):
>> -        return "{table}({data})".format(
>> +        return "{table}(uuid={uuid}, {data})".format(
>>              table=self._table.name,
>> -            data=", ".join("{col}={val}".format(col=c, val=getattr(self, c))
>> -                           for c in sorted(self._table.columns)
>> -                           if hasattr(self, c)))
>> +            uuid=self.uuid,
>> +            data=", ".join("{col}={val}".format(
>> +                col=c, val=_rows_to_uuid_str(getattr(self, c)))
>> +                for c in sorted(self._table.columns) if hasattr(self, c)))
>>  
>>      def _uuid_to_row(self, atom, base):
>>          if base.ref_table:
>> diff --git a/tests/test-ovsdb.py b/tests/test-ovsdb.py
>> index b690b4f07..32e1894e1 100644
>> --- a/tests/test-ovsdb.py
>> +++ b/tests/test-ovsdb.py
>> @@ -214,6 +214,9 @@ def do_parse_schema(schema_string):
>>  
>>  
>>  def get_simple_printable_row_string(row, columns):
>> +    # NOTE(twilson):This turns out to be a particularly good place to test that
>> +    # Row object stringification doesn't crash on a large variety of row types
>> +    assert(str(row))
> 
> flake8 complains that this should be 'assert str(row)' instead.
> 
> It would be nice if we could just use the result here instead of
> table-specific printing functions, but that will require a large
> amount of test changes, so it may be good to do sometime later.
> 
> Best regards, Ilya Maximets.
diff mbox series

Patch

diff --git a/python/ovs/db/idl.py b/python/ovs/db/idl.py
index f01e21b5e..4188d1842 100644
--- a/python/ovs/db/idl.py
+++ b/python/ovs/db/idl.py
@@ -1229,6 +1229,29 @@  def _row_to_uuid(value):
         return value
 
 
+def _rows_to_uuid_str(value):
+    if isinstance(value, collections.abc.Mapping):
+        try:
+            k, v = next(iter(value.items()))
+            # Pass through early without iterating if not Rows
+            if isinstance(k, Row) or isinstance(v, Row):
+                return {str(_row_to_uuid(x)): str(_row_to_uuid(y))
+                        for x, y in value.items()}
+        except StopIteration:
+            # Empty, return default
+            pass
+    elif (isinstance(value, collections.abc.Iterable)
+        and not isinstance(value, str)):
+        try:
+            # Pass through early without iterating if not Rows
+            if value and isinstance(value[0], Row):
+                return type(value)(str(_row_to_uuid(x)) for x in value)
+        except TypeError:
+            # Weird Iterable, pass through
+            pass
+    return str(_row_to_uuid(value))
+
+
 @functools.total_ordering
 class Row(object):
     """A row within an IDL.
@@ -1334,11 +1357,12 @@  class Row(object):
         return int(self.__dict__['uuid'])
 
     def __str__(self):
-        return "{table}({data})".format(
+        return "{table}(uuid={uuid}, {data})".format(
             table=self._table.name,
-            data=", ".join("{col}={val}".format(col=c, val=getattr(self, c))
-                           for c in sorted(self._table.columns)
-                           if hasattr(self, c)))
+            uuid=self.uuid,
+            data=", ".join("{col}={val}".format(
+                col=c, val=_rows_to_uuid_str(getattr(self, c)))
+                for c in sorted(self._table.columns) if hasattr(self, c)))
 
     def _uuid_to_row(self, atom, base):
         if base.ref_table:
diff --git a/tests/test-ovsdb.py b/tests/test-ovsdb.py
index b690b4f07..32e1894e1 100644
--- a/tests/test-ovsdb.py
+++ b/tests/test-ovsdb.py
@@ -214,6 +214,9 @@  def do_parse_schema(schema_string):
 
 
 def get_simple_printable_row_string(row, columns):
+    # NOTE(twilson):This turns out to be a particularly good place to test that
+    # Row object stringification doesn't crash on a large variety of row types
+    assert(str(row))
     s = ""
     for column in columns:
         if hasattr(row, column) and not (type(getattr(row, column))