Skip to content

Commit 0cb0fb3

Browse files
Merge pull request #390 from maxfordham/final-changes-for-import-export-excel
Final changes for import/export excel
2 parents 7b946a0 + 35cbdd0 commit 0cb0fb3

6 files changed

Lines changed: 352 additions & 304 deletions

File tree

pixi.lock

Lines changed: 276 additions & 260 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ dependencies = [
4444
"wcmatch",
4545
"deepdiff",
4646
"datamodel-code-generator>=0.28.5",
47-
"xlsxdatagrid>=0.3.2,<0.4",
47+
"xlsxdatagrid>=0.3.3,<0.4",
4848
]
4949

5050
[project.urls]
@@ -72,6 +72,8 @@ platforms = ["linux-64"]
7272

7373
[tool.pixi.pypi-dependencies]
7474
ipyautoui = { path = ".", editable = true }
75+
# datamodel-code-generator = { git = "https://github.com/Arshadwaqas115/datamodel-code-generator", branch = "main" }
76+
# xlsxdatagrid = { path = "/home/jovyan/xlsxdatagrid", editable = true }
7577

7678
[tool.pixi.feature.tests.pypi-dependencies]
7779
pytest_examples = "*"

src/ipyautoui/automapschema.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def pydantic_model_file_from_json_schema(json_schema, fpth):
3131
output=fpth,
3232
output_model_type=DataModelType.PydanticV2BaseModel,
3333
capitalise_enum_members=True,
34+
field_include_all_keys=True
3435
)
3536

3637
def pydantic_model_from_json_schema(json_schema: dict) -> ty.Type[BaseModel]:
@@ -76,18 +77,22 @@ def _init_model_schema(
7677
}
7778
if isinstance(schema, dict):
7879
if generate_pydantic_model_from_json_schema:
80+
schema = replace_refs(schema, merge_props=True)
81+
schema = {k: v for k, v in schema.items() if k != "$defs"}
7982
model = pydantic_model_from_json_schema(schema)
8083
else:
8184
model = None
85+
schema = replace_refs(schema, merge_props=True)
86+
schema = {k: v for k, v in schema.items() if k != "$defs"}
8287
# IDEA: Possible implementations -@jovyan at 8/24/2022, 12:05:02 PM
8388
# jsonschema_to_pydantic
8489
# https://koxudaxi.github.io/datamodel-code-generator/using_as_module/
8590
else:
8691
model = schema # the "model" passed is a pydantic model
8792
schema = model.model_json_schema(by_alias=by_alias).copy()
88-
89-
schema = replace_refs(schema, merge_props=True)
90-
schema = {k: v for k, v in schema.items() if k != "$defs"}
93+
schema = replace_refs(schema, merge_props=True)
94+
schema = {k: v for k, v in schema.items() if k != "$defs"}
95+
9196
return model, schema
9297

9398

src/ipyautoui/autoui.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -210,14 +210,7 @@ def get_autoui(schema: ty.Union[ty.Type[BaseModel], dict], **kwargs):
210210
li = [caller.autoui, TitleDescription, ShowRaw, AutoUiFileMethods]
211211

212212
class AutoUi(*li):
213-
def _set_children(self):
214-
self.children = [
215-
w.HBox([self.bn_showraw, self.html_title]),
216-
self.html_description,
217-
self.vbx_error,
218-
self.vbx_widget,
219-
self.vbx_showraw,
220-
]
213+
pass
221214

222215
else:
223216

src/ipyautoui/custom/edittsv.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ class EditTsv(CopyToClipboard):
143143
allow_download = tr.Bool(default_value=True)
144144
exclude_metadata = tr.Bool(default_value=True)
145145
header_depth = tr.Int(default_value=1)
146-
disable_text_editing = tr.Bool(default_value=False)
146+
disable_text_editing = tr.Bool(default_value=True)
147+
filename_suffix = tr.Unicode(default_value="", allow_none=True)
147148

148149
@tr.observe("upload_status")
149150
def upload_status_onchange(self, on_change):
@@ -239,6 +240,12 @@ def create_file(self):
239240
is_transposed=self.transposed,
240241
exclude_metadata=self.exclude_metadata,
241242
)[0]
243+
# Append suffix to filename if provided
244+
if self.filename_suffix:
245+
new_name = fpth.stem + self.filename_suffix + fpth.suffix
246+
new_fpth = fpth.parent / new_name
247+
fpth.rename(new_fpth)
248+
fpth = new_fpth
242249
return fpth
243250

244251
def _text(self, change):
@@ -249,7 +256,7 @@ def _text(self, change):
249256
is_transposed=self.transposed,
250257
model=self.model,
251258
delimiter="\t",
252-
header_depth=self.header_depth)
259+
)
253260
if self.errors:
254261
self.vbx_errors.children = [
255262
w.HTML(markdown(markdown_error(e))) for e in self.errors
@@ -265,7 +272,7 @@ def tsv_data(self): # TODO: ensure header row unchanged
265272
is_transposed=self.transposed,
266273
model=self.model,
267274
delimiter="\t",
268-
header_depth=self.header_depth)
275+
)
269276

270277
@property
271278
def pydantic_object(self):
@@ -484,6 +491,12 @@ def __init__(self, **kwargs):
484491

485492
self.bn_confirmation.on_click(self._bn_check_upload)
486493
self.bn_cross.on_click(self._bn_cross_clicked)
494+
495+
self.file_uploader = TempFileUploadProcessor(
496+
fn_process=self._process_uploaded_file,
497+
allowed_file_type=".xlsx",
498+
)
499+
487500
super().__init__(**kwargs)
488501
self.value = value
489502
self.upload_status = "None"
@@ -495,9 +508,9 @@ def _update_changes(self, change):
495508

496509
def _set_children(self):
497510
if self.allow_download:
498-
self.vbx_bns.children = [self.bn_copy, self.bn_upload_text, self.mfdld, self.bn_confirmation, self.bn_cross]
511+
self.vbx_bns.children = [self.bn_copy, self.bn_upload_text, self.mfdld, self.bn_confirmation, self.bn_cross, self.file_uploader]
499512
else:
500-
self.vbx_bns.children = [self.bn_copy, self.bn_upload_text, self.bn_confirmation, self.bn_cross]
513+
self.vbx_bns.children = [self.bn_copy, self.bn_upload_text, self.bn_confirmation, self.bn_cross, self.file_uploader]
501514
self.hbx_main.children = [self.vbx_bns, self.text, self.ddiff, self.output]
502515
self.children = [self.vbx_errors, self.hbx_main]
503516

@@ -549,6 +562,9 @@ def _bn_check_upload(self, onclick):
549562

550563
def _bn_cross_clicked(self, onclick):
551564
self.show_upload_button_and_hide_deepdiff(upload_disabled=False)
565+
566+
def _process_uploaded_file(self, path: Path):
567+
pass
552568

553569
def show_upload_button_and_hide_deepdiff(self, upload_disabled=True):
554570
# Hide check button and show upload button as well as text area
@@ -590,7 +606,6 @@ def deepdiff_to_crud(self, diff: DeepDiff):
590606
path_list = delta.path(output_format="list")
591607
primary_key = str(path_list[0]) # first key (primary key)
592608
changes.deletions.append(primary_key)
593-
594609

595610
if "values_changed" in diff:
596611
for delta in diff["values_changed"]:
@@ -653,23 +668,8 @@ def __init__(self, **kwargs):
653668
# Ensure text editing is disabled by default
654669
kwargs.setdefault("disable_text_editing", True)
655670

656-
# Create uploader before parent init so it's available during layout build
657-
self.file_uploader = TempFileUploadProcessor(
658-
fn_process=self._process_uploaded_file,
659-
allowed_file_type=".xlsx",
660-
)
661-
662671
# Initialize base class
663672
super().__init__(**kwargs)
664-
self.last_upload_metadata = None
665-
666-
def _set_children(self):
667-
super()._set_children()
668-
buttons = list(self.vbx_bns.children)
669-
if self.file_uploader not in buttons:
670-
insert_at = 1 if buttons else 0
671-
buttons.insert(insert_at, self.file_uploader)
672-
self.vbx_bns.children = tuple(buttons)
673673

674674
def _text(self, change):
675675
pass
@@ -684,7 +684,6 @@ def _process_uploaded_file(self, path: Path):
684684
data, errors = xdg.read_excel(
685685
path,
686686
is_transposed=self.transposed,
687-
header_depth=self.header_depth,
688687
model=self.model,
689688
)
690689
except Exception:

src/ipyautoui/custom/edittsv_with_diff_and_key_mapping.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from deepdiff import DeepDiff
2-
from pydantic import RootModel, create_model
3-
from ipyautoui.custom.edittsv import Changes, EditTsvWithDiff
2+
from pydantic import RootModel, create_model, ConfigDict
3+
from ipyautoui.custom.edittsv import Changes, EditTsvFileUpload
44
import traitlets as tr
55
import typing as ty
66

7-
class EditTsvWithDiffAndKeyMapping(EditTsvWithDiff):
7+
class EditTsvWithDiffAndKeyMapping(EditTsvFileUpload):
88
primary_key_name = tr.List(tr.Unicode(),
99
default_value=None,
1010
allow_none=True,
@@ -14,24 +14,57 @@ class EditTsvWithDiffAndKeyMapping(EditTsvWithDiff):
1414
def __init__(self, **kwargs):
1515
super().__init__(**kwargs)
1616
if self.exclude_fields_from_model:
17+
# Get original schema from RootModel BEFORE we replace it
18+
original_schema = self.model.model_json_schema()
19+
1720
inner_model = self.model.model_fields['root'].annotation.__args__[0]
18-
# Keep alias names in the new model
21+
22+
# Preserve the original model_config
23+
original_config = getattr(inner_model, 'model_config', {})
24+
25+
config_dict = dict(original_config) if original_config else {}
26+
27+
# Keep alias names in the new model and preserve json_schema_extra
1928
fields = {}
2029
for name, field in inner_model.model_fields.items():
2130
converted_name = field.alias or name
2231
if converted_name not in self.exclude_fields_from_model:
2332
alias = field.alias or name
24-
fields[alias] = (field.annotation, field.default)
33+
# Preserve the field with all its metadata
34+
fields[alias] = (field.annotation, field)
2535

36+
# Create new model with preserved config
2637
stripped_inner = create_model(
2738
inner_model.__name__,
39+
__config__=ConfigDict(**config_dict) if config_dict else None,
2840
**fields,
2941
)
30-
self.model = type(
31-
self.model.__name__,
32-
(RootModel[list[stripped_inner]],),
33-
{},
34-
)
42+
43+
# Create new RootModel wrapping the stripped inner model
44+
class StrippedRootModel(RootModel[list[stripped_inner]]):
45+
@classmethod
46+
def model_json_schema(cls, **kwargs):
47+
"""Override to preserve datagrid_index_name and other schema metadata."""
48+
schema = super().model_json_schema(**kwargs)
49+
# Preserve top-level schema metadata from original RootModel
50+
for key in ['datagrid_index_name', 'hide_nan', 'format', 'is_transposed']:
51+
if key in original_schema:
52+
schema[key] = original_schema[key]
53+
54+
# Modify datagrid_index_name: remove 'title', append 'name' at end
55+
if 'datagrid_index_name' in schema:
56+
index_list = schema['datagrid_index_name']
57+
if index_list and isinstance(index_list, list) and 'title' in index_list:
58+
# Remove 'title' and append 'name' at the end
59+
schema['datagrid_index_name'] = [
60+
item for item in index_list if item != 'title'
61+
]
62+
schema['datagrid_index_name'].append('name')
63+
64+
return schema
65+
66+
StrippedRootModel.__name__ = self.model.__name__
67+
self.model = StrippedRootModel
3568

3669
def _bn_upload_text(self, on_click):
3770
# hide grid/errors

0 commit comments

Comments
 (0)