Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jgunstone committed Jul 26, 2024
1 parent 8669250 commit 9714832
Show file tree
Hide file tree
Showing 8 changed files with 2,839 additions and 2 deletions.
154 changes: 154 additions & 0 deletions docs/codegen-issue-1624.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 8,
"id": "adc73f33-9585-4780-bfc9-9ecd3496cd9b",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/jovyan/miniforge3/envs/xlsxdatagrid-dev/lib/python3.12/site-packages/datamodel_code_generator/parser/jsonschema.py:338: UserWarning: format of 'duration' not understood for 'string' - using default\n",
" warn(f'format of {format__!r} not understood for {type_!r} - using default' '')\n"
]
},
{
"data": {
"text/plain": [
"{'a_int': FieldInfo(annotation=Union[int, NoneType], required=False, default=1, title='A Int'),\n",
" 'i_duration': FieldInfo(annotation=Union[str, NoneType], required=False, default='PT2H33M3S', title='I Duration')}"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import typing as ty\n",
"import importlib\n",
"import json\n",
"from pathlib import Path\n",
"from tempfile import TemporaryDirectory\n",
"from datamodel_code_generator import InputFileType, generate, DataModelType\n",
"from pydantic import BaseModel\n",
"import sys\n",
"\n",
"def pydantic_model_from_json_schema(json_schema: str) -> ty.Type[BaseModel]:\n",
" load = json_schema[\"title\"] if \"title\" in json_schema else \"Model\"\n",
"\n",
" with TemporaryDirectory() as temporary_directory_name:\n",
" temporary_directory = Path(temporary_directory_name)\n",
" file_path = \"model.py\"\n",
" module_name = file_path.split(\".\")[0]\n",
" output = Path(temporary_directory / file_path)\n",
" generate(\n",
" json.dumps(json_schema),\n",
" input_file_type=InputFileType.JsonSchema,\n",
" input_filename=\"example.json\",\n",
" output=output,\n",
" output_model_type=DataModelType.PydanticV2BaseModel,\n",
" )\n",
" spec = importlib.util.spec_from_file_location(module_name, output)\n",
" module = importlib.util.module_from_spec(spec)\n",
" sys.modules[module_name] = module\n",
" spec.loader.exec_module(module)\n",
" return getattr(module, load)\n",
"\n",
"schema = {\n",
" \"title\": \"Test\",\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"a_int\": {\n",
" \"default\": 1,\n",
" \"title\": \"A Int\",\n",
" \"type\": \"integer\"\n",
" },\n",
" \"i_duration\": {\n",
" \"default\": \"PT2H33M3S\",\n",
" \"format\": \"duration\",\n",
" \"title\": \"I Duration\",\n",
" \"type\": \"string\"\n",
" }\n",
" },\n",
" }\n",
"\n",
"Model = pydantic_model_from_json_schema(schema)\n",
"Model.model_fields"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "0b1c0296-f241-4c1d-b5d3-aca152cf2be6",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'a_int': FieldInfo(annotation=Union[int, NoneType], required=False, default=1, title='A Int'),\n",
" 'i_duration': FieldInfo(annotation=timedelta, required=False, default='PT2H33M3S')}"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from datetime import timedelta\n",
"from pydantic import create_model\n",
"\n",
"\n",
"def get_timedelta_fields(schema: dict) -> list[str]:\n",
" pr = schema[\"properties\"]\n",
" return [k for k, v in pr.items() if \"format\" in v and v[\"format\"] == \"duration\"]\n",
" \n",
"def update_timedelta_field(model: BaseModel, timedelta_fields: list[str]) -> BaseModel:\n",
" \"\"\"returns a new pydantic model where serialization validators have been added to dates,\n",
" datetimes and durations for compatibility with excel\"\"\"\n",
" get_default = lambda obj: obj.default if hasattr(obj, \"default\") else ...\n",
" deltas = {\n",
" k: (timedelta, get_default(v))\n",
" for k, v in model.model_fields.items()\n",
" if k in timedelta_fields\n",
" } | {\"__base__\": model}\n",
" return create_model(model.__name__ + \"New\", **deltas)\n",
"\n",
"\n",
"li = get_timedelta_fields(schema)\n",
"Model1 = update_timedelta_field(Model, li)\n",
"Model1.model_fields"
]
},
{
"cell_type": "markdown",
"id": "636bc3be-627c-4720-ae48-21ba5440118d",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ classifiers = [
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = ["xlsxwriter", "pydantic>2", "jsonref", "palettable", "pydantic_extra_types", "python-calamine", "datamodel-code-generator", "stringcase"]
dependencies = ["xlsxwriter", "pydantic>2", "jsonref", "palettable", "pydantic_extra_types", "python-calamine", "datamodel-code-generator", "stringcase", "frictionless"]

[project.urls]
Documentation = "https://github.com/maxfordham/xlsxdatagrid#readme"
Expand Down
Empty file added src/xlsxdatagrid/write.py
Empty file.
30 changes: 29 additions & 1 deletion src/xlsxdatagrid/xlsxdatagrid.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pathlib
from annotated_types import doc
import annotated_types
from pydantic import (
Expand Down Expand Up @@ -551,7 +552,6 @@ def build(self) -> "XlTableWriter":
]
self.format_arrays[d] = "duration"


return self


Expand Down Expand Up @@ -750,3 +750,31 @@ def write_table(workbook, xl_tbl: XlTableWriter):
worksheet.write(*xl_tbl.xy, xl_tbl.metadata, header_label_cell_format)

return None


def from_jsonschema_and_data(data: dict, gridschema: dict, fpth: pathlib.Path = None):

if fpth is None:
fpth = pathlib.Path(gridschema.title + ".xlsx")

xl_tbl = XlTableWriter(data=data, gridschema=gridschema)
workbook = xw.Workbook(str(fpth))
write_table(workbook, xl_tbl)
workbook.close()
return fpth


def from_pydantic_object(
pydantic_object: ty.Type[BaseModel], fpth: pathlib.Path = None
) -> pathlib.Path:

data, gridschema = get_data_and_schema(pydantic_object)

return from_jsonschema_and_data(data, gridschema, fpth=fpth)


def from_pydantic_objects(
pydantic_objects: list[ty.Type[BaseModel]], fpth: pathlib.Path
) -> pathlib.Path:

pass
1 change: 1 addition & 0 deletions tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


PATH_XL = pathlib.Path(__file__).parent / "ExcelOut.xlsx"
PATH_XL_MANY_SHEETS = pathlib.Path(__file__).parent / "ExcelOutManySheets.xlsx"
PATH_XL_TRANSPOSED = pathlib.Path(__file__).parent / "ExcelOutTransposed.xlsx"
PATH_XL_FROM_SCHEMA = pathlib.Path(__file__).parent / "ExcelOutFromSchema.xlsx"
PATH_XL_FROM_SCHEMA_TRANSPOSED = (
Expand Down
Loading

0 comments on commit 9714832

Please sign in to comment.