33from dataclasses import dataclass
44from datetime import datetime
55from enum import Enum , auto
6- from typing import Any , Dict , List , Optional , TypeVar , get_args , get_origin
6+ from typing import Any , Callable , Dict , List , Optional , TypeVar
77
8+ import csp
89import orjson
910import pyarrow .parquet as pq
10- from csp import ts
1111from csp .impl .pushadapter import PushInputAdapter
1212from csp .impl .types .container_type_normalizer import ContainerTypeNormalizer
1313from csp .impl .wiring import py_push_adapter_def
2626
2727
2828class FileDropType (Enum ):
29+ CUSTOM = auto ()
2930 CSV = auto ()
3031 JSON = auto ()
3132 PARQUET = auto ()
@@ -39,46 +40,35 @@ class FileDropAdapterConfiguration:
3940 dir_path : str
4041 # Format of files to expect to load properly i.e parquet, json, csv
4142 filedrop_type : FileDropType
42- # Map the data fields from the file to the fields of the structs
43- field_map : Dict [str , str ]
4443 # List of extensions to filter, empty list means all extensions are allowed
4544 extensions : List [str ]
45+ # custom loader for loading files into list of struct like data
46+ loader : Optional [Callable [[str ], List [Any ]]]
47+ # deserialize each data point to data type expected by channel type
48+ deserializer : Optional [Callable [[Any ], Any ]]
4649 # Extra args to the type adapter deserializer
4750 type_adapter_args : Dict [str , Any ]
4851
4952
5053class FileReaderBase :
5154 """The base file reader that reads data from files and generates structs"""
5255
53- def __init__ (self , config : FileDropAdapterConfiguration , ts_typ : object , deserializer : Optional [object ] = None ):
54- self .field_map = config .field_map
56+ def __init__ (self , config : FileDropAdapterConfiguration , ts_typ : object ):
5557 self .extensions = config .extensions
5658 if hasattr (config , "type_adapter_args" ):
5759 self .context = config .type_adapter_args
5860 else :
5961 self .context = {}
60- if not deserializer :
62+ if hasattr (config , "deserializer" ) and config .deserializer :
63+ self .deserializer = config .deserializer
64+ else :
6165 normalized_type = ContainerTypeNormalizer .normalize_type (ts_typ )
6266 type_adapter = TypeAdapter (normalized_type )
63- if get_origin (normalized_type ) is list :
64-
65- def deserialize_tick (data , type_adapter = type_adapter , apply_field_map = self .apply_field_map , context = self .context ):
66- data = [apply_field_map (d ) for d in data ]
67- return type_adapter .validate_python (data , context = context )
68- elif get_origin (normalized_type ) is dict :
69- key_type , inner_type = get_args (normalized_type )
70-
71- def deserialize_tick (data , type_adapter = type_adapter , apply_field_map = self .apply_field_map , context = self .context ):
72- data = {k : apply_field_map (d ) for k , d in data .items ()}
73- return type_adapter .validate_python (data , context = context )
74- else :
7567
76- def deserialize_tick (data , type_adapter = type_adapter , apply_field_map = self . apply_field_map , context = self .context ):
77- return type_adapter .validate_python (apply_field_map ( data ) , context = context )
68+ def deserialize_tick (data , type_adapter = type_adapter , context = self .context ):
69+ return type_adapter .validate_python (data , context = context )
7870
7971 self .deserializer = deserialize_tick
80- else :
81- self .deserializer = deserializer
8272
8373 def read (self , src_path : str ) -> object :
8474 """Generator to return stucts from a filepath"""
@@ -98,23 +88,11 @@ def read_impl(self, src_path: str) -> List[dict]:
9888
9989 raise Exception (f"read not implemented for { self } " )
10090
101- def apply_field_map (self , data : dict ) -> dict :
102- """Convert the keys in the data to the field names of the struct"""
103-
104- if self .field_map :
105- new_data = {}
106- for k , v in data .items ():
107- new_k = self .field_map .get (k , k )
108- new_data [new_k ] = v
109- return new_data
110- else :
111- return data
112-
11391
11492class FileReaderCsv (FileReaderBase ):
11593 """File reader for json file type"""
11694
117- def read_impl (self , src_path : str ) -> List [dict ]:
95+ def read_impl (self , src_path : str ) -> List [Any ]:
11896 data = []
11997 with open (src_path , "r" ) as f :
12098 reader = csv .DictReader (f )
@@ -126,7 +104,7 @@ def read_impl(self, src_path: str) -> List[dict]:
126104class FileReaderJson (FileReaderBase ):
127105 """File reader for json file type"""
128106
129- def read_impl (self , src_path : str ) -> List [dict ]:
107+ def read_impl (self , src_path : str ) -> List [Any ]:
130108 with open (src_path , "rb" ) as f :
131109 data = orjson .loads (f .read ())
132110 if isinstance (data , list ):
@@ -139,11 +117,22 @@ def read_impl(self, src_path: str) -> List[dict]:
139117class FileReaderParquet (FileReaderBase ):
140118 """File reader for parquet file type"""
141119
142- def read_impl (self , src_path : str ) -> List [dict ]:
120+ def read_impl (self , src_path : str ) -> List [Any ]:
143121 table = pq .read_table (src_path )
144122 return table .to_pylist ()
145123
146124
125+ class FileReaderCustom (FileReaderBase ):
126+ """File reader for a custom file loader"""
127+
128+ def __init__ (self , config : FileDropAdapterConfiguration , ts_typ : object ):
129+ super ().__init__ (config , ts_typ )
130+ self ._loader = config .loader
131+
132+ def read_impl (self , src_path : str ) -> List [Any ]:
133+ return self ._loader (src_path )
134+
135+
147136class EventHandlerCustom (FileSystemEventHandler ):
148137 def __init__ (self , adapter : PushInputAdapter , file_reader : FileReaderBase ):
149138 self .file_reader = file_reader
@@ -185,14 +174,15 @@ class _FileDropImpl(PushInputAdapter):
185174 FileDropType .CSV : FileReaderCsv ,
186175 FileDropType .JSON : FileReaderJson ,
187176 FileDropType .PARQUET : FileReaderParquet ,
177+ FileDropType .CUSTOM : FileReaderCustom ,
188178 }
189179
190- def __init__ (self , config : FileDropAdapterConfiguration , ts_typ : T , deserializer : Optional [ object ] = None ):
180+ def __init__ (self , config : FileDropAdapterConfiguration , ts_typ : T ):
191181 # NOTE ts_typ is assumed to be a List["Y"] type where "Y" is the actual type
192182 self .dir_path = config .dir_path
193183 self .observer = Observer ()
194184 reader = self .FILEREADER_MAP [config .filedrop_type ]
195- file_reader = reader (config , ts_typ [0 ], deserializer )
185+ file_reader = reader (config , ts_typ [0 ])
196186 self .event_handler = EventHandlerCustom (self , file_reader )
197187 self .observer .schedule (self .event_handler , self .dir_path , recursive = False )
198188
@@ -213,11 +203,17 @@ def stop(self):
213203# Further we can only take the object type as output due to issues caused by using List[T] instead of T and bypassing
214204# the type normalization process in csp. The user of the adapter, needs to keep track of the type they pass in ts_type,
215205# and cast the ts[object] to the required ts[T], they want using csp.appy
216- filedrop_adapter_def = py_push_adapter_def (
217- "filedrop_adapter_def " ,
206+ _filedrop_adapter_def = py_push_adapter_def (
207+ "_filedrop_adapter_def " ,
218208 _FileDropImpl ,
219- ts [object ],
209+ csp . ts [object ],
220210 config = FileDropAdapterConfiguration ,
221211 ts_typ = List [T ],
222- deserializer = Optional [object ],
223212)
213+
214+
215+ @csp .graph
216+ def filedrop_adapter_def (config : FileDropAdapterConfiguration , ts_typ : "T" ) -> csp .ts ["T" ]:
217+ object_data = _filedrop_adapter_def (config = config , ts_typ = [ts_typ ])
218+ data = csp .apply (object_data , lambda x : x , ts_typ )
219+ return data
0 commit comments