2
2
Structures related to configuration files.
3
3
"""
4
4
5
- from dataclasses import dataclass , field
5
+ from dataclasses import dataclass
6
6
from os import PathLike
7
+ from pathlib import Path
7
8
from typing import cast
8
9
from typing import Dict
10
+ from typing import Optional
9
11
from typing import Sequence
10
12
from typing import Tuple
11
13
@@ -34,7 +36,7 @@ class ObjectLabelSet:
34
36
35
37
def __post_init__ (self ):
36
38
# coerce nested label objects into the ObjectLabel type.
37
- if self .labels and not isinstance (self .labels , ObjectLabel ):
39
+ if self .labels and not isinstance (self .labels [ 0 ] , ObjectLabel ):
38
40
raw_labels = cast (Sequence [Dict ], self .labels )
39
41
self .labels = tuple (ObjectLabel (** rl ) for rl in raw_labels )
40
42
@@ -50,3 +52,159 @@ def load_object_label_set(filepath: PathLike) -> ObjectLabelSet:
50
52
with open (filepath ) as infile :
51
53
data = yaml .safe_load (infile )
52
54
return ObjectLabelSet (** data )
55
+
56
+
57
+ @dataclass
58
+ class ActivityLabel :
59
+ """
60
+ One activity classification ID and paired label information.
61
+ """
62
+
63
+ # Identifier integer for this activity label
64
+ id : int
65
+ # Concise string label for this activity. Should not contain any spaces.
66
+ label : str
67
+ # Full sentence description of this activity.
68
+ full_str : str
69
+ # Optional integer representing how many times an activity should be
70
+ # repeated to be considered "full"
71
+ # TODO: This parameter has ambiguous and violated meaning (not used as
72
+ # intended if at all).
73
+ repeat : Optional [int ] = None
74
+
75
+
76
+ @dataclass
77
+ class ActivityLabelSet :
78
+ version : str
79
+ title : str
80
+ labels : Tuple [ActivityLabel ]
81
+
82
+ def __post_init__ (self ):
83
+ # coerce nested label objects into the ObjectLabel type.
84
+ if self .labels and not isinstance (self .labels [0 ], ActivityLabel ):
85
+ raw_labels = cast (Sequence [Dict ], self .labels )
86
+ self .labels = tuple (ActivityLabel (** rl ) for rl in raw_labels )
87
+
88
+
89
+ def load_activity_label_set (filepath : PathLike ) -> ActivityLabelSet :
90
+ """
91
+ Load from YAML file an activity label set configuration.
92
+
93
+ :param filepath: Filepath to load from.
94
+
95
+ :return: Structure containing the loaded configuration.
96
+ """
97
+ with open (filepath ) as infile :
98
+ data = yaml .safe_load (infile )
99
+ return ActivityLabelSet (** data )
100
+
101
+
102
+ @dataclass
103
+ class TaskStep :
104
+ """
105
+ A single task step with activity components.
106
+ """
107
+
108
+ id : int
109
+ label : str
110
+ full_str : str
111
+ activity_ids : Tuple [int ]
112
+
113
+
114
+ @dataclass
115
+ class LinearTask :
116
+ """
117
+ A linear task with steps composed of activities.
118
+ """
119
+
120
+ version : str
121
+ title : str
122
+ labels : Tuple [TaskStep ]
123
+
124
+ def __post_init__ (self ):
125
+ # Coerce pathlike input (str) into a Path instance if not already.
126
+ if self .labels and not isinstance (self .labels [0 ], TaskStep ):
127
+ raw = cast (Sequence [Dict ], self .labels )
128
+ self .labels = tuple (TaskStep (** r ) for r in raw )
129
+
130
+
131
+ def load_linear_task_config (filepath : PathLike ) -> LinearTask :
132
+ """
133
+ Load from YAML file a linear task configuration.
134
+
135
+ :param filepath: Filepath to load from.
136
+
137
+ :return: Structure containing the loaded configuration.
138
+ """
139
+ with open (filepath ) as infile :
140
+ data = yaml .safe_load (infile )
141
+ return LinearTask (** data )
142
+
143
+
144
+ @dataclass
145
+ class OneTaskConfig :
146
+ """
147
+ Specification of where one task configuration is located.
148
+ """
149
+
150
+ id : int
151
+ label : str
152
+ config_file : Path
153
+ active : bool
154
+
155
+ def __post_init__ (self ):
156
+ # Coerce pathlike input (str) into a Path instance if not already.
157
+ # Interpret relative paths now to absolute based on current working
158
+ # directory.
159
+ if not isinstance (self .config_file , Path ):
160
+ self .config_file = Path (self .config_file ).absolute ()
161
+
162
+
163
+ @dataclass
164
+ class MultiTaskConfig :
165
+ """
166
+ A collection of linear task configurations.
167
+ """
168
+
169
+ version : str
170
+ title : str
171
+ tasks : Tuple [OneTaskConfig ]
172
+
173
+ def __post_init__ (self ):
174
+ # coerce nested task objects into OneTaskConfig types
175
+ if self .tasks and not isinstance (self .tasks [0 ], OneTaskConfig ):
176
+ raw = cast (Sequence [Dict ], self .tasks )
177
+ self .tasks = tuple (OneTaskConfig (** r ) for r in raw )
178
+
179
+
180
+ def load_multi_task_config (filepath : PathLike ):
181
+ """
182
+ Relative file paths are currently interpreted relative to the current
183
+ working directory and resolved to be absolute.
184
+
185
+ :param filepath: Filepath to load from.
186
+
187
+ :return: Structure containing the loaded configuration.
188
+ """
189
+ with open (filepath ) as infile :
190
+ data = yaml .safe_load (infile )
191
+ return MultiTaskConfig (** data )
192
+
193
+
194
+ def load_active_task_configs (cfg : MultiTaskConfig ) -> Dict [str , LinearTask ]:
195
+ """
196
+ Load task configurations that are enabled in the multitask configuration.
197
+
198
+ :param cfg: Multitask configuration to base loading on.
199
+
200
+ :raises FileNotFoundError: Configured task configuration file did not refer
201
+ to an open-able file.
202
+
203
+ :return: Mapping of task label from the input configuration to the
204
+ LinearTask instance loaded.
205
+ """
206
+ return {
207
+ ct .label : load_linear_task_config (ct .config_file )
208
+ for ct in cfg .tasks
209
+ if ct .active
210
+ }
0 commit comments