Skip to content

Commit

Permalink
Optional Trait - Docs, linting, and another test
Browse files Browse the repository at this point in the history
  • Loading branch information
k2bd committed Apr 10, 2024
1 parent 290993e commit 7c988a8
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 4 deletions.
3 changes: 3 additions & 0 deletions docs/source/traits_api_reference/trait_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ Traits
.. autoclass:: Union
:show-inheritance:

.. autoclass:: Optional
:show-inheritance:

.. autoclass:: Either
:show-inheritance:

Expand Down
43 changes: 42 additions & 1 deletion docs/source/traits_user_manual/defining.rst
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ the table.
.. index:: Directory(), Disallow, Either(), Enum()
.. index:: Event(), Expression(), false, File()
.. index:: Instance(), List(), Method(), Module()
.. index:: Password(), Property(), Python()
.. index:: Optional(), Password(), Property(), Python()
.. index:: PythonValue(), Range(), ReadOnly(), Regex()
.. index:: Set() String(), This, Time()
.. index:: ToolbarButton(), true, Tuple(), Type()
Expand Down Expand Up @@ -355,6 +355,8 @@ the table.
+------------------+----------------------------------------------------------+
| Module | Module([\*\*\ *metadata*]) |
+------------------+----------------------------------------------------------+
| Optional | Optional(*trait*\ [, \*\*\ *metadata*]) |
+------------------+----------------------------------------------------------+
| Password | Password([*value* = '', *minlen* = 0, *maxlen* = |
| | sys.maxsize, *regex* = '', \*\*\ *metadata*]) |
+------------------+----------------------------------------------------------+
Expand Down Expand Up @@ -700,6 +702,45 @@ The following example illustrates the difference between `Either` and `Union`::
... primes = Union([2], None, {'3':6}, 5, 7, 11)
ValueError: Union trait declaration expects a trait type or an instance of trait type or None, but got [2] instead

.. index:: Optional trait

.. _optional:

Optional
::::::::
The Optional trait is a shorthand for ``Union(None, *trait*)``. It allows
the value of the trait to be either None or a specified type. The default
value of the trait is None unless specified by ``default_value``.

.. index::
pair: Optional trait; examples

The following is an example of using Optional::

# optional.py --- Example of Optional predefined trait

from traits.api import HasTraits, Optional, Str

class Person(HasTraits):
name = Str
nickname = Optional(Str)

This example defines a ``Person`` with a ``name`` and an optional ``nickname``.
Their ``nickname`` can be ``None`` or a string. For example::

>>> from traits.api import HasTraits, Optional, Str
>>> class Person(HasTraits):
... name = Str
... nickname = Optional(Str)
...
>>> joseph = Person(name="Joseph")
>>> # Joseph has no nickname
>>> joseph.nickname is None
True
>>> joseph.nickname = "Joe"
>>> joseph.nickname
'Joe'

.. index:: Either trait

.. _either:
Expand Down
1 change: 1 addition & 0 deletions traits/api.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ from .trait_types import (
ToolbarButton as ToolbarButton,
Either as Either,
Union as Union,
Optional as Optional,
Type as Type,
Subclass as Subclass,
Symbol as Symbol,
Expand Down
42 changes: 40 additions & 2 deletions traits/tests/test_optional.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import unittest

from traits.api import (
Bytes,
DefaultValue,
Float,
HasTraits,
Expand All @@ -21,8 +20,8 @@
Str,
TraitError,
TraitType,
Type,
Optional,
Union,
Constant,
)
from traits.trait_types import _NoneTrait
Expand Down Expand Up @@ -234,6 +233,45 @@ def _attribute_changed(self, new):
obj.attribute = None
self.assertIsNone(obj.shadow_attribute)

def test_optional_nested(self):
"""
You can nest ``Optional``... if you want to
"""

class TestClass(HasTraits):
attribute = Optional(Optional(Int))

self.assertIsNone(TestClass(attribute=None).attribute)
self.assertIsNone(TestClass().attribute)

obj = TestClass(attribute=3)

obj.attribute = 5
self.assertEqual(obj.attribute, 5)

obj.attribute = None
self.assertIsNone(obj.attribute)

def test_optional_union_of_optional(self):
"""
``Union(T1, Optional(T2))`` acts like ``Union(T1, None, T2)``
"""
class TestClass(HasTraits):
attribute = Union(Int, Optional(Float))

self.assertEqual(TestClass(attribute=3).attribute, 3)
self.assertEqual(TestClass(attribute=3.0).attribute, 3.0)
self.assertIsNone(TestClass(attribute=None).attribute)
self.assertEqual(TestClass().attribute, 0)

a = TestClass(attribute=3)
a.attribute = 5
self.assertEqual(a.attribute, 5)
a.attribute = 5.0
self.assertEqual(a.attribute, 5.0)
a.attribute = None
self.assertIsNone(a.attribute)

def test_optional_extend_trait(self):
class OptionalOrStr(Optional):
def validate(self, obj, name, value):
Expand Down
2 changes: 1 addition & 1 deletion traits/trait_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4297,7 +4297,7 @@ class Optional(Union):
trait : a trait or value that can be converted using trait_from()
The type of item that the set contains. Must not be ``None``.
"""
def __init__(self, trait, **metadata):
def __init__(self, trait, **metadata):
if trait is None:
raise TraitError("Optional type must not be None.")

Expand Down

0 comments on commit 7c988a8

Please sign in to comment.