Skip to content

Enforce header entity length in grammar #15

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

Merged

Conversation

Ghesselink
Copy link
Contributor

No description provided.

@Ghesselink Ghesselink marked this pull request as draft May 6, 2025 16:08
@Ghesselink
Copy link
Contributor Author

Doesn't work so simple, have to take another look

self = <__init__.file object at 0x7f45d99f5bd0>

    @property
    def header(self):
        HEADER_FIELDS = {
            "file_description": namedtuple('file_description', ['description', 'implementation_level']),
            "file_name": namedtuple('file_name', ['name', 'time_stamp', 'author', 'organization', 'preprocessor_version', 'originating_system', 'authorization']),
            "file_schema":  namedtuple('file_schema', ['schema_identifiers']),
        }
        header = {}
    
        for field_name, namedtuple_class in HEADER_FIELDS.items():
            field_data = self.header_.get(field_name.upper(), [])
>           header[field_name.lower()] = namedtuple_class(*field_data)
E           TypeError: file_name.__new__() takes 8 positional arguments but 20 were given

for

HEADER;
FILE_DESCRIPTION(('ViewDefinition [Alignment-basedView]'),'2;1');
FILE_NAME('Header example2.ifc', '2022-09-16T10:35:07', ('Evandro Alfieri'), ('buildingSMART Int.'), 'IFC Motor 1.0', 'Company - Application - 26.0.0.0', 'none');
FILE_SCHEMA(('IFC4X3_ADD2'));
ENDSEC;

@aothms
Copy link
Member

aothms commented May 6, 2025

Some things are easier when you do them not in the grammar, but on the parsed ast tree. e.g assert len(f.header.file_description) == len(HEADER_FIELDS['file_description']) (in pseudo code)

@Ghesselink Ghesselink force-pushed the enforce-header-entity-length-in-grammar branch from 126c1d1 to 083f962 Compare July 10, 2025 16:31
@Ghesselink Ghesselink marked this pull request as ready for review July 10, 2025 16:31
__init__.py Outdated
observed = header.get(field.upper(), [])
expected = HEADER_FIELDS.get(field)._fields
if len(header.get(field.upper(), [])) != len(expected):
raise HeaderFieldError(field.upper(), len(observed), len(expected))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to really raise an exception and terminate or wait for potentially additional errors?

Copy link
Member

@aothms aothms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice, just would prefer without the getattr() if possible, but understand where it's coming from: exceptions are for exceptional control flow, but we are accumulating them.

__main__.py Outdated
@@ -26,7 +26,7 @@ def main():
if not args.json:
print(exc, file=sys.stderr)
else:
json.dump(exc.asdict(), sys.stdout)
json.dump([e.asdict() for e in getattr(exc, "errors", [exc])], sys.stdout, indent=2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we actually catch both cases separately CollectedValidationErrors, ValidationError instead, or refactor ValidationError to always point to a list of errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I designed it in a way that ValidationErrors is never raised directly; the the issues are represented in subclasses (e.g. SyntaxError, DuplicateNameError, etc) and collected into a single CollectedValidationErrors instance, which is then raised. As a result, the getattr was already redundant and we can rely on exc.errors directly
json.dump([e.asdict() for e in exc.errors], sys.stdout, indent=2)
ValidationError is now only used for testing as it covers all sub classes with pytest.raises(_ValidationError).

I've also renamed it to _ValidationErrors and added documentation, clarifying that we only use it internally. The parser itself also doesn't import it directly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds great!

@aothms aothms merged commit 2849a31 into IfcOpenShell:master Jul 28, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants