param icon indicating copy to clipboard operation
param copied to clipboard

Feature: Support for binary strings

Open mdk73 opened this issue 4 years ago • 6 comments

Currently param.String works with strings ("string"), but throws an error when a binary string b"binary string" is used.

When accessing hardware devices (e.g. a camera via usb), the returned strings are very often binary strings.

So I would like to suggest a new param type: param.BinaryString

(I had a look into the source code myself and tried to understand how it works, but I failed...)

mdk73 avatar May 12 '20 18:05 mdk73

Sure. BinaryString or maybe BinaryBuffer or ByteArray. I assume you'd copy the String implementation but then change the type to a bytearray instead.

jbednar avatar May 12 '20 18:05 jbednar

Somehow I overlooked the String class while scrolling through the github file in the browser. I found the definition of the class now, so I guess I will be able to adapt the code.

mdk73 avatar May 14 '20 19:05 mdk73

Oh, I should have warned you that unlike nearly every other Parameter, String is defined in parameterized.py and not __init__.py, because of a bootstrapping issue that means that we need it declared to be able to have Parameterized objects have a name parameter. I think that's the only parameter that is special like that; ByteArray (or whatever) would go into __init__.py like all the rest.

jbednar avatar May 14 '20 19:05 jbednar

My suggestion of a Bytes class (based on the String class):

class Bytes(Parameter):
    """
    A Bytes Parameter, with a default value and optional regular expression (regex) matching.

    A class similar to the String class, but instead of type basestring (Python2) or str (recent Python3) (e.g. 'string')
    the type bytes (e.g. b'bytes') is used.

    """
    
    def __init__(self, default=b"", regex=None, allow_None=False, **kwargs):
        super(Bytes, self).__init__(default=default, allow_None=allow_None, **kwargs)
        
    def _validate(self, val):
        if self.allow_None and val is None:
            return

        if not isinstance(val, bytes):
            raise ValueError("Bytes '%s' only takes a byte string value."%self.name)

        if self.regex is not None and re.match(self.regex, val) is None:
            raise ValueError("Bytes '%s': '%s' does not match regex '%s'."%(self.name,val,self.regex))

Simple test:

class Test(param.Parameterized):
    b = Bytes(b'bytes')

test = Test()
test.b

Test similar to the given test in the String documentation:

class IPAddress(Bytes):
    '''IPv4 address as bytes (dotted decimal notation)'''
       
    def __init__(self, default=b"0.0.0.0", allow_None=False, **kwargs):
        ip_regex = b'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
        super(IPAddress, self).__init__(default=default, regex=ip_regex, **kwargs)
            
ipaddress = IPAddress()
ipaddress.default

mdk73 avatar May 18 '20 18:05 mdk73

Looks good. Could probably use that with some magic numbers to have a PNG, JPG, etc. Parameter!

jbednar avatar May 18 '20 19:05 jbednar

Meanwhile I am working with param.Parameter like this:

class TestWithParameter(param.Parameterized):
    b = param.Parameter()

    @param.depends('b', watch=True)
    def on_b(self):
        print('b changed to "{}"'.format(self.b))

test_with_parameter = TestWithParameter(b=b'test')
print(test_with_parameter.b)
test_with_parameter.b = b'test2'

When run in a notebookcell the output is

b'test'
b changed to "b'test2'"

A separate class Bytes seems still more elegant to me, but it is not strictly necessary (unless the bytes type shall be checked and enforced).

mdk73 avatar Mar 08 '21 12:03 mdk73

Implemented in #542 🎉

maximlt avatar Feb 21 '23 12:02 maximlt