Skip to content

Commit 2392ea1

Browse files
Merge pull request #263 from DashAISoftware/improvement/file-upload-and-docs
Improvement dataloaders and added plugins in the documentation
2 parents d1b0810 + 1c7f286 commit 2392ea1

18 files changed

Lines changed: 1373 additions & 41 deletions

File tree

DashAI/back/app.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,4 @@ def create_app(
129129
app.container = container
130130
logger.debug("Application successfully created.")
131131

132-
@app.on_event("startup")
133-
async def maybe_start_job_loop():
134-
if not hasattr(app.state, "job_loop") or app.state.job_loop.done():
135-
app.state.job_loop = asyncio.create_task(
136-
job_queue_loop(stop_when_queue_empties=False)
137-
)
138-
139132
return app

DashAI/back/dataloaders/classes/csv_dataloader.py

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from datasets import load_dataset
88

99
from DashAI.back.core.schema_fields import (
10+
bool_field,
1011
enum_field,
12+
int_field,
1113
none_type,
1214
schema_field,
1315
string_field,
@@ -30,18 +32,102 @@ class CSVDataloaderSchema(BaseSchema):
3032
),
3133
) # type: ignore
3234
separator: schema_field(
33-
enum_field([",", ";", "\u0020", "\t"]),
35+
enum_field([",", ";", "blank space", "tab"]),
3436
",",
3537
"A separator character delimits the data in a CSV file.",
3638
) # type: ignore
3739

40+
header: schema_field(
41+
string_field(),
42+
"infer",
43+
(
44+
"Row number(s) containing column labels and marking the start of the data "
45+
"(zero-indexed). Default behavior is to infer the column names. If column "
46+
"names are passed explicitly, this should be set to '0'. "
47+
"Header can also be a list of integers that specify row locations "
48+
"for MultiIndex on the columns."
49+
),
50+
) # type: ignore
51+
52+
names: schema_field(
53+
none_type(string_field()),
54+
None,
55+
(
56+
"Comma-separated list of column names to use. If the file contains a "
57+
"header row, "
58+
"then you should explicitly pass header=0 to override the column names. "
59+
"Example: 'col1,col2,col3'. Leave empty to use file headers."
60+
),
61+
) # type: ignore
62+
63+
encoding: schema_field(
64+
enum_field(["utf-8", "latin1", "cp1252", "iso-8859-1"]),
65+
"utf-8",
66+
"Encoding to use for UTF when reading/writing. Most common encodings provided.",
67+
) # type: ignore
68+
69+
na_values: schema_field(
70+
none_type(string_field()),
71+
None,
72+
(
73+
"Comma-separated additional strings to recognize as NA/NaN. "
74+
"Example: 'NULL,missing,n/a'"
75+
),
76+
) # type: ignore
77+
78+
keep_default_na: schema_field(
79+
bool_field(),
80+
True,
81+
(
82+
"Whether to include the default NaN values when parsing the data "
83+
"(True recommended)."
84+
),
85+
) # type: ignore
86+
87+
true_values: schema_field(
88+
none_type(string_field()),
89+
None,
90+
"Comma-separated values to consider as True. Example: 'yes,true,1,on'",
91+
) # type: ignore
92+
93+
false_values: schema_field(
94+
none_type(string_field()),
95+
None,
96+
"Comma-separated values to consider as False. Example: 'no,false,0,off'",
97+
) # type: ignore
98+
99+
skip_blank_lines: schema_field(
100+
bool_field(),
101+
True,
102+
"If True, skip over blank lines rather than interpreting as NaN values.",
103+
) # type: ignore
104+
105+
skiprows: schema_field(
106+
none_type(int_field()),
107+
None,
108+
"Number of lines to skip at the beginning of the file. "
109+
"Leave empty to skip none.",
110+
) # type: ignore
111+
112+
nrows: schema_field(
113+
none_type(int_field()),
114+
None,
115+
"Number of rows to read from the file. Leave empty to read all rows.",
116+
) # type: ignore
117+
38118

39119
class CSVDataLoader(BaseDataLoader):
40120
"""Data loader for tabular data in CSV files."""
41121

42122
COMPATIBLE_COMPONENTS = ["TabularClassificationTask"]
43123
SCHEMA = CSVDataloaderSchema
44124

125+
DESCRIPTION: str = """
126+
Data loader for tabular data in CSV files.
127+
All uploaded CSV files must have the same column structure and use
128+
consistent separators.
129+
"""
130+
45131
def _check_params(
46132
self,
47133
params: Dict[str, Any],
@@ -51,12 +137,49 @@ def _check_params(
51137
"Error trying to load the CSV dataset: "
52138
"separator parameter was not provided."
53139
)
54-
separator = params["separator"]
55140

141+
clean_params = {}
142+
143+
separator = params["separator"]
144+
if separator == "blank space":
145+
separator = " "
146+
elif separator == "tab":
147+
separator = "\t"
56148
if not isinstance(separator, str):
57149
raise TypeError(
58150
f"Param separator should be a string, got {type(params['separator'])}"
59151
)
152+
clean_params["delimiter"] = separator
153+
154+
if params.get("header") is not None:
155+
clean_params["header"] = params["header"]
156+
157+
list_params = ["names", "na_values", "true_values", "false_values"]
158+
for param in list_params:
159+
if param in params and params[param]:
160+
clean_params[param] = [val.strip() for val in params[param].split(",")]
161+
162+
bool_params = ["keep_default_na", "skip_blank_lines"]
163+
for param in bool_params:
164+
if param in params and params[param] is not None:
165+
clean_params[param] = params[param]
166+
167+
int_params = ["skiprows", "nrows"]
168+
for param in int_params:
169+
if param in params and params[param] is not None:
170+
if not isinstance(params[param], int):
171+
raise TypeError(
172+
f"Param {param} should be an integer, got {type(params[param])}"
173+
)
174+
clean_params[param] = params[param]
175+
176+
if "encoding" in params and params["encoding"]:
177+
valid_encodings = ["utf-8", "latin1", "cp1252", "iso-8859-1"]
178+
if params["encoding"] not in valid_encodings:
179+
raise ValueError(f"Invalid encoding: {params['encoding']}")
180+
clean_params["encoding"] = params["encoding"]
181+
182+
return clean_params
60183

61184
@beartype
62185
def load_data(
@@ -83,20 +206,21 @@ def load_data(
83206
DatasetDict
84207
A HuggingFace's Dataset with the loaded data.
85208
"""
86-
self._check_params(params)
87-
separator = params["separator"]
209+
print("parameters are", params)
210+
clean_params = self._check_params(params)
211+
print("cleaned parameters are", clean_params)
88212
prepared_path = self.prepare_files(filepath_or_buffer, temp_path)
89213
if prepared_path[1] == "file":
90214
dataset = load_dataset(
91215
"csv",
92216
data_files=prepared_path[0],
93-
delimiter=separator,
217+
**clean_params,
94218
)
95219
else:
96220
dataset = load_dataset(
97221
"csv",
98222
data_dir=prepared_path[0],
99-
delimiter=separator,
223+
**clean_params,
100224
)
101225
shutil.rmtree(prepared_path[0])
102226

DashAI/back/dataloaders/classes/excel_dataloader.py

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from datasets.builder import DatasetGenerationError
1111

1212
from DashAI.back.core.schema_fields import (
13+
bool_field,
1314
int_field,
1415
none_type,
1516
schema_field,
@@ -47,7 +48,7 @@ class ExcelDataloaderSchema(BaseSchema):
4748
) # type: ignore
4849
header: schema_field(
4950
none_type(int_field(ge=0)),
50-
placeholder=0,
51+
placeholder=None,
5152
description="""
5253
The row number where the column names are located, indexed from 0.
5354
If null, the file will be considered to have no column names.
@@ -63,13 +64,112 @@ class ExcelDataloaderSchema(BaseSchema):
6364
""",
6465
) # type: ignore
6566

67+
skiprows: schema_field(
68+
none_type(int_field(ge=0)),
69+
None,
70+
(
71+
"Number of rows to skip at the start of the file. "
72+
"Leave empty to not skip any rows."
73+
),
74+
) # type: ignore
75+
76+
nrows: schema_field(
77+
none_type(int_field(ge=1)),
78+
None,
79+
"Number of rows to read. Leave empty to read all rows.",
80+
) # type: ignore
81+
82+
names: schema_field(
83+
none_type(string_field()),
84+
None,
85+
(
86+
"Comma-separated list of column names to use. Example: 'col1,col2,col3'. "
87+
"Leave empty to use header row."
88+
),
89+
) # type: ignore
90+
91+
na_values: schema_field(
92+
none_type(string_field()),
93+
None,
94+
(
95+
"Comma-separated additional strings to recognize as NA/NaN. "
96+
"Example: 'NA,N/A,null'."
97+
),
98+
) # type: ignore
99+
100+
keep_default_na: schema_field(
101+
bool_field(),
102+
True,
103+
"Whether to include the default NaN values when parsing the data.",
104+
) # type: ignore
105+
106+
true_values: schema_field(
107+
none_type(string_field()),
108+
None,
109+
"Comma-separated values to consider as True. Example: 'yes,true,1'.",
110+
) # type: ignore
111+
112+
false_values: schema_field(
113+
none_type(string_field()),
114+
None,
115+
"Comma-separated values to consider as False. Example: 'no,false,0'.",
116+
) # type: ignore
117+
66118

67119
class ExcelDataLoader(BaseDataLoader):
68120
"""Data loader for tabular data in Excel files."""
69121

70122
COMPATIBLE_COMPONENTS = ["TabularClassificationTask"]
71123
SCHEMA = ExcelDataloaderSchema
72124

125+
DESCRIPTION: str = """
126+
Data loader for tabular data in Excel files.
127+
Supports xls, xlsx, xlsm, xlsb, odf, ods and odt file extensions.
128+
"""
129+
130+
def _prepare_pandas_params(self, params: Dict[str, Any]) -> Dict[str, Any]:
131+
"""Prepare parameters for pandas.read_excel."""
132+
pandas_params = {}
133+
134+
if "sheet" in params and params["sheet"] is not None:
135+
pandas_params["sheet_name"] = params["sheet"]
136+
137+
pandas_params["header"] = params.get("header", 0)
138+
139+
if "usecols" in params and params["usecols"] is not None:
140+
pandas_params["usecols"] = params["usecols"]
141+
142+
if "skiprows" in params and params["skiprows"] is not None:
143+
pandas_params["skiprows"] = params["skiprows"]
144+
145+
if "nrows" in params and params["nrows"] is not None:
146+
pandas_params["nrows"] = params["nrows"]
147+
148+
if "names" in params and params["names"] is not None:
149+
pandas_params["names"] = [
150+
name.strip() for name in params["names"].split(",")
151+
]
152+
153+
if "na_values" in params and params["na_values"] is not None:
154+
pandas_params["na_values"] = [
155+
val.strip() for val in params["na_values"].split(",")
156+
]
157+
158+
if "keep_default_na" in params and params["keep_default_na"] is not None:
159+
pandas_params["keep_default_na"] = params["keep_default_na"]
160+
161+
if "true_values" in params and params["true_values"] is not None:
162+
pandas_params["true_values"] = [
163+
val.strip() for val in params["true_values"].split(",")
164+
]
165+
166+
if "false_values" in params and params["false_values"] is not None:
167+
pandas_params["false_values"] = [
168+
val.strip() for val in params["false_values"].split(",")
169+
]
170+
171+
return pandas_params
172+
73173
@beartype
74174
def load_data(
75175
self,
@@ -95,13 +195,15 @@ def load_data(
95195
A HuggingFace's Dataset with the loaded data.
96196
"""
97197
prepared_path = self.prepare_files(filepath_or_buffer, temp_path)
198+
print("path prepared", prepared_path)
199+
200+
pandas_params = self._prepare_pandas_params(params)
201+
98202
if prepared_path[1] == "file":
99203
try:
100204
dataset = pd.read_excel(
101205
io=prepared_path[0],
102-
sheet_name=params["sheet"],
103-
header=params["header"],
104-
usecols=params["usecols"],
206+
**pandas_params,
105207
)
106208
except ValueError as e:
107209
raise DatasetGenerationError from e
@@ -116,9 +218,7 @@ def load_data(
116218
train_df_list = [
117219
pd.read_excel(
118220
io=file_path,
119-
sheet_name=params["sheet"],
120-
header=params["header"],
121-
usecols=params["usecols"],
221+
**pandas_params,
122222
)
123223
for file_path in sorted(train_files)
124224
]
@@ -127,9 +227,7 @@ def load_data(
127227
test_df_list = [
128228
pd.read_excel(
129229
io=file_path,
130-
sheet_name=params["sheet"],
131-
header=params["header"],
132-
usecols=params["usecols"],
230+
**pandas_params,
133231
)
134232
for file_path in sorted(test_files)
135233
]
@@ -138,9 +236,7 @@ def load_data(
138236
val_df_list = [
139237
pd.read_excel(
140238
io=file_path,
141-
sheet_name=params["sheet"],
142-
header=params["header"],
143-
usecols=params["usecols"],
239+
**pandas_params,
144240
)
145241
for file_path in sorted(val_files)
146242
]

0 commit comments

Comments
 (0)