pyproject: flip ty rules to error-by-default, review all new rules added since version bump

This commit is contained in:
Andrej730
2026-07-15 11:18:00 +05:00
parent c013b9aca7
commit 8718db63da
16 changed files with 94 additions and 110 deletions
+26 -78
View File
@@ -79,91 +79,39 @@ ignore = [
] ]
[tool.ty.rules] [tool.ty.rules]
all = "ignore" all = "error"
# Structural rules (no deep type inference needed, easier to adapt). # 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. # Has false positives due to ty walrus operator bug.
# possibly-unresolved-reference = "error" possibly-unresolved-reference = "ignore"
raw-string-type-annotation = "error" # Maybe later, requires to specify element types for all generics.
redundant-final-classvar = "error" missing-type-argument = "ignore"
shadowed-type-variable = "error" # Conflicts with `bpy` props defined using annotations.
subclass-of-final-class = "error" invalid-type-form = "ignore"
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"
# Non-structural rules: # 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. # 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. # Too many false positives.
# invalid-argument-type = "error" invalid-argument-type = "ignore"
missing-argument = "error" invalid-method-override = "ignore"
parameter-already-assigned = "error" invalid-assignment = "ignore"
positional-only-parameter-as-kwarg = "error" invalid-parameter-default = "ignore"
too-many-positional-arguments = "error" missing-override-decorator = "ignore"
unknown-argument = "error" invalid-yield = "ignore"
# Has a lot of warnings due to current ty walrus operator issues. invalid-return-type = "ignore"
# index-out-of-bounds = "error" non-callable-init-subclass = "ignore"
# unresolved-attribute = "error" 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] [tool.ty.environment]
extra-paths = [ extra-paths = [
+1 -1
View File
@@ -46,7 +46,7 @@ IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
class OperationData(TypedDict): class OperationData(TypedDict):
id: int id: int
guid: NotRequired[str] guid: NotRequired[str]
obj: str obj: NotRequired[str]
class EditObjectOperationData(TypedDict): class EditObjectOperationData(TypedDict):
@@ -18,7 +18,7 @@
import json import json
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, cast from typing import TYPE_CHECKING
import bpy import bpy
import ifcopenshell import ifcopenshell
@@ -122,8 +122,8 @@ class ExecuteIfcPatch(bpy.types.Operator):
if props.should_load_from_memory and tool.Ifc.get(): if props.should_load_from_memory and tool.Ifc.get():
args["file"] = tool.Ifc.get() args["file"] = tool.Ifc.get()
else: else:
args["input"] = cast(str, props.ifc_patch_input) args["input"] = props.ifc_patch_input
args["file"] = cast(ifcopenshell.file, ifcopenshell.open(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. # 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 ifc_patch_output = props.ifc_patch_output or props.ifc_patch_input
@@ -71,7 +71,11 @@ class LoadByDirection(TypedDict):
ProcessedLoad = TypedDict( ProcessedLoad = TypedDict(
"ProcessedLoad", "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]) v = l1[1] + fac * (pos - l1[0])
return v 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""" """interpolate the result vectors between load poits"""
result = np.zeros(6) result = np.zeros(6)
for i in range(6): for i in range(6):
value1 = [loadinfo[start]["pos"], loadinfo[start][key][i]] # [position, force_component] # [position, force_component]
value2 = [loadinfo[end]["pos"], loadinfo[end][key][i]] # [position, force_component] value1 = [loadinfo[start]["pos"], loadinfo[start]["load values"][i]]
result[i] = self.interp1d(value1, value2, pos) # interpolated [position, force_component] # [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 return result
def get_before_and_after(self, pos: float, load_config_list: list[list[LoadConfigItem]]) -> dict[str, list[float]]: 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"] load_before += config[end]["load values"]
elif end - start == 1: elif end - start == 1:
load_before += self.interpolate(pos, config, start, end, "load values") load_before += self.interpolate(pos, config, start, end)
load_after += self.interpolate(pos, config, start, end, "load values") load_after += self.interpolate(pos, config, start, end)
start += 1 start += 1
end -= 1 end -= 1
return_value = {"before": load_before.tolist(), "after": load_after.tolist()} return_value = {"before": load_before.tolist(), "after": load_after.tolist()}
+1 -2
View File
@@ -33,7 +33,6 @@ from typing import (
Optional, Optional,
TypeGuard, TypeGuard,
Union, Union,
cast,
get_args, get_args,
) )
@@ -2187,7 +2186,7 @@ class Geometry(bonsai.core.tool.Geometry):
setattr(item, attribute.name, attribute.get_value()) setattr(item, attribute.name, attribute.get_value())
if item.is_a("IfcSweptAreaSolid"): if item.is_a("IfcSweptAreaSolid"):
item_profile = cast(str, props.item_profile) item_profile = props.item_profile
profile = item.SweptArea profile = item.SweptArea
profile_name: Union[str, None] = profile.ProfileName profile_name: Union[str, None] = profile.ProfileName
if item_profile == "-": if item_profile == "-":
-1
View File
@@ -197,7 +197,6 @@ class Loader(bonsai.core.tool.Loader):
cls, blender_material: bpy.types.Material, surface_style: ifcopenshell.entity_instance cls, blender_material: bpy.types.Material, surface_style: ifcopenshell.entity_instance
) -> None: ) -> None:
surface_style = cls.surface_style_to_dict(surface_style) surface_style = cls.surface_style_to_dict(surface_style)
surface_style: dict[str, Any]
cls.create_surface_style_shading(blender_material, surface_style) cls.create_surface_style_shading(blender_material, surface_style)
+1 -1
View File
@@ -232,7 +232,7 @@ class Raycast(bonsai.core.tool.Raycast):
return final_2d, v2 return final_2d, v2
@classmethod @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 x, y = mouse_pos
xmin, xmax, ymin, ymax = bbox xmin, xmax, ymin, ymax = bbox
+2 -2
View File
@@ -30,7 +30,7 @@ from collections.abc import Generator
from inspect import signature from inspect import signature
from math import radians from math import radians
from pathlib import Path from pathlib import Path
from typing import Any, Union from typing import Any, Union, cast
import bpy import bpy
import ifcopenshell import ifcopenshell
@@ -140,7 +140,7 @@ class PanelSpy:
else: else:
props = kwargs.get("data") props = kwargs.get("data")
name = kwargs.get("property") 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) text = kwargs.get("text", props.bl_rna.properties[name].name)
icon = kwargs.get("icon", None) icon = kwargs.get("icon", None)
prop_type = props.bl_rna.properties[name].type prop_type = props.bl_rna.properties[name].type
+7 -1
View File
@@ -82,6 +82,11 @@ MAIN_CSV_HEADER_COLUMNS.extend(
) )
class CostRate(TypedDict):
Schedule: str | None
RateID: str | None
class CostItem(TypedDict): class CostItem(TypedDict):
children: list[CostItem] children: list[CostItem]
ifc: NotRequired[ifcopenshell.entity_instance] ifc: NotRequired[ifcopenshell.entity_instance]
@@ -97,6 +102,7 @@ class CostItem(TypedDict):
Property: Union[str, None] Property: Union[str, None]
Query: Union[str, None] Query: Union[str, None]
CostRate: CostRate | None
Formula: Union[str, None] Formula: Union[str, None]
# QuantityClass: Union[str, None] # QuantityClass: Union[str, None]
@@ -239,7 +245,7 @@ class Csv2Ifc:
cost_values = float(cost_values) if cost_values else None cost_values = float(cost_values) if cost_values else None
if self.has_rates: if self.has_rates:
cost_rate = { cost_rate: CostRate = {
"Schedule": row[(self.headers["RateSchedule"])] if "RateSchedule" in self.headers else None, "Schedule": row[(self.headers["RateSchedule"])] if "RateSchedule" in self.headers else None,
"RateID": row[(self.headers["RateID"])] if "RateID" in self.headers else None, "RateID": row[(self.headers["RateID"])] if "RateID" in self.headers else None,
} }
+3 -2
View File
@@ -26,7 +26,8 @@ import logging
import os import os
import time import time
from collections import Counter from collections import Counter
from typing import Optional, TypedDict, Union from typing import Optional, Union
from typing_extensions import TypedDict
import ifcopenshell import ifcopenshell
import ifcopenshell.util.cost import ifcopenshell.util.cost
@@ -35,7 +36,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.unit import ifcopenshell.util.unit
class CostItem(TypedDict): class CostItem(TypedDict, extra_items=float):
# Exported columns. # Exported columns.
Index: int Index: int
Hierarchy: str Hierarchy: str
+1
View File
@@ -17,6 +17,7 @@ classifiers = [
] ]
dependencies = [ dependencies = [
"ifcopenshell", "ifcopenshell",
"typing_extensions",
] ]
[project.optional-dependencies] [project.optional-dependencies]
+2
View File
@@ -53,6 +53,8 @@ class ClashResult(TypedDict):
p1: list[float] p1: list[float]
p2: list[float] p2: list[float]
distance: float distance: float
# Added by `Clasher.smart_group_clashes`.
smart_group: NotRequired[int]
class ClashSet(TypedDict): class ClashSet(TypedDict):
@@ -473,7 +473,6 @@ class ShapeBuilder:
if arc_points and self.file.schema == "IFC2X3": if arc_points and self.file.schema == "IFC2X3":
raise Exception("Arcs are not supported for IFC2X3.") raise Exception("Arcs are not supported for IFC2X3.")
points: np.ndarray
points = np.array(points) points = np.array(points)
if position_offset is not None: if position_offset is not None:
points = points + position_offset points = points + position_offset
@@ -635,7 +634,6 @@ class ShapeBuilder:
diff = (0.01, 0.01) * diff_sign diff = (0.01, 0.01) * diff_sign
middle_point = points[0] + diff middle_point = points[0] + diff
points: list[VectorType]
points = [points[0], middle_point, points[1]] points = [points[0], middle_point, points[1]]
points = [ifc_safe_vector_type(p) for p in points] points = [ifc_safe_vector_type(p) for p in points]
seg = self.file.createIfcArcIndex((1, 2, 3)) seg = self.file.createIfcArcIndex((1, 2, 3))
+3 -6
View File
@@ -184,7 +184,7 @@ class PatcherDoc(TypedDict):
class InputDoc(TypedDict): class InputDoc(TypedDict):
name: str name: str
description: str description: str
type: Union[str, list[str]] type: NotRequired[Union[str, list[str]]]
default: NotRequired[Any] default: NotRequired[Any]
generic_type: NotRequired[str] generic_type: NotRequired[str]
enum_items: NotRequired[list[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(): for name, parameter in signature.parameters.items():
if name == "self" or name in boilerplate_args: if name == "self" or name in boilerplate_args:
continue continue
input_doc: InputDoc = {"name": name} input_doc: InputDoc = {"name": name, "description": "Undocumented"}
inputs[name] = input_doc inputs[name] = input_doc
if isinstance(parameter.default, (str, float, int, bool)): if isinstance(parameter.default, (str, float, int, bool)):
input_doc["default"] = parameter.default 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}'." assert is_valid_filter_glob(filter_glob), f"Invalid filter_glob pattern: '{filter_glob}'."
inputs[param_name]["filter_glob"] = 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( docs = PatcherDoc(
class_=cls, class_=cls,
description=doc_description, description=doc_description,
@@ -309,6 +305,7 @@ def parse_docstring(docstring: str) -> DocstringData:
line = line[1:] line = line[1:]
if line.startswith(PREFIXES): if line.startswith(PREFIXES):
prefix = line.split(" ")[0] prefix = line.split(" ")[0]
assert prefix in PREFIXES, f"Invalid line: '{line}'."
current_section = prefix current_section = prefix
match_ = re.match(rf"{prefix}\s+(\w+):\s+(.*)", line) match_ = re.match(rf"{prefix}\s+(\w+):\s+(.*)", line)
assert match_, f"Invalid line: '{line}'." assert match_, f"Invalid line: '{line}'."
+19 -3
View File
@@ -24,6 +24,7 @@ import os
import re import re
import sys import sys
from typing import Literal, Optional, TypedDict, Union from typing import Literal, Optional, TypedDict, Union
from typing_extensions import NotRequired
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
@@ -96,6 +97,12 @@ class ResultsSpecification(TypedDict):
applicability: list[str] applicability: list[str]
requirements: list[ResultsRequirement] 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): class ResultsRequirement(TypedDict):
facet_type: str facet_type: str
@@ -111,6 +118,15 @@ class ResultsRequirement(TypedDict):
total_fail: int total_fail: int
percent_pass: ResultsPercent 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 # use different syntax because of the "class" key
ResultsEntity = TypedDict( ResultsEntity = TypedDict(
@@ -244,7 +260,7 @@ class Txt(Console):
class Json(Reporter): class Json(Reporter):
def __init__(self, ids: Ids, hide_skipped=False): def __init__(self, ids: Ids, hide_skipped=False):
super().__init__(ids) super().__init__(ids)
self.results = Results() self.results = Results() # ty:ignore[missing-typed-dict-key]
self.results["hide_skipped"] = hide_skipped self.results["hide_skipped"] = hide_skipped
def report(self) -> Results: def report(self) -> Results:
@@ -410,7 +426,7 @@ class Json(Reporter):
"id": e.id(), "id": e.id(),
"global_id": getattr(e, "GlobalId", None), "global_id": getattr(e, "GlobalId", None),
"tag": getattr(e, "Tag", None), "tag": getattr(e, "Tag", None),
} } # ty:ignore[missing-typed-dict-key]
) )
for e in specification.applicable_entities for e in specification.applicable_entities
] ]
@@ -428,7 +444,7 @@ class Json(Reporter):
"id": e.id(), "id": e.id(),
"global_id": getattr(e, "GlobalId", None), "global_id": getattr(e, "GlobalId", None),
"tag": getattr(e, "Tag", None), "tag": getattr(e, "Tag", None),
} } # ty:ignore[missing-typed-dict-key]
) )
for e in requirement.passed_entities for e in requirement.passed_entities
] ]
+11 -1
View File
@@ -15,7 +15,17 @@ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", "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] [project.optional-dependencies]
advanced = [ advanced = [