From 8718db63da4819f31674991537d08b315313064c Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 15 Jul 2026 11:18:00 +0500 Subject: [PATCH] pyproject: flip ty rules to error-by-default, review all new rules added since version bump --- pyproject.toml | 104 +++++------------- src/bonsai/bonsai/bim/ifc.py | 2 +- .../bonsai/bim/module/patch/operator.py | 6 +- .../module/structural/load_decoration_data.py | 21 ++-- src/bonsai/bonsai/tool/geometry.py | 3 +- src/bonsai/bonsai/tool/loader.py | 1 - src/bonsai/bonsai/tool/raycast.py | 2 +- src/bonsai/test/bim/test_feature.py | 4 +- src/ifc5d/ifc5d/csv2ifc.py | 8 +- src/ifc5d/ifc5d/ifc5Dspreadsheet.py | 5 +- src/ifc5d/pyproject.toml | 1 + src/ifcclash/ifcclash/ifcclash.py | 2 + .../ifcopenshell/util/shape_builder.py | 2 - src/ifcpatch/ifcpatch/__init__.py | 9 +- src/ifctester/ifctester/reporter.py | 22 +++- src/ifctester/pyproject.toml | 12 +- 16 files changed, 94 insertions(+), 110 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 28b39ba457..d832213792 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,91 +79,39 @@ ignore = [ ] [tool.ty.rules] -all = "ignore" +all = "error" # Structural rules (no deep type inference needed, easier to adapt). -abstract-method-in-final-class = "error" -ambiguous-protocol-member = "error" -conflicting-declarations = "error" -conflicting-metaclass = "error" -cyclic-class-definition = "error" -cyclic-type-alias-definition = "error" -dataclass-field-order = "error" -duplicate-base = "error" -duplicate-kw-only = "error" -empty-body = "error" -escape-character-in-forward-annotation = "error" -final-on-non-method = "error" -final-without-value = "error" -ignore-comment-unknown-rule = "error" -implicit-concatenated-string-type-annotation = "error" -inconsistent-mro = "error" -ineffective-final = "error" -instance-layout-conflict = "error" -invalid-dataclass = "error" -invalid-dataclass-override = "error" -invalid-enum-member-annotation = "error" -invalid-explicit-override = "error" -invalid-frozen-dataclass-subclass = "error" -invalid-generic-class = "error" -invalid-generic-enum = "error" -invalid-ignore-comment = "error" -invalid-legacy-positional-parameter = "error" -invalid-legacy-type-variable = "error" -invalid-named-tuple = "error" -invalid-newtype = "error" -invalid-overload = "error" -invalid-paramspec = "error" -invalid-protocol = "error" -invalid-syntax-in-forward-annotation = "error" -invalid-total-ordering = "error" -invalid-type-alias-type = "error" -invalid-type-checking-constant = "error" -invalid-type-guard-definition = "error" -invalid-type-variable-bound = "error" -invalid-type-variable-constraints = "error" -invalid-typed-dict-header = "error" -invalid-typed-dict-statement = "error" -override-of-final-method = "error" -override-of-final-variable = "error" -possibly-missing-import = "error" -possibly-missing-submodule = "error" # Has false positives due to ty walrus operator bug. -# possibly-unresolved-reference = "error" -raw-string-type-annotation = "error" -redundant-final-classvar = "error" -shadowed-type-variable = "error" -subclass-of-final-class = "error" -super-call-in-named-tuple-method = "error" -unavailable-implicit-super-arguments = "error" -unbound-type-variable = "error" -undefined-reveal = "error" -unresolved-global = "error" -unresolved-import = "error" -unresolved-reference = "error" -unused-ignore-comment = "error" -unused-type-ignore-comment = "error" -useless-overload-body = "error" +possibly-unresolved-reference = "ignore" +# Maybe later, requires to specify element types for all generics. +missing-type-argument = "ignore" +# Conflicts with `bpy` props defined using annotations. +invalid-type-form = "ignore" # Non-structural rules: -deprecated = "error" -zero-stepsize-in-slice = "error" -possibly-missing-implicit-call = "error" -unused-awaitable = "error" - -# Function argument rules: # Conflicts with `ifcopenshell.api.geometry.add_representation` type of callables we have, confusing them with a module. -# call-non-callable = "error" +call-non-callable = "ignore" +# bpy is missing some context manager implementations. +invalid-context-manager = "ignore" +# Doesn't go well with `bpy.ops.xxx.yyy`. +unresolved-attribute = "ignore" # Too many false positives. -# invalid-argument-type = "error" -missing-argument = "error" -parameter-already-assigned = "error" -positional-only-parameter-as-kwarg = "error" -too-many-positional-arguments = "error" -unknown-argument = "error" -# Has a lot of warnings due to current ty walrus operator issues. -# index-out-of-bounds = "error" -# unresolved-attribute = "error" +invalid-argument-type = "ignore" +invalid-method-override = "ignore" +invalid-assignment = "ignore" +invalid-parameter-default = "ignore" +missing-override-decorator = "ignore" +invalid-yield = "ignore" +invalid-return-type = "ignore" +non-callable-init-subclass = "ignore" +not-iterable = "ignore" +possibly-missing-attribute = "ignore" +no-matching-overload = "ignore" +not-subscriptable = "ignore" +unsupported-dynamic-base = "ignore" +unsupported-operator = "ignore" +type-assertion-failure = "ignore" [tool.ty.environment] extra-paths = [ diff --git a/src/bonsai/bonsai/bim/ifc.py b/src/bonsai/bonsai/bim/ifc.py index 62dc8c2288..3cfe0e3106 100644 --- a/src/bonsai/bonsai/bim/ifc.py +++ b/src/bonsai/bonsai/bim/ifc.py @@ -46,7 +46,7 @@ IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object] class OperationData(TypedDict): id: int guid: NotRequired[str] - obj: str + obj: NotRequired[str] class EditObjectOperationData(TypedDict): diff --git a/src/bonsai/bonsai/bim/module/patch/operator.py b/src/bonsai/bonsai/bim/module/patch/operator.py index 531b7c581c..bffea62689 100644 --- a/src/bonsai/bonsai/bim/module/patch/operator.py +++ b/src/bonsai/bonsai/bim/module/patch/operator.py @@ -18,7 +18,7 @@ import json from pathlib import Path -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING import bpy import ifcopenshell @@ -122,8 +122,8 @@ class ExecuteIfcPatch(bpy.types.Operator): if props.should_load_from_memory and tool.Ifc.get(): args["file"] = tool.Ifc.get() else: - args["input"] = cast(str, props.ifc_patch_input) - args["file"] = cast(ifcopenshell.file, ifcopenshell.open(props.ifc_patch_input)) + args["input"] = props.ifc_patch_input + args["file"] = ifcopenshell.open(props.ifc_patch_input) # Store this in case the patch recipe resets the Blender session, such as by loading a new project. ifc_patch_output = props.ifc_patch_output or props.ifc_patch_input diff --git a/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py b/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py index dc05fab89c..ebeb529f75 100644 --- a/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py +++ b/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py @@ -71,7 +71,11 @@ class LoadByDirection(TypedDict): ProcessedLoad = TypedDict( "ProcessedLoad", - {"linear loads": LoadByDirection, "max linear load": float, "discrete loads": list[list[DiscreteConfigItem]]}, + { + "linear loads": dict[str, LoadByDirection] | None, + "max linear load": float, + "discrete loads": list[list[DiscreteConfigItem]], + }, ) @@ -845,13 +849,16 @@ class ShaderInfo: v = l1[1] + fac * (pos - l1[0]) return v - def interpolate(self, pos: float, loadinfo: list[LoadConfigItem], start: int, end: int, key: str) -> np.ndarray: + def interpolate(self, pos: float, loadinfo: list[LoadConfigItem], start: int, end: int) -> np.ndarray: """interpolate the result vectors between load poits""" result = np.zeros(6) for i in range(6): - value1 = [loadinfo[start]["pos"], loadinfo[start][key][i]] # [position, force_component] - value2 = [loadinfo[end]["pos"], loadinfo[end][key][i]] # [position, force_component] - result[i] = self.interp1d(value1, value2, pos) # interpolated [position, force_component] + # [position, force_component] + value1 = [loadinfo[start]["pos"], loadinfo[start]["load values"][i]] + # [position, force_component] + value2 = [loadinfo[end]["pos"], loadinfo[end]["load values"][i]] + # interpolated [position, force_component] + result[i] = self.interp1d(value1, value2, pos) return result def get_before_and_after(self, pos: float, load_config_list: list[list[LoadConfigItem]]) -> dict[str, list[float]]: @@ -895,8 +902,8 @@ class ShaderInfo: load_before += config[end]["load values"] elif end - start == 1: - load_before += self.interpolate(pos, config, start, end, "load values") - load_after += self.interpolate(pos, config, start, end, "load values") + load_before += self.interpolate(pos, config, start, end) + load_after += self.interpolate(pos, config, start, end) start += 1 end -= 1 return_value = {"before": load_before.tolist(), "after": load_after.tolist()} diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index c4b7cd0ef6..08abd38b22 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -33,7 +33,6 @@ from typing import ( Optional, TypeGuard, Union, - cast, get_args, ) @@ -2187,7 +2186,7 @@ class Geometry(bonsai.core.tool.Geometry): setattr(item, attribute.name, attribute.get_value()) if item.is_a("IfcSweptAreaSolid"): - item_profile = cast(str, props.item_profile) + item_profile = props.item_profile profile = item.SweptArea profile_name: Union[str, None] = profile.ProfileName if item_profile == "-": diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index 47613db47c..383be821e2 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -197,7 +197,6 @@ class Loader(bonsai.core.tool.Loader): cls, blender_material: bpy.types.Material, surface_style: ifcopenshell.entity_instance ) -> None: surface_style = cls.surface_style_to_dict(surface_style) - surface_style: dict[str, Any] cls.create_surface_style_shading(blender_material, surface_style) diff --git a/src/bonsai/bonsai/tool/raycast.py b/src/bonsai/bonsai/tool/raycast.py index 68402260cd..2b8b2a46c7 100644 --- a/src/bonsai/bonsai/tool/raycast.py +++ b/src/bonsai/bonsai/tool/raycast.py @@ -232,7 +232,7 @@ class Raycast(bonsai.core.tool.Raycast): return final_2d, v2 @classmethod - def intersect_mouse_2d_bounding_box(cls, mouse_pos: tuple[int, int], bbox: list[float, float, float, float]): + def intersect_mouse_2d_bounding_box(cls, mouse_pos: tuple[int, int], bbox: list[float]): x, y = mouse_pos xmin, xmax, ymin, ymax = bbox diff --git a/src/bonsai/test/bim/test_feature.py b/src/bonsai/test/bim/test_feature.py index 39a4d7710a..1b5e9141ca 100644 --- a/src/bonsai/test/bim/test_feature.py +++ b/src/bonsai/test/bim/test_feature.py @@ -30,7 +30,7 @@ from collections.abc import Generator from inspect import signature from math import radians from pathlib import Path -from typing import Any, Union +from typing import Any, Union, cast import bpy import ifcopenshell @@ -140,7 +140,7 @@ class PanelSpy: else: props = kwargs.get("data") name = kwargs.get("property") - props: bpy.types.bpy_struct + props = cast(bpy.types.bpy_struct, props) text = kwargs.get("text", props.bl_rna.properties[name].name) icon = kwargs.get("icon", None) prop_type = props.bl_rna.properties[name].type diff --git a/src/ifc5d/ifc5d/csv2ifc.py b/src/ifc5d/ifc5d/csv2ifc.py index fcfc0d119c..d3e5240bd0 100644 --- a/src/ifc5d/ifc5d/csv2ifc.py +++ b/src/ifc5d/ifc5d/csv2ifc.py @@ -82,6 +82,11 @@ MAIN_CSV_HEADER_COLUMNS.extend( ) +class CostRate(TypedDict): + Schedule: str | None + RateID: str | None + + class CostItem(TypedDict): children: list[CostItem] ifc: NotRequired[ifcopenshell.entity_instance] @@ -97,6 +102,7 @@ class CostItem(TypedDict): Property: Union[str, None] Query: Union[str, None] + CostRate: CostRate | None Formula: Union[str, None] # QuantityClass: Union[str, None] @@ -239,7 +245,7 @@ class Csv2Ifc: cost_values = float(cost_values) if cost_values else None if self.has_rates: - cost_rate = { + cost_rate: CostRate = { "Schedule": row[(self.headers["RateSchedule"])] if "RateSchedule" in self.headers else None, "RateID": row[(self.headers["RateID"])] if "RateID" in self.headers else None, } diff --git a/src/ifc5d/ifc5d/ifc5Dspreadsheet.py b/src/ifc5d/ifc5d/ifc5Dspreadsheet.py index a57e52426c..bd38a8f53b 100644 --- a/src/ifc5d/ifc5d/ifc5Dspreadsheet.py +++ b/src/ifc5d/ifc5d/ifc5Dspreadsheet.py @@ -26,7 +26,8 @@ import logging import os import time from collections import Counter -from typing import Optional, TypedDict, Union +from typing import Optional, Union +from typing_extensions import TypedDict import ifcopenshell import ifcopenshell.util.cost @@ -35,7 +36,7 @@ import ifcopenshell.util.element import ifcopenshell.util.unit -class CostItem(TypedDict): +class CostItem(TypedDict, extra_items=float): # Exported columns. Index: int Hierarchy: str diff --git a/src/ifc5d/pyproject.toml b/src/ifc5d/pyproject.toml index 8d51a63819..eee7b5f07d 100644 --- a/src/ifc5d/pyproject.toml +++ b/src/ifc5d/pyproject.toml @@ -17,6 +17,7 @@ classifiers = [ ] dependencies = [ "ifcopenshell", + "typing_extensions", ] [project.optional-dependencies] diff --git a/src/ifcclash/ifcclash/ifcclash.py b/src/ifcclash/ifcclash/ifcclash.py index 34245dc764..3363e2454a 100644 --- a/src/ifcclash/ifcclash/ifcclash.py +++ b/src/ifcclash/ifcclash/ifcclash.py @@ -53,6 +53,8 @@ class ClashResult(TypedDict): p1: list[float] p2: list[float] distance: float + # Added by `Clasher.smart_group_clashes`. + smart_group: NotRequired[int] class ClashSet(TypedDict): diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py index eb5bbbdcdb..c3c33e2054 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py @@ -473,7 +473,6 @@ class ShapeBuilder: if arc_points and self.file.schema == "IFC2X3": raise Exception("Arcs are not supported for IFC2X3.") - points: np.ndarray points = np.array(points) if position_offset is not None: points = points + position_offset @@ -635,7 +634,6 @@ class ShapeBuilder: diff = (0.01, 0.01) * diff_sign middle_point = points[0] + diff - points: list[VectorType] points = [points[0], middle_point, points[1]] points = [ifc_safe_vector_type(p) for p in points] seg = self.file.createIfcArcIndex((1, 2, 3)) diff --git a/src/ifcpatch/ifcpatch/__init__.py b/src/ifcpatch/ifcpatch/__init__.py index 3b6b8fdf40..8928582d77 100644 --- a/src/ifcpatch/ifcpatch/__init__.py +++ b/src/ifcpatch/ifcpatch/__init__.py @@ -184,7 +184,7 @@ class PatcherDoc(TypedDict): class InputDoc(TypedDict): name: str description: str - type: Union[str, list[str]] + type: NotRequired[Union[str, list[str]]] default: NotRequired[Any] generic_type: NotRequired[str] enum_items: NotRequired[list[str]] @@ -201,7 +201,7 @@ def _extract_docs(cls: type, method_name: str, boilerplate_args: Union[Sequence[ for name, parameter in signature.parameters.items(): if name == "self" or name in boilerplate_args: continue - input_doc: InputDoc = {"name": name} + input_doc: InputDoc = {"name": name, "description": "Undocumented"} inputs[name] = input_doc if isinstance(parameter.default, (str, float, int, bool)): input_doc["default"] = parameter.default @@ -267,10 +267,6 @@ def _extract_docs(cls: type, method_name: str, boilerplate_args: Union[Sequence[ assert is_valid_filter_glob(filter_glob), f"Invalid filter_glob pattern: '{filter_glob}'." inputs[param_name]["filter_glob"] = filter_glob - for param_name in inputs: - if "description" not in inputs[param_name]: - inputs[param_name]["description"] = "Undocumented" - docs = PatcherDoc( class_=cls, description=doc_description, @@ -309,6 +305,7 @@ def parse_docstring(docstring: str) -> DocstringData: line = line[1:] if line.startswith(PREFIXES): prefix = line.split(" ")[0] + assert prefix in PREFIXES, f"Invalid line: '{line}'." current_section = prefix match_ = re.match(rf"{prefix}\s+(\w+):\s+(.*)", line) assert match_, f"Invalid line: '{line}'." diff --git a/src/ifctester/ifctester/reporter.py b/src/ifctester/ifctester/reporter.py index 38b3ba8f5b..de70879f09 100644 --- a/src/ifctester/ifctester/reporter.py +++ b/src/ifctester/ifctester/reporter.py @@ -24,6 +24,7 @@ import os import re import sys from typing import Literal, Optional, TypedDict, Union +from typing_extensions import NotRequired import ifcopenshell import ifcopenshell.util.element @@ -96,6 +97,12 @@ class ResultsSpecification(TypedDict): applicability: list[str] requirements: list[ResultsRequirement] + # Filled in by `Html.report()`. + is_prohibited: NotRequired[bool] + has_requirements: NotRequired[bool] + has_omitted_applicable: NotRequired[bool] + total_omitted_applicable: NotRequired[int] + class ResultsRequirement(TypedDict): facet_type: str @@ -111,6 +118,15 @@ class ResultsRequirement(TypedDict): total_fail: int percent_pass: ResultsPercent + # Filled in by `Html.report()`. + total_failed_entities: NotRequired[int] + total_omitted_failures: NotRequired[int] + has_omitted_failures: NotRequired[bool] + total_passed_entities: NotRequired[int] + total_omitted_passes: NotRequired[int] + has_omitted_passes: NotRequired[bool] + instructions: NotRequired[str | None] + # use different syntax because of the "class" key ResultsEntity = TypedDict( @@ -244,7 +260,7 @@ class Txt(Console): class Json(Reporter): def __init__(self, ids: Ids, hide_skipped=False): super().__init__(ids) - self.results = Results() + self.results = Results() # ty:ignore[missing-typed-dict-key] self.results["hide_skipped"] = hide_skipped def report(self) -> Results: @@ -410,7 +426,7 @@ class Json(Reporter): "id": e.id(), "global_id": getattr(e, "GlobalId", None), "tag": getattr(e, "Tag", None), - } + } # ty:ignore[missing-typed-dict-key] ) for e in specification.applicable_entities ] @@ -428,7 +444,7 @@ class Json(Reporter): "id": e.id(), "global_id": getattr(e, "GlobalId", None), "tag": getattr(e, "Tag", None), - } + } # ty:ignore[missing-typed-dict-key] ) for e in requirement.passed_entities ] diff --git a/src/ifctester/pyproject.toml b/src/ifctester/pyproject.toml index 1be89ed68b..b522e651d5 100644 --- a/src/ifctester/pyproject.toml +++ b/src/ifctester/pyproject.toml @@ -15,7 +15,17 @@ classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", ] -dependencies = ["ifcopenshell", "python-dateutil", "xmlschema", "numpy", "odfpy", "pystache", "bcf-client", "flask"] +dependencies = [ + "ifcopenshell", + "python-dateutil", + "xmlschema", + "numpy", + "odfpy", + "pystache", + "bcf-client", + "flask", + "typing_extensions", +] [project.optional-dependencies] advanced = [