Skip to content

Commit

Permalink
include isAssociationUsed() only for modernpython (#27)
Browse files Browse the repository at this point in the history
isAssociationUsed() breaks the cpp and java code generation since the
associated langpacks use json.load(). isAssociationUsed() introduces
True/False into the json object which is not valid json. In json it
would be true/false.

Maybe we should only use dicts and avoid json objects entirely. Changing
this attribute to true/false does not work with the mustache template.
For now, I included the call to isAssociationUsed() only for
modernpython.

I have also added devcontainer files, fixed the dockerfile and updated
the README.
  • Loading branch information
m-mirz authored Apr 11, 2024
2 parents e345ccf + 171fb03 commit 61c3564
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 53 deletions.
4 changes: 4 additions & 0 deletions .devcontainer/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM ubuntu:22.04
RUN apt update
RUN apt install -y python3 git python3-pip libxml2-dev
RUN python3 -m pip install lxml xmltodict chevron beautifulsoup4
13 changes: 13 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "cimgen",
"build": {
"dockerfile": "Dockerfile.dev"
},
"customizations": {
"vscode": {
"extensions": [
"ms-python.python"
]
}
}
}
44 changes: 23 additions & 21 deletions CIMgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def __init__(self, jsonObject):
self.jsonDefinition = jsonObject
return

def asJson(self):
def asJson(self, lang_pack):
jsonObject = {}
if self.about() != None:
jsonObject['about'] = self.about()
Expand Down Expand Up @@ -44,7 +44,8 @@ def asJson(self):
jsonObject['inverseRole'] = self.inverseRole()
if self.associationUsed() != None:
jsonObject['associationUsed'] = self.associationUsed()
jsonObject["isAssociationUsed"] = self.isAssociationUsed()
if "modernpython" in lang_pack.__name__:
jsonObject["isAssociationUsed"] = self.isAssociationUsed()
return jsonObject

def about(self):
Expand All @@ -59,6 +60,7 @@ def associationUsed(self):
else:
return None

# Capitalized True/False is valid in python but not in json. Do not use this function in combination with json.load()
def isAssociationUsed(self) -> bool:
if "cims:AssociationUsed" in self.jsonDefinition:
return "yes" == RDFSEntry._extract_string(self.jsonDefinition["cims:AssociationUsed"]).lower()
Expand Down Expand Up @@ -381,7 +383,7 @@ def _add_profile_to_packages(profile_name, short_profile_name, profile_iri):
else:
package_listed_by_short_name[short_profile_name].append(profile_iri)

def _parse_rdf(input_dic, version):
def _parse_rdf(input_dic, version, lang_pack):
classes_map = {}
profile_name = ""
profile_iri = None
Expand All @@ -395,7 +397,7 @@ def _parse_rdf(input_dic, version):
# Iterate over list elements
for list_elem in descriptions:
rdfsEntry = RDFSEntry(list_elem)
object_dic = rdfsEntry.asJson()
object_dic = rdfsEntry.asJson(lang_pack)
rdfs_entry_types = _rdfs_entry_types(rdfsEntry, version)

if "class" in rdfs_entry_types:
Expand Down Expand Up @@ -442,7 +444,7 @@ def _parse_rdf(input_dic, version):
# This function extracts all information needed for the creation of the python class files like the comments or the
# class name. After the extraction the function write_files is called to write the files with the template engine
# chevron
def _write_python_files(elem_dict, langPack, outputPath, version):
def _write_python_files(elem_dict, lang_pack, output_path, version):

float_classes = {}
enum_classes = {}
Expand All @@ -454,20 +456,20 @@ def _write_python_files(elem_dict, langPack, outputPath, version):
if elem_dict[class_definition].has_instances():
enum_classes[class_definition] = True

langPack.set_float_classes(float_classes)
langPack.set_enum_classes(enum_classes)
lang_pack.set_float_classes(float_classes)
lang_pack.set_enum_classes(enum_classes)

for class_name in elem_dict.keys():

class_details = {
"attributes": _find_multiple_attributes(elem_dict[class_name].attributes()),
"class_location": langPack.get_class_location(class_name, elem_dict, outputPath),
"class_location": lang_pack.get_class_location(class_name, elem_dict, output_path),
"class_name": class_name,
"class_origin": elem_dict[class_name].origins(),
"instances": elem_dict[class_name].instances(),
"has_instances": elem_dict[class_name].has_instances(),
"is_a_float": elem_dict[class_name].is_a_float(),
"langPack": langPack,
"langPack": lang_pack,
"sub_class_of": elem_dict[class_name].superClass(),
"sub_classes": elem_dict[class_name].subClasses(),
}
Expand All @@ -489,7 +491,7 @@ def _write_python_files(elem_dict, langPack, outputPath, version):
initial_indent="",
subsequent_indent=" " * 6,
)
_write_files(class_details, outputPath, version)
_write_files(class_details, output_path, version)

def get_rid_of_hash(name):
tokens = name.split('#')
Expand All @@ -505,8 +507,8 @@ def format_class(_range, _dataType):
else:
return get_rid_of_hash(_range)

def _write_files(class_details, outputPath, version):
class_details['langPack'].setup(outputPath, package_listed_by_short_name)
def _write_files(class_details, output_path, version):
class_details['langPack'].setup(output_path, package_listed_by_short_name)

if class_details['sub_class_of'] == None:
# If class has no subClassOf key it is a subclass of the Base class
Expand All @@ -532,7 +534,7 @@ def _write_files(class_details, outputPath, version):
_dataType = attr['dataType']
attr['class_name'] = format_class( _range, _dataType )

class_details['langPack'].run_template( outputPath, class_details )
class_details['langPack'].run_template( output_path, class_details )

# Find multiple entries for the same attribute
def _find_multiple_attributes(attributes_array):
Expand Down Expand Up @@ -651,7 +653,7 @@ def addSubClassesOfSubClasses(class_dict):
for className in class_dict:
class_dict[className].setSubClasses(recursivelyAddSubClasses(class_dict, className))

def cim_generate(directory, outputPath, version, langPack):
def cim_generate(directory, output_path, version, lang_pack):
"""Generates cgmes python classes from cgmes ontology
This function uses package xmltodict to parse the RDF files. The parse_rdf function sorts the classes to
Expand All @@ -665,8 +667,8 @@ def cim_generate(directory, outputPath, version, langPack):
created classes are stored. This folder should not exist and is created in the class generation procedure.
:param directory: path to RDF files containing cgmes ontology, e.g. directory = "./examples/cgmes_schema/cgmes_v2_4_15_schema"
:param outputPath: CGMES version, e.g. version = "cgmes_v2_4_15"
:param langPack: python module containing language specific functions
:param output_path: CGMES version, e.g. version = "cgmes_v2_4_15"
:param lang_pack: python module containing language specific functions
"""
profiles_array = []

Expand All @@ -682,7 +684,7 @@ def cim_generate(directory, outputPath, version, langPack):

# parse RDF files and create a dictionary from the RDF file
parse_result = xmltodict.parse(xmlstring, attr_prefix="$", cdata_key="_", dict_constructor=dict)
parsed = _parse_rdf(parse_result, version)
parsed = _parse_rdf(parse_result, version, lang_pack)
profiles_array.append(parsed)

# merge multiple profile definitions into one profile
Expand All @@ -705,11 +707,11 @@ def cim_generate(directory, outputPath, version, langPack):
addSubClassesOfSubClasses(class_dict_with_origins)

# get information for writing python files and write python files
_write_python_files(class_dict_with_origins, langPack, outputPath, version)
_write_python_files(class_dict_with_origins, lang_pack, output_path, version)

if "modernpython" in langPack.__name__:
langPack.resolve_headers(outputPath, version)
if "modernpython" in lang_pack.__name__:
lang_pack.resolve_headers(output_path, version)
else:
langPack.resolve_headers(outputPath)
lang_pack.resolve_headers(output_path)

logger.info("Elapsed Time: {}s\n\n".format(time() - t0))
25 changes: 14 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
FROM alpine
FROM alpine:3.19.1
RUN apk update
RUN apk add python3 git py3-pip py3-lxml file
RUN pip3 install --upgrade pip
RUN pip3 install xmltodict chevron
RUN python3 -m pip install --break-system-packages xmltodict chevron pydantic beautifulsoup4

COPY cpp/ /CIMgen/cpp/
COPY java/ /CIMgen/java/
COPY javascript/ /CIMgen/javascript/
COPY python/ /CIMgen/python/
COPY CIMgen.py build.py /CIMgen/
WORKDIR /CIMgen
ENTRYPOINT [ "/usr/bin/python3", "build.py", "--outdir=/cgmes_output", "--schemadir=/cgmes_schema" ]
CMD [ "--langdir=cpp" ]
WORKDIR /cimgen
COPY ./cpp /cimgen/cpp
COPY ./java /cimgen/java
COPY ./javascript /cimgen/javascript
COPY ./modernpython /cimgen/modernpython
COPY ./python /cimgen/python
COPY ./cgmes_schema /cimgen/cgmes_schema
COPY ./build.py /cimgen/build.py
COPY ./CIMgen.py /cimgen/CIMgen.py

ENTRYPOINT [ "/usr/bin/python3", "/cimgen/build.py", "--outdir=/cimgen/cgmes_output", "--schemadir=/cimgen/cgmes_schema" ]
CMD [ "--langdir=/cimgen/cpp" ]
27 changes: 6 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,18 @@ Python tool for code generation from CIM data model for several programming lang
sudo apt install python
curl -sSL https://install.python-poetry.org | python3 - # install poetry
poetry install --no-root # install dependencies
poetry run cmake -P CMakeLists.txt
poetry run python ./build.py --outdir=CGMES_2.4.15_27JAN2020_cpp --schemadir=cgmes_schema/CGMES_2.4.15_27JAN2020 --langdir=cpp
```

This will build version CGMES_2.4.15_27JAN2020 in the subfolder with the same name.
This will build version CGMES_2.4.15_27JAN2020 in the subfolder CGMES_2.4.15_27JAN2020_cpp.

If you wish to build an alternative version, you can see available options in the subfolder called cgmes_schema.

They can be built using a cmake variable:

```bash
poetry run cmake -DUSE_CIM_VERSION=CGMES_2.4.15_16FEB2016 -P CMakeLists.txt
```

#### Generating C++ files in a Docker container

```bash
docker build -t cimgen -f Dockerfile .
export OUTPUT_DIR=$(pwd)/CGMES_2.4.15_27JAN2020_cpp
export SCHEMA_DIR=$(pwd)/cgmes_schema/CGMES_2.4.15_27JAN2020
docker run -v ${OUTPUT_DIR}:/cgmes_output -v ${SCHEMA_DIR}:/cgmes_schema cimgen
docker run -v $(pwd)/CGMES_2.4.15_27JAN2020_cpp:/cimgen/cgmes_output cimgen --schemadir=cgmes_schema/CGMES_2.4.15_27JAN2020 --langdir=cpp
```

### Generating Python files
Expand All @@ -46,23 +38,16 @@ docker run -v ${OUTPUT_DIR}:/cgmes_output -v ${SCHEMA_DIR}:/cgmes_schema cimgen
sudo apt install python
curl -sSL https://install.python-poetry.org | python3 - # install poetry
poetry install --no-root # install dependencies
export OUTPUT_DIR=$(pwd)/CGMES_2.4.15_27JAN2020_python
export SCHEMA_DIR=$(pwd)/cgmes_schema/CGMES_2.4.15_27JAN2020
./build.py --outdir=${OUTPUT_DIR} --schemadir=${SCHEMA_DIR} --langdir=python
poetry run python ./build.py --outdir=CGMES_2.4.15_27JAN2020_python --schemadir=cgmes_schema/CGMES_2.4.15_27JAN2020 --langdir=python
```

`OUTPUT_DIR` can be set to whichever absolute path you wish to create the files
in.
If you wish to build an alternative version, you can see available options in
the subfolder called cgmes_schema
`outdir` can be set to whichever absolute path you wish to create the files in.

#### Generating Python files in a Docker container

```bash
docker build -t cimgen -f Dockerfile .
export OUTPUT_DIR=$(pwd)/CGMES_2.4.15_27JAN2020_python
export SCHEMA_DIR=$(pwd)/cgmes_schema/CGMES_2.4.15_27JAN2020
docker run -v ${OUTPUT_DIR}:/cgmes_output -v ${SCHEMA_DIR}:/cgmes_schema cimgen --langdir=python
docker run -v $(pwd)/CGMES_2.4.15_27JAN2020_python:/cimgen/cgmes_output cimgen --schemadir=cgmes_schema/CGMES_2.4.15_27JAN2020 --langdir=python
```

## Publications
Expand Down

0 comments on commit 61c3564

Please sign in to comment.