1
1
import os
2
2
import itertools
3
+ import sys
3
4
import typing as ty
4
5
from pathlib import Path
5
6
import tempfile
8
9
from ...engine .specs import File , LazyOutField
9
10
from ..typing import TypeParser
10
11
from pydra import Workflow
11
- from fileformats .application import Json
12
+ from fileformats .application import Json , Yaml , Xml
12
13
from .utils import (
13
14
generic_func_task ,
14
15
GenericShellTask ,
15
16
specific_func_task ,
16
17
SpecificShellTask ,
18
+ other_specific_func_task ,
19
+ OtherSpecificShellTask ,
17
20
MyFormatX ,
21
+ MyOtherFormatX ,
18
22
MyHeader ,
19
23
)
20
24
@@ -152,8 +156,12 @@ def test_type_check_nested6():
152
156
153
157
154
158
def test_type_check_nested7 ():
159
+ TypeParser (ty .Tuple [float , float , float ])(lz (ty .List [int ]))
160
+
161
+
162
+ def test_type_check_nested7a ():
155
163
with pytest .raises (TypeError , match = "Wrong number of type arguments" ):
156
- TypeParser (ty .Tuple [float , float , float ])(lz (ty .List [int ]))
164
+ TypeParser (ty .Tuple [float , float , float ])(lz (ty .Tuple [int ]))
157
165
158
166
159
167
def test_type_check_nested8 ():
@@ -164,6 +172,18 @@ def test_type_check_nested8():
164
172
)(lz (ty .List [float ]))
165
173
166
174
175
+ def test_type_check_permit_superclass ():
176
+ # Typical case as Json is subclass of File
177
+ TypeParser (ty .List [File ])(lz (ty .List [Json ]))
178
+ # Permissive super class, as File is superclass of Json
179
+ TypeParser (ty .List [Json ], superclass_auto_cast = True )(lz (ty .List [File ]))
180
+ with pytest .raises (TypeError , match = "Cannot coerce" ):
181
+ TypeParser (ty .List [Json ], superclass_auto_cast = False )(lz (ty .List [File ]))
182
+ # Fails because Yaml is neither sub or super class of Json
183
+ with pytest .raises (TypeError , match = "Cannot coerce" ):
184
+ TypeParser (ty .List [Json ], superclass_auto_cast = True )(lz (ty .List [Yaml ]))
185
+
186
+
167
187
def test_type_check_fail1 ():
168
188
with pytest .raises (TypeError , match = "Wrong number of type arguments in tuple" ):
169
189
TypeParser (ty .Tuple [int , int , int ])(lz (ty .Tuple [float , float , float , float ]))
@@ -490,14 +510,29 @@ def test_matches_type_tuple():
490
510
assert not TypeParser .matches_type (ty .Tuple [int ], ty .Tuple [int , int ])
491
511
492
512
493
- def test_matches_type_tuple_ellipsis ():
513
+ def test_matches_type_tuple_ellipsis1 ():
494
514
assert TypeParser .matches_type (ty .Tuple [int ], ty .Tuple [int , ...])
515
+
516
+
517
+ def test_matches_type_tuple_ellipsis2 ():
495
518
assert TypeParser .matches_type (ty .Tuple [int , int ], ty .Tuple [int , ...])
519
+
520
+
521
+ def test_matches_type_tuple_ellipsis3 ():
496
522
assert not TypeParser .matches_type (ty .Tuple [int , float ], ty .Tuple [int , ...])
497
- assert not TypeParser .matches_type (ty .Tuple [int , ...], ty .Tuple [int ])
523
+
524
+
525
+ def test_matches_type_tuple_ellipsis4 ():
526
+ assert TypeParser .matches_type (ty .Tuple [int , ...], ty .Tuple [int ])
527
+
528
+
529
+ def test_matches_type_tuple_ellipsis5 ():
498
530
assert TypeParser .matches_type (
499
531
ty .Tuple [int ], ty .List [int ], coercible = [(tuple , list )]
500
532
)
533
+
534
+
535
+ def test_matches_type_tuple_ellipsis6 ():
501
536
assert TypeParser .matches_type (
502
537
ty .Tuple [int , ...], ty .List [int ], coercible = [(tuple , list )]
503
538
)
@@ -538,7 +573,17 @@ def specific_task(request):
538
573
assert False
539
574
540
575
541
- def test_typing_cast (tmp_path , generic_task , specific_task ):
576
+ @pytest .fixture (params = ["func" , "shell" ])
577
+ def other_specific_task (request ):
578
+ if request .param == "func" :
579
+ return other_specific_func_task
580
+ elif request .param == "shell" :
581
+ return OtherSpecificShellTask
582
+ else :
583
+ assert False
584
+
585
+
586
+ def test_typing_implicit_cast_from_super (tmp_path , generic_task , specific_task ):
542
587
"""Check the casting of lazy fields and whether specific file-sets can be recovered
543
588
from generic `File` classes"""
544
589
@@ -562,33 +607,86 @@ def test_typing_cast(tmp_path, generic_task, specific_task):
562
607
)
563
608
)
564
609
610
+ wf .add (
611
+ specific_task (
612
+ in_file = wf .generic .lzout .out ,
613
+ name = "specific2" ,
614
+ )
615
+ )
616
+
617
+ wf .set_output (
618
+ [
619
+ ("out_file" , wf .specific2 .lzout .out ),
620
+ ]
621
+ )
622
+
623
+ in_file = MyFormatX .sample ()
624
+
625
+ result = wf (in_file = in_file , plugin = "serial" )
626
+
627
+ out_file : MyFormatX = result .output .out_file
628
+ assert type (out_file ) is MyFormatX
629
+ assert out_file .parent != in_file .parent
630
+ assert type (out_file .header ) is MyHeader
631
+ assert out_file .header .parent != in_file .header .parent
632
+
633
+
634
+ def test_typing_cast (tmp_path , specific_task , other_specific_task ):
635
+ """Check the casting of lazy fields and whether specific file-sets can be recovered
636
+ from generic `File` classes"""
637
+
638
+ wf = Workflow (
639
+ name = "test" ,
640
+ input_spec = {"in_file" : MyFormatX },
641
+ output_spec = {"out_file" : MyFormatX },
642
+ )
643
+
644
+ wf .add (
645
+ specific_task (
646
+ in_file = wf .lzin .in_file ,
647
+ name = "entry" ,
648
+ )
649
+ )
650
+
651
+ with pytest .raises (TypeError , match = "Cannot coerce" ):
652
+ # No cast of generic task output to MyFormatX
653
+ wf .add ( # Generic task
654
+ other_specific_task (
655
+ in_file = wf .entry .lzout .out ,
656
+ name = "inner" ,
657
+ )
658
+ )
659
+
660
+ wf .add ( # Generic task
661
+ other_specific_task (
662
+ in_file = wf .entry .lzout .out .cast (MyOtherFormatX ),
663
+ name = "inner" ,
664
+ )
665
+ )
666
+
565
667
with pytest .raises (TypeError , match = "Cannot coerce" ):
566
668
# No cast of generic task output to MyFormatX
567
669
wf .add (
568
670
specific_task (
569
- in_file = wf .generic .lzout .out ,
570
- name = "specific2 " ,
671
+ in_file = wf .inner .lzout .out ,
672
+ name = "exit " ,
571
673
)
572
674
)
573
675
574
676
wf .add (
575
677
specific_task (
576
- in_file = wf .generic .lzout .out .cast (MyFormatX ),
577
- name = "specific2 " ,
678
+ in_file = wf .inner .lzout .out .cast (MyFormatX ),
679
+ name = "exit " ,
578
680
)
579
681
)
580
682
581
683
wf .set_output (
582
684
[
583
- ("out_file" , wf .specific2 .lzout .out ),
685
+ ("out_file" , wf .exit .lzout .out ),
584
686
]
585
687
)
586
688
587
- my_fspath = tmp_path / "in_file.my"
588
- hdr_fspath = tmp_path / "in_file.hdr"
589
- my_fspath .write_text ("my-format" )
590
- hdr_fspath .write_text ("my-header" )
591
- in_file = MyFormatX ([my_fspath , hdr_fspath ])
689
+ in_file = MyFormatX .sample ()
592
690
593
691
result = wf (in_file = in_file , plugin = "serial" )
594
692
@@ -611,6 +709,63 @@ def test_type_is_subclass3():
611
709
assert TypeParser .is_subclass (ty .Type [Json ], ty .Type [File ])
612
710
613
711
712
+ def test_union_is_subclass1 ():
713
+ assert TypeParser .is_subclass (ty .Union [Json , Yaml ], ty .Union [Json , Yaml , Xml ])
714
+
715
+
716
+ def test_union_is_subclass2 ():
717
+ assert not TypeParser .is_subclass (ty .Union [Json , Yaml , Xml ], ty .Union [Json , Yaml ])
718
+
719
+
720
+ def test_union_is_subclass3 ():
721
+ assert TypeParser .is_subclass (Json , ty .Union [Json , Yaml ])
722
+
723
+
724
+ def test_union_is_subclass4 ():
725
+ assert not TypeParser .is_subclass (ty .Union [Json , Yaml ], Json )
726
+
727
+
728
+ def test_generic_is_subclass1 ():
729
+ assert TypeParser .is_subclass (ty .List [int ], list )
730
+
731
+
732
+ def test_generic_is_subclass2 ():
733
+ assert not TypeParser .is_subclass (list , ty .List [int ])
734
+
735
+
736
+ def test_generic_is_subclass3 ():
737
+ assert not TypeParser .is_subclass (ty .List [float ], ty .List [int ])
738
+
739
+
740
+ @pytest .mark .skipif (
741
+ sys .version_info < (3 , 9 ), reason = "Cannot subscript tuple in < Py3.9"
742
+ )
743
+ def test_generic_is_subclass4 ():
744
+ class MyTuple (tuple ):
745
+ pass
746
+
747
+ class A :
748
+ pass
749
+
750
+ class B (A ):
751
+ pass
752
+
753
+ assert TypeParser .is_subclass (MyTuple [A ], ty .Tuple [A ])
754
+ assert TypeParser .is_subclass (ty .Tuple [B ], ty .Tuple [A ])
755
+ assert TypeParser .is_subclass (MyTuple [B ], ty .Tuple [A ])
756
+ assert not TypeParser .is_subclass (ty .Tuple [A ], ty .Tuple [B ])
757
+ assert not TypeParser .is_subclass (ty .Tuple [A ], MyTuple [A ])
758
+ assert not TypeParser .is_subclass (MyTuple [A ], ty .Tuple [B ])
759
+ assert TypeParser .is_subclass (MyTuple [A , int ], ty .Tuple [A , int ])
760
+ assert TypeParser .is_subclass (ty .Tuple [B , int ], ty .Tuple [A , int ])
761
+ assert TypeParser .is_subclass (MyTuple [B , int ], ty .Tuple [A , int ])
762
+ assert TypeParser .is_subclass (MyTuple [int , B ], ty .Tuple [int , A ])
763
+ assert not TypeParser .is_subclass (MyTuple [B , int ], ty .Tuple [int , A ])
764
+ assert not TypeParser .is_subclass (MyTuple [int , B ], ty .Tuple [A , int ])
765
+ assert not TypeParser .is_subclass (MyTuple [B , int ], ty .Tuple [A ])
766
+ assert not TypeParser .is_subclass (MyTuple [B ], ty .Tuple [A , int ])
767
+
768
+
614
769
def test_type_is_instance1 ():
615
770
assert TypeParser .is_instance (File , ty .Type [File ])
616
771
0 commit comments