Skip to content

Commit

Permalink
Merge pull request #329 from enthought/feature/bytes
Browse files Browse the repository at this point in the history
Add Bytes Trait and Related Traits
  • Loading branch information
corranwebster committed Oct 31, 2016
2 parents 4c11c75 + ea2a110 commit b2128b8
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 14 deletions.
8 changes: 8 additions & 0 deletions docs/source/traits_api_reference/trait_types.rst
Expand Up @@ -37,6 +37,10 @@ Traits

.. autoclass:: Unicode

.. autoclass:: BaseBytes

.. autoclass:: Bytes

.. autoclass:: BaseBool

.. autoclass:: Bool
Expand Down Expand Up @@ -65,6 +69,10 @@ Traits

.. autoclass:: CUnicode

.. autoclass:: BaseCBytes

.. autoclass:: CBytes

.. autoclass:: BaseCBool

.. autoclass:: CBool
Expand Down
4 changes: 3 additions & 1 deletion docs/source/traits_user_manual/defining.rst
Expand Up @@ -133,7 +133,7 @@ in the following table.
.. index:: Boolean type, Bool trait, CBool trait, Complex trait, CComplex trait
.. index:: complex number type, Float trait, CFloat trait, Int trait, CInt trait
.. index:: floating point number type, Long trait, CLong trait, Str trait
.. index:: CStr trait, Unicode; trait, CUnicode trait
.. index:: CStr trait, Unicode; trait, CUnicode trait, Bytes trait, CBytes trait

.. _predefined-defaults-for-simple-types-table:

Expand All @@ -149,6 +149,7 @@ Int CInt Plain integer 0
Long CLong Long integer 0L
Str CStr String ''
Unicode CUnicode Unicode u''
Bytes CBytes Bytes b''
============== ============= ====================== ======================

.. index::
Expand Down Expand Up @@ -202,6 +203,7 @@ Python built-in functions for type conversion:
* int()
* str()
* unicode()
* bytes()

.. index::
single: examples; coercing vs. casting
Expand Down
12 changes: 6 additions & 6 deletions traits/api.py
Expand Up @@ -36,9 +36,9 @@
RGBColor, Font)

from .trait_types import (Any, Generic, Int, Long, Float, Complex, Str, Title,
Unicode, Bool, CInt, CLong, CFloat, CComplex, CStr, CUnicode, CBool,
String, Regex, Code, HTML, Password, Callable, This, self, Function,
Method, Module, Python, ReadOnly, Disallow, Constant,
Unicode, Bytes, Bool, CInt, CLong, CFloat, CComplex, CStr, CUnicode,
CBytes, CBool, String, Regex, Code, HTML, Password, Callable, This,
self, Function, Method, Module, Python, ReadOnly, Disallow, Constant,
Delegate, DelegatesTo, PrototypedFrom, Expression, PythonValue, File,
Directory, Range, Enum, Tuple, List, CList, Set, CSet, Dict, Instance,
AdaptedTo, AdaptsTo, Event, Button, ToolbarButton, Either, Type,
Expand All @@ -58,9 +58,9 @@
pass

from .trait_types import (BaseInt, BaseLong, BaseFloat, BaseComplex, BaseStr,
BaseUnicode, BaseBool, BaseCInt, BaseCLong, BaseCFloat, BaseCComplex,
BaseCStr, BaseCUnicode, BaseCBool, BaseFile, BaseDirectory, BaseRange,
BaseEnum, BaseTuple, BaseInstance)
BaseUnicode, BaseBytes, BaseBool, BaseCInt, BaseCLong, BaseCFloat,
BaseCComplex, BaseCStr, BaseCUnicode, BaseCBool, BaseFile,
BaseDirectory, BaseRange, BaseEnum, BaseTuple, BaseInstance)

from .trait_types import UUID, ValidatedTuple

Expand Down
55 changes: 51 additions & 4 deletions traits/tests/test_traits.py
Expand Up @@ -19,10 +19,11 @@

from traits.testing.unittest_tools import unittest

from ..api import (Any, CFloat, CInt, CLong, Delegate, Float, HasTraits,
Instance, Int, List, Long, Str, Trait, TraitError,
TraitList, TraitPrefixList, TraitPrefixMap, TraitRange,
Tuple, pop_exception_handler, push_exception_handler)
from ..api import (Any, Bytes, CBytes, CFloat, CInt, CLong, Delegate, Float,
HasTraits, Instance, Int, List, Long, Str, Trait,
TraitError, TraitList, TraitPrefixList, TraitPrefixMap,
TraitRange, Tuple, pop_exception_handler,
push_exception_handler)

# Base unit test classes:

Expand Down Expand Up @@ -323,6 +324,52 @@ def coerce(self, value):
return str(value)


class BytesTrait(HasTraits):
value = Bytes(b'bytes')


version_dependent = ['', 'string']


class BytesTest(StringTest):

obj = BytesTrait()

_default_value = b'bytes'
_good_values = [b'', b'10', b'-10'] + (version_dependent
if sys.version_info[0] == 2 else [])
_bad_values = [10, -10, 10L, 10.1, u'unicode', u'', [b''], [b'bytes'], [0],
{b'ten': b'10'}, (b'',), None, True] + (version_dependent
if sys.version_info[0] == 3 else [])

def coerce(self, value):
return bytes(value)


class CoercibleBytesTrait(HasTraits):
value = CBytes(b'bytes')

version_dependent = [
'', 'string', u'unicode', u'', -10, 10.1, [b''], [b'bytes'],
[-10], (-10,), {-10: 'foo'}, set([-10]),
[256], (256,), {256: 'foo'}, set([256]),
{b'ten': b'10'}, (b'',), None,
]

class CoercibleBytesTest(StringTest):

obj = CoercibleBytesTrait()

_default_value = b'bytes'
_good_values = [
b'', b'10', b'-10', 10, 10L, [10], (10,), set([10]), {10: 'foo'},
True] + (version_dependent if sys.version_info[0] == 2 else [])
_bad_values = (version_dependent if sys.version_info[0] == 3 else [])

def coerce(self, value):
return bytes(value)


class EnumTrait(HasTraits):
value = Trait([1, 'one', 2, 'two', 3, 'three', 4.4, u'four.four'])

Expand Down
77 changes: 77 additions & 0 deletions traits/trait_types.py
Expand Up @@ -430,6 +430,54 @@ class Unicode ( BaseUnicode ):
#: The C-level fast validator to use:
fast_validate = ( 11, unicode, None, str )


#-------------------------------------------------------------------------------
# 'BaseBytes' and 'Bytes' traits:
#-------------------------------------------------------------------------------

class BaseBytes(TraitType):
""" Defines a trait whose value must be a Python bytes string.
"""

#: The default value for the trait:
default_value = b''

#: A description of the type of value this trait accepts:
info_text = 'a bytes string'

#: An encoding to use with TraitsUI editors
encoding = None

def validate(self, object, name, value):
""" Validates that a specified value is valid for this trait.
Note: The 'fast validator' version performs this check in C.
"""
if isinstance(value, bytes):
return value

self.error(object, name, value)

def create_editor(self):
""" Returns the default traits UI editor for this type of trait.
"""
from .traits import bytes_editor
auto_set = self.auto_set
if auto_set is None:
auto_set = True
enter_set = self.enter_set or False

return bytes_editor(auto_set, enter_set, self.encoding)


class Bytes(BaseBytes):
""" Defines a trait whose value must be a Python bytes string using a
C-level fast validator.
"""

#: The C-level fast validator to use:
fast_validate = (11, bytes)

#-------------------------------------------------------------------------------
# 'BaseBool' and 'Bool' traits:
#-------------------------------------------------------------------------------
Expand Down Expand Up @@ -659,6 +707,35 @@ class CUnicode ( BaseCUnicode ):
#: The C-level fast validator to use:
fast_validate = ( 12, unicode )

#-------------------------------------------------------------------------------
# 'BaseCBytes' and 'CBytes' traits:
#-------------------------------------------------------------------------------

class BaseCBytes(BaseBytes):
""" Defines a trait whose value must be a Python bytes object and which
supports coercions of non-bytes values to bytes.
"""

def validate(self, object, name, value):
""" Validates that a specified value is valid for this trait.
Note: The 'fast validator' version performs this check in C.
"""
try:
return bytes(value)
except:
self.error(object, name, value)


class CBytes(BaseCBytes):
""" Defines a trait whose value must be a Python bytes and which
supports coercions of non-bytes values bytes using a C-level
fast validator.
"""

#: The C-level fast validator to use:
fast_validate = (12, bytes)

#-------------------------------------------------------------------------------
# 'BaseCBool' and 'CBool' traits:
#-------------------------------------------------------------------------------
Expand Down
38 changes: 35 additions & 3 deletions traits/traits.py
Expand Up @@ -87,6 +87,7 @@

PasswordEditor = None
MultilineTextEditor = None
BytesEditors = {}
SourceCodeEditor = None
HTMLTextEditor = None
PythonShellEditor = None
Expand Down Expand Up @@ -119,6 +120,38 @@ def multi_line_text_editor ( auto_set=True, enter_set=False ):

return MultilineTextEditor

def bytes_editor(auto_set=True, enter_set=False, encoding=None):
""" Factory function that returns a text editor for bytes.
"""

global BytesEditors

if encoding is not None:
if isinstance(encoding, str):
import codecs
encoding = codecs.lookup(encoding)

if (auto_set, enter_set, encoding) not in BytesEditors:
from traitsui.api import TextEditor

if encoding is None:
# py3-compatible bytes <-> hex unicode string
format = lambda b: b.encode('hex').decode('ascii')
evaluate = lambda s: s.encode('ascii').decode('hex')
else:
format = encoding.decode
evaluate = encoding.encode

BytesEditors[(auto_set, enter_set, encoding)] = TextEditor(
multi_line=True,
format_func=format,
evaluate=evaluate,
auto_set=auto_set,
enter_set=enter_set
)

return BytesEditors[(auto_set, enter_set, encoding)]

def code_editor ( ):
""" Factory function that returns an editor that treats a multi-line string
as source code.
Expand Down Expand Up @@ -1033,10 +1066,10 @@ def Property ( fget = None, fset = None, fvalidate = None, force = False,
attribute this trait is assigned to. For example::
class Bar(HasTraits):
# A float traits Property that should be always positive.
foo = Property(Float)
# Shadow trait attribute
_foo = Float
Expand Down Expand Up @@ -1249,4 +1282,3 @@ def Font ( *args, **metadata ):
return FontTrait( *args, **metadata )

Font = TraitFactory( Font )

0 comments on commit b2128b8

Please sign in to comment.