def test_Int_type(self): self.assertEqual(Int(Size(8)).as_numpy_type(), np.int8) self.assertEqual(Int(Size(16)).as_numpy_type(), np.int16) self.assertEqual(Int(Size(32)).as_numpy_type(), np.int32) self.assertEqual(Int(Size(64)).as_numpy_type(), np.int64) with self.assertRaises(NotImplementedError): Int(Size(128)).as_numpy_type()
def test_Array(self): # primitive elements are properly handled # int are passed as Int, not UInt self.check([1, 2, 3], Array(Int(), Size(3))) # for floats use a dot # might be a potential problem, due to python silent type downcasting self.check([1.0, 2.0, 3.0], Array(Float(), Size(3))) # bool are passed as Bool, not Int self.check([True, False, False], Array(Bool(), Size(3))) # encapsulation of compound types self.check([[1, 2, 3], [4, 5, 6]], Array(Array(Int(), Size(3)), Size(2))) with self.assertRaises(DeltaTypeError): self.check([1, 2, 3, 4, 5, 6], Array(Array(Int(), Size(3)), Size(2))) with self.assertRaises(AssertionError): self.check([1, 2, 3, 4, 5, 6], Array(Int(), Size(6)), Array(Array(Int(), Size(3)), Size(2))) # mixed types self.check([(1, 2, 3), (4, 5, 6)], Array(Tuple([int, int, int]), Size(2))) self.check(["hello", "world"], Array(Str(Size(5)), Size(2))) # numpy self.check_numpy([1, 2, 3, 4, 5], Array(int, Size(5)))
def setUp(self): # obligatory and optional ports g = DeltaGraph() out_port_obl = OutPort( 'out', Int(), InPort(None, Int(), None, 0), RealNode(g, [], name='node_name'), ) out_port_opt = OutPort( 'out', Int(), InPort(None, Optional(Int()), None, 0), RealNode(g, [], name='node_name'), ) # 4 types of queues self.delta_queue_obl = DeltaQueue(out_port_obl) self.delta_queue_opt = DeltaQueue(out_port_opt) self.const_queue_obl = ConstQueue(out_port_obl) self.const_queue_opt = ConstQueue(out_port_opt) # test messages self.msg1 = QueueMessage(1) self.msg2 = QueueMessage(2) self.msg_with_none = QueueMessage(None) self.msg_unpackable = QueueMessage("abcde") # these messages should be received self.msg1_answer = QueueMessage(1) self.msg2_answer = QueueMessage(2) self.msg_with_none_answer = QueueMessage(None)
def test_primitive_types(self): """Strict typing without subtyping.""" self.assertTrue(DeltaGraph.check_wire(Int(), Int())) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Int(), UInt()) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(UInt(), Int()) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(int, int)
def test_Int(self): """Only 8, 16, 32 and 64 bits are supported.""" for bits in (8, 16, 32, 64): self.check(-2**(bits - 1), Int(Size(bits))) self.check(2**(bits - 1) - 1, Int(Size(bits))) for _ in range(1000): self.check(random.randint(-2**(bits - 1), 2**(bits - 1) - 1), Int(Size(bits))) self.assertTrue(DeltaGraph.check_wire(Raw(int), Raw(int))) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Raw(Int(Size(32))), Raw(Int(Size(64))))
def test_delta_type(self): """Test mapping python objects to Deltaflow data types.""" # special with self.assertRaises(DeltaTypeError): delta_type(None) # primitive self.assertEqual(delta_type(False), Bool()) self.assertEqual(delta_type(np.bool_(False)), Bool()) self.assertEqual(delta_type(5), Int(Size(32))) self.assertEqual(delta_type(np.int16(5)), Int(Size(16))) self.assertEqual(delta_type(np.int32(5)), Int(Size(32))) self.assertEqual(delta_type(np.int64(5)), Int(Size(64))) self.assertEqual(delta_type(np.uint16(5)), UInt(Size(16))) self.assertEqual(delta_type(np.uint32(5)), UInt(Size(32))) self.assertEqual(delta_type(np.uint64(5)), UInt(Size(64))) self.assertEqual(delta_type(4.2), Float(Size(32))) self.assertEqual(delta_type(np.float32(4.2)), Float(Size(32))) self.assertEqual(delta_type(np.float64(4.2)), Float(Size(64))) self.assertEqual(delta_type(3 + 1j), Complex(Size(64))) self.assertEqual(delta_type(np.complex64(3 + 1j)), Complex(Size(64))) self.assertEqual(delta_type(np.complex128(3 + 1j)), Complex(Size(128))) self.assertEqual(delta_type('c'), Char()) # compound self.assertEqual(delta_type((1, True, 3.7)), Tuple([int, bool, float])) self.assertEqual(delta_type([1, 2, 4]), Array(int, Size(3))) self.assertEqual(delta_type(RecBI(True, 5)), Record(RecBI)) # numpy compound self.assertEqual(delta_type(np.array([1, 2, 3, 4, 5])), Array(Int(Size(64)), Size(5))) self.assertEqual(delta_type(np.array([1, 2.0, 3, 4, 5])), Array(Float(Size(64)), Size(5))) self.assertEqual(delta_type(Str(Size(5)).as_numpy_object("abcde")), Str(Size(5))) self.assertEqual( delta_type( Tuple([int, float, bool]).as_numpy_object((1, 2.0, True))), Tuple([int, float, bool])) self.assertEqual( delta_type(Record(RecBI).as_numpy_object(RecBI(True, 2))), Record(RecBI)) self.assertEqual( delta_type(Union([bool, float, int]).as_numpy_object(5.0)), Union([bool, float, int])) # different combinations self.assertEqual(delta_type([(4, 4.3), (2, 3.3)]), Array(Tuple([int, float]), Size(2)))
def test_in_port_capnp_optional(self): """Generate optional in port.""" in_port = InPort("index", Optional(int), None, 0) capnp_in_port = dotdf_capnp.InPort.new_message() in_port.capnp(capnp_in_port) self.assertEqual(capnp_in_port.name, "index") self.assertEqual(dill.loads(capnp_in_port.type), Int()) self.assertEqual(capnp_in_port.optional, True)
def test_Int(self): """Only 8, 16, 32, and 64 bits are supported.""" for bits in (8, 16, 32, 64): self.check(-2**(bits - 1), Int(Size(bits))) self.check(2**(bits - 1) - 1, Int(Size(bits))) for _ in range(1000): self.check(random.randint(-2**(bits - 1), 2**(bits - 1) - 1), Int(Size(bits))) # Booleans can be packed self.check(True, Int(Size(bits))) self.check(False, Int(Size(bits))) # Floats, strings and overflowing or # complex numbers are not packable self.assertFalse(Int(Size(bits)).is_packable(3.5)) self.assertFalse(Int(Size(bits)).is_packable(3.0)) self.assertFalse(Int(Size(bits)).is_packable("abc")) self.assertFalse(Int(Size(bits)).is_packable(2**bits - 1)) self.assertFalse(Int(Size(bits)).is_packable(3 + 5j))
def test_as_python_type(self): """Test conversion of Deltaflow data types to python.""" # special self.assertEqual(Top().as_python_type(), typing.Any) # primitive self.assertEqual(Int(Size(32)).as_python_type(), int) self.assertEqual(Int(Size(64)).as_python_type(), int) self.assertEqual(UInt(Size(32)).as_python_type(), int) self.assertEqual(UInt(Size(64)).as_python_type(), int) self.assertEqual(Bool().as_python_type(), bool) with self.assertRaises(NotImplementedError): Char().as_python_type() self.assertEqual(Float(Size(32)).as_python_type(), float) self.assertEqual(Float(Size(64)).as_python_type(), float) self.assertEqual(Complex(Size(64)).as_python_type(), complex) self.assertEqual(Complex(Size(128)).as_python_type(), complex) # compound self.assertEqual( Tuple([int, bool]).as_python_type(), typing.Tuple[int, bool]) self.assertEqual( Tuple([int, Tuple([int, bool])]).as_python_type(), typing.Tuple[int, typing.Tuple[int, bool]]) self.assertEqual( Array(int, Size(3)).as_python_type(), typing.List[int]) self.assertEqual(Str().as_python_type(), str) self.assertEqual(Str(Size(10)).as_python_type(), str) self.assertEqual(Record(RecBI).as_python_type(), RecBI) self.assertEqual(Record(RecBDi).as_python_type(), RecBDi) self.assertNotEqual(Record(RecBI).as_python_type(), RecBI_copy) # compound: Union self.assertEqual( Union([bool, int]).as_python_type(), typing.Union[bool, int]) self.assertEqual( Union([bool, Tuple([int, bool])]).as_python_type(), typing.Union[bool, typing.Tuple[int, bool]])
def test_str(self): """Test string representation of data types.""" # primitive self.assertEqual(str(Int()), "Int32") self.assertEqual(str(Int(Size(64))), "Int64") self.assertEqual(str(UInt()), "UInt32") self.assertEqual(str(UInt(Size(64))), "UInt64") self.assertEqual(str(Bool()), "Bool") self.assertEqual(str(Char()), "Char8") self.assertEqual(str(Float()), "Float32") self.assertEqual(str(Float(Size(64))), "Float64") # compound self.assertEqual(str(Array(int, Size(8))), "[Int32 x 8]") self.assertEqual(str(Str()), "Str8192") self.assertEqual(str(Str(Size(100))), "Str800") self.assertEqual(str(Tuple([int, bool])), "(Int32, Bool)") self.assertEqual(str(Record(RecBIS)), "{x: Bool, y: Int32, z: Str8192}") self.assertEqual(str(Union([int, bool])), "<Bool | Int32>") # compound: Union self.assertEqual(str(Union([int])), "<Int32>") self.assertEqual(str(Union([int, Union([int, bool])])), "<Bool | Int32>") self.assertEqual(str(Union([int, Union([int, Union([int, bool])])])), "<Bool | Int32>") # encapsulation of various types self.assertEqual(str(Union([int, Tuple([int, bool])])), "<(Int32, Bool) | Int32>") self.assertEqual(str(Array(Tuple([int, bool]), Size(8))), "[(Int32, Bool) x 8]") # special self.assertEqual(str(Top()), "T") self.assertEqual(str(Size(5)), "5") self.assertEqual(str(Size(NamespacedName("a", "b"))), "(a.b)")
def test_custom_name_interactive(self): saver = StateSaver(int) with DeltaGraph() as graph: r = send_5.call().out s = saver.save_and_exit(r) self.assertTrue(graph.check()) self.assertEqual(len(r.out_ports), 1) port_under_test = r.out_ports[0] self.assertEqual(port_under_test.destination.node, s) self.assertEqual(port_under_test.port_type, Int(Size(32))) self.assertEqual(port_under_test.index, 'out')
def test_default_name_func_block(self): saver = StateSaver(int) with DeltaGraph() as graph: r = return_5_default_name().output s = saver.save_and_exit(r) self.assertTrue(graph.check()) self.assertEqual(len(r.out_ports), 1) port_under_test = r.out_ports[0] self.assertEqual(port_under_test.destination.node, s) self.assertEqual(port_under_test.port_type, Int(Size(32))) self.assertEqual(port_under_test.index, 'output')
def test_custom_name_method_block(self): saver = StateSaver(int) m = MethodReturner() with DeltaGraph() as graph: r = m.return_5_m().out s = saver.save_and_exit(r) self.assertTrue(graph.check()) self.assertEqual(len(r.out_ports), 1) port_under_test = r.out_ports[0] self.assertEqual(port_under_test.destination.node, s) self.assertEqual(port_under_test.port_type, Int(Size(32))) self.assertEqual(port_under_test.index, 'out')
def test_Top(self): """Everything can be accepted as Top().""" self.assertTrue(DeltaGraph.check_wire(Int(), Top())) self.assertTrue(DeltaGraph.check_wire(UInt(), Top())) self.assertTrue(DeltaGraph.check_wire(Bool(), Top())) self.assertTrue(DeltaGraph.check_wire(Tuple([int, bool]), Top())) self.assertTrue(DeltaGraph.check_wire(Union([int, bool]), Top())) self.assertTrue(DeltaGraph.check_wire(Array(int, Size(8)), Top())) self.assertTrue(DeltaGraph.check_wire(Str(), Top())) self.assertTrue(DeltaGraph.check_wire(Record(RecBI), Top())) # however this isn't true if allow_top is set to False with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Int(), Top(), allow_top=False) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(UInt(), Top(), allow_top=False) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Bool(), Top(), allow_top=False) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Tuple([int, bool]), Top(), allow_top=False) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Union([int, bool]), Top(), allow_top=False) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Array(int, Size(8)), Top(), allow_top=False) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Str(), Top(), allow_top=False) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Record(RecBI), Top(), allow_top=False) # the sending port cannot have type Top() with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Top(), Top()) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Top(), Int()) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Union([Int(), Top()]), Int())
def test_Union(self): """Test wires with Union.""" # examples of obvious behaiviour self.assertTrue( DeltaGraph.check_wire(Union([int, bool]), Union([int, bool]))) self.assertTrue( DeltaGraph.check_wire(Union([int, bool]), Union([bool, int]))) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Union([int, bool]), Int()) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Union([int, bool, float]), Union([int, bool])) # strict typing even with Union, i.e. all subtypes should match with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Union([bool, int]), Union([bool, int, float])) # Union changes packing method, thus these tests should fail with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Int(), Union([int])) with self.assertRaises(DeltaTypeError): DeltaGraph.check_wire(Union([int]), Int())
def test_Record(self): # primitive self.check(RecBI(True, 5), Record(RecBI)) self.check(-4, Int()) self.check(RecBII(True, 5, -4), Record(RecBII)) # mixed self.check(RecIT(-4.0, (1, 2)), Record(RecIT)) self.check(RecATI([1, 2], (3.0, 4), 5), Record(RecATI)) self.check((RecIT(-4.0, (1, 2)), 1), Tuple([Record(RecIT), int])) self.check([RecIT(-4.0, (1, 2)), RecIT(5.0, (-3, -4))], Array(Record(RecIT), Size(2))) self.assertTrue( DeltaGraph.check_wire(Raw(Record(RecIT)), Raw(Record(RecIT))))
def test_compound_objects(self): t = Array(Tuple([bool, int]), Size(3)) val = [(True, 1), (False, 2), (True, 3), (False, 4), (True, 5)] self.check(val, t) t = Tuple([int, Tuple([bool, int])]) val = (12, (True, 8)) self.check(val, t) t = Tuple([int, Array(int, Size(2))]) val = (12, [14, 18]) self.check(val, t) t = Tuple([int, Str()]) val = (12, "hello") self.check(val, t) t = Tuple([int, Record(RecBI)]) val = (12, RecBI(True, 8)) self.check(val, t) t = Tuple([int, Union([bool, int])]) val = (12, True) np_val = t.as_numpy_object(val) self.assertEqual(Int().from_numpy_object(np_val[0][0]), 12) self.assertEqual(Bool().from_numpy_object(np_val[0][1][1]), True) t = Record(RecATI) val = RecATI([1, 2], (3.0, 4), 5) self.check(val, t) t = Union([Array(int, Size(2)), int]) val = [1, 2] np_val = t.as_numpy_object(val) new_val = Array(int, Size(2)).from_numpy_object(np_val[0][1]) self.assertEqual(val, new_val) t = Union([str, int]) val = "abcde" np_val = t.as_numpy_object(val) new_val = Str().from_numpy_object(np_val[0][1]) self.assertEqual(val, new_val)
def test_size(self): """Test how many bits each data type takes.""" # primitive self.assertEqual(Int().size, Size(32)) self.assertEqual(UInt().size, Size(32)) self.assertEqual(Bool().size, Size(1)) self.assertEqual(Char().size, Size(8)) self.assertEqual(Float().size, Size(32)) # compound self.assertEqual(Tuple([int, bool]).size, Size(33)) self.assertEqual(Array(int, Size(10)).size, Size(320)) self.assertEqual(Str().size, Size(8192)) self.assertEqual(Record(RecBI).size, Size(33)) # compound: Union self.assertEqual(Union([bool]).size, Size(9)) self.assertEqual(Union([int, bool]).size, Size(40)) self.assertEqual( Union([int, Tuple([int, int])]).size, Size(2 * 32 + 8))
def test_Record(self): # primitive self.check(RecBI(True, 5), Record(RecBI)) self.check(-4, Int()) self.check(RecBII(True, 5, -4), Record(RecBII)) with self.assertRaises(DeltaTypeError): self.check(RecBI(True, 5), Record(RecIB)) # mixed self.check(RecIT(-4.0, (1, 2)), Record(RecIT)) self.check(RecATI([1, 2], (3.0, 4), 5), Record(RecATI)) self.check((RecIT(-4.0, (1, 2)), 1), Tuple([Record(RecIT), int])) self.check([RecIT(-4.0, (1, 2)), RecIT(5.0, (-3, -4))], Array(Record(RecIT), Size(2))) # numpy self.check_numpy(RecBI(False, 2), Record(RecBI))
def test_types_comparison(self): """Various tests of types comparison.""" # primitive self.assertEqual(Int(Size(32)), Int()) self.assertNotEqual(Int(), UInt()) self.assertNotEqual(Int(), Int(Size(64))) # compound self.assertEqual(Tuple([int, bool]), Tuple([int, bool])) self.assertNotEqual(Tuple([int, bool]), Tuple([bool, int])) self.assertEqual(Array(int, Size(4)), Array(int, Size(4))) self.assertEqual(Array(int, Size(4)), Array(Int(), Size(4))) self.assertNotEqual(Array(int, Size(4)), Array(int, Size(5))) self.assertNotEqual(Str(), Str(Size(100))) self.assertEqual(Record(RecBI), Record(RecBI)) # compound: Union self.assertEqual(Union([int, bool]), Union([bool, int])) self.assertEqual(Union([int, Union([int, bool])]), Union([int, bool])) self.assertEqual(Union([int, Union([int, Union([int, bool])])]), Union([int, bool])) self.assertEqual(Union([int, int]), Union([int])) self.assertNotEqual(Union([Int()]), Int())
def test_Size(self): """Test various use Size.""" with self.assertRaises(DeltaTypeError): Int(5) with self.assertRaises(ValueError): Size(-1) with self.assertRaises(ValueError): dummy = Size(4) + Size(NamespacedName("a", "b")) # add d16_32 = Size(16) d16_32 += Size(32) self.assertEqual(d16_32, Size(48)) self.assertEqual(Size(4) + Size(5), Size(9)) # sub d32_16 = Size(32) d32_16 -= Size(16) self.assertEqual(d32_16, Size(16)) self.assertEqual(Size(5) - Size(2), Size(3)) with self.assertRaises(ValueError): dummy = Size(5) - Size(6) # mul self.assertEqual(Size(4) * 5, Size(20)) self.assertEqual(5 * Size(4), Size(20)) d16x4 = Size(16) d16x4 *= 4 self.assertEqual(d16x4, Size(64)) d16x4 = Size(16) d16x4 *= Size(4) self.assertEqual(d16x4, Size(64)) # comparison self.assertTrue(Size(8) == Size(8)) self.assertTrue(Size(8) > Size(6)) self.assertTrue(Size(4) < Size(6))
def test_Optional(self): port = OutPort(None, Optional(Int()), None, None) with self.assertRaises(TypeError): dummy = port.port_type
def test_Int_object(self): self.check(5, Int(Size(8))) self.check(10, Int(Size(16))) self.check(15, Int(Size(32))) self.check(20, Int(Size(64)))
def test_Optional(self): port = InPort(None, Optional(Int()), None, 0) self.assertEqual(port.port_type, Int()) self.assertEqual(port.is_optional, True)
def test_non_Optional(self): port = OutPort(None, Int(), None, None) self.assertEqual(port.port_type, Int())
def test_as_delta_type(self): """Test conversion from python to Deltaflow data types.""" # special self.assertEqual(as_delta_type(object), Top()) self.assertEqual(as_delta_type(type(object)), Top()) self.assertEqual(as_delta_type(type), Top()) self.assertEqual(as_delta_type('random_text'), Top()) with self.assertRaises(DeltaTypeError): as_delta_type(None) with self.assertRaises(DeltaTypeError): as_delta_type(type(None)) # primitive self.assertEqual(as_delta_type(bool), Bool()) self.assertEqual(as_delta_type(np.bool_), Bool()) self.assertEqual(as_delta_type(int), Int(Size(32))) self.assertEqual(as_delta_type(np.int8), Char()) self.assertEqual(as_delta_type(np.int16), Int(Size(16))) self.assertEqual(as_delta_type(np.int32), Int(Size(32))) self.assertEqual(as_delta_type(np.int64), Int(Size(64))) self.assertEqual(as_delta_type(np.uint8), Char()) self.assertEqual(as_delta_type(np.uint16), UInt(Size(16))) self.assertEqual(as_delta_type(np.uint32), UInt(Size(32))) self.assertEqual(as_delta_type(np.uint64), UInt(Size(64))) self.assertEqual(as_delta_type(float), Float()) self.assertEqual(as_delta_type(np.float32), Float(Size(32))) self.assertEqual(as_delta_type(np.float64), Float(Size(64))) self.assertEqual(as_delta_type(complex), Complex()) self.assertEqual(as_delta_type(np.complex64), Complex(Size(64))) self.assertEqual(as_delta_type(np.complex128), Complex(Size(128))) # compound with self.assertRaises(DeltaTypeError): as_delta_type(typing.Tuple[int, bool]) with self.assertRaises(DeltaTypeError): as_delta_type(typing.List[int]) self.assertNotEqual(as_delta_type(str), Array(Char(), Size(1024))) self.assertEqual(as_delta_type(str), Str()) self.assertEqual(as_delta_type(RecBI), Record(RecBI)) # numpy compound self.assertEqual(as_delta_type(Array(int, Size(5)).as_numpy_type()), Array(int, Size(5))) self.assertEqual(as_delta_type(Str().as_numpy_type()), Str()) self.assertEqual( as_delta_type(Tuple([int, bool, float]).as_numpy_type()), Tuple([int, bool, float])) self.assertEqual(as_delta_type(Record(RecBI).as_numpy_type()), Record(RecBI)) self.assertEqual( as_delta_type(Union([bool, float, int]).as_numpy_type()), Union([bool, float, int])) # from string self.assertEqual(as_delta_type('bool'), Bool()) self.assertEqual(as_delta_type('\'bool\''), Bool()) self.assertEqual(as_delta_type('np.bool_'), Bool()) self.assertEqual(as_delta_type('int'), Int(Size(32))) self.assertEqual(as_delta_type('np.int32'), Int(Size(32))) self.assertEqual(as_delta_type('np.uint32'), UInt(Size(32))) self.assertEqual(as_delta_type('float'), Float()) self.assertEqual(as_delta_type('np.float32'), Float(Size(32))) self.assertEqual(as_delta_type('complex'), Complex()) self.assertEqual(as_delta_type('np.complex64'), Complex(Size(64))) self.assertEqual(as_delta_type('str'), Str()) self.assertEqual(as_delta_type("Int(Size(32))"), Int(Size(32))) # 'RecBI' is out of scope when the string is evaluated self.assertEqual(as_delta_type('RecBI'), Top())
def test_Optional_of_Union(self): port = InPort(None, Optional(Union([Int(), Float()])), None, 0) self.assertEqual(port.port_type, Union([Int(), Float()])) self.assertEqual(port.is_optional, True)
def test_Union_of_single(self): """Union of a single type is not converted to a single type.""" port = InPort(None, Union([Int()]), None, 0) self.assertEqual(port.port_type, Union([Int()])) self.assertEqual(port.is_optional, False)
def test_Union(self): port = InPort(None, Union([Int(), Float()]), None, 0) self.assertEqual(port.port_type, Union([Int(), Float()])) self.assertEqual(port.is_optional, False)
class RecBDi: x: bool = attr.ib() y: Int() = attr.ib()