Skip to content

Commit b4fe9b5

Browse files
committed
Optional Trait - Docs, linting, and another test
1 parent 9bf82df commit b4fe9b5

File tree

5 files changed

+87
-4
lines changed

5 files changed

+87
-4
lines changed

docs/source/traits_api_reference/trait_types.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ Traits
235235
.. autoclass:: Union
236236
:show-inheritance:
237237

238+
.. autoclass:: Optional
239+
:show-inheritance:
240+
238241
.. autoclass:: Either
239242
:show-inheritance:
240243

docs/source/traits_user_manual/defining.rst

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ the table.
265265
.. index:: Directory(), Disallow, Either(), Enum()
266266
.. index:: Event(), Expression(), false, File()
267267
.. index:: Instance(), List(), Module()
268-
.. index:: Password(), Property(), Python()
268+
.. index:: Optional(), Password(), Property(), Python()
269269
.. index:: PythonValue(), Range(), ReadOnly(), Regex()
270270
.. index:: Set() String(), This, Time()
271271
.. index:: ToolbarButton(), true, Tuple(), Type()
@@ -351,6 +351,8 @@ the table.
351351
+------------------+----------------------------------------------------------+
352352
| Module | Module([\*\*\ *metadata*]) |
353353
+------------------+----------------------------------------------------------+
354+
| Optional | Optional(*trait*\ [, \*\*\ *metadata*]) |
355+
+------------------+----------------------------------------------------------+
354356
| Password | Password([*value* = '', *minlen* = 0, *maxlen* = |
355357
| | sys.maxsize, *regex* = '', \*\*\ *metadata*]) |
356358
+------------------+----------------------------------------------------------+
@@ -696,6 +698,45 @@ The following example illustrates the difference between `Either` and `Union`::
696698
... primes = Union([2], None, {'3':6}, 5, 7, 11)
697699
ValueError: Union trait declaration expects a trait type or an instance of trait type or None, but got [2] instead
698700

701+
.. index:: Optional trait
702+
703+
.. _optional:
704+
705+
Optional
706+
::::::::
707+
The Optional trait is a shorthand for ``Union(None, *trait*)``. It allows
708+
the value of the trait to be either None or a specified type. The default
709+
value of the trait is None unless specified by ``default_value``.
710+
711+
.. index::
712+
pair: Optional trait; examples
713+
714+
The following is an example of using Optional::
715+
716+
# optional.py --- Example of Optional predefined trait
717+
718+
from traits.api import HasTraits, Optional, Str
719+
720+
class Person(HasTraits):
721+
name = Str
722+
nickname = Optional(Str)
723+
724+
This example defines a ``Person`` with a ``name`` and an optional ``nickname``.
725+
Their ``nickname`` can be ``None`` or a string. For example::
726+
727+
>>> from traits.api import HasTraits, Optional, Str
728+
>>> class Person(HasTraits):
729+
... name = Str
730+
... nickname = Optional(Str)
731+
...
732+
>>> joseph = Person(name="Joseph")
733+
>>> # Joseph has no nickname
734+
>>> joseph.nickname is None
735+
True
736+
>>> joseph.nickname = "Joe"
737+
>>> joseph.nickname
738+
'Joe'
739+
699740
.. index:: Either trait
700741

701742
.. _either:

traits/api.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ from .trait_types import (
107107
ToolbarButton as ToolbarButton,
108108
Either as Either,
109109
Union as Union,
110+
Optional as Optional,
110111
Type as Type,
111112
Subclass as Subclass,
112113
WeakRef as WeakRef,

traits/tests/test_optional.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import unittest
1212

1313
from traits.api import (
14-
Bytes,
1514
DefaultValue,
1615
Float,
1716
HasTraits,
@@ -21,8 +20,8 @@
2120
Str,
2221
TraitError,
2322
TraitType,
24-
Type,
2523
Optional,
24+
Union,
2625
Constant,
2726
)
2827
from traits.trait_types import _NoneTrait
@@ -234,6 +233,45 @@ def _attribute_changed(self, new):
234233
obj.attribute = None
235234
self.assertIsNone(obj.shadow_attribute)
236235

236+
def test_optional_nested(self):
237+
"""
238+
You can nest ``Optional``... if you want to
239+
"""
240+
241+
class TestClass(HasTraits):
242+
attribute = Optional(Optional(Int))
243+
244+
self.assertIsNone(TestClass(attribute=None).attribute)
245+
self.assertIsNone(TestClass().attribute)
246+
247+
obj = TestClass(attribute=3)
248+
249+
obj.attribute = 5
250+
self.assertEqual(obj.attribute, 5)
251+
252+
obj.attribute = None
253+
self.assertIsNone(obj.attribute)
254+
255+
def test_optional_union_of_optional(self):
256+
"""
257+
``Union(T1, Optional(T2))`` acts like ``Union(T1, None, T2)``
258+
"""
259+
class TestClass(HasTraits):
260+
attribute = Union(Int, Optional(Float))
261+
262+
self.assertEqual(TestClass(attribute=3).attribute, 3)
263+
self.assertEqual(TestClass(attribute=3.0).attribute, 3.0)
264+
self.assertIsNone(TestClass(attribute=None).attribute)
265+
self.assertEqual(TestClass().attribute, 0)
266+
267+
a = TestClass(attribute=3)
268+
a.attribute = 5
269+
self.assertEqual(a.attribute, 5)
270+
a.attribute = 5.0
271+
self.assertEqual(a.attribute, 5.0)
272+
a.attribute = None
273+
self.assertIsNone(a.attribute)
274+
237275
def test_optional_extend_trait(self):
238276
class OptionalOrStr(Optional):
239277
def validate(self, obj, name, value):

traits/trait_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4236,7 +4236,7 @@ class Optional(Union):
42364236
trait : a trait or value that can be converted using trait_from()
42374237
The type of item that the set contains. Must not be ``None``.
42384238
"""
4239-
def __init__(self, trait, **metadata):
4239+
def __init__(self, trait, **metadata):
42404240
if trait is None:
42414241
raise TraitError("Optional type must not be None.")
42424242

0 commit comments

Comments
 (0)