Skip to content

Commit 6e54a46

Browse files
authored
Merge pull request #188 from buildingSMART/IVS-516-Use-spf-only-in-parse-info-task
IVS-516 - Use simple_spf only in parse_info task
2 parents b587d3b + 46c2219 commit 6e54a46

File tree

10 files changed

+40
-172
lines changed

10 files changed

+40
-172
lines changed

.github/workflows/ci_cd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ jobs:
8686
source venv/bin/activate
8787
# use version of ifcopenshell with desired schema parsing
8888
# TODO: revert to pyPI when schema parsing is published in the future
89-
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.1-c49ca69-linux64.zip"
89+
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.3-ec13294-linux64.zip"
9090
mkdir -p venv/lib/python3.11/site-packages
9191
unzip -d venv/lib/python3.11/site-packages /tmp/ifcopenshell_python.zip
9292

backend/Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@ venv:
1414
install: venv
1515
$(PIP) install --upgrade pip
1616
$(PIP) install -r requirements.txt
17-
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.1-c49ca69-linux64.zip"
17+
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.3-ec13294-linux64.zip"
1818
mkdir -p $(VIRTUAL_ENV)/lib/python3.11/site-packages
1919
unzip -f -d $(VIRTUAL_ENV)/lib/python3.11/site-packages /tmp/ifcopenshell_python.zip
2020
rm /tmp/ifcopenshell_python.zip
2121

2222
install-macos: venv
2323
$(PIP) install --upgrade pip
2424
$(PIP) install -r requirements.txt
25-
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.1-c49ca69-macos64.zip"
25+
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.3-ec13294-macos64.zip"
2626
mkdir -p $(VIRTUAL_ENV)/lib/python3.11/site-packages
2727
unzip /tmp/ifcopenshell_python.zip -d $(VIRTUAL_ENV)/lib/python3.11/site-packages
2828
rm /tmp/ifcopenshell_python.zip
2929

3030
install-macos-m1: venv
3131
$(PIP) install --upgrade pip
3232
$(PIP) install -r requirements.txt
33-
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.1-c49ca69-macosm164.zip"
33+
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.3-ec13294-macosm164.zip"
3434
mkdir -p $(VIRTUAL_ENV)/lib/python3.11/site-packages
3535
unzip /tmp/ifcopenshell_python.zip -d $(VIRTUAL_ENV)/lib/python3.11/site-packages
3636
rm /tmp/ifcopenshell_python.zip

backend/apps/ifc_validation/checks/header_policy/mvd_parser.py

Lines changed: 0 additions & 111 deletions
This file was deleted.

backend/apps/ifc_validation/checks/header_policy/test_header_validation.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,17 @@ def collect_test_files():
1616
@pytest.mark.parametrize("f", collect_test_files())
1717
def test_invocation(f):
1818
filename, outcome, field = f[0], f[1], f[2]
19-
try:
20-
file = ifcopenshell.open(filename)
21-
header = HeaderStructure(file=file, purepythonparser=False)
22-
except ifcopenshell.SchemaError:
23-
file = ifcopenshell.simple_spf.open(filename)
24-
header = HeaderStructure(file=file, purepythonparser=True)
19+
file = ifcopenshell.simple_spf.open(filename, only_header=True)
20+
header = HeaderStructure(file=file)
2521

2622
assert (field not in header.validation_errors) if outcome == 'pass' else (field in header.validation_errors)
2723

2824

2925
def run_single_file(filename=''):
3026
if filename:
31-
try:
32-
file = ifcopenshell.open(filename)
33-
header = HeaderStructure(file=file, purepythonparser=False)
34-
print(header.validation_errors)
35-
except ifcopenshell.SchemaError:
36-
file = ifcopenshell.simple_spf.open(filename)
37-
header = HeaderStructure(file=file, purepythonparser=True)
38-
print(header.validation_errors)
27+
file = ifcopenshell.simple_spf.open(filename, only_header=True)
28+
print(header.validation_errors)
29+
header = HeaderStructure(file=file)
3930

4031
if __name__ == "__main__":
4132
if len(sys.argv) == 2:

backend/apps/ifc_validation/checks/header_policy/validate_header.py

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
from pydantic import Field, field_validator, model_validator
3-
from typing import Tuple, Union, Optional
3+
from typing import Tuple, Any, Optional
44
import ifcopenshell
55
from ifcopenshell import validate, SchemaError, simple_spf
66
import re
@@ -12,7 +12,6 @@
1212
import yaml
1313
import os
1414
from config import ConfiguredBaseModel
15-
from mvd_parser import parse_mvd
1615

1716

1817
def ifcopenshell_pre_validation(file):
@@ -80,8 +79,7 @@ def validate_and_split_originating_system(attributes):
8079

8180

8281
class HeaderStructure(ConfiguredBaseModel):
83-
file: Union[ifcopenshell.file, simple_spf.file]
84-
purepythonparser : bool = False
82+
file: Optional[simple_spf.file] = None
8583
validation_errors : list = Field(default_factory=list)
8684

8785
description: Optional[Tuple[str, ...]] = Field(default=None)
@@ -98,20 +96,19 @@ class HeaderStructure(ConfiguredBaseModel):
9896
application_name: Optional[str] = Field(default=None)
9997
schema_identifier: Optional[str] = Field(default=None)
10098
version: Optional[str] = Field(default=None)
101-
mvd: Optional[str] = Field(default=None)
99+
mvd: Optional[Any] = Field(default=None) # Expected: ifcopenshell.util.mvd_info.AutoCommitList
100+
comments: Optional[Any] = Field(default=None) # Expected: ifcopenshell.util.mvd_info.AutoCommitList
102101

103102

104103
@model_validator(mode='before')
105104
def populate_header(cls, values):
106105
if (file := values.get('file')):
107106

108-
purepythonparser = values.get('purepythonparser')
109107
header = file.header
110108
file_description = header.file_description
111109
file_name = header.file_name
112110

113111
fields = [
114-
(file_description, 'description', 0),
115112
(file_description, 'implementation_level', 1),
116113
(file_name, 'name', 0),
117114
(file_name, 'time_stamp', 1),
@@ -122,41 +119,35 @@ def populate_header(cls, values):
122119
(file_name, 'authorization', 6)
123120
]
124121

125-
attributes = {field: getattr(obj, field) if not purepythonparser else obj[index]
126-
for obj, field, index in fields
127-
}
122+
attributes = {field: getattr(obj, field) for obj, field, index in fields}
128123

129124
attributes['validation_errors'] = []
130-
attributes['mvd'] = ''
125+
attributes['mvd'] = file.mvd.view_definitions
126+
attributes['comments'] = file.mvd.comments
131127
attributes['schema_identifier'] = ''
132128

133129
attributes = validate_and_split_originating_system(attributes)
134130

135-
errors_from_pre_validation = ifcopenshell_pre_validation(file) if not purepythonparser else []
136-
for error in errors_from_pre_validation:
137-
attributes['validation_errors'].append(error)
138-
attributes[error] = None
131+
if file.mvd.description:
132+
errors_from_pre_validation = ifcopenshell_pre_validation(file)
133+
for error in errors_from_pre_validation:
134+
attributes['validation_errors'].append(error)
135+
attributes[error] = None
139136

140-
values.update(attributes)
137+
values.update(attributes)
141138

142139
return values
143140

144-
145-
@field_validator('description')
141+
142+
@field_validator('comments', mode='after')
146143
def validate_description(cls, v, values):
147144
"""
148145
https://github.com/buildingSMART/IFC4.x-IF/tree/header-policy/docs/IFC-file-header#description
149146
For grammar refer to https://standards.buildingsmart.org/documents/Implementation/ImplementationGuide_IFCHeaderData_Version_1.0.2.pdf
150147
"""
151-
if v:
152-
header_description_text = ' '.join(v)
153-
parsed_description = parse_mvd(header_description_text)
154-
view_definitions = parsed_description.mvd
155-
values.data['mvd'] = view_definitions
156-
157-
148+
if v:
158149
# comments is a free textfield, but constrainted to 256 characters
159-
if len(parsed_description.comments) > 256:
150+
if len(v) > 256:
160151
values.data.get('validation_errors').append(values.field_name)
161152
return v if type(v) == tuple else v
162153

@@ -167,30 +158,30 @@ def validate_description(cls, v, values):
167158
@field_validator('mvd', mode='after')
168159
def validate_and_set_mvd(cls, v, values):
169160
"""
161+
https://github.com/buildingSMART/IFC4.x-IF/tree/header-policy/docs/IFC-file-header#description
162+
For grammar refer to https://standards.buildingsmart.org/documents/Implementation/ImplementationGuide_IFCHeaderData_Version_1.0.2.pdf
170163
This function runs after the other fields. It validates the mvd based on the grammar done in
171164
the 'description' field. The function checks the constraints on the mvds and returns
172165
the comma separated list into a single string.
173166
"""
174-
view_definitions = values.data.get('mvd')
175-
if not view_definitions:
167+
if not v:
176168
values.data.get('validation_errors').append('description')
177169
return v
178170

179171
allowed_descriptions = yaml.safe_load(open(os.path.join(os.path.dirname(__file__), 'valid_descriptions.yaml')))
180172
schema_identifier = values.data.get('file').schema_identifier
181173

182-
view_definitions_set = {view for view in view_definitions}
174+
view_definitions_set = {view for view in v}
183175

184176
if any("AddOnView" in view for view in view_definitions_set):
185177
if not any("CoordinationView" in view for view in view_definitions_set):
186178
values.data.get('validation_errors').append('description') # AddOnView MVD without CoordinationView
187179

188-
for mvd in view_definitions:
180+
for mvd in v:
189181
if mvd not in allowed_descriptions.get(schema_identifier, [False]) and not 'AddOnView' in mvd:
190182
values.data.get('validation_errors').append('description')
191183

192-
return ', '.join(view_definitions)
193-
184+
return ', '.join(v)
194185

195186

196187
@field_validator('time_stamp')
@@ -238,11 +229,10 @@ def main():
238229

239230
filename = sys.argv[1]
240231
try:
241-
file = ifcopenshell.open(filename)
242-
header = HeaderStructure(file=file, purepythonparser=False)
243-
except SchemaError as e:
244-
file = ifcopenshell.simple_spf.open(filename)
245-
header = HeaderStructure(file=file, purepythonparser=True)
232+
file = ifcopenshell.simple_spf.open(filename, only_header=True)
233+
header = HeaderStructure(file=file)
234+
except ifcopenshell.simple_spf.SyntaxError:
235+
header = HeaderStructure(file=None, validation_errors=["syntax_error"])
246236
except Exception as e:
247237
print(f"Error opening file '{filename}': {e}")
248238
sys.exit(1)

backend/apps/ifc_validation/tests/tests_schema_validation_task.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ def test_schema_validation_task_creates_error_validation_outcomes(self):
104104
for outcome in outcomes:
105105
self.assertEqual(outcome.severity, ValidationOutcome.OutcomeSeverity.ERROR)
106106
self.assertEqual(outcome.outcome_code, ValidationOutcome.ValidationOutcomeCode.SCHEMA_ERROR)
107-
self.assertIsNotNone(outcome.instance)
108107

109108
def test_schema_validation_task_creates_error_validation_outcomes_2(self):
110109

@@ -124,11 +123,10 @@ def test_schema_validation_task_creates_error_validation_outcomes_2(self):
124123

125124
outcomes = ValidationOutcome.objects.filter(validation_task__request_id=request.id)
126125
self.assertIsNotNone(outcomes)
127-
self.assertEqual(len(outcomes), 3)
126+
self.assertEqual(len(outcomes), 5)
128127
for outcome in outcomes:
129128
self.assertEqual(outcome.severity, ValidationOutcome.OutcomeSeverity.ERROR)
130129
self.assertEqual(outcome.outcome_code, ValidationOutcome.ValidationOutcomeCode.SCHEMA_ERROR)
131-
self.assertIsNotNone(outcome.instance)
132130

133131
def test_schema_validation_task_creates_error_validation_outcomes_3(self):
134132

@@ -148,7 +146,7 @@ def test_schema_validation_task_creates_error_validation_outcomes_3(self):
148146

149147
outcomes = ValidationOutcome.objects.filter(validation_task__request_id=request.id)
150148
self.assertIsNotNone(outcomes)
151-
self.assertEqual(len(outcomes), 8)
149+
self.assertEqual(len(outcomes), 11)
152150
for outcome in outcomes:
153151
self.assertEqual(outcome.severity, ValidationOutcome.OutcomeSeverity.ERROR)
154152
self.assertEqual(outcome.outcome_code, ValidationOutcome.ValidationOutcomeCode.SCHEMA_ERROR)
@@ -171,7 +169,7 @@ def test_schema_validation_task_creates_error_validation_outcomes_4(self):
171169

172170
outcomes = ValidationOutcome.objects.filter(validation_task__request_id=request.id)
173171
self.assertIsNotNone(outcomes)
174-
self.assertEqual(len(outcomes), 8)
172+
self.assertEqual(len(outcomes), 11)
175173
for outcome in outcomes:
176174
self.assertEqual(outcome.severity, ValidationOutcome.OutcomeSeverity.ERROR)
177175
self.assertEqual(outcome.outcome_code, ValidationOutcome.ValidationOutcomeCode.SCHEMA_ERROR)

docker/backend/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ RUN --mount=type=cache,target=/root/.cache \
3737
pip install --no-cache-dir -r /app/backend/requirements.txt && \
3838
# use version of ifcopenshell with desired schema parsing
3939
# TODO: revert to pyPI when schema parsing is published in the future
40-
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.1-c49ca69-linux64.zip" && \
40+
wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.3-ec13294-linux64.zip" && \
4141
mkdir -p /opt/venv/lib/python3.11/site-packages && \
4242
unzip -d /opt/venv/lib/python3.11/site-packages /tmp/ifcopenshell_python.zip && \
4343
# some cleanup

0 commit comments

Comments
 (0)