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

Check IfcDateTime according to ISO standards #3864

Open
wants to merge 8 commits into
base: v0.7.0
Choose a base branch
from

Conversation

johltn
Copy link
Contributor

@johltn johltn commented Oct 8, 2023

No description provided.

Comment on lines 28 to 29
def year(self, items):
return ''.join(token.value for token in items)
Copy link
Member

Choose a reason for hiding this comment

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

How about something like this:

Suggested change
def year(self, items):
return ''.join(token.value for token in items)
def _integer(self, items):
return int(''.join(token.value for token in items))
year = _integer

and same as below.

I also added the int() I would convert to the appropriate type in the transformer already.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed in 5c5aaf1


day: DIGIT DIGIT
month: DIGIT DIGIT
year: MINUS? DIGIT+
Copy link
Member

Choose a reason for hiding this comment

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

Wikipedia says:

ISO 8601 prescribes, as a minimum, a four-digit year

Should we add that to the grammar?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added 4a2f34a

src/ifcopenshell-python/ifcopenshell/type_checker.py Outdated Show resolved Hide resolved
hour: DIGIT DIGIT
minute: DIGIT DIGIT
second: DIGIT DIGIT
millisecond: DIGIT+
Copy link
Member

Choose a reason for hiding this comment

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

Is 10:10:10.1 allowed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed in 0f75565

print("syntax error")


if __name__ == '__main__':
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Also we should add some test cases of actual IFC file content to check whether the values of such types are correctly discovered. For those you can follow the same convention as here https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/ifcopenshell-python/test/fixtures/validate

E.g create an IfcPropertyListValue with multiple IfcDateTime values. IfcDateTime is IfcSimpleValue is IfcValue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

test files created d0abe8f

)

except Exception as e:
#todo: handle aggregations
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added in 73834ea

@aothms
Copy link
Member

aothms commented Oct 23, 2023

Fails when we run the tests https://github.com/IfcOpenShell/IfcOpenShell/actions/runs/6607354944/job/17944635714#step:8:37

Probably needs to be a relative import. (relative) Python module import never fails to confuse me.


parsing = parse_datetime(val)

if isinstance(parsing, DateErrorParsing().__class__):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if isinstance(parsing, DateErrorParsing().__class__):
if isinstance(parsing, DateErrorParsing):

DateErrorParsing().class == DateErrorParsing

Comment on lines +425 to +441
logger.error(
"For instance:\n %s\n %s\nInvalid attribute value %s for %s.%s",
inst,
annotate_inst_attr_pos(inst, i),
parsing.msg,
entity,
attrs[i],
)
elif isinstance(validate_datetime(parsing), DateErrorContent().__class__):
logger.error(
"For instance:\n %s\n %s\nInvalid attribute value %s for %s.%s",
inst,
annotate_inst_attr_pos(inst, i),
parsing.msg,
entity,
attrs[i],
)
Copy link
Member

Choose a reason for hiding this comment

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

I think these two branches are equal?

# unpack the type instance
yield from check(value[0], value.is_a(), instance=instance)

if value.is_a() == "IfcDateTime" and value[0]:
Copy link
Member

Choose a reason for hiding this comment

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

This function still does the type check here, so can't be reused.

Rather yield the values and do the check outside of the function. That way the same function can also be used for checking uniqueness for example.

# unpack the type instance
yield from check(value[0], value.is_a(), instance=instance)

if value.is_a() == "IfcDateTime" and value[0]:
Copy link
Member

Choose a reason for hiding this comment

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

Is it safe to always use value[0]? We need to add some test files where the IfcDateTime is used as an actual attribute such as IfcLibraryInformation and not just a simple type in a select, such as IfcPropertyListValue.

@@ -365,7 +412,61 @@ def validate(f, logger, express_rules=False):

if not has_invalid_value:
for i, (attr, val, is_derived) in enumerate(zip(attrs, values, entity.derived())):

try:
Copy link
Member

Choose a reason for hiding this comment

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

Why is a try catch statement used here? Catching all subtypes of Exception silences a lot of other issues. Please rewrite to proper if statement.

)
else:
other_check = list(validate_datetime(r["msg"]))
if other_check and isinstance(other_check[0], DateErrorContent().__class__):
Copy link
Member

Choose a reason for hiding this comment

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

Why only other_check[0]?

declared_type = type_of_attribute.declared_type()
type_name = declared_type.name()

if type_name == "IfcDateTime" and val:
Copy link
Member

Choose a reason for hiding this comment

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

I would implement validation of https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDuration.htm in this PR as well. And really make sure that the code is as general and reusable as possible. So that validators are stored for example in a global mapping like:

type_validators = {
  'IfcDateTime: [parse_datetime, validate_datetime],
  'IfcDuration: [ ... ]
  ...
}

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.

None yet

2 participants