From a16b8fa37672be2968cc2fdc5cacccbd60688f4f Mon Sep 17 00:00:00 2001 From: Andrej Date: Wed, 28 May 2025 12:29:32 +0500 Subject: [PATCH] Fill ifcopenshell_wrapper.pyi with symbols #6684 As described in #6744 partial stub wrapper is completely replacing the type information from the wrapper, so having partial stub is making things worse than they were before. This commit is adding all available symbols from the wrapper to the stub and allows to gradually include type information to the stub. It also adds a safe net to ensure stub and wrapper won't go out of sync. --- pyproject.toml | 2 +- .../ifcopenshell/ifcopenshell_wrapper.pyi | 1286 ++++++++++++++++- .../util/scripts/validate_stub.py | 235 +++ .../test/util/scripts/test_validate_stub.py | 25 + 4 files changed, 1543 insertions(+), 5 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/util/scripts/validate_stub.py create mode 100644 src/ifcopenshell-python/test/util/scripts/test_validate_stub.py diff --git a/pyproject.toml b/pyproject.toml index ea92b0ad6c..1e0121dedd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ include = ''' |ifcopenshell-python |ifcsverchok |opencdeserver - )/.*.py$ + )/.*.pyi?$ ''' extend-exclude = ''' src/ifcopenshell-python/ifcopenshell/express/* diff --git a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi index 85cb0171f2..a9de6b7bb4 100644 --- a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi +++ b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi @@ -1,10 +1,28 @@ -# ifcopenshell_wrapper.pyi +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Thomas Krijnen +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . -class FileDescription: +from typing import Any + +class FileDescription(HeaderEntity): description: tuple[str, ...] implementation_level: str -class FileName: +class FileName(HeaderEntity): name: str time_stamp: str author: tuple[str, ...] @@ -14,8 +32,1268 @@ class FileName: authorization: str class IfcSpfHeader: + def file(self, *args): ... @property def file_description(self) -> FileDescription: ... - @property def file_name(self) -> FileName: ... + @property + def file_schema(self): ... + def read(self): ... + def tryRead(self): ... + def write(self, out): ... + +class BRep(Representation): + def as_compound(self, force_meters): ... + def begin(self): ... + def calculate_projected_surface_area(self, ax, along_x, along_y, along_z): ... + def calculate_surface_area(self, arg2): ... + def calculate_volume(self, arg2): ... + def end(self): ... + def item(self, i): ... + def item_id(self, i): ... + def shapes(self): ... + def size(self): ... + +class BRepElement(Element): + def calculate_projected_surface_area(self, along_x, along_y, along_z): ... + @property + def geometry(self): ... + def geometry_pointer(self): ... + @property + def surface_area(self): ... + @property + def volume(self): ... + +class CacheShapes: + name: Any + description: Any + defaultvalue: Any + +class CgalEmitOriginalEdges: + name: Any + description: Any + defaultvalue: Any + +class ColladaSerializer(WriteOnlyGeometrySerializer): + def finalize(self): ... + def isTesselated(self): ... + def object_id(self, o): ... + def ready(self): ... + def setFile(self, arg2): ... + def setUnitNameAndMagnitude(self, name, magnitude): ... + def write(self, *args): ... + def writeHeader(self): ... + +class ComputeCurvature: + name: Any + description: Any + defaultvalue: Any + +class ConversionResult: + def ItemId(self): ... + def Placement(self): ... + def Shape(self): ... + def Style(self): ... + def StylePtr(self): ... + def append(self, trsf): ... + def hasStyle(self): ... + def prepend(self, trsf): ... + def setStyle(self, newStyle): ... + +class ConversionResultShape: + def Serialize(self, place, arg3): ... + def Triangulate(self, *args): ... + def add(self, arg2): ... + def area(self): ... + def axis(self): ... + def bounding_box(self, *args): ... + def box(self): ... + def convex_decomposition(self): ... + def convex_tag(self, b): ... + def edges(self): ... + def facets(self): ... + def halfspaces(self): ... + def intersect(self, arg2): ... + def is_manifold(self): ... + def length(self): ... + def map(self, *args): ... + def moved(self, arg2): ... + def num_edges(self): ... + def num_faces(self): ... + def num_vertices(self): ... + def plane_equation(self): ... + def position(self): ... + def serialize(self): ... + def serialize_obj(self): ... + def set_box(self, b): ... + def solid(self): ... + def solid_mt(self): ... + def subtract(self, arg2): ... + def surface_genus(self): ... + def vertices(self): ... + def volume(self): ... + +class DoubleArray3: + def back(self): ... + def begin(self): ... + def empty(self): ... + def end(self): ... + def fill(self, u): ... + def front(self): ... + def iterator(self): ... + def rbegin(self): ... + def rend(self): ... + def size(self): ... + def swap(self, v): ... + +class Element: + def SetParents(self, newparents): ... + @property + def context(self): ... + @property + def guid(self): ... + @property + def id(self): ... + @property + def name(self): ... + @property + def parent_id(self): ... + def parents(self): ... + @property + def product(self): ... + @property + def transformation(self): ... + @property + def transformation_buffer(self): ... + @property + def type(self): ... + @property + def unique_id(self): ... + +class FileSchema(HeaderEntity): + schema_identifiers: Any + +class FunctionStepParam: + name: Any + description: Any + defaultvalue: Any + +class FunctionStepType: + name: Any + description: Any + defaultvalue: Any + +class GeometrySerializer: + READ_BREP: Any + READ_TRIANGULATION: Any + def geometry_settings(self, *args): ... + def isTesselated(self): ... + def object_id(self, o): ... + def read(self, *args): ... + def setUnitNameAndMagnitude(self, name, magnitude): ... + def settings(self, *args): ... + def write(self, *args): ... + +class HdfSerializer(GeometrySerializer): + def finalize(self): ... + def isTesselated(self): ... + def read(self, *args): ... + def ready(self): ... + def remove(self, guid): ... + def setFile(self, arg2): ... + def setUnitNameAndMagnitude(self, arg2, arg3): ... + def write(self, *args): ... + def writeHeader(self): ... + +class HeaderEntity: + def getArgument(self, index): ... + def getArgumentCount(self): ... + def toString(self, upper): ... + +class IfcBaseEntity(entity_instance): + def declaration(self): ... + def get(self, name): ... + def get_inverse(self, name): ... + def populate_derived(self): ... + def set_id(self, i): ... + +class IfcBaseType(entity_instance): + def declaration(self): ... + +class IfcEntityInstanceData: ... + +class IfcLateBoundEntity(IfcBaseEntity): + def declaration(self): ... + +class InstanceReference: ... + +class Iterator: + def bounds_max(self): ... + def bounds_min(self): ... + def compute_bounds(self, with_geometry): ... + def create(self): ... + def file(self): ... + def filters(self, *args): ... + def get(self): ... + def getLog(self): ... + def get_native(self): ... + def get_object(self, id): ... + def had_error_processing_elements(self): ... + def initialize(self): ... + def next(self): ... + def process_concurrently(self): ... + def process_finished_rep(self, rep): ... + def progress(self): ... + def set_cache(self, cache): ... + def unit_magnitude(self): ... + def unit_name(self): ... + +class OcctNoCleanTriangulation: + name: Any + description: Any + defaultvalue: Any + +class OpaqueCoordinate_3: + def get(self, i): ... + def set(self, i, n): ... + +class OpaqueCoordinate_4: + def get(self, i): ... + def set(self, i, n): ... + +class OpaqueNumber: + def clone(self): ... + def to_double(self): ... + def to_string(self): ... + +class PermissiveShapeReuse: + name: Any + description: Any + defaultvalue: Any + +class Representation: + def entity(self): ... + @property + def id(self): ... + def settings(self): ... + +class Serialization(Representation): + @property + def brep_data(self): ... + @property + def surface_style_ids(self): ... + @property + def surface_styles(self): ... + +class SerializedElement(Element): + @property + def geometry(self): ... + +class SerializerSettings: + def get_(self, name): ... + def get_type(self, name): ... + def set_(self, *args): ... + def setting_names(self): ... + +class Settings: + def get_(self, name): ... + def get_type(self, name): ... + def set_(self, *args): ... + def setting_names(self): ... + +class SvgSerializer (WriteOnlyGeometrySerializer): + SH_NONE: Any + SH_FULL: Any + SH_LEFT: Any + def addDrawing(self, pos, dir, ref, name, include_projection): ... + def addSizeComponent(self, fi): ... + def addTextAnnotations(self, k): ... + def addXCoordinate(self, fi): ... + def addYCoordinate(self, fi): ... + def doWriteHeader(self): ... + def finalize(self): ... + def getMirrorX(self): ... + def getMirrorY(self): ... + def getOnlyValid(self, b): ... + def getProfileThreshold(self): ... + def getSegmentProjection(self): ... + def getSubtractionSettings(self): ... + def getUnifyInputs(self): ... + def getUsePrefiltering(self): ... + def growBoundingBox(self, x, y): ... + def idElement(self, elem): ... + def isTesselated(self): ... + def nameElement(self, *args): ... + def object_id(self, storey, o): ... + def ready(self): ... + def resetScale(self): ... + def resize(self): ... + def setAlwaysProject(self, b): ... + def setAutoElevation(self, b): ... + def setAutoSection(self, b): ... + def setBoundingRectangle(self, width, height): ... + def setDrawDoorArcs(self, b): ... + def setDrawStoreyHeights(self, sh): ... + def setDrawingCenter(self, x, y): ... + def setElevationRef(self, s): ... + def setElevationRefGuid(self, s): ... + def setFile(self, f): ... + def setMirrorX(self, b): ... + def setMirrorY(self, b): ... + def setNoCSS(self, b): ... + def setOnlyValid(self, b): ... + def setPolygonal(self, b): ... + def setPrintSpaceAreas(self, b): ... + def setPrintSpaceNames(self, b): ... + def setProfileThreshold(self, i): ... + def setScale(self, s): ... + def setSectionHeight(self, h, storey): ... + def setSectionHeightsFromStoreys(self, offset): ... + def setSectionRef(self, s): ... + def setSegmentProjection(self, b): ... + def setSpaceNameTransform(self, v): ... + def setStoreyHeightLineLength(self, d): ... + def setSubtractionSettings(self, sbp): ... + def setUnifyInputs(self, b): ... + def setUnitNameAndMagnitude(self, arg2, arg3): ... + def setUseHlrPoly(self, b): ... + def setUseNamespace(self, b): ... + def setUsePrefiltering(self, b): ... + def setWithoutStoreys(self, b): ... + def start_path(self, *args): ... + def write(self, *args): ... + def writeHeader(self): ... + +class SwigPyIterator: + def advance(self, n): ... + def copy(self): ... + def decr(self, n): ... + def distance(self, x): ... + def equal(self, x): ... + def incr(self, n): ... + def next(self): ... + def previous(self): ... + def value(self): ... + +class Transformation: + def data(self): ... + @property + def matrix(self): ... + +class Triangulation(Representation): + def addEdge(self, item_id, style, i0, i1): ... + def addFace(self, *args): ... + def addNormal(self, X, Y, Z): ... + def addVertex(self, item_index, material_index, X, Y, Z): ... + @staticmethod + def box_project_uvs(vertices, normals): ... + @property + def colors_buffer(self): ... + @property + def edges(self): ... + @property + def edges_buffer(self): ... + def edges_item_ids(self): ... + @property + def edges_item_ids_buffer(self): ... + @staticmethod + def empty(settings): ... + @property + def faces(self): ... + @property + def faces_buffer(self): ... + @property + def faces_tri(self): ... + @property + def item_ids(self): ... + @property + def item_ids_buffer(self): ... + @property + def material_ids(self): ... + @property + def material_ids_buffer(self): ... + @property + def materials(self): ... + @property + def normals(self): ... + @property + def normals_buffer(self): ... + @property + def polyhedral_faces_with_holes(self): ... + @property + def polyhedral_faces_without_holes(self): ... + def registerEdge(self, item_id, i0, i1): ... + def registerEdgeCount(self, n1, n2, edgecount): ... + def resetWelds(self): ... + @property + def uvs(self): ... + def uvs_ref(self): ... + @property + def verts(self): ... + @property + def verts_buffer(self): ... + +class TriangulationElement(Element): + @property + def geometry(self): ... + def geometry_pointer(self): ... + +class TriangulationType: + name: Any + description: Any + defaultvalue: Any + +class TtlWktSerializer(WriteOnlyGeometrySerializer): + def finalize(self): ... + def isTesselated(self): ... + def ready(self): ... + def setFile(self, arg2): ... + def setUnitNameAndMagnitude(self, arg2, arg3): ... + def ttl_object_id(self, o, postfix): ... + def write(self, *args): ... + def writeHeader(self): ... + +class WaveFrontOBJSerializer(WriteOnlyGeometrySerializer): + def finalize(self): ... + def isTesselated(self): ... + def ready(self): ... + def setFile(self, arg2): ... + def setUnitNameAndMagnitude(self, arg2, arg3): ... + def write(self, *args): ... + def writeHeader(self): ... + def writeMaterial(self, style): ... + +class WriteOnlyGeometrySerializer(GeometrySerializer): + def read(self, *args): ... + +class XmlSerializer: + def finalize(self): ... + def ready(self): ... + def setFile(self, arg2): ... + def writeHeader(self): ... + +class XmlSerializerFactory: + @staticmethod + def implementations(): ... + +class abstract_arrangement: + def get_face_pairs(self): ... + def merge(self, edge_indices): ... + def num_edges(self): ... + def num_faces(self): ... + def write(self, polygons, progress): ... + +class aggregation_type(parameter_type): + array_type: Any + bag_type: Any + list_type: Any + set_type: Any + def as_aggregation_type(self): ... + def bound1(self): ... + def bound2(self): ... + def type_of_aggregation(self): ... + def type_of_aggregation_string(self): ... + def type_of_element(self): ... + +class attribute: + def name(self): ... + def optional(self): ... + def type_of_attribute(self): ... + +class attribute_value_derived: ... + +class boolean_result: + INTERSECTION: Any + SUBTRACTION: Any + UNION: Any + def calc_hash(self): ... + @property + def children(self): ... + def clone_(self): ... + def kind(self): ... + @staticmethod + def operation_str(op): ... + +class bspline_curve(curve): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + +class bspline_surface(surface): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + +class buffer: + def filename(self): ... + def get_value(self): ... + def is_ready(self): ... + +class cant_function(function_item): + def calc_hash(self): ... + def clone_(self): ... + def end(self): ... + def get_cant(self): ... + def get_gradient(self): ... + def kind(self): ... + def start(self): ... + +class circle(curve): + def calc_hash(self): ... + def clone_(self): ... + @staticmethod + def from_3_points(p1, p2, p3): ... + def kind(self): ... + @property + def matrix(self): ... + +class clash: ... + +class clashes: + def append(self, x): ... + def assign(self, n, x): ... + def back(self): ... + def begin(self): ... + def capacity(self): ... + def clear(self): ... + def empty(self): ... + def end(self): ... + def erase(self, *args): ... + def front(self): ... + def get_allocator(self): ... + def insert(self, *args): ... + def iterator(self): ... + def pop(self): ... + def pop_back(self): ... + def push_back(self, x): ... + def rbegin(self): ... + def rend(self): ... + def reserve(self, n): ... + def resize(self, *args): ... + def size(self): ... + def swap(self, v): ... + +class collection: + def calc_hash(self): ... + @property + def children(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + +class colour(item): + def b(self): ... + def calc_hash(self): ... + def clone_(self): ... + @property + def components(self): ... + def g(self): ... + def kind(self): ... + def r(self): ... + +class context: + def add(self, segments): ... + def build(self): ... + def get_face_pairs(self): ... + def merge(self, edge_indices): ... + def num_edges(self): ... + def num_faces(self): ... + def write(self, arg2): ... + +class curve(geom_item): + def print_impl(self, o, classname, indent): ... + +class cylinder(surface): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + +class declaration: + def as_entity(self): ... + def as_enumeration_type(self): ... + def as_select_type(self): ... + def as_type_declaration(self): ... + def index_in_schema(self): ... + def name(self): ... + def name_uc(self): ... + def schema(self): ... + def type(self): ... + +class direction3: + def calc_hash(self): ... + def clone_(self): ... + @property + def components(self): ... + def kind(self): ... + +class drawing_meta: ... + +class edge(trimmed_curve): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + +class ellipse(curve): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + +class entity(declaration): + def all_attributes(self): ... + def all_inverse_attributes(self): ... + def argument_types(self): ... + def as_entity(self): ... + def attribute_by_index(self, index): ... + def attribute_count(self): ... + def attribute_index(self, *args): ... + def attributes(self): ... + def derived(self): ... + def is_abstract(self): ... + def set_attributes(self, attributes, derived): ... + def set_inverse_attributes(self, inverse_attributes): ... + def set_subtypes(self, subtypes): ... + def subtypes(self): ... + def supertype(self): ... + +class entity_instance: + def data(self, *args): ... + def declaration(self): ... + def file_pointer(self): ... + def get_argument(self, *args): ... + def get_argument_index(self, a): ... + def get_argument_name(self, i): ... + def get_argument_type(self, i): ... + def get_attribute_category(self, name): ... + def get_attribute_names(self): ... + def get_inverse(self, a): ... + def get_inverse_attribute_names(self): ... + def id(self): ... + def identity(self): ... + def is_a(self, *args): ... + def setArgumentAsAggregateOfAggregateOfDouble(self, i, v): ... + def setArgumentAsAggregateOfAggregateOfEntityInstance(self, i, v): ... + def setArgumentAsAggregateOfAggregateOfInt(self, i, v): ... + def setArgumentAsAggregateOfDouble(self, i, v): ... + def setArgumentAsAggregateOfEntityInstance(self, i, v): ... + def setArgumentAsAggregateOfInt(self, i, v): ... + def setArgumentAsAggregateOfString(self, i, v): ... + def setArgumentAsBool(self, i, v): ... + def setArgumentAsDouble(self, i, v): ... + def setArgumentAsEntityInstance(self, i, v): ... + def setArgumentAsInt(self, i, v): ... + def setArgumentAsLogical(self, i, v): ... + def setArgumentAsNull(self, i): ... + def setArgumentAsString(self, i, a): ... + def toString(self, arg2, upper): ... + def to_string(self, valid_spf): ... + def unset_attribute_value(self, i): ... + +class enumeration_type(declaration): + def argument_types(self): ... + def as_enumeration_type(self): ... + def enumeration_items(self): ... + def lookup_enum_offset(self, string): ... + def lookup_enum_value(self, i): ... + +class equal_functor: ... + +class extrusion(sweep): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + +class face: + def calc_hash(self): ... + @property + def children(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + def print_impl(self, o, indent): ... + +class file: + INSTANCE_ID: Any + INSTANCE_TYPE: Any + ATTRIBUTE_INDEX: Any + def FreshId(self): ... + def add(self, entity, id): ... + def addEntities(self, entities): ... + def batch(self): ... + def build_inverses(self): ... + def by_guid(self, guid): ... + def by_id(self, id): ... + def by_type(self, *args): ... + def by_type_excl_subtypes(self, *args): ... + @staticmethod + def createTimestamp(): ... + def entity_names(self): ... + def file_pointer(self): ... + def getInverse(self, instance_id, type, attribute_index): ... + def getMaxId(self): ... + def getTotalInverses(self, instance_id): ... + def getUnit(self, unit_type): ... + def get_inverse(self, e): ... + def get_inverse_indices(self, *args): ... + def get_total_inverses(self, e): ... + def good(self): ... + @staticmethod + def guid_map(*args): ... + @property + def header(self): ... + def ifcroot_type(self): ... + def instance_by_guid(self, guid): ... + def instances_by_reference(self, id): ... + def internal_guid_map(self): ... + def load(self, entity_instance_name, entity, arg4, attribute_index): ... + def recalculate_id_counter(self): ... + def remove(self, entity): ... + @property + def schema(self): ... + def to_string(self): ... + @staticmethod + def traverse(instance, max_level): ... + @staticmethod + def traverse_breadth_first(instance, max_level): ... + def try_read_semicolon(self): ... + def types(self): ... + def types_begin(self): ... + def types_end(self): ... + def unbatch(self): ... + def write(self, fn): ... + +class file_open_status: + SUCCESS: Any + READ_ERROR: Any + NO_HEADER: Any + UNSUPPORTED_SCHEMA: Any + INVALID_SYNTAX: Any + def value(self): ... + +class fn_evaluator: + def clone(self): ... + def end(self): ... + def evaluate(self, u): ... + def length(self): ... + def start(self): ... + +class function_item(implicit_item): + def calc_hash(self): ... + def end(self): ... + def kind(self): ... + def length(self): ... + def start(self): ... + +class function_item_evaluator: + def evaluate(self, *args): ... + def evaluation_points(self, *args): ... + +class functor_item(function_item): + def calc_hash(self): ... + def clone_(self): ... + def end(self): ... + def kind(self): ... + def start(self): ... + +class geom_item(item): ... + +class geometry_exception: + def what(self): ... + +class gradient_function(function_item): + def calc_hash(self): ... + def clone_(self): ... + def end(self): ... + def get_horizontal(self): ... + def get_vertical(self): ... + def kind(self): ... + def start(self): ... + +class hash_functor: ... +class horizontal_plan_at_element: ... +class implicit_item(geom_item): ... + +class inverse_attribute: + bag_type: Any + set_type: Any + unspecified_type: Any + def attribute_reference(self): ... + def bound1(self): ... + def bound2(self): ... + def entity_reference(self): ... + def name(self): ... + def type_of_aggregation(self): ... + def type_of_aggregation_string(self): ... + +class item: + def calc_hash(self): ... + def clone_(self): ... + def hash(self): ... + def identity(self): ... + def kind(self): ... + def reverse(self): ... + +class less_functor: ... + +class line(curve): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + +class line_segment: + def back(self): ... + def begin(self): ... + def empty(self): ... + def end(self): ... + def fill(self, u): ... + def front(self): ... + def iterator(self): ... + def rbegin(self): ... + def rend(self): ... + def size(self): ... + def swap(self, v): ... + +class loft: + def calc_hash(self): ... + @property + def children(self): ... + def clone_(self): ... + def kind(self): ... + def print_impl(self, o, indent): ... + +class loop: + def calc_hash(self): ... + def calculate_linear_edge_curves(self): ... + @property + def children(self): ... + def clone_(self): ... + def is_polyhedron(self): ... + def kind(self): ... + def remove_linear_edge_curves(self): ... + +class matrix4(item): + IDENTITY: Any + AFFINE_WO_SCALE: Any + AFFINE_W_UNIFORM_SCALE: Any + AFFINE_W_NONUNIFORM_SCALE: Any + OTHER: Any + def calc_hash(self): ... + def clone_(self): ... + @property + def components(self): ... + def is_identity(self): ... + def kind(self): ... + def translation_part(self): ... + +class named_type(parameter_type): + def as_named_type(self): ... + def declared_type(self): ... + +class node(item): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + +class offset_curve(curve): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + +class offset_function(function_item): + def calc_hash(self): ... + def clone_(self): ... + def end(self): ... + def get_basis(self): ... + def get_offset(self): ... + def kind(self): ... + def start(self): ... + +class parameter_type: + def as_aggregation_type(self): ... + def as_named_type(self): ... + def as_simple_type(self): ... + +class piecewise_function(function_item): + def calc_hash(self): ... + def clone_(self): ... + def end(self): ... + def is_empty(self): ... + def kind(self): ... + def length(self): ... + def span_count(self): ... + def span_fn(self, i): ... + def spans(self): ... + def start(self): ... + +class plane(surface): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + +class point3: + def calc_hash(self): ... + def clone_(self): ... + @property + def components(self): ... + def kind(self): ... + +class polygon_2: ... +class ray_intersection_result: ... + +class ray_intersection_results: + def append(self, x): ... + def assign(self, n, x): ... + def back(self): ... + def begin(self): ... + def capacity(self): ... + def clear(self): ... + def empty(self): ... + def end(self): ... + def erase(self, *args): ... + def front(self): ... + def get_allocator(self): ... + def insert(self, *args): ... + def iterator(self): ... + def pop(self): ... + def pop_back(self): ... + def push_back(self, x): ... + def rbegin(self): ... + def rend(self): ... + def reserve(self, n): ... + def resize(self, *args): ... + def size(self): ... + def swap(self, v): ... + +class revolve(sweep): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + +class schema_definition: + def declaration_by_name(self, *args): ... + def declarations(self): ... + def entities(self): ... + def enumeration_types(self): ... + def instantiate(self, decl, data): ... + def name(self): ... + def select_types(self): ... + def type_declarations(self): ... + +class select_type(declaration): + def as_select_type(self): ... + def select_list(self): ... + +class shell: + def calc_hash(self): ... + @property + def children(self): ... + def clone_(self): ... + def kind(self): ... + def print_impl(self, o, indent): ... + +class simple_type(parameter_type): + binary_type: Any + boolean_type: Any + integer_type: Any + logical_type: Any + number_type: Any + real_type: Any + string_type: Any + datatype_COUNT: Any + def as_simple_type(self): ... + def declared_type(self): ... + +class solid: + def calc_hash(self): ... + @property + def children(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + +class sphere(surface): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + +class style(item): + def calc_hash(self): ... + def clone_(self): ... + def get_color(self): ... + def has_specularity(self): ... + def has_transparency(self): ... + def instance_id(self): ... + def kind(self): ... + +class surface(geom_item): ... + +class svg_groups_of_line_segments: + def append(self, x): ... + def assign(self, n, x): ... + def back(self): ... + def begin(self): ... + def capacity(self): ... + def clear(self): ... + def empty(self): ... + def end(self): ... + def erase(self, *args): ... + def front(self): ... + def get_allocator(self): ... + def insert(self, *args): ... + def iterator(self): ... + def pop(self): ... + def pop_back(self): ... + def push_back(self, x): ... + def rbegin(self): ... + def rend(self): ... + def reserve(self, n): ... + def resize(self, *args): ... + def size(self): ... + def swap(self, v): ... + +class svg_groups_of_polygons: + def append(self, x): ... + def assign(self, n, x): ... + def back(self): ... + def begin(self): ... + def capacity(self): ... + def clear(self): ... + def empty(self): ... + def end(self): ... + def erase(self, *args): ... + def front(self): ... + def get_allocator(self): ... + def insert(self, *args): ... + def iterator(self): ... + def pop(self): ... + def pop_back(self): ... + def push_back(self, x): ... + def rbegin(self): ... + def rend(self): ... + def reserve(self, n): ... + def resize(self, *args): ... + def size(self): ... + def swap(self, v): ... + +class svg_line_segments: + def append(self, x): ... + def assign(self, n, x): ... + def back(self): ... + def begin(self): ... + def capacity(self): ... + def clear(self): ... + def empty(self): ... + def end(self): ... + def erase(self, *args): ... + def front(self): ... + def get_allocator(self): ... + def insert(self, *args): ... + def iterator(self): ... + def pop(self): ... + def pop_back(self): ... + def push_back(self, x): ... + def rbegin(self): ... + def rend(self): ... + def reserve(self, n): ... + def resize(self, *args): ... + def size(self): ... + def swap(self, v): ... + +class svg_loop: + def append(self, x): ... + def assign(self, n, x): ... + def back(self): ... + def begin(self): ... + def capacity(self): ... + def clear(self): ... + def empty(self): ... + def end(self): ... + def erase(self, *args): ... + def front(self): ... + def get_allocator(self): ... + def insert(self, *args): ... + def iterator(self): ... + def pop(self): ... + def pop_back(self): ... + def push_back(self, x): ... + def rbegin(self): ... + def rend(self): ... + def reserve(self, n): ... + def resize(self, *args): ... + def size(self): ... + def swap(self, v): ... + +class svg_loops: + def append(self, x): ... + def assign(self, n, x): ... + def back(self): ... + def begin(self): ... + def capacity(self): ... + def clear(self): ... + def empty(self): ... + def end(self): ... + def erase(self, *args): ... + def front(self): ... + def get_allocator(self): ... + def insert(self, *args): ... + def iterator(self): ... + def pop(self): ... + def pop_back(self): ... + def push_back(self, x): ... + def rbegin(self): ... + def rend(self): ... + def reserve(self, n): ... + def resize(self, *args): ... + def size(self): ... + def swap(self, v): ... + +class svg_point: + def back(self): ... + def begin(self): ... + def empty(self): ... + def end(self): ... + def fill(self, u): ... + def front(self): ... + def iterator(self): ... + def rbegin(self): ... + def rend(self): ... + def size(self): ... + def swap(self, v): ... + +class svg_polygons: + def append(self, x): ... + def assign(self, n, x): ... + def back(self): ... + def begin(self): ... + def capacity(self): ... + def clear(self): ... + def empty(self): ... + def end(self): ... + def erase(self, *args): ... + def front(self): ... + def get_allocator(self): ... + def insert(self, *args): ... + def iterator(self): ... + def pop(self): ... + def pop_back(self): ... + def push_back(self, x): ... + def rbegin(self): ... + def rend(self): ... + def reserve(self, n): ... + def resize(self, *args): ... + def size(self): ... + def swap(self, v): ... + +class sweep(geom_item): ... + +class sweep_along_curve(sweep): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + +class too_many_faces_exception(geometry_exception): ... +class topology_error: ... + +class torus(surface): + def calc_hash(self): ... + def clone_(self): ... + def kind(self): ... + @property + def matrix(self): ... + +class tree: + def add_element(self, *args): ... + def add_file(self, *args): ... + def clash_clearance_many(self, set_a, set_b, clearance, check_all): ... + def clash_collision_many(self, set_a, set_b, allow_touching): ... + def clash_intersection_many(self, set_a, set_b, tolerance, check_all): ... + def distances(self): ... + def enable_face_styles(self, *args): ... + @staticmethod + def is_manifold(fs): ... + def protrusion_distances(self): ... + def select(self, *args): ... + def select_box(self, *args): ... + def select_ray(self, p0, d, length): ... + def styles(self): ... + def uint8_to_b64(self, uuids_array): ... + @staticmethod + def vector_to_list(ps): ... + def write_h5(self): ... + +class trimmed_curve(geom_item): + def reverse(self): ... + +class type_by_kind: + max: Any + +class type_declaration(declaration): + def argument_types(self): ... + def as_type_declaration(self): ... + def declared_type(self): ... + +def Triangulation_box_project_uvs(vertices, normals): ... +def Triangulation_empty(settings): ... +def XmlSerializerFactory_implementations(): ... +def arrange_polygons(polygons): ... +def boolean_result_operation_str(op): ... +def circle_from_3_points(p1, p2, p3): ... +def clear_schemas(): ... +def construct_iterator_with_include_exclude(geometry_library, settings, file, elems, include, num_threads): ... +def construct_iterator_with_include_exclude_globalid(geometry_library, settings, file, elems, include, num_threads): ... +def construct_iterator_with_include_exclude_id(geometry_library, settings, file, elems, include, num_threads): ... +def create_box(*args): ... +def create_epeck(*args): ... +def create_shape(*args): ... +def file_createTimestamp(): ... +def file_guid_map(*args): ... +def file_traverse(instance, max_level): ... +def file_traverse_breadth_first(instance, max_level): ... +def flatten(deep): ... +def get_feature(x): ... +def get_info_cpp(v, include_identifier): ... +def get_log(): ... +def helmert_curve_point(A0, A1, A2, s): ... +def kind_to_string(k): ... +def less(arg1, arg2): ... +def line_segments_to_polygons(s, eps, segments): ... +def map_shape(settings, instance): ... +def nary_union(sequence): ... +def new_IfcBaseClass(schema_identifier, name): ... +def open(fn): ... +def parse_ifcxml(filename): ... +def polygons_to_svg(*args): ... +def read(data): ... +def register_schema(arg1): ... +def schema_by_name(arg1): ... +def schema_names(): ... +def serialise(schema_name, shape_str, advanced): ... +def set_feature(x, v): ... +def set_log_format_json(): ... +def set_log_format_text(): ... +def svg_to_line_segments(data, class_name): ... +def svg_to_polygons(data, class_name): ... +def taxonomy_item_repr(i): ... +def tesselate(schema_name, shape_str, d): ... +def tree_is_manifold(fs): ... +def tree_vector_to_list(ps): ... +def turn_off_detailed_logging(): ... +def turn_on_detailed_logging(): ... +def version(): ... diff --git a/src/ifcopenshell-python/ifcopenshell/util/scripts/validate_stub.py b/src/ifcopenshell-python/ifcopenshell/util/scripts/validate_stub.py new file mode 100644 index 0000000000..3b90fc1c03 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/scripts/validate_stub.py @@ -0,0 +1,235 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Thomas Krijnen +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +"""Script to ensure ifcopenshell_wrapper.pyi and ifcopenshell_wrapper.py work in sync. + +Things we do check: +- all symbols from the wrapper present in the stub and vice versa +- functions and methods signatures +- read-only and settable properties, staticmethods +- class hierarchy +""" + + +import ast +import difflib +from pathlib import Path +from typing import Union +from typing_extensions import assert_never + + +def format_diff(lines: list[str]) -> None: + RED = "\033[91m" + GREEN = "\033[92m" + CYAN = "\033[96m" + RESET = "\033[0m" + for line in lines: + if line.startswith("+") and not line.startswith("+++"): + print(f"{GREEN}{line}{RESET}") + elif line.startswith("-") and not line.startswith("---"): + print(f"{RED}{line}{RESET}") + elif line.startswith("@@"): + print(f"{CYAN}{line}{RESET}") + else: + print(line) + + +SubnameType = Union[str, tuple[str, str]] + + +def get_function_node_name(node: ast.FunctionDef) -> Union[SubnameType, None]: + """ + :return: Function node name as ``SubnameType`` or ``None``, if function wasn't processed and can be skipped. + """ + node_name = node.name + if node_name.startswith("_"): + return None + args = [a.arg for a in node.args.args] + if node.args.vararg: + args.append("*args") + + node_name = f"def {node.name}" + node_name = f"{node_name}({', '.join(args)}): ..." + + decorators = tuple(f"@{d.id}" for d in node.decorator_list if isinstance(d, ast.Name)) + if len(decorators) == 1: + return decorators + (node_name,) + return node_name + + +def get_names_tree_lines(tree: ast.Module) -> list[str]: + # Get class tree. + names_tree: dict[str, set[SubnameType]] = {} + for node in tree.body: + subnames: set[SubnameType] = set() + node_name = None + if isinstance(node, ast.ClassDef): + # Skip `object_` as it's just a reference to `object`, + # which is implied by default. + bases = [b.id for b in node.bases if isinstance(b, ast.Name) and b.id != "_object"] + bases_str = f"({', '.join(bases)})" if bases else "" + node_name = f"class {node.name}{bases_str}:" + + for subnode in node.body: + subname = None + + if isinstance(subnode, ast.AnnAssign): + target = subnode.target + assert isinstance(target, ast.Name) + subname = target.id + + elif isinstance(subnode, ast.FunctionDef): + subname = get_function_node_name(subnode) + + elif isinstance(subnode, ast.Assign): + targets = subnode.targets + if not len(targets) == 1 or not isinstance(target := targets[0], ast.Name): + continue + subname_ = target.id + + if subname_.startswith("_"): + continue + + value = subnode.value + # Catching wrappers like: + # - `matrix = property(matrix_getter)` + # - `matrix = property(matrix_getter, matrix_setter)` + # - `operation_str = staticmethod(operation_str)` + if isinstance(value, ast.Call): + func = value.func + if not isinstance(func, ast.Name) or ((func_id := func.id) not in ("property", "staticmethod")): + continue + args = [arg.id for arg in value.args if isinstance(arg, ast.Name)] + len_args = len(args) + assert len_args in (1, 2) + + def find_method_by_name(name: str) -> Union[str, None]: + function_def = f"def {name}(" + return next( + ( + func_ + for func_ in subnames + if isinstance(func_, str) and func_.startswith(function_def) + ), + None, + ) + + # Use `set` for cases like `description = property(description, description)`. + wrapped_function = None + for arg in set(args): + assert (wrapped_function := find_method_by_name(arg)) + subnames.remove(wrapped_function) + + # TODO: sort it out in wrapper.py + # There's one annoying case in Element.product + # when property is overriding existing function, without using it. + # We should probably just exclude that function from the wrapper. + overridden_name = find_method_by_name(subname_) + if overridden_name: + subnames.remove(overridden_name) + + if len_args == 2: + # Has both getter and setter, can be defined as a simple attribute. + subname = subname_ + elif len_args == 1: + if func_id == "property": + # Has just getter, read-only, need to define it using a wrapper. + subname = (f"@{func_id}", f"def {subname_}(self): ...") + elif func_id == "staticmethod": + assert wrapped_function is not None + subname = (f"@{func_id}", f"def {subname_}({wrapped_function.split('(')[1]}") + else: + assert_never(func_id) + else: + assert_never(len_args) + else: + subname = subname_ + + if subname is not None: + subnames.add(subname) + if not subnames: + node_name += " ..." + + elif isinstance(node, ast.FunctionDef): + if node.name.startswith("_"): + continue + node_name = get_function_node_name(node) + assert isinstance(node_name, str) + + if node_name is not None: + names_tree[node_name] = subnames + + # Convert names tree to lines. + lines: list[str] = [] + indent = " " * 4 + + def subname_sort(subname: SubnameType) -> str: + if isinstance(subname, str): + if subname.startswith("def "): + return subname.split("(")[0].removeprefix("def ") + return subname + return subname_sort(subname[1]) + + for name, subnames in sorted(names_tree.items(), key=lambda x: x[0]): + lines.append(name) + for subname in sorted(subnames, key=subname_sort): + subitems = (subname,) if isinstance(subname, str) else subname + for item in subitems: + lines.append(f"{indent}{item}") + + return lines + + +def main() -> None: + package = Path(__file__).parent.parent.parent + stub_path = package / "ifcopenshell_wrapper.pyi" + wrapper_path = package / "ifcopenshell_wrapper.py" + + # Parse files + stub_tree = ast.parse(stub_path.read_text()) + wrapper_tree = ast.parse(wrapper_path.read_text()) + + # Extract class names + stub_classes = get_names_tree_lines(stub_tree) + wrapper_classes = get_names_tree_lines(wrapper_tree) + + # Use difflib to create a unified diff of class names + diff = difflib.unified_diff( + stub_classes, + wrapper_classes, + fromfile="stub.pyi classes", + tofile="wrapper.py classes", + lineterm="", + ) + diff = list(diff) + + format_diff(diff) + diff_no_header = diff[2:] + added = len([l for l in diff_no_header if l.startswith("+")]) + removed = len([l for l in diff_no_header if l.startswith("-")]) + + if added or removed: + print(f"Added lines: {added}") + print(f"Removed lines: {removed}") + raise Exception("Found discrepancies between stub and wrapper.") + else: + print(f"All good, no discrepancies between stub and wrapper. 🎉🎉") + + +if __name__ == "__main__": + main() diff --git a/src/ifcopenshell-python/test/util/scripts/test_validate_stub.py b/src/ifcopenshell-python/test/util/scripts/test_validate_stub.py new file mode 100644 index 0000000000..86d18bd356 --- /dev/null +++ b/src/ifcopenshell-python/test/util/scripts/test_validate_stub.py @@ -0,0 +1,25 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Thomas Krijnen +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import pytest +import ifcopenshell.util.scripts.validate_stub as subject + + +class TestValidateStub: + def test_run(self): + subject.main()