11"""Module to handle smartsheet api responses"""
22
3- from typing import Any , List , Optional , Type
3+ from typing import Any , Dict , List , Optional , Type , TypeVar
44
5- from aind_smartsheet_api .client import SmartsheetClient
6- from pydantic import BaseModel
5+ from pydantic import BaseModel , ValidationError
76
87from aind_smartsheet_service_server .models import (
98 FundingModel ,
109 PerfusionsModel ,
1110 ProtocolsModel ,
11+ SheetFields ,
12+ SheetRow ,
1213)
1314
15+ T = TypeVar ("T" , bound = BaseModel )
1416
15- class SessionHandler :
16- """Handle session object to get data"""
1717
18- def __init__ (self , session : SmartsheetClient ):
18+ class SheetHandler :
19+ """Handle raw sheet object"""
20+
21+ def __init__ (self , raw_sheet : str , validate : bool = True ):
1922 """Class constructor"""
20- self .session = session
23+ self .raw_sheet = raw_sheet
24+ self .validate = validate
25+
26+ def _get_sheet_fields (self ) -> SheetFields :
27+ """
28+ Returns
29+ -------
30+ SheetFields
31+
32+ """
33+ return SheetFields .model_validate_json (self .raw_sheet )
2134
22- def get_parsed_sheet (
23- self , sheet_id : int , model : Type [BaseModel ]
24- ) -> List [Any ]:
35+ @staticmethod
36+ def _column_id_map (sheet_fields : SheetFields ) -> Dict [int , str ]:
2537 """
26- Converts the rows in a Smart Sheet into a Pydantic model
38+ SmartSheet uses integer ids for the columns. We need a way to
39+ map the column titles to the ids so we can retrieve information using
40+ just the titles.
2741 Parameters
2842 ----------
29- sheet_id : int
30- ID number of smart sheet
31- model : Type[BaseModel]
32- Pydantic model to parse the sheet to
43+ sheet_fields : SheetFields
3344
3445 Returns
3546 -------
36- List[Any]
37- A list of Pydantic models
47+ Dict[int, str]
3848
3949 """
40- sheet = self .session .get_parsed_sheet_model (
41- sheet_id = sheet_id , model = model , validate = False
42- )
43- return sheet
50+ return {c .id : c .title for c in sheet_fields .columns }
4451
4552 @staticmethod
53+ def _map_row_to_dict (
54+ row : SheetRow , column_id_map : Dict [int , str ]
55+ ) -> Dict [str , Any ]:
56+ """
57+ Maps a row into a dictionary that maps the column names to their values
58+ Parameters
59+ ----------
60+ row : SheetRow
61+ A SheetRow that will be parsed. This a list of cells with a columnId
62+ and a cell value.
63+ column_id_map: Dict[int, str]
64+ Map of column ids into the column names
65+
66+ Returns
67+ -------
68+ Dict[str, Any]
69+ The list of row cells is converted to a dictionary.
70+
71+ """
72+ output_dict = {}
73+ for cell in row .cells :
74+ column_id = cell .columnId
75+ column_name = column_id_map [column_id ]
76+ output_dict [column_name ] = cell .value
77+ return output_dict
78+
79+ def _get_parsed_sheet_model (self , model : Type [T ]) -> List [T ]:
80+ """
81+ Parse raw sheet json into basic dictionary of {"name": "value"}
82+ Parameters
83+ ----------
84+ model : T
85+ BaseModel type
86+
87+ Returns
88+ -------
89+ List[T]
90+
91+ """
92+
93+ sheet_fields = self ._get_sheet_fields ()
94+ column_id_map = self ._column_id_map (sheet_fields = sheet_fields )
95+ parsed_rows = list ()
96+ for row in sheet_fields .rows :
97+ row_dict = self ._map_row_to_dict (row , column_id_map = column_id_map )
98+ try :
99+ row_as_model = model .model_validate (row_dict )
100+ parsed_rows .append (row_as_model )
101+
102+ except ValidationError as e :
103+ if self .validate :
104+ raise e
105+ else :
106+ row_as_model = model .model_construct (** row_dict )
107+ parsed_rows .append (row_as_model )
108+ return parsed_rows
109+
46110 def get_project_funding_info (
47- sheet_model : List [ FundingModel ] ,
111+ self ,
48112 project_name : Optional [str ] = None ,
49113 subproject : Optional [str ] = None ,
50114 ) -> List [FundingModel ]:
51115 """
52116 Filters funding sheet by specific project name
53117 Parameters
54118 ----------
55- sheet_model : List[FundingModel]
56119 project_name : str | None
57120 subproject : str | None
58121
@@ -63,6 +126,7 @@ def get_project_funding_info(
63126 A list of all of that match the project name.
64127
65128 """
129+ sheet_model = self ._get_parsed_sheet_model (model = FundingModel )
66130 filtered_rows = [
67131 r
68132 for r in sheet_model
@@ -74,20 +138,17 @@ def get_project_funding_info(
74138 ]
75139 return filtered_rows
76140
77- @staticmethod
78- def get_project_names (sheet_model : List [FundingModel ]) -> List [str ]:
141+ def get_project_names (self ) -> List [str ]:
79142 """
80143 Returns a sorted list of unique project names found in the Funding
81144 SmartSheet.
82- Parameters
83- ----------
84- sheet_model : List[FundingModel]
85145
86146 Returns
87147 -------
88148 List[str]
89149
90150 """
151+ sheet_model = self ._get_parsed_sheet_model (model = FundingModel )
91152 project_names = set ()
92153 for funding_model in sheet_model :
93154 project_name = funding_model .project_name
@@ -99,15 +160,13 @@ def get_project_names(sheet_model: List[FundingModel]) -> List[str]:
99160 sorted_names = sorted (list (set (project_names )))
100161 return sorted_names
101162
102- @staticmethod
103163 def get_protocols_info (
104- sheet_model : List [ ProtocolsModel ] , protocol_name : Optional [str ] = None
164+ self , protocol_name : Optional [str ] = None
105165 ) -> List [ProtocolsModel ]:
106166 """
107167 Filter protocols Smartsheet by protocols name
108168 Parameters
109169 ----------
110- sheet_model : List[ProtocolsModel]
111170 protocol_name : str | None
112171
113172 Returns
@@ -117,22 +176,21 @@ def get_protocols_info(
117176 A list of all of that match the project name.
118177
119178 """
179+ sheet_model = self ._get_parsed_sheet_model (model = ProtocolsModel )
120180 filtered_rows = [
121181 r
122182 for r in sheet_model
123183 if r .protocol_name == protocol_name or protocol_name is None
124184 ]
125185 return filtered_rows
126186
127- @staticmethod
128187 def get_perfusions_info (
129- sheet_model : List [ PerfusionsModel ] , subject_id : Optional [str ] = None
188+ self , subject_id : Optional [str ] = None
130189 ) -> List [PerfusionsModel ]:
131190 """
132191
133192 Parameters
134193 ----------
135- sheet_model : List[PerfusionsModel]
136194 subject_id : str | None
137195
138196 Returns
@@ -141,6 +199,7 @@ def get_perfusions_info(
141199 In the event that there are multiple project names, will return
142200 A list of all of that match the project name.
143201 """
202+ sheet_model = self ._get_parsed_sheet_model (model = PerfusionsModel )
144203 filtered_rows = [
145204 r
146205 for r in sheet_model
0 commit comments