From b0f2785e99b68fdab2ba18aa5445c6a23240058f Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 6 May 2025 11:11:02 +0500 Subject: [PATCH] black . --- src/ifcopenshell-python/ifcopenshell/file.py | 9 +- .../ifcopenshell/util/mvd_info.py | 49 +++---- .../ifcopenshell/validate.py | 10 +- src/ifcopenshell-python/test/test_mvd_info.py | 124 +++++++++--------- 4 files changed, 100 insertions(+), 92 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index db027239fc..a4a4f3d679 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -209,6 +209,7 @@ NO_HEADER = ifcopenshell_wrapper.file_open_status.NO_HEADER UNSUPPORTED_SCHEMA = ifcopenshell_wrapper.file_open_status.UNSUPPORTED_SCHEMA INVALID_SYNTAX = ifcopenshell_wrapper.file_open_status.INVALID_SYNTAX + class file: """Base class for containing IFC files. @@ -466,14 +467,12 @@ class file: number = re.search(prefix + r"(\d)", schema) version.append(int(number.group(1)) if number else 0) return tuple(version) - - @property + + @property def mvd(self): if not LARK_AVAILABLE: return None - return MvdInfo( - self.header - ) + return MvdInfo(self.header) def __getattr__(self, attr) -> Union[Any, Callable[..., ifcopenshell.entity_instance]]: if attr[0:6] == "create": diff --git a/src/ifcopenshell-python/ifcopenshell/util/mvd_info.py b/src/ifcopenshell-python/ifcopenshell/util/mvd_info.py index 8156f3f20e..c7d6562b3a 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/mvd_info.py +++ b/src/ifcopenshell-python/ifcopenshell/util/mvd_info.py @@ -1,6 +1,7 @@ try: from lark import Lark, Transformer from lark.exceptions import UnexpectedCharacters, UnexpectedEOF, UnexpectedToken + LARK_AVAILABLE = True except ImportError: LARK_AVAILABLE = False @@ -9,7 +10,7 @@ from typing import Callable import re if LARK_AVAILABLE: - mvd_grammar = r''' + mvd_grammar = r""" start: entry+ entry: "ViewDefinition" "[" simple_value_list "]" -> view_definition @@ -36,9 +37,9 @@ if LARK_AVAILABLE: %import common.WS %ignore WS - ''' + """ - parser = Lark(mvd_grammar, parser='lalr') + parser = Lark(mvd_grammar, parser="lalr") class DescriptionTransform(Transformer): def __init__(self): @@ -50,7 +51,7 @@ if LARK_AVAILABLE: self._dynamic = {} def view_definition(self, args): - self.keywords.add('view_definitions') + self.keywords.add("view_definitions") self.view_definitions.extend(args[0]) def store_text_attribute(self, args, keyword): @@ -66,17 +67,17 @@ if LARK_AVAILABLE: def option(self, args): if v := parse_semicolon_separated_kv(" ".join(" ".join(str(child) for child in args[0].children).split())): - setattr(self, 'options', v) + setattr(self, "options", v) else: self.store_text_attribute(args, "options") def dynamic_option(self, args): try: - original_keyword = str(args[0]) - key = original_keyword.lower() + original_keyword = str(args[0]) + key = original_keyword.lower() raw_text = args[1].children[0].value parsed_value = parse_semicolon_separated_kv(raw_text) - self._dynamic[key] = (parsed_value, original_keyword) + self._dynamic[key] = (parsed_value, original_keyword) self.keywords.add(key) setattr(self, key, parsed_value) except Exception: @@ -98,7 +99,7 @@ if LARK_AVAILABLE: return str(args[0]) def parse_mvd(description): - text = ' '.join(description) + text = " ".join(description) parsed_description = DescriptionTransform() try: if not text: @@ -111,21 +112,23 @@ if LARK_AVAILABLE: return parsed_description def parse_semicolon_separated_kv(text: str) -> dict[str, str | list[str]] | None: - if not re.search(r'\w+\s*:\s*[^:]+', text): + if not re.search(r"\w+\s*:\s*[^:]+", text): return None result = {} try: - pairs = text.split(';') + pairs = text.split(";") for pair in pairs: - if ':' in pair: - key, value = pair.split(':', 1) + if ":" in pair: + key, value = pair.split(":", 1) key = key.strip() - values = [v.strip() for v in value.split(',')] + values = [v.strip() for v in value.split(",")] result[key] = values[0] if len(values) == 1 else values return result except Exception: return None + else: + def parse_mvd(description): return None @@ -161,7 +164,7 @@ class MvdInfo: return AutoCommitList( vd_list, callback=lambda val: (self._update_keyword("ViewDefinition", val), setattr(self, "_parsed", None)), - formatter=lambda lst: ",".join(str(i) for i in lst) + formatter=lambda lst: ",".join(str(i) for i in lst), ) @view_definitions.setter @@ -180,7 +183,7 @@ class MvdInfo: return AutoCommitList( comment_list, callback=lambda val: self._update_keyword("Comment", val), - formatter=lambda lst: ", ".join(str(i) for i in lst) + formatter=lambda lst: ", ".join(str(i) for i in lst), ) @comments.setter @@ -232,7 +235,7 @@ class MvdInfo: def __getattr__(self, name): self._ensure_parsed() - if hasattr(self._parsed, '_dynamic'): + if hasattr(self._parsed, "_dynamic"): name_lc = name.lower() if name_lc in self._parsed._dynamic: value, original_keyword = self._parsed._dynamic[name_lc] @@ -241,7 +244,7 @@ class MvdInfo: def __dir__(self): base = super().__dir__() - if self._parsed and hasattr(self._parsed, '_dynamic'): + if self._parsed and hasattr(self._parsed, "_dynamic"): return base + [kw for _, kw in self._parsed._dynamic.values()] return base @@ -258,10 +261,7 @@ class DictionaryHandler(dict): super().__setitem__(k, v) def _commit(self): - new_value = "; ".join( - f"{k}: {', '.join(v) if isinstance(v, list) else v}" - for k, v in self.items() - ) + new_value = "; ".join(f"{k}: {', '.join(v) if isinstance(v, list) else v}" for k, v in self.items()) self._mvdinfo._update_keyword(self._keyword, new_value) def __setitem__(self, key, value): @@ -277,10 +277,11 @@ class DictionaryHandler(dict): class AutoCommitList(list): "ensures keyword attributes are written back to ifcopenshell.file.header" + def __init__(self, iterable, callback, formatter=None): super().__init__(iterable) self._callback = callback - self._formatter = formatter + self._formatter = formatter def _commit(self): if self._formatter: @@ -319,4 +320,4 @@ class AutoCommitList(list): def __delitem__(self, index): super().__delitem__(index) - self._commit() \ No newline at end of file + self._commit() diff --git a/src/ifcopenshell-python/ifcopenshell/validate.py b/src/ifcopenshell-python/ifcopenshell/validate.py index ef63c205ee..ed81f10cec 100644 --- a/src/ifcopenshell-python/ifcopenshell/validate.py +++ b/src/ifcopenshell-python/ifcopenshell/validate.py @@ -597,24 +597,28 @@ def validate_guid(guid: str) -> Union[str, None]: return "Couldn't decompress guid, it's not base64 encoded." return None + def to_string_header_entity(header_entity): """Recreate IFC header string representation, like FILE_NAME(...)""" - + # Prefer native .toString() if available (native IfcOpenShell wrapper) if isinstance(header_entity, W.HeaderEntity): return header_entity.toString() - elif hasattr(header_entity, '_fields'): + elif hasattr(header_entity, "_fields"): values = [repr(getattr(header_entity, f)) for f in header_entity._fields] return f"{type(header_entity).__name__.upper()}({','.join(values)})" else: raise TypeError(f"Cannot stringify header_entity of type {type(header_entity)}") + def validate_ifc_header(f: Union[ifcopenshell.file, ifcopenshell.simple_spf.file], logger: Logger) -> None: header: Union[W.IfcSpfHeader, types.SimpleNamespace] = f.header AGGREGATE_TYPE = "LIST [ 1 : ? ] OF STRING (256)" STRING_TYPE = "STRING (256)" - def log_error(header_entity: Union[W.HeaderEntity, tuple], name: str, index: int, expected_type: str, provided_type: str) -> None: + def log_error( + header_entity: Union[W.HeaderEntity, tuple], name: str, index: int, expected_type: str, provided_type: str + ) -> None: logger.error( ( "For instance:\n %s\n %s\n" diff --git a/src/ifcopenshell-python/test/test_mvd_info.py b/src/ifcopenshell-python/test/test_mvd_info.py index 8c0710767c..80fe2f2154 100644 --- a/src/ifcopenshell-python/test/test_mvd_info.py +++ b/src/ifcopenshell-python/test/test_mvd_info.py @@ -8,8 +8,10 @@ from ifcopenshell.util.mvd_info import parse_mvd @pytest.fixture def load_fixture(): base_dir = os.path.join(os.path.dirname(__file__), "fixtures", "mvd_parsing") + def _load(filename): return ifcopenshell.open(os.path.join(base_dir, filename)) + return _load @@ -20,54 +22,55 @@ class TestViewDefinition: def test_multiple_views(self, load_fixture): f = load_fixture("two_views.ifc") - assert f.mvd.view_definitions == ['CoordinationView_V2.0', 'SpaceBoundaryAddonView'] - + assert f.mvd.view_definitions == ["CoordinationView_V2.0", "SpaceBoundaryAddonView"] + def test_add_view(self): header = MockHeader(("ViewDefinition [CoordinationView_V2.0]",)) mvd = mvd_info.MvdInfo(header) assert mvd.view_definitions == ["CoordinationView_V2.0"] mvd.view_definitions.append("SpaceBoundaryAddonView") - assert mvd.view_definitions == ['CoordinationView_V2.0', 'SpaceBoundaryAddonView'] + assert mvd.view_definitions == ["CoordinationView_V2.0", "SpaceBoundaryAddonView"] + class TestExchangeRequirements: def test_parsing(self, load_fixture): f = load_fixture("contains_exchange_requirement.ifc") parsed = parse_mvd(f.mvd.description) - assert parsed.exchange_requirements == 'Any' + assert parsed.exchange_requirements == "Any" def test_access_and_modification(self, load_fixture): f = load_fixture("contains_exchange_requirement.ifc") f.header.file_description.description = ( - 'ViewDefinition [Alignment-basedView]', - 'ExchangeRequirement [SomethingElse]' + "ViewDefinition [Alignment-basedView]", + "ExchangeRequirement [SomethingElse]", ) - assert f.mvd.exchange_requirements == 'SomethingElse' - f.mvd.view_definitions = ['CoordinationView_V2.0'] - assert f.mvd.view_definitions == ['CoordinationView_V2.0'] + assert f.mvd.exchange_requirements == "SomethingElse" + f.mvd.view_definitions = ["CoordinationView_V2.0"] + assert f.mvd.view_definitions == ["CoordinationView_V2.0"] class TestComments: def test_read_and_append(self, load_fixture): f = load_fixture("contains_comment.ifc") - assert f.mvd.comments == ['Any'] - f.mvd.comments = ['SomethingElse'] - assert f.mvd.comments == ['SomethingElse'] - f.mvd.comments.append('AnotherComment') - assert f.mvd.comments == ['SomethingElse', ' AnotherComment'] - assert f.mvd.description[1] == 'Comment [SomethingElse, AnotherComment]' - + assert f.mvd.comments == ["Any"] + f.mvd.comments = ["SomethingElse"] + assert f.mvd.comments == ["SomethingElse"] + f.mvd.comments.append("AnotherComment") + assert f.mvd.comments == ["SomethingElse", " AnotherComment"] + assert f.mvd.description[1] == "Comment [SomethingElse, AnotherComment]" + def test_comment_list_modifications(self, load_fixture): f = load_fixture("contains_comment.ifc") - f.mvd.comments = '' - f.mvd.comments.append('OnlyOne') - assert 'OnlyOne' in f.mvd.comments - - f.mvd.comments.insert(0, 'FirstOne') - f.mvd.comments[0] == 'FirstOne' + f.mvd.comments = "" + f.mvd.comments.append("OnlyOne") + assert "OnlyOne" in f.mvd.comments + + f.mvd.comments.insert(0, "FirstOne") + f.mvd.comments[0] == "FirstOne" f.mvd.comments.pop() - assert f.mvd.comments[0] == 'FirstOne' - + assert f.mvd.comments[0] == "FirstOne" + del f.mvd.comments[0] assert not f.mvd.comments @@ -75,55 +78,58 @@ class TestComments: class TestOptions: def test_string_options(self, load_fixture): f = load_fixture("contains_options.ifc") - assert f.mvd.options == 'Any' - assert 'options' in f.mvd.keywords + assert f.mvd.options == "Any" + assert "options" in f.mvd.keywords class TestDynamicFields: def test_options_modifications(self, load_fixture): f = load_fixture("dynamic_fields.ifc") - f.mvd.options['ExcludedObjects'].append('Chair') - f.mvd.options['SplitLevel'] = 'Off' - f.mvd.options['OtherAttr'] = 'SomeValue' + f.mvd.options["ExcludedObjects"].append("Chair") + f.mvd.options["SplitLevel"] = "Off" + f.mvd.options["OtherAttr"] = "SomeValue" - assert f.mvd.description[2].startswith('Option [') - assert 'OtherAttr: SomeValue' in f.mvd.description[2] + assert f.mvd.description[2].startswith("Option [") + assert "OtherAttr: SomeValue" in f.mvd.description[2] assert f.mvd.description == f.header.file_description.description def test_remark_editing(self, load_fixture): f = load_fixture("dynamic_fields.ifc") - assert f.mvd.remark == {'SomeKey': 'SomeValue', 'AnotherKey': 'AnotherValue'} - f.mvd.remark['AnotherKey'] = 'SometingElse' - f.mvd.remark['IncludedObjects'] = ['Floor', 'Roof'] - assert f.mvd.remark['AnotherKey'] == 'SometingElse' - assert f.mvd.remark['IncludedObjects'] == ['Floor', 'Roof'] - assert 'remark' in f.mvd.keywords - + assert f.mvd.remark == {"SomeKey": "SomeValue", "AnotherKey": "AnotherValue"} + f.mvd.remark["AnotherKey"] = "SometingElse" + f.mvd.remark["IncludedObjects"] = ["Floor", "Roof"] + assert f.mvd.remark["AnotherKey"] == "SometingElse" + assert f.mvd.remark["IncludedObjects"] == ["Floor", "Roof"] + assert "remark" in f.mvd.keywords + def test_custom_dict_behavior(self, load_fixture): f = load_fixture("dynamic_fields.ifc") - + # delete - del f.mvd.options['SplitLevel'] - assert not f.mvd.options.get('SplitLevel') - - # keys, values, items - assert set(f.mvd.remark.keys()) == {'SomeKey', 'AnotherKey'} - assert 'SomeValue' in f.mvd.remark.values() - assert ('SomeKey', 'SomeValue') in f.mvd.remark.items() - + del f.mvd.options["SplitLevel"] + assert not f.mvd.options.get("SplitLevel") + + # keys, values, items + assert set(f.mvd.remark.keys()) == {"SomeKey", "AnotherKey"} + assert "SomeValue" in f.mvd.remark.values() + assert ("SomeKey", "SomeValue") in f.mvd.remark.items() + # containment - assert 'SomeKey' in f.mvd.remark - assert 'MissingKey' not in f.mvd.remark + assert "SomeKey" in f.mvd.remark + assert "MissingKey" not in f.mvd.remark class TestKeywords: - @pytest.mark.parametrize("filename, expected_keywords", [ - ("contains_comment.ifc", {"view_definitions", "comments"}), - ("contains_exchange_requirement.ifc", {"view_definitions", "exchange_requirements"}), - ("contains_options.ifc", {"view_definitions", "options"}), - ("dynamic_fields.ifc", {"view_definitions", "exchange_requirements", "comments", "remark"}) - ]) + @pytest.mark.parametrize( + "filename, expected_keywords", + [ + ("contains_comment.ifc", {"view_definitions", "comments"}), + ("contains_exchange_requirement.ifc", {"view_definitions", "exchange_requirements"}), + ("contains_options.ifc", {"view_definitions", "options"}), + ("dynamic_fields.ifc", {"view_definitions", "exchange_requirements", "comments", "remark"}), + ], + ) def test_keywords_present(self, load_fixture, filename, expected_keywords): f = load_fixture(filename) assert f.mvd.keywords == expected_keywords @@ -133,13 +139,11 @@ class TestFallbackBehavior: def test_parse_mvd_fallback(self, monkeypatch): monkeypatch.setattr(mvd_info, "LARK_AVAILABLE", False) header = MockHeader(("ViewDefinition [ShouldNotParse]",)) - mvd = mvd_info.MvdInfo( - header - ) + mvd = mvd_info.MvdInfo(header) assert mvd.view_definitions is None assert mvd.keywords == set() - + + class MockHeader: def __init__(self, description): self.file_description = type("FileDescription", (), {"description": description}) -