Skip to content

Commit 082ebec

Browse files
feat: Adding a script to generate schema (#198)
* Adding a script to generate pydantic class from GEOS XSD schema. * Get the schema from github REST/API * Fix hard coded variable * Discard all but ProblemType * Regen schema * Update ReadMe --------- Co-authored-by: jacques franc <jacquesfrancdev@gmail.com>
1 parent a854dba commit 082ebec

13 files changed

Lines changed: 15254 additions & 23893 deletions

File tree

geos-trame/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ dependencies = [
4242
"trame-matplotlib==2.0.3",
4343
"trame-components==2.4.2",
4444
"mpld3<0.5.11",
45-
"xsdata==24.5",
45+
"xsdata[cli]>=25.4",
4646
"xsdata-pydantic[lxml]==24.5",
4747
"pyvista==0.45.2",
4848
"dpath==2.2.0",

geos-trame/src/geos/trame/app/deck/file.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from geos.trame.app.geosTrameException import GeosTrameException
1515
from geos.trame.app.io.xml_parser import XMLParser
1616
from geos.trame.app.utils.file_utils import normalize_path
17-
from geos.trame.schema_generated.schema_mod import Problem
17+
from geos.trame.schema_generated.schema_mod import ProblemType
1818

1919

2020
class DeckFile( object ):
@@ -30,7 +30,7 @@ def __init__( self, filename: str, **kwargs: Any ) -> None:
3030

3131
self.inspect_tree: dict[ Any, Any ] | None = None
3232
self.pb_dict: dict[ str, Any ] | None = None
33-
self.problem: Problem | None = None
33+
self.problem: ProblemType | None = None
3434
self.xml_parser: XMLParser | None = None
3535
self.root_node = None
3636
self.filename = normalize_path( filename )
@@ -79,7 +79,7 @@ def open_deck_file( self, filename: str ) -> None:
7979
)
8080
parser = XmlParser( context=context, config=ParserConfig() )
8181
try:
82-
self.problem = parser.parse( simulation_deck, Problem )
82+
self.problem = parser.parse( simulation_deck, ProblemType )
8383
except ElementTree.XMLSyntaxError as e:
8484
msg = "Failed to parse input file %s:\n%s\n" % ( filename, e )
8585
raise GeosTrameException( msg ) from e

geos-trame/src/geos/trame/app/deck/tree.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
from geos.trame.app.deck.file import DeckFile
2222
from geos.trame.app.geosTrameException import GeosTrameException
23-
from geos.trame.schema_generated.schema_mod import Problem, Included, File, Functions
23+
from geos.trame.schema_generated.schema_mod import ProblemType, Included, File, Functions
2424
from geos.trame.app.utils.file_utils import normalize_path, format_xml
2525

2626
import logging
@@ -127,14 +127,14 @@ def encode_data( data: BaseModel ) -> dict:
127127
return nodeDict
128128

129129
@staticmethod
130-
def decode_data( data: dict ) -> Problem:
130+
def decode_data( data: dict ) -> ProblemType:
131131
"""Convert a data to a xml serializable file."""
132132
context = XmlContext(
133133
element_name_generator=text.pascal_case,
134134
attribute_name_generator=text.camel_case,
135135
)
136136
decoder = DictDecoder( context=context, config=ParserConfig() )
137-
node: Problem = decoder.decode( data )
137+
node: ProblemType = decoder.decode( data )
138138
return node
139139

140140
@staticmethod
@@ -200,8 +200,8 @@ def write_files( self ) -> None:
200200
files = self._split( pb )
201201

202202
for filepath, content in files.items():
203-
model_loaded: Problem = DeckTree.decode_data( content )
204-
model_with_changes: Problem = self._apply_changed_properties( model_loaded )
203+
model_loaded: ProblemType = DeckTree.decode_data( content )
204+
model_with_changes: ProblemType = self._apply_changed_properties( model_loaded )
205205

206206
assert ( self.input_file is not None and self.input_file.xml_parser is not None )
207207
if self.input_file.xml_parser.contains_include_files():
@@ -221,7 +221,7 @@ def write_files( self ) -> None:
221221
self._ctrl.on_add_success( title="File saved", message=f"File {basename} has been saved." )
222222

223223
@staticmethod
224-
def _append_include_file( model: Problem, included_file_path: str ) -> None:
224+
def _append_include_file( model: ProblemType, included_file_path: str ) -> None:
225225
"""Append an Included object which follows this structure according to the documentation.
226226
227227
<Included>
@@ -278,7 +278,7 @@ def _convert_to_snake_case( content: str ) -> str:
278278
"""
279279
return "".join( [ "_" + char.lower() if char.isupper() else char for char in content ] ).lstrip( "_" )
280280

281-
def _apply_changed_properties( self, model: Problem ) -> Problem:
281+
def _apply_changed_properties( self, model: ProblemType ) -> ProblemType:
282282
"""Retrieves all edited 'properties' from the simput_manager and apply it to a given model."""
283283
manager = get_simput_manager( self._sm_id )
284284
modified_proxy_ids: set[ str ] = manager.proxymanager.dirty_proxy_data

geos-trame/src/geos/trame/app/io/data_loader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def _update_vtkwell( self, well: Vtkwell, path: str, show: bool ) -> None:
113113
self.well_viewer.add_mesh( well_polydata, path )
114114

115115
def _update_internalwell( self, well: InternalWell, path: str, show: bool ) -> None:
116-
"""Used to control the visibility of the InternalWell.
116+
"""Used to control the visibility of the InternalWellType.
117117
118118
This method will create the mesh if it doesn't exist.
119119
"""
@@ -134,7 +134,7 @@ def _update_internalwell( self, well: InternalWell, path: str, show: bool ) -> N
134134

135135
@staticmethod
136136
def __parse_polyline_property( polyline_property: str, dtype: Type[ Any ] ) -> np.ndarray:
137-
"""Internal method used to parse and convert a property, such as polyline_node_coords, from an InternalWell.
137+
"""Internal method used to parse and convert a property, such as polyline_node_coords, from an InternalWellType.
138138
139139
This string always follow this for :
140140
"{ { 800, 1450, 395.646 }, { 800, 1450, -554.354 } }"

geos-trame/src/geos/trame/app/ui/inspector.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from geos.trame.app.data_types.tree_node import TreeNode
1616
from geos.trame.app.deck.tree import DeckTree
1717
from geos.trame.app.utils.dict_utils import iterate_nested_dict
18-
from geos.trame.schema_generated.schema_mod import Problem
18+
from geos.trame.schema_generated.schema_mod import ProblemType
1919

2020
vuetify.enable_lab()
2121

@@ -112,7 +112,7 @@ def source( self ) -> dict | None:
112112
# TODO
113113
# v should be a proxy like the one in paraview simple
114114
# maybe it can be Any of schema_mod (e.g. Problem)
115-
def _set_source( self, v: Problem | None ) -> None:
115+
def _set_source( self, v: ProblemType | None ) -> None:
116116

117117
# TODO replace this snippet
118118
from xsdata.formats.dataclass.serializers.config import SerializerConfig

geos-trame/src/geos/trame/app/ui/viewer/boxViewer.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ def __init__( self, mesh: pv.UnstructuredGrid, box: Box ) -> None:
1919
self._mesh: pv.UnstructuredGrid = mesh
2020

2121
self._box: Box = box
22-
self._box_polydata: pv.PolyData = None
23-
self._box_polydata_actor: pv.Actor = None
22+
self._box_polydata: pv.PolyData | None = None
23+
self._box_polydata_actor: pv.Actor | None = None
2424

25-
self._extracted_cells: pv.UnstructuredGrid = None
26-
self._extracted_cells_actor: pv.Actor = None
25+
self._extracted_cells: pv.UnstructuredGrid | None = None
26+
self._extracted_cells_actor: pv.Actor | None = None
2727

2828
self._compute_box_as_polydata()
2929
self._compute_intersected_cell()
@@ -32,7 +32,7 @@ def get_box_polydata( self ) -> pv.PolyData | None:
3232
"""Get the box polydata."""
3333
return self._box_polydata
3434

35-
def get_box_polydata_actor( self ) -> pv.Actor:
35+
def get_box_polydata_actor( self ) -> pv.Actor | None:
3636
"""Get the actor generated by a pv.Plotter for the box polydata."""
3737
return self._box_polydata_actor
3838

@@ -86,16 +86,18 @@ def _retrieve_bounding_box( self ) -> list[ float ]:
8686

8787
def _compute_intersected_cell( self ) -> None:
8888
"""Extract the cells from the mesh that are inside the box."""
89-
ids = self._mesh.find_cells_within_bounds( self._box_polydata.bounds )
89+
if self._box_polydata:
90+
ids = self._mesh.find_cells_within_bounds( self._box_polydata.bounds )
9091

9192
saved_ids: list[ int ] = []
9293

9394
for id in ids:
9495
cell: pv.vtkCell = self._mesh.GetCell( id )
9596

96-
is_inside = self._check_cell_inside_box( cell, self._box_polydata.bounds )
97-
if is_inside:
98-
saved_ids.append( id )
97+
if self._box_polydata:
98+
is_inside = self._check_cell_inside_box( cell, self._box_polydata.bounds )
99+
if is_inside:
100+
saved_ids.append( id )
99101

100102
if len( saved_ids ) > 0:
101103
self._extracted_cells = self._mesh.extract_cells( saved_ids )

geos-trame/src/geos/trame/app/ui/viewer/viewer.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ def __init__(
5050
self._cell_data_array_names: list[ str ] = []
5151
self._source = source
5252
self._pl = pv.Plotter()
53-
self._pl.iren.initialize()
53+
if self._pl.iren:
54+
self._pl.iren.initialize()
5455
self._mesh_actor: vtkActor | None = None
5556

5657
self.CUT_PLANE = "on_cut_plane_visibility_change"
@@ -126,7 +127,7 @@ def rendering_menu_extra_items( self ) -> None:
126127
def update_viewer( self, active_block: BaseModel, path: str, show_obj: bool ) -> None:
127128
"""Add from path the dataset given by the user.
128129
129-
Supported data type is: Vtkwell, Vtkmesh, InternalWell, Perforation, Box.
130+
Supported data type is: VtkwellType, VtkmeshType, InternalWellType, PerforationType, BoxType.
130131
131132
object_state : array used to store path to the data and if we want to show it or not.
132133
"""
@@ -253,7 +254,7 @@ def _get_perforation_size( self ) -> float | None:
253254
return None
254255

255256
def _update_internalwell( self, path: str, show: bool ) -> None:
256-
"""Used to control the visibility of the InternalWell.
257+
"""Used to control the visibility of the InternalWellType.
257258
258259
This method will create the mesh if it doesn't exist.
259260
"""
@@ -269,7 +270,7 @@ def _update_internalwell( self, path: str, show: bool ) -> None:
269270
self.ctrl.view_update()
270271

271272
def _update_vtkwell( self, path: str, show: bool ) -> None:
272-
"""Used to control the visibility of the Vtkwell.
273+
"""Used to control the visibility of the VtkwellType.
273274
274275
This method will create the mesh if it doesn't exist.
275276
"""
@@ -308,7 +309,7 @@ def _update_actor_array( self, **_: Any ) -> None:
308309
self.ctrl.view_update()
309310

310311
def _update_vtkmesh( self, show: bool ) -> None:
311-
"""Used to control the visibility of the Vtkmesh.
312+
"""Used to control the visibility of the VtkmeshType.
312313
313314
This method will create the mesh if it doesn't exist.
314315

geos-trame/src/geos/trame/schema_generated/README.md

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,49 +11,57 @@ to parse, serialize and deserialize these files in trame with `trame-simput`, we
1111
generate a serializable class for each balise described in the schema used by GEOS.
1212

1313
For that we use a python module named `xsd-pydantic` which allows us to generate a file.
14-
It will contain all class for a given xsd schema file.
14+
It will contain all class for a given xsd schema file.
1515

1616
When starting the trame application, we can instantiate the expected dataclass when parsing
1717
the input file.
1818

1919
## How to generate a new file
2020

21-
#### 1. Clone the GEOS Repository
21+
First, retrieve the `schema.xsd` corresponding to the GEOS version you want to use.
2222

23-
```bash
24-
git clone git@github.com:GEOS-DEV/GEOS.git
25-
```
23+
> [!WARNING]
24+
> We advise to use GEOS version from commit [#1e617be](https://github.com/GEOS-DEV/GEOS/commit/1e617be8614817d92f0a7a159994cbed1661ff98). You may encounter compatibility issues with older versions.
2625
27-
#### 2. Create a dedicate venv
26+
In a sourced virtual environement set for geos_trame,
2827

2928
```bash
30-
cd <path-to-geos-trame>
31-
python -m venv pydantic-venv
32-
source pydantic-venv/bin/activate
33-
pip install -e .
34-
pip install "xsdata[cli]"
35-
cd src/geos_trame/schema_generated
29+
(venv) cd geosPythonPackages/geos-trame/src/geos/trame/schema_generated
30+
(venv) python generate_schema.py -g
31+
(venv) mv schema_<GEOS-commit-sha>.xsd schema.xsd
32+
(venv) python generate_schema.py -v <GEOS-commit-sha>
3633
```
3734

38-
#### 3. Generate the new file
39-
40-
The full documentation is [here](https://xsdata-pydantic.readthedocs.io/en/latest/codegen/).
35+
This two stage approach is defaulted:
4136

42-
```bash
43-
xsdata <path-to-GEOS>/src/coreComponents/schema/schema.xsd --output pydantic --package generated_models --structure-style single-package --include-header
44-
```
37+
1. To take the latest commit on GEOS' `develop`. However, if a particular commit on `develop` is of interest,
38+
you can pass it through the option `-c <GEOS-commit-sha>`. It will generate `schema_<GEOS-commit-sha>.xsd`.
39+
2. To generate the `schema_mod.py` packages, metadata-ing the commit number in the header.
4540

46-
Then update the generated file, at the beginning, by adding the geos commit used:
41+
In any other case, `schema.xsd` can be found in [GEOS Github repository](https://github.com/GEOS-DEV/GEOS) under `GEOS/src/coreComponents/schema/schema.xsd`
42+
and the first step can be skipped.
4743

48-
For example:
44+
The second stage relies on `xsdata[cli]` interfaced with `pydantic` as driver.
45+
The full documentation can be found [here](https://xsdata-pydantic.readthedocs.io/en/latest/codegen/).
4946

50-
```py
51-
"""This file was generated by xsdata, v24.6.1, on 2024-11-29
52-
53-
Generator: PydanticGenerator
54-
See: https://xsdata.readthedocs.io/
55-
56-
GEOS commit hash: <add-the-commit-hash-here>
57-
"""
58-
```
47+
Options to the helper script can be displayed with `--help` parameters:
5948

49+
```bash
50+
$ python generate_schema.py --help
51+
52+
Generate schema from schema.xsd file
53+
54+
optional arguments:
55+
-h, --help show this help message and exit
56+
-g, --get-schema Get the latest schema files.
57+
-c COMMIT, --commit COMMIT
58+
Force a specific GEOS develop's commit for schema
59+
download
60+
-s SCHEMAFILE, --schemaFile SCHEMAFILE
61+
Filepath to GEOS schema file.
62+
-cf CONFIGFILE, --configFile CONFIGFILE
63+
Filepath to xml configuration file for schema
64+
generation.
65+
-v VERSION, --version VERSION
66+
GEOS commit sha or version identification.
67+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
4+
<Config xmlns="http://pypi.org/project/xsdata" version="25.4">
5+
<Output maxLineLength="79">
6+
<Package>schema_mod</Package>
7+
<Format repr="true" eq="true" order="false" unsafeHash="false" frozen="false" slots="false" kwOnly="false">pydantic</Format>
8+
<Structure>single-package</Structure>
9+
<IncludeHeader>false</IncludeHeader>
10+
<DocstringStyle>reStructuredText</DocstringStyle>
11+
<RelativeImports>false</RelativeImports>
12+
</Output>
13+
<Substitutions>
14+
<Substitution type="class" search="(?&lt;!Problem)Type$" replace=""/>
15+
</Substitutions>
16+
</Config>

0 commit comments

Comments
 (0)