From b520fb66bd0629fd88eeb3eb0bad36e0071d9737 Mon Sep 17 00:00:00 2001 From: Jean Henry Date: Thu, 10 Jul 2025 16:35:02 +0200 Subject: [PATCH 1/8] fix: resolve linter errors --- src/ansys/scade/python_wrapper/gui.py | 19 +- .../scade/python_wrapper/kcg_data_parser.py | 27 +- src/ansys/scade/python_wrapper/kcgpython.py | 4 +- src/ansys/scade/python_wrapper/props.py | 22 +- src/ansys/scade/python_wrapper/pydata.py | 31 +- src/ansys/scade/python_wrapper/rd/c_gen.py | 2 +- src/ansys/scade/python_wrapper/rd/def_gen.py | 3 +- .../scade/python_wrapper/rd/python_gen.py | 19 +- .../scade/python_wrapper/swan_data_parser.py | 21 +- tests/conftest.py | 3 +- tests/test_int_cvt.py | 3 +- tests/test_int_types.py | 9 +- tools/Models/pydata.aird | 282 +++++++++++++----- tools/Models/pydata.ecore | 83 ++++-- 14 files changed, 384 insertions(+), 144 deletions(-) diff --git a/src/ansys/scade/python_wrapper/gui.py b/src/ansys/scade/python_wrapper/gui.py index 19c3ea0..090f648 100644 --- a/src/ansys/scade/python_wrapper/gui.py +++ b/src/ansys/scade/python_wrapper/gui.py @@ -22,7 +22,8 @@ """Provides the Ansys SCADE Python Wrapper's Settings page.""" -from typing import List +from collections.abc import Callable +from typing import Any, List, Tuple import scade from scade.model.project.stdproject import Configuration, Project @@ -88,24 +89,28 @@ class PageUtils: """Utilities for settings pages.""" def __init__(self): - scade.output('initialized controls\n') + # scade is a CPython module defined dynamically + scade.output('initialized controls\n') # type: ignore # self.controls = [] def add_edit(self, y: int, text: str) -> EditBox: """Add an edit box.""" edit = LabelEditBox(self, text, wl1, x=xl1, y=y, w=wl1 + wf1) - self.controls.append(edit) + # self.controls is defined in sub-class + self.controls.append(edit) # type: ignore return edit def add_cb(self, y: int, text: str) -> CheckBox: """Add a check box.""" cb = CheckBoxEx(self, text, x=xl1, y=y, w=wl1 + wf1) - self.controls.append(cb) + # self.controls is defined in sub-class + self.controls.append(cb) # type: ignore return cb def layout_controls(self): """Layout the controls.""" - for control in self.controls: + # self.controls is defined in sub-class + for control in self.controls: # type: ignore control.on_layout() @@ -118,7 +123,7 @@ def __init__(self, *args): # runtime properties self.controls = [] # get, set, prop, prop_default - self.properties: List[callable, callable, str, str] = [] + self.properties: List[Tuple[Callable[[], Any], Callable[[Any], None], Any, Any]] = [] def on_layout(self): """Layout the page.""" @@ -186,7 +191,7 @@ def on_build(self): # remove the option, requirements to be refined # self.cb_pep8 = self.add_cb(y, '&Apply PEP8 naming rules'); y += dy - self.properties = [ + self.properties = [ # type: ignore # set/get functions have incorrect type annotations ( self.ed_module.get_name, self.ed_module.set_name, diff --git a/src/ansys/scade/python_wrapper/kcg_data_parser.py b/src/ansys/scade/python_wrapper/kcg_data_parser.py index 652984f..b6ad8bb 100644 --- a/src/ansys/scade/python_wrapper/kcg_data_parser.py +++ b/src/ansys/scade/python_wrapper/kcg_data_parser.py @@ -22,6 +22,8 @@ """Intermediate model for Python proxy for SCADE standalone DLL.""" +from typing import List, Optional, Tuple + import scade.code.suite.mapping.c as c import scade.code.suite.mapping.model as m @@ -42,7 +44,7 @@ def parse_from_kcg_mapping(mf) -> data.Model: return model -def _build_sensor(model: data.Model, m_sensor: m.Sensor) -> data.Feature: +def _build_sensor(model: data.Model, m_sensor: m.Sensor) -> Optional[data.Feature]: c_sensor = m_sensor.get_generated() c_type = c_sensor.get_type() if not c_sensor: @@ -58,13 +60,15 @@ def _build_sensor(model: data.Model, m_sensor: m.Sensor) -> data.Feature: return sensor -def _build_type(model: data.Model, c_type: c.Type): +def _build_type(model: data.Model, c_type: c.Type) -> Tuple[List[int], data.Type]: """Return a tuple list, .""" if not c_type: return [], None elif c_type.is_typedef(): + assert isinstance(c_type, c.TypeDef) # nosec B101 # addresses linter return _build_type(model, c_type.get_aliased_type()) elif c_type.is_array(): + assert isinstance(c_type, c.Array) # nosec B101 # addresses linter sizes, type_ = _build_type(model, c_type.get_base_type()) return sizes + [c_type.get_size()], type_ @@ -75,6 +79,7 @@ def _build_type(model: data.Model, c_type: c.Type): # extract model name from kcg_ type_ = data.Scalar(m_name=c_name.split('_')[-1], c_name=c_name) elif c_type.is_struct(): + assert isinstance(c_type, c.Struct) # nosec B101 # addresses linter type_ = data.Structure( m_name=c_type.get_model().get_name() if c_type.get_model() else '', c_name=c_name, @@ -109,6 +114,8 @@ def _build_type(model: data.Model, c_type: c.Type): model.add_type(type_) model.map_item(c_type, type_) + else: + assert isinstance(type_, data.Type) # nosec B101 # addresses linter return [], type_ @@ -123,6 +130,7 @@ def _build_operator(model: data.Model, m_op: m.Operator): # functions op.set_cycle(data.Function(c_name=c_op.get_cycle().get_name())) + assert op.cycle is not None # nosec B101 # addresses linter pointers = {_.get_name() for _ in c_op.get_cycle().get_parameters() if _.is_pointer()} if c_op.get_init(): op.set_init(data.Function(c_name=c_op.get_init().get_name())) @@ -133,8 +141,11 @@ def _build_operator(model: data.Model, m_op: m.Operator): # _add_c_type(model, c_op.get_state_vector()) _, type_ = _build_type(model, c_op.get_input_struct()) if type_: + # must be a structure + assert isinstance(type_, data.Structure) # nosec B101 # addresses linter # no names op.set_in_context(data.Context(kind=data.CK.INPUT)) + assert op.in_context is not None # nosec B101 # addresses linter op.in_context.link_type(type_) op.in_context.c_type = c_op.get_input_struct().get_name() op.in_context.pointer = True @@ -142,13 +153,19 @@ def _build_operator(model: data.Model, m_op: m.Operator): # _add_c_type(model, c_op.get_output_struct()) _, type_ = _build_type(model, c_op.get_context()) if type_: + # must be a structure + assert isinstance(type_, data.Structure) # nosec B101 # addresses linter # no names op.set_context(data.Context(kind=data.CK.CONTEXT)) + assert op.context is not None # nosec B101 # addresses linter op.context.link_type(type_) op.context.c_type = c_op.get_context().get_name() op.context.pointer = True # if assertions fail, remove shortcomings in the implementation assert len(c_op.get_init().get_parameters()) == 1 + # if there is a context, init and op functions must exist + assert op.init is not None # nosec B101 # addresses linter + assert op.reset is not None # nosec B101 # addresses linter op.init.add_parameter(op.context) assert len(c_op.get_reset().get_parameters()) == 1 op.reset.add_parameter(op.context) @@ -168,10 +185,9 @@ def _build_operator(model: data.Model, m_op: m.Operator): io.sizes, io.type = _build_type(model, c_type) op.add_io(io) if isinstance(c_input, c.Parameter): - assert op.cycle op.cycle.add_parameter(io) else: - assert op.in_context + assert op.in_context is not None # nosec B101 # addresses linter op.in_context.add_io(io) model.map_item(c_input, io) @@ -190,10 +206,9 @@ def _build_operator(model: data.Model, m_op: m.Operator): ) io.sizes, io.type = _build_type(model, c_type) if isinstance(c_output, c.Parameter): - assert op.cycle op.cycle.add_parameter(io) else: - assert op.context + assert op.context is not None # nosec B101 # addresses linter op.context.add_io(io) model.map_item(c_output, io) else: diff --git a/src/ansys/scade/python_wrapper/kcgpython.py b/src/ansys/scade/python_wrapper/kcgpython.py index 1e08753..4489198 100644 --- a/src/ansys/scade/python_wrapper/kcgpython.py +++ b/src/ansys/scade/python_wrapper/kcgpython.py @@ -28,7 +28,7 @@ from typing import Optional from scade.code.suite.mapping.c import MappingFile -import scade.code.suite.sctoc as sctoc +import scade.code.suite.sctoc as sctoc # type: ignore # CPython module defined dynamically from scade.code.suite.wrapgen.c import InterfacePrinter from scade.code.suite.wrapgen.model import MappingHelpers from scade.model.project.stdproject import Configuration, Project @@ -324,7 +324,7 @@ def _generate_cosim( f.write('_project = "%s"\n' % Path(project.pathname).as_posix()) f.write('_configuration = "Simulation"\n') # take the first root - assert wux2.mf + assert wux2.mf is not None # nosec B101 # addresses linter root = wux2.mf.get_root_operators()[0].get_scade_path().strip('/') f.write('_root = "%s"\n' % root) port = project.get_scalar_tool_prop_def('SSM', 'PROXYLISTENPORT', '64064', None) diff --git a/src/ansys/scade/python_wrapper/props.py b/src/ansys/scade/python_wrapper/props.py index a816ebe..290c8dd 100644 --- a/src/ansys/scade/python_wrapper/props.py +++ b/src/ansys/scade/python_wrapper/props.py @@ -22,7 +22,7 @@ """Defines the properties used for the settings.""" -from typing import List +from typing import List, Optional from scade.model.project.stdproject import Annotable, Configuration @@ -50,21 +50,21 @@ def get_tool_prop( - object: Annotable, name: str, default: List[str], configuration: Configuration = None + object: Annotable, name: str, default: List[str], configuration: Optional[Configuration] = None ) -> List[str]: """Get the values of a property for the current tool.""" return object.get_tool_prop_def(_TOOL, name, default, configuration) def get_scalar_tool_prop( - object: Annotable, name: str, default: str, configuration: Configuration = None + object: Annotable, name: str, default: str, configuration: Optional[Configuration] = None ) -> str: """Get the value of a scalar property for the current tool.""" return object.get_scalar_tool_prop_def(_TOOL, name, default, configuration) def get_bool_tool_prop( - object: Annotable, name: str, default: bool, configuration: Configuration = None + object: Annotable, name: str, default: bool, configuration: Optional[Configuration] = None ) -> bool: """Get the bool value of a property for the current tool.""" return object.get_bool_tool_prop_def(_TOOL, name, default, configuration) @@ -75,21 +75,29 @@ def set_tool_prop( name: str, values: List[str], default: List[str], - configuration: Configuration = None, + configuration: Optional[Configuration] = None, ): """Set the values of a property for the current tool.""" object.set_tool_prop_def(_TOOL, name, values, default, configuration) def set_scalar_tool_prop( - object: Annotable, name: str, value: str, default: str, configuration: Configuration = None + object: Annotable, + name: str, + value: str, + default: str, + configuration: Optional[Configuration] = None, ): """Get the scalar value of a property for the current tool.""" object.set_scalar_tool_prop_def(_TOOL, name, value, default, configuration) def set_bool_tool_prop( - object: Annotable, name: str, value: bool, default: bool, configuration: Configuration = None + object: Annotable, + name: str, + value: bool, + default: bool, + configuration: Optional[Configuration] = None, ): """Set the bool value of a property for the current tool.""" object.set_bool_tool_prop_def(_TOOL, name, value, default, configuration) diff --git a/src/ansys/scade/python_wrapper/pydata.py b/src/ansys/scade/python_wrapper/pydata.py index 15074db..e510c4f 100644 --- a/src/ansys/scade/python_wrapper/pydata.py +++ b/src/ansys/scade/python_wrapper/pydata.py @@ -34,7 +34,7 @@ #%% import from enum import Enum -from typing import List +from typing import List, Optional #%% types @@ -53,11 +53,11 @@ def __init__(self, c_name: str = '', m_name: str = '', path: str = '', *args, ** self.m_name: str = m_name self.path: str = path #<>init @property - def owner(self) -> 'Entity': + def owner(self) -> 'Optional[Entity]': #<<6 return self._owner #>>6 @@ -81,7 +81,7 @@ def scalar(self) -> bool: class Typed(Entity): def __init__(self, c_type: str = '', *args, **kwargs): super().__init__(*args, **kwargs) - self.type: Type = None + self.type: Optional[Type] = None self.c_type: str = c_type #}}class @@ -94,7 +94,7 @@ def __init__(self, sizes: List[int] = [], *args, **kwargs): #< bool: - return self.type and self.type.scalar and not self.sizes + return self.type is not None and self.type.scalar and not self.sizes #>>cls #}}class @@ -105,7 +105,7 @@ def __init__(self, input: bool = False, return_: bool = False, pointer: bool = F super().__init__(*args, **kwargs) self.input: bool = input self.return_: bool = return_ - self.context: Context = None + self.context: Optional[Context] = None self.pointer: bool = pointer def set_context(self, context: 'Context'): @@ -126,7 +126,7 @@ class Structure(Type): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields: List[Feature] = [] - self.context: Context = None + self.context: Optional[Context] = None def add_field(self, field: 'Feature'): self.fields.append(field) @@ -149,7 +149,7 @@ def add_io(self, io: 'IO'): io.context = self #<>cls @@ -169,7 +169,7 @@ class Function(Entity): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.parameters: List[Typed] = [] - self.return_: Typed = None + self.return_: Optional[Typed] = None def add_parameter(self, parameter: 'Typed'): self.parameters.append(parameter) @@ -185,12 +185,12 @@ def link_return(self, typed: Typed): class Operator(Entity): def __init__(self, header: str = '', *args, **kwargs): super().__init__(*args, **kwargs) - self.in_context: Context = None - self.context: Context = None + self.in_context: Optional[Context] = None + self.context: Optional[Context] = None self.ios: List[IO] = [] - self.cycle: Function = None - self.init: Function = None - self.reset: Function = None + self.cycle: Optional[Function] = None + self.init: Optional[Function] = None + self.reset: Optional[Function] = None self.header: str = header def set_in_context(self, in_context: 'Context'): @@ -232,8 +232,9 @@ def set_reset(self, reset: 'Function'): #{{class(26) -class Model: +class Model(Entity): def __init__(self, prefix: str = '', elaboration: str = '', *args, **kwargs): + super().__init__(*args, **kwargs) self.types: List[Type] = [] self.operators: List[Operator] = [] self.sensors: List[Global] = [] diff --git a/src/ansys/scade/python_wrapper/rd/c_gen.py b/src/ansys/scade/python_wrapper/rd/c_gen.py index 8468949..be9520d 100644 --- a/src/ansys/scade/python_wrapper/rd/c_gen.py +++ b/src/ansys/scade/python_wrapper/rd/c_gen.py @@ -55,7 +55,7 @@ def generate_c(model: data.Model, c_pathname: Path, banner: str = '') -> None: f.write(' return 0;\n') else: # if there is a context, the operator must have a reset function - assert op.reset + assert op.reset is not None # nosec B101 # addresses linter if not op.reset.parameters: # global context: nothing to allocate if op.init is not None: diff --git a/src/ansys/scade/python_wrapper/rd/def_gen.py b/src/ansys/scade/python_wrapper/rd/def_gen.py index b95a4c1..dd253b3 100644 --- a/src/ansys/scade/python_wrapper/rd/def_gen.py +++ b/src/ansys/scade/python_wrapper/rd/def_gen.py @@ -23,6 +23,7 @@ """Produces the def file for declaring the exported functions of the DLL.""" from pathlib import Path +from typing import Optional import ansys.scade.python_wrapper.pydata as data @@ -30,7 +31,7 @@ def generate_def(model: data.Model, def_pathname: Path, cosim: bool, banner: str = '') -> None: """Generate the C definition file for the DLL.""" - def add_export(function: data.Function): + def add_export(function: Optional[data.Function]): nonlocal f, i if function: f.write('\t%s @ %d;\n' % (function.c_name, i)) diff --git a/src/ansys/scade/python_wrapper/rd/python_gen.py b/src/ansys/scade/python_wrapper/rd/python_gen.py index 2849f96..93f11bb 100644 --- a/src/ansys/scade/python_wrapper/rd/python_gen.py +++ b/src/ansys/scade/python_wrapper/rd/python_gen.py @@ -37,6 +37,7 @@ from collections import namedtuple from keyword import iskeyword from pathlib import Path +from typing import Optional import ansys.scade.python_wrapper.pydata as data import ansys.scade.python_wrapper.utils as utils @@ -82,7 +83,7 @@ _pep8 = None -def _get_predef_info(c_type_name: str, native: bool) -> PredefInfo: +def _get_predef_info(c_type_name: str, native: bool) -> Optional[PredefInfo]: if native: return predefs_native[c_type_name] if c_type_name in predefs_native else None else: @@ -108,10 +109,11 @@ def _get_python_type_name(type_: data.Type, native: bool, sizes=None) -> str: # must be a predefined type # TODO: what about imported scalar types? pi = _get_predef_info(type_.m_name, native) + assert pi is not None # nosec B101 # addresses linter name = pi.type_name else: # the type names are less visible: use the path to ensure a unique name - assert isinstance(type_, data.Structure) + assert isinstance(type_, data.Structure) # nosec B101 # addresses linter prefix = '' if type_.m_name else '_' if not native: prefix += 'C' @@ -206,8 +208,13 @@ def generate_python( def write_accessors(typed: data.Feature): # typed is either a sensor or an i/o type_ = typed.type + assert type_ is not None # nosec B101 # addresses linter # generate setters for sensors and inputs - setter = isinstance(typed, data.Global) or typed.input + if isinstance(typed, data.Global): + setter = True + else: + assert isinstance(typed, data.IO) # nosec B101 # addresses linter + setter = typed.input # TODO: no name for not scalar types if typed.scalar(): type_name = _get_python_type_name(type_, True) @@ -314,6 +321,7 @@ def write_accessors(typed: data.Feature): f.write('class _Sensors:\n') f.write(' def __init__(self):\n') for sensor in model.sensors: + assert sensor.type is not None # nosec B101 # addresses linter type_name = _get_python_type_name(sensor.type, False, sensor.sizes) f.write( ' %s = %s.in_dll(_lib, "%s")\n' @@ -403,6 +411,7 @@ def write_accessors(typed: data.Feature): f.write(' set_ssm_proxy(proxy)\n') f.write('\n') if op.in_context: + assert op.in_context.type is not None # nosec B101 # addresses linter f.write( ' %s = %s()\n' % (op.in_context.py_member, _get_python_type_name(op.in_context.type, False)) @@ -423,6 +432,7 @@ def write_accessors(typed: data.Feature): if op.reset: f.write(' self.reset_fct = _lib.%s\n' % (op.reset.c_name)) f.write(' self.reset_fct.restype = ctypes.c_void_p\n') + assert op.cycle is not None # nosec B101 # addresses linter f.write(' self.cycle_fct = _lib.%s\n' % (op.cycle.c_name)) f.write(' self.cycle_fct.argtypes = [\n') for parameter in op.cycle.parameters: @@ -430,7 +440,9 @@ def write_accessors(typed: data.Feature): # opaque pointer py_type = 'ctypes.c_void_p' else: + assert isinstance(parameter, data.IO) # nosec B101 # addresses linter sizes = None if isinstance(parameter, data.Context) else parameter.sizes + assert parameter.type is not None # nosec B101 # addresses linter py_type = _get_python_type_name(parameter.type, False, sizes) if parameter.pointer: py_type = 'ctypes.POINTER(%s)' % py_type @@ -450,6 +462,7 @@ def write_accessors(typed: data.Feature): index = 0 for io in op.ios: # if io.py_member != io.py_value: + assert io.type is not None # nosec B101 # addresses linter if not io.input and io.context: # use the offset py_type = _get_python_type_name(io.type, False, io.sizes) diff --git a/src/ansys/scade/python_wrapper/swan_data_parser.py b/src/ansys/scade/python_wrapper/swan_data_parser.py index c295462..9278798 100644 --- a/src/ansys/scade/python_wrapper/swan_data_parser.py +++ b/src/ansys/scade/python_wrapper/swan_data_parser.py @@ -26,6 +26,7 @@ import json from pathlib import Path import re +from typing import List, Optional, Tuple import ansys.scade.python_wrapper.pydata as data @@ -62,7 +63,7 @@ class EK(Enum): _c_id_j = {} -def _get_model(model: data.Model, c_id: int, role: str = None) -> data.Entity: +def _get_model(model: data.Model, c_id: int, role: str) -> data.Entity: m_id = _links.get((c_id, role), c_id) return model.get_mapped_entity(m_id) @@ -154,16 +155,19 @@ def _index_mapping(model: data.Model, j): def _build_typed(model, typed: data.Typed, id: int): - _, atts = _c_id_j.get(id) + _, atts = _c_id_j[id] typed.c_type = atts['name'] if isinstance(typed, data.Context): - typed.link_type(_build_type(model, id)[1]) - assert isinstance(typed.type, data.Structure) + _, type_ = _build_type(model, id) + # The context's type must be a structure + assert isinstance(type_, data.Structure) # nosec B101 # addresses linter + typed.link_type(type_) else: + assert isinstance(typed, data.Feature) # nosec B101 # addresses linter typed.sizes, typed.type = _build_type(model, id) -def _build_type(model: data.Model, id: int): +def _build_type(model: data.Model, id: int) -> Tuple[List[int], Optional[data.Type]]: """Return a tuple list, .""" if not id: return [], None @@ -177,6 +181,7 @@ def _build_type(model: data.Model, id: int): type_ = model.get_mapped_entity(id) if type_: # type already built + assert isinstance(type_, data.Type) # nosec B101 # addresses linter return [], type_ # model counterpart, if any... @@ -274,7 +279,7 @@ def _build_model(model: data.Model, j): model.map_item(output['id'], io) -def _build_sensor(model: data.Model, c_atts) -> data.Feature: +def _build_sensor(model: data.Model, c_atts) -> Optional[data.Feature]: m_decl = _m_id_j.get(c_atts['id']) if not m_decl: return None @@ -300,6 +305,7 @@ def _build_function(model: data.Model, file, atts): else: # not a root operator or unexpected role return + assert isinstance(op, data.Operator) # nosec B101 # addresses linter function = data.Function(c_name=atts['name']) # link the function to the operator @@ -331,6 +337,8 @@ def _build_function(model: data.Model, file, atts): if not typed: print('%s/%s: parameter not found' % (op.path, parameter['name'])) else: + # typed must be either an IO or a context + assert isinstance(typed, data.IO) or isinstance(typed, data.Context) # nosec B101 # addresses linter typed.c_name = parameter['name'] typed.pointer = parameter['pointer'] if not typed.type: @@ -351,6 +359,7 @@ def _build_function(model: data.Model, file, atts): if role == 'CycleMethod' and op.context: # bind the context to optional ios ios = {_.m_name: _ for _ in op.ios} + assert isinstance(op.context.type, data.Structure) # nosec B101 # addresses linter for field in op.context.type.fields: io = ios.get(field.m_name) if io: diff --git a/tests/conftest.py b/tests/conftest.py index 6fc5898..0803fbc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -84,7 +84,8 @@ def load_project(path: Path) -> std.Project: Note: Undocumented API. """ - project = scade.load_project(str(path)) + # scade is a CPython module defined dynamically + project = scade.load_project(str(path)) # type: ignore return project diff --git a/tests/test_int_cvt.py b/tests/test_int_cvt.py index 4191cf8..a2a9fab 100644 --- a/tests/test_int_cvt.py +++ b/tests/test_int_cvt.py @@ -80,7 +80,8 @@ def test_int_cvt(proxy_types): old_path = sys.path.copy() sys.path.append(str(proxy_types.parent)) try: - import types_ as t + # types_ has been produced by proxy_types + import types_ as t # type: ignore import_error = '' except BaseException as e: diff --git a/tests/test_int_types.py b/tests/test_int_types.py index bca97fb..cbec711 100644 --- a/tests/test_int_types.py +++ b/tests/test_int_types.py @@ -130,7 +130,8 @@ def test_int_kcg_types(proxy_kcg_types): old_path = sys.path.copy() sys.path.append(str(proxy_kcg_types.parent)) try: - import types_ as t + # types_ has been produced by proxy_kcg_types + import types_ as t # type: ignore import_error = '' except BaseException as e: @@ -156,7 +157,8 @@ def test_int_kcg_types_io(proxy_kcg_types_io): old_path = sys.path.copy() sys.path.append(str(proxy_kcg_types_io.parent)) try: - import types_io as t + # types_ has been produced by proxy_kcg_types_io + import types_io as t # type: ignore import_error = '' except BaseException as e: @@ -179,7 +181,8 @@ def test_int_swancg_types(proxy_swancg_types): old_path = sys.path.copy() sys.path.append(str(proxy_swancg_types.parent)) try: - import types_ as t + # types_ has been produced by proxy_swancg_types + import types_ as t # type: ignore import_error = '' except BaseException as e: diff --git a/tools/Models/pydata.aird b/tools/Models/pydata.aird index 33289f2..7ebb3f9 100644 --- a/tools/Models/pydata.aird +++ b/tools/Models/pydata.aird @@ -5,11 +5,11 @@ http://www.eclipse.org/emf/2002/Ecore - + - + @@ -17,7 +17,7 @@ - + @@ -44,16 +44,6 @@ - - - - bold - - - - - - italic @@ -594,14 +584,12 @@ - + - - - bold - - + + + @@ -799,12 +787,6 @@ - - - bold - - - @@ -1093,9 +1075,25 @@ + + + + + + + + + + + + + + + + - + KEEP_LOCATION @@ -1204,8 +1202,8 @@ - - bold + + @@ -1274,7 +1272,7 @@ - + KEEP_LOCATION @@ -1426,6 +1424,18 @@ + + + + + + + italic + + + + + @@ -1960,16 +1970,6 @@ - - - - bold - - - - - - @@ -2002,7 +2002,7 @@ - + @@ -2011,7 +2011,7 @@ - + @@ -2024,7 +2024,7 @@ - + @@ -2037,12 +2037,12 @@ - + - + @@ -2050,7 +2050,7 @@ - + @@ -2071,7 +2071,7 @@ - + @@ -2080,7 +2080,7 @@ - + @@ -2088,7 +2088,7 @@ - + @@ -2101,7 +2101,7 @@ - + @@ -2110,7 +2110,7 @@ - + @@ -2119,7 +2119,7 @@ - + @@ -2128,7 +2128,24 @@ - + + + + + + + + + + + + + + + + + + @@ -2160,7 +2177,7 @@ - + @@ -2499,9 +2516,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + KEEP_LOCATION @@ -2640,7 +2721,7 @@ - + KEEP_LOCATION @@ -2686,7 +2767,7 @@ - + KEEP_LOCATION @@ -2696,27 +2777,23 @@ - + + - + - - - labelSize - bold - - - labelSize - + + + @@ -2831,9 +2908,10 @@ - + + @@ -2984,7 +3062,7 @@ - + KEEP_LOCATION @@ -3034,6 +3112,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + italic + + + + + diff --git a/tools/Models/pydata.ecore b/tools/Models/pydata.ecore index fe1713b..db403cd 100644 --- a/tools/Models/pydata.ecore +++ b/tools/Models/pydata.ecore @@ -1,18 +1,36 @@ - + +
-
-
-
-
+ +
+ + + +
+ + + + +
+ + + + +
+ + +
- +
@@ -22,7 +40,11 @@
-
+ + +
+ + @@ -38,7 +60,8 @@
- +
@@ -63,7 +86,8 @@
- +
@@ -88,12 +112,14 @@
- +
- +
@@ -104,21 +130,24 @@ - +
- +
- +
- +
@@ -138,32 +167,38 @@
- +
- +
- +
- +
- +
- +
@@ -183,7 +218,8 @@
- +
@@ -198,7 +234,8 @@
- +
From 4316fef4404fd80b0f1cc83e4940bf5c57005b2e Mon Sep 17 00:00:00 2001 From: Jean Henry Date: Thu, 10 Jul 2025 16:35:35 +0200 Subject: [PATCH 2/8] fix: do not register or unregister if APPDATA is not defined --- src/ansys/scade/python_wrapper/register.py | 4 ++-- src/ansys/scade/python_wrapper/unregister.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ansys/scade/python_wrapper/register.py b/src/ansys/scade/python_wrapper/register.py index 8b5ad9d..c7292ad 100644 --- a/src/ansys/scade/python_wrapper/register.py +++ b/src/ansys/scade/python_wrapper/register.py @@ -38,12 +38,12 @@ from ansys.scade.python_wrapper import get_srg_name -_APPDATA = os.getenv('APPDATA') +# APPDATA must be defined +_APPDATA = os.environ['APPDATA'] def _register_srg_file(srg: Path, install: Path): # copy the srg file to Customize and patch it with the installation directory. - assert _APPDATA text = srg.open().read() text = text.replace('%TARGETDIR%', install.as_posix()) dst = Path(_APPDATA, 'SCADE', 'Customize', srg.name) diff --git a/src/ansys/scade/python_wrapper/unregister.py b/src/ansys/scade/python_wrapper/unregister.py index 46f8238..fdab3a2 100644 --- a/src/ansys/scade/python_wrapper/unregister.py +++ b/src/ansys/scade/python_wrapper/unregister.py @@ -38,12 +38,12 @@ from ansys.scade.python_wrapper import get_srg_name -_APPDATA = os.getenv('APPDATA') +# APPDATA must be defined +_APPDATA = os.environ['APPDATA'] def _unregister_srg_file(name: str): """Delete the srg file from Customize.""" - assert _APPDATA dst = Path(_APPDATA, 'SCADE', 'Customize', name) dst.unlink(missing_ok=True) From 59d413f0857546c293ec89f0249738685b5d82c0 Mon Sep 17 00:00:00 2001 From: Jean Henry Date: Thu, 10 Jul 2025 16:37:51 +0200 Subject: [PATCH 3/8] fix: check assertions --- .../scade/python_wrapper/kcg_data_parser.py | 11 +++-------- src/ansys/scade/python_wrapper/pydata.py | 1 - .../scade/python_wrapper/rd/python_gen.py | 4 ---- .../scade/python_wrapper/swan_data_parser.py | 19 ++++++++++--------- 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/ansys/scade/python_wrapper/kcg_data_parser.py b/src/ansys/scade/python_wrapper/kcg_data_parser.py index b6ad8bb..20f2408 100644 --- a/src/ansys/scade/python_wrapper/kcg_data_parser.py +++ b/src/ansys/scade/python_wrapper/kcg_data_parser.py @@ -93,7 +93,7 @@ def _build_type(model: data.Model, c_type: c.Type) -> Tuple[List[int], data.Type else: c_fields = [_ for _ in c_type.get_fields()] for c_field in c_fields: - assert c_field.get_model() + assert c_field.get_model() # nosec B101 # addresses linter c_field_type = c_field.get_type() field = data.Feature( m_name=c_field.get_model().get_name(), @@ -103,14 +103,13 @@ def _build_type(model: data.Model, c_type: c.Type) -> Tuple[List[int], data.Type field.sizes, field.type = _build_type(model, c_field_type) type_.add_field(field) model.map_item(c_field, field) - elif c_type.is_enum(): + else: + assert c_type.is_enum() # nosec B101 # addresses linter type_ = data.Scalar( m_name='int32', c_name='kcg_int32', path=c_type.get_model().get_scade_path(), ) - else: - assert False model.add_type(type_) model.map_item(c_type, type_) @@ -161,13 +160,10 @@ def _build_operator(model: data.Model, m_op: m.Operator): op.context.link_type(type_) op.context.c_type = c_op.get_context().get_name() op.context.pointer = True - # if assertions fail, remove shortcomings in the implementation - assert len(c_op.get_init().get_parameters()) == 1 # if there is a context, init and op functions must exist assert op.init is not None # nosec B101 # addresses linter assert op.reset is not None # nosec B101 # addresses linter op.init.add_parameter(op.context) - assert len(c_op.get_reset().get_parameters()) == 1 op.reset.add_parameter(op.context) for m_input in m_op.get_inputs(): @@ -229,7 +225,6 @@ def _build_operator(model: data.Model, m_op: m.Operator): # update the parameters of cycle w.r.t. the contexts if op.in_context: - assert not op.cycle.parameters op.cycle.add_parameter(op.in_context) if op.context: op.cycle.add_parameter(op.context) diff --git a/src/ansys/scade/python_wrapper/pydata.py b/src/ansys/scade/python_wrapper/pydata.py index e510c4f..3544c27 100644 --- a/src/ansys/scade/python_wrapper/pydata.py +++ b/src/ansys/scade/python_wrapper/pydata.py @@ -268,7 +268,6 @@ def get_mapped_entity(self, item: object) -> Entity: return self._mapping.get(item, None) def map_item(self, item: object, entity: Entity): - assert entity not in self._mapping self._mapping[item] = entity #>>cls #}}class diff --git a/src/ansys/scade/python_wrapper/rd/python_gen.py b/src/ansys/scade/python_wrapper/rd/python_gen.py index 93f11bb..1fa41a2 100644 --- a/src/ansys/scade/python_wrapper/rd/python_gen.py +++ b/src/ansys/scade/python_wrapper/rd/python_gen.py @@ -118,7 +118,6 @@ def _get_python_type_name(type_: data.Type, native: bool, sizes=None) -> str: if not native: prefix += 'C' if False and type_.m_name: - assert type_.path # the type names are less visible: use the path to ensure a unique name name = '_'.join(type_.path.strip('/').split('::')) # prefix = '' @@ -137,9 +136,6 @@ def _get_python_type_name(type_: data.Type, native: bool, sizes=None) -> str: def _get_python_typed_name(typed: data.Typed) -> str: - # if I'm not wrong, we should always have elements - # generated from the model, else remove the assertion - assert typed.m_name name = typed.m_name if typed.m_name else typed.c_name return utils.lower_name(name) if _pep8 else name diff --git a/src/ansys/scade/python_wrapper/swan_data_parser.py b/src/ansys/scade/python_wrapper/swan_data_parser.py index 9278798..ec3a6c9 100644 --- a/src/ansys/scade/python_wrapper/swan_data_parser.py +++ b/src/ansys/scade/python_wrapper/swan_data_parser.py @@ -189,7 +189,7 @@ def _build_type(model: data.Model, id: int) -> Tuple[List[int], Optional[data.Ty # the same ids w/o link, such as structs, and other have different ids # and links, like predefined operators m_type_decl = _get_model_decl(id) - assert not m_type_decl or m_type_decl[0] == c_ek + # assert not m_type_decl or m_type_decl[0] == c_ek m_atts = m_type_decl[1] if m_type_decl else {} @@ -214,7 +214,8 @@ def _build_type(model: data.Model, id: int) -> Tuple[List[int], Optional[data.Ty _build_typed(model, field, c_field_atts['type']) type_.add_field(field) model.map_item(c_field_atts['id'], field) - elif c_ek == EK.ENUM: + else: + assert c_ek == EK.ENUM # nosec B101 # last enumeration's values for types type_ = data.Scalar( # no name/path for enums m_name=m_atts.get('name', 'int32'), @@ -223,8 +224,6 @@ def _build_type(model: data.Model, id: int) -> Tuple[List[int], Optional[data.Ty c_name='swan_int32', path=m_atts.get('path'), ) - else: - assert False model.add_type(type_) model.map_item(id, type_) @@ -283,8 +282,10 @@ def _build_sensor(model: data.Model, c_atts) -> Optional[data.Feature]: m_decl = _m_id_j.get(c_atts['id']) if not m_decl: return None - m_ek, m_atts = m_decl - assert m_ek == EK.SENSOR + # avoid usage of assert and unreferenced local variables + # m_ek, m_atts = m_decl + # assert m_ek == EK.SENSOR + _, m_atts = m_decl sensor = data.Global( m_name=m_atts['path'].split('::')[-1], path=m_atts['path'], @@ -318,7 +319,7 @@ def _build_function(model: data.Model, file, atts): elif role == 'ResetMethod': op.set_reset(function) else: - assert role == 'InitMethod' + # assert role == 'InitMethod' op.set_init(function) for parameter in atts.get('parameters', []): @@ -350,10 +351,10 @@ def _build_function(model: data.Model, file, atts): # must be a single (scalar) output # and function must be the cycle function io = op.ios[-1] - assert not io.input + # assert not io.input io.return_ = True _build_typed(model, io, type_id) - assert function == op.cycle + # assert function == op.cycle function.link_return(io) if role == 'CycleMethod' and op.context: From b444c146467db02cbbdd5377afa1b84bf448e39f Mon Sep 17 00:00:00 2001 From: Jean Henry Date: Thu, 10 Jul 2025 16:38:19 +0200 Subject: [PATCH 4/8] chore: add a design note on remaining linter errors --- src/ansys/scade/python_wrapper/rd/python_gen.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ansys/scade/python_wrapper/rd/python_gen.py b/src/ansys/scade/python_wrapper/rd/python_gen.py index 1fa41a2..df775a7 100644 --- a/src/ansys/scade/python_wrapper/rd/python_gen.py +++ b/src/ansys/scade/python_wrapper/rd/python_gen.py @@ -22,6 +22,12 @@ """Provides a Python interface to the SCADE DLL.""" +# design note: linters complain about unknown attributes such as py_name, py_value, etc. +# these attributes are added dynamically to SCADE Python API entities at startup. +# there are two workarounds, not applied since they would introduce too much noise in this file: +# * add type: ignore comments +# * use setattr/getattr functions + # TODO: # * error (or warning multiple instances not supported) with global context # * rename when name of io is either a Python keyword or conflicts with one of: From e6341243240f55941ec029986b461382f0a2c260 Mon Sep 17 00:00:00 2001 From: Jean Henry Date: Thu, 10 Jul 2025 16:39:14 +0200 Subject: [PATCH 5/8] chore: verify the usage of subprocess --- src/ansys/scade/python_wrapper/swanpython.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ansys/scade/python_wrapper/swanpython.py b/src/ansys/scade/python_wrapper/swanpython.py index 8c0387a..f0da60d 100644 --- a/src/ansys/scade/python_wrapper/swanpython.py +++ b/src/ansys/scade/python_wrapper/swanpython.py @@ -31,7 +31,7 @@ import re from shutil import copy import string -import subprocess +import subprocess # nosec B404 # used to call Scade One and make command line tools from ansys.scade.python_wrapper.rd.c_gen import generate_c from ansys.scade.python_wrapper.rd.def_gen import generate_def @@ -183,7 +183,7 @@ def generate_code(self) -> bool: os.path.join(os.environ['S_ONE_HOME'], 'tools', 'swan_cg.exe'), str(self.cmdjson), ] - gencode = subprocess.run( + gencode = subprocess.run( # nosec B603 # inputs checked cmd, capture_output=True, text=True, @@ -273,7 +273,7 @@ def build(self): 'Makefile', 'MODULE={}'.format(self.module), ] - build = subprocess.run( + build = subprocess.run( # nosec B602 # inputs checked cmd, capture_output=True, text=True, From facba1136c9cd3465987e3b7988bfddfaa8bceb0 Mon Sep 17 00:00:00 2001 From: Jean Henry Date: Tue, 15 Jul 2025 11:32:34 +0200 Subject: [PATCH 6/8] fix: replace exec calls by getattr and setattr calls --- .../scade/python_wrapper/lib/sdyproxy.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ansys/scade/python_wrapper/lib/sdyproxy.py b/src/ansys/scade/python_wrapper/lib/sdyproxy.py index 101c28b..fd8325d 100644 --- a/src/ansys/scade/python_wrapper/lib/sdyproxy.py +++ b/src/ansys/scade/python_wrapper/lib/sdyproxy.py @@ -39,40 +39,40 @@ def __init__(self, lib, basename: str, layer_types: List[Tuple[str, SdyLayer]]): self._lib = lib # self._lib.py_load_sdy_dlls() + # predefined interface for name in ['init', 'draw', 'lockio', 'unlockio', 'cancelled']: - exec('self._{0} = self._lib.{1}__{0}'.format(name, basename)) - exec('self._{0}.argtypes = []'.format(name)) - exec('self._{0}.restype = ctypes.c_int'.format(name)) + layer_fct = getattr(self._lib, f'{basename}__{name}') + layer_fct.argtypes = [] + layer_fct.restype = ctypes.c_int + setattr(self, f'_{name}', layer_fct) self.init() + # layer functions for layer_name, layer_type in layer_types: - layer_fct = eval('self._lib.{0}_L_{1}'.format(basename, layer_name)) + layer_fct = getattr(self._lib, f'{basename}_L_{layer_name}') layer_fct.argtypes = [] layer_fct.restype = ctypes.c_void_p - exec( - 'self.{0} = layer_type.from_address(layer_fct())'.format( - layer_name, - ) - ) + setattr(self, f'{layer_name}', layer_type.from_address(layer_fct())) def init(self) -> int: """Call DLL's ``init`` function.""" - return self._init() + return self._init() # type: ignore # method added dynamically + # return self._init() def draw(self) -> int: """Call DLL's ``draw`` function.""" - return self._draw() + return self._draw() # type: ignore # method added dynamically def lockio(self) -> int: """Call DLL's ``lockio`` function.""" - return self._lockio() + return self._lockio() # type: ignore # method added dynamically def unlockio(self) -> int: """Call DLL's ``unlockio`` function.""" - return self._unlockio() + return self._unlockio() # type: ignore # method added dynamically def cancelled(self) -> bool: """Call DLL's ``cancelled`` function.""" - return self._cancelled() != 0 + return self._cancelled() != 0 # type: ignore # method added dynamically # def __del__(self): # self._lib.py_unload_sdy_dlls() From 4734cf3d61ab4ea80d7252c66c15b5c74978b40d Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:49:44 +0000 Subject: [PATCH 7/8] chore: adding changelog file 37.miscellaneous.md [dependabot-skip] --- doc/changelog.d/37.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/37.miscellaneous.md diff --git a/doc/changelog.d/37.miscellaneous.md b/doc/changelog.d/37.miscellaneous.md new file mode 100644 index 0000000..80073ca --- /dev/null +++ b/doc/changelog.d/37.miscellaneous.md @@ -0,0 +1 @@ +Fix: enhance robustness \ No newline at end of file From 42cb7ce9e5eaec026920e45106e590f0b2c7095e Mon Sep 17 00:00:00 2001 From: Jean Henry Date: Tue, 15 Jul 2025 14:12:07 +0200 Subject: [PATCH 8/8] fix: fix regression --- src/ansys/scade/python_wrapper/rd/python_gen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/scade/python_wrapper/rd/python_gen.py b/src/ansys/scade/python_wrapper/rd/python_gen.py index df775a7..f81431d 100644 --- a/src/ansys/scade/python_wrapper/rd/python_gen.py +++ b/src/ansys/scade/python_wrapper/rd/python_gen.py @@ -438,11 +438,11 @@ def write_accessors(typed: data.Feature): f.write(' self.cycle_fct = _lib.%s\n' % (op.cycle.c_name)) f.write(' self.cycle_fct.argtypes = [\n') for parameter in op.cycle.parameters: + assert isinstance(parameter, (data.IO, data.Context)) # nosec B101 # addresses linter if isinstance(parameter, data.Context) and parameter.kind == data.CK.CONTEXT: # opaque pointer py_type = 'ctypes.c_void_p' else: - assert isinstance(parameter, data.IO) # nosec B101 # addresses linter sizes = None if isinstance(parameter, data.Context) else parameter.sizes assert parameter.type is not None # nosec B101 # addresses linter py_type = _get_python_type_name(parameter.type, False, sizes)