Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare 2.1.1 release #547

Merged
merged 8 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/server_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,16 @@ jobs:
path: doc/build/latex/*.pdf
retention-days: 7

check_workflow_runs:
name: Check if there are active workflow runs
needs: docs
uses: ansys/pygranta/.github/workflows/check-concurrent-workflows.yml@main

stop_vm:
name: Stop Azure VM
runs-on: ubuntu-latest
needs: docs
if: ${{ always() && !(inputs.skip-vm-management)}}
needs: check_workflow_runs
if: always() && !cancelled() && !(inputs.skip-vm-management) && needs.check_workflow_runs.outputs.active-runs != 'true'

steps:
- name: Stop Azure VM
Expand Down
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
# Changelog

## grantami-bomanalytics 2.1.0, TBD
## grantami-bomanalytics 2.1.1, TBD

### Bug fixes

* [Issue #545](https://github.com/ansys/grantami-bomanalytics/issues/545),
[Pull request #546](https://github.com/ansys/grantami-bomanalytics/pull/546): Empty elements are no longer removed when deserializing a BoM file

### Contributors

* Andy Grigg (Ansys)
* Doug Addy (Ansys)
* Ludovic Steinbach (Ansys)

## grantami-bomanalytics 2.1.0, 2024-05-08

### New features

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "ansys-grantami-bomanalytics"
description = "Perform compliance and sustainability analysis on materials data stored in Granta MI."
version = "2.1.0"
version = "2.1.1"
license = "MIT"
authors = ["ANSYS, Inc. <pyansys.core@ansys.com>"]
maintainers = ["ANSYS, Inc. <pyansys.core@ansys.com>"]
Expand Down
23 changes: 20 additions & 3 deletions src/ansys/grantami/bomanalytics/_bom_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# SOFTWARE.

from pathlib import Path
from typing import TYPE_CHECKING, Tuple, cast
from typing import TYPE_CHECKING, Any, Dict, List, TextIO, Tuple, Union, cast

import xmlschema
from xmlschema import XMLSchema
Expand Down Expand Up @@ -63,7 +63,7 @@ def load_bom_from_file(self, file_path: Path) -> "BillOfMaterials":
:class:`~._bom_types.BillOfMaterials`
"""
with open(file_path, "r", encoding="utf8") as fp:
obj, errors = cast(Tuple, self._schema.decode(fp, validation="lax"))
obj, errors = self._deserialize_bom(fp)

if len(errors) > 0:
newline = "\n"
Expand All @@ -86,7 +86,7 @@ def load_bom_from_text(self, bom_text: str) -> "BillOfMaterials":
-------
:class:`~._bom_types.BillOfMaterials`
"""
obj, errors = cast(Tuple, self._schema.decode(bom_text, validation="lax", keep_empty=True))
obj, errors = self._deserialize_bom(bom_text)

if len(errors) > 0:
newline = "\n"
Expand All @@ -96,6 +96,23 @@ def load_bom_from_text(self, bom_text: str) -> "BillOfMaterials":

return self._reader.read_bom(obj)

def _deserialize_bom(self, bom: Union[TextIO, str]) -> Tuple[Dict[str, Any], List]:
"""
Deserialize either a string or I/O stream BoM.

Parameters
----------
bom : Union[TextIO, str]
Object containing an XML representation of a BoM, either as text or I/O stream.

Returns
-------
Tuple[Dict[str, Any], List]
A tuple of the deserialized dictionary and a list of errors.
"""
result = self._schema.decode(bom, validation="lax", keep_empty=True, xmlns_processing="collapsed")
return cast(Tuple[Dict[str, Any], List], result)

def dump_bom(self, bom: "BillOfMaterials") -> str:
"""
Convert a BillOfMaterials object into a string XML representation.
Expand Down
12 changes: 6 additions & 6 deletions tests/inputs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,25 @@
inputs_dir = pathlib.Path(__file__).parent

_sample_bom_1711_path = inputs_dir / "bom.xml"
with open(_sample_bom_1711_path, "r") as f:
with open(_sample_bom_1711_path, "r", encoding="utf8") as f:
sample_bom_1711 = f.read()

_sample_compliance_bom_1711_path = (
repository_root / "examples" / "3_Advanced_Topics" / "supporting-files" / "bom-complex.xml"
)
with open(_sample_compliance_bom_1711_path, "r") as f:
with open(_sample_compliance_bom_1711_path, "r", encoding="utf8") as f:
sample_compliance_bom_1711 = f.read()

sample_bom_custom_db = sample_compliance_bom_1711.replace(
"MI_Restricted_Substances", "MI_Restricted_Substances_Custom_Tables"
)

_sample_sustainability_bom_2301_path = (
sample_sustainability_bom_2301_path = (
repository_root / "examples" / "4_Sustainability" / "supporting-files" / "bom-2301-assembly.xml"
)
with open(_sample_sustainability_bom_2301_path, "r") as f:
with open(sample_sustainability_bom_2301_path, "r", encoding="utf8") as f:
sample_sustainability_bom_2301 = f.read()

_large_bom_2301_path = inputs_dir / "medium-test-bom.xml"
with open(_large_bom_2301_path, "r") as f:
large_bom_2301_path = inputs_dir / "medium-test-bom.xml"
with open(large_bom_2301_path, "r", encoding="utf8") as f:
large_bom_2301 = f.read()
2 changes: 1 addition & 1 deletion tests/inputs/bom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<Part>
<Quantity Unit="Each">1.0</Quantity>
<MassPerUom Unit="kg/Part">2.0</MassPerUom>
<PartNumber>3333</PartNumber>
<PartNumber />
<Name>Part Two</Name>
<Materials>
<Material>
Expand Down
45 changes: 36 additions & 9 deletions tests/test_bom_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@

from ansys.grantami.bomanalytics import BoMHandler, bom_types

from .inputs import large_bom_2301, sample_bom_1711, sample_sustainability_bom_2301
from .inputs import (
large_bom_2301,
large_bom_2301_path,
sample_bom_1711,
sample_sustainability_bom_2301,
sample_sustainability_bom_2301_path,
)


class _TestableBoMHandler(BoMHandler):
Expand Down Expand Up @@ -80,33 +86,54 @@ def _compare_boms(*, source_bom: str, result_bom: str):
output_lines.append(diff_item)
return output_lines

def test_roundtrip_from_text_with_assertions(self):
bom_handler = _TestableBoMHandler(
default_namespace=self._default_namespace, namespace_mapping=self._namespace_map
)
deserialized_bom = bom_handler.load_bom_from_text(large_bom_2301)
output_bom = bom_handler.dump_bom(deserialized_bom)

diff = self._compare_boms(source_bom=large_bom_2301, result_bom=output_bom)

assert len(diff) == 0, "\n".join(diff)

@pytest.mark.parametrize(
"input_bom",
[
pytest.param(large_bom_2301, id="large_bom"),
pytest.param(sample_sustainability_bom_2301, id="sustainability_bom"),
],
)
def test_roundtrip_with_assertions(self, input_bom: str):
def test_roundtrip_from_text_parsing_succeeds(self, input_bom: str):
bom_handler = BoMHandler()
deserialized_bom = bom_handler.load_bom_from_text(input_bom)

rendered_bom = bom_handler.dump_bom(deserialized_bom)
deserialized_bom_roundtriped = bom_handler.load_bom_from_text(rendered_bom)

assert deserialized_bom == deserialized_bom_roundtriped

def test_roundtrip_from_file_with_assertions(self):
bom_handler = _TestableBoMHandler(
default_namespace=self._default_namespace, namespace_mapping=self._namespace_map
)
deserialized_bom = bom_handler.load_bom_from_text(input_bom)
deserialized_bom = bom_handler.load_bom_from_file(large_bom_2301_path)
output_bom = bom_handler.dump_bom(deserialized_bom)

diff = self._compare_boms(source_bom=input_bom, result_bom=output_bom)
diff = self._compare_boms(source_bom=large_bom_2301, result_bom=output_bom)

assert len(diff) == 0, "\n".join(diff)

@pytest.mark.parametrize(
"input_bom",
[
pytest.param(large_bom_2301, id="large_bom"),
pytest.param(sample_sustainability_bom_2301, id="sustainability_bom"),
pytest.param(large_bom_2301_path, id="large_bom"),
pytest.param(sample_sustainability_bom_2301_path, id="sustainability_bom"),
],
)
def test_roundtrip_parsing_succeeds(self, input_bom: str):
def test_roundtrip_from_file_parsing_succeeds(self, input_bom: str):
bom_handler = BoMHandler()
deserialized_bom = bom_handler.load_bom_from_text(input_bom)
deserialized_bom = bom_handler.load_bom_from_file(input_bom)

rendered_bom = bom_handler.dump_bom(deserialized_bom)
deserialized_bom_roundtriped = bom_handler.load_bom_from_text(rendered_bom)
Expand Down Expand Up @@ -175,7 +202,7 @@ def get_field(self, obj: bom_types.BaseType, p_path: str) -> Any:
("components[0]/components[1]/quantity/value", pytest.approx(1.0)),
("components[0]/components[1]/mass_per_unit_of_measure/unit", "kg/Part"),
("components[0]/components[1]/mass_per_unit_of_measure/value", pytest.approx(2.0)),
("components[0]/components[1]/part_number", "3333"),
("components[0]/components[1]/part_number", ""),
("components[0]/components[1]/part_name", "Part Two"),
("components[0]/components[1]/materials[0]/percentage", pytest.approx(80.0)),
("components[0]/components[1]/materials[0]/mi_material_reference/db_key", "MI_Restricted_Substances"),
Expand Down
Loading