diff --git a/setup.py b/setup.py index 053e58d..b1e30c3 100644 --- a/setup.py +++ b/setup.py @@ -103,6 +103,7 @@ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', #'Programming Language :: Python :: 3 :: Only', ], @@ -142,7 +143,6 @@ # For an analysis of "install_requires" vs pip's requirements files see: # https://packaging.python.org/en/latest/requirements.html install_requires=[ # Optional - 'future', 'jsonschema', 'colorama', 'deepdiff', @@ -160,7 +160,7 @@ extras_require={ # Optional 'dist': ['build'], 'lint': ['pylint', 'flake8', 'mypy'], - 'test': ['pytest', 'coverage', 'pytest-cov', 'pytest-mock', 'attrs'], + 'test': ['pytest', 'coverage', 'pytest-cov', 'pytest-mock'], }, # If there are data files included in your packages that need to be diff --git a/src/objdictgen/__init__.py b/src/objdictgen/__init__.py index 96ad551..7714d1a 100644 --- a/src/objdictgen/__init__.py +++ b/src/objdictgen/__init__.py @@ -1,26 +1,27 @@ +"""Object Dictionary tool for Canfestival, a CanOpen stack.""" # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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. +# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA import os -from objdictgen.node import Node, ImportProfile, Find -from objdictgen.nodemanager import NodeManager from objdictgen.maps import OD +from objdictgen.node import Find, ImportProfile, Node +from objdictgen.nodemanager import NodeManager # Shortcuts LoadFile = Node.LoadFile diff --git a/src/objdictgen/__main__.py b/src/objdictgen/__main__.py index 7552823..db73f6b 100644 --- a/src/objdictgen/__main__.py +++ b/src/objdictgen/__main__.py @@ -1,30 +1,31 @@ +"""Main entry point for objdictgen""" # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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. +# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA -from __future__ import absolute_import -from pprint import pformat -import sys -import getopt import argparse import functools +import getopt import logging -import attr -from colorama import init, Fore, Style +import sys +from dataclasses import dataclass, field +from pprint import pformat + +from colorama import Fore, Style, init import objdictgen from objdictgen import jsonod @@ -38,10 +39,10 @@ log.addHandler(logging.StreamHandler(sys.stdout)) -@attr.s -class DebugOpts(object): +@dataclass +class DebugOpts: ''' Options for main to control the debug_wrapper ''' - show_debug = attr.ib(default=False) + show_debug: bool = field(default=False) def set_debug(self, dbg): self.show_debug = dbg @@ -129,10 +130,9 @@ def main(debugopts, args=None): # FIXME: New options: new file, add parameter, delete parameter, copy parameter - kw = dict(required=True) if sys.version_info[0] >= 3 else {} subparser = parser.add_subparsers(title="command", dest="command", metavar="command", help=''' Commands - ''', **kw) + ''', required=True) # -- COMMON -- @@ -149,10 +149,9 @@ def main(debugopts, args=None): subp.add_argument('-D', '--debug', **opt_debug) # -- CONVERT -- - kw = dict(aliases=['gen', 'conv']) if sys.version_info[0] >= 3 else {} subp = subparser.add_parser('convert', help=''' Generate - ''', **kw) + ''', aliases=['gen', 'conv']) subp.add_argument('od', **opt_od) subp.add_argument('out', default=None, help="Output file") subp.add_argument('-i', '--index', action="append", help="OD Index to include. Filter out the rest.") @@ -167,10 +166,9 @@ def main(debugopts, args=None): subp.add_argument('-D', '--debug', **opt_debug) # -- DIFF -- - kw = dict(aliases=['compare']) if sys.version_info[0] >= 3 else {} subp = subparser.add_parser('diff', help=''' Compare OD files - ''', **kw) + ''', aliases=['compare']) subp.add_argument('od1', **opt_od) subp.add_argument('od2', **opt_od) subp.add_argument('--internal', action="store_true", help="Diff internal object") @@ -279,9 +277,6 @@ def main(debugopts, args=None): # -- DIFF command -- elif opts.command in ("diff", "compare"): - if sys.version_info[0] < 3: - parser.error("diff does not work with python 2") - od1 = open_od(opts.od1, validate=not opts.novalidate) od2 = open_od(opts.od2, validate=not opts.novalidate) diff --git a/src/objdictgen/eds_utils.py b/src/objdictgen/eds_utils.py index 741d10e..84b056d 100644 --- a/src/objdictgen/eds_utils.py +++ b/src/objdictgen/eds_utils.py @@ -1,45 +1,30 @@ -# -*- coding: utf-8 -*- +"""Handler for EDS files, a standard file format in CANopen.""" # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import print_function -from __future__ import absolute_import -from builtins import range +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA import os import re -import sys from time import localtime, strftime -from past.builtins import long # type: ignore -from future.utils import raise_from import objdictgen from objdictgen.maps import OD -if sys.version_info[0] >= 3: - unicode = str # pylint: disable=invalid-name - INT_TYPES = int # pylint: disable=invalid-name -else: - INT_TYPES = (int, long) - # Regular expression for finding index section names RE_INDEX = re.compile(r'([0-9A-F]{1,4}$)') # Regular expression for finding subindex section names @@ -62,7 +47,7 @@ # Function for verifying data values is_integer = lambda x: isinstance(x, int) # noqa: E731 -is_string = lambda x: isinstance(x, (str, unicode)) # noqa: E731 +is_string = lambda x: isinstance(x, str) # noqa: E731 is_boolean = lambda x: x in (0, 1) # noqa: E731 # Define checking of value for each attribute @@ -188,7 +173,7 @@ def ParseCPJFile(filepath): try: computed_value = int(value, 16) except ValueError: - raise_from(ValueError("'%s' is not a valid value for attribute '%s' of section '[%s]'" % (value, keyname, section_name)), None) + raise ValueError("'%s' is not a valid value for attribute '%s' of section '[%s]'" % (value, keyname, section_name)) from None elif value.isdigit() or value.startswith("-") and value[1:].isdigit(): # Second case, value is a number and starts with "0" or "-0", then it's an octal value if value.startswith("0") or value.startswith("-0"): @@ -348,13 +333,13 @@ def ParseEDSFile(filepath): _ = int(value.upper().replace("$NODEID+", ""), 16) computed_value = '"%s"' % value except ValueError: - raise_from(ValueError("'%s' is not a valid formula for attribute '%s' of section '[%s]'" % (value, keyname, section_name)), None) + raise ValueError("'%s' is not a valid formula for attribute '%s' of section '[%s]'" % (value, keyname, section_name)) from None # Second case, value starts with "0x", then it's an hexadecimal value elif value.startswith("0x") or value.startswith("-0x"): try: computed_value = int(value, 16) except ValueError: - raise_from(ValueError("'%s' is not a valid value for attribute '%s' of section '[%s]'" % (value, keyname, section_name)), None) + raise ValueError("'%s' is not a valid value for attribute '%s' of section '[%s]'" % (value, keyname, section_name)) from None elif value.isdigit() or value.startswith("-") and value[1:].isdigit(): # Third case, value is a number and starts with "0", then it's an octal value if value.startswith("0") or value.startswith("-0"): @@ -428,10 +413,10 @@ def VerifyValue(values, section_name, param): values[uparam] = float(values[uparam]) elif values["DATATYPE"] == 0x01: values[uparam] = {0: False, 1: True}[values[uparam]] - elif not isinstance(values[uparam], INT_TYPES) and "$NODEID" not in values[uparam].upper(): - raise ValueError() + elif not isinstance(values[uparam], int) and "$NODEID" not in values[uparam].upper(): + raise ValueError() # FIXME: Should this get something more specific? except ValueError: - raise_from(ValueError("Error on section '[%s]': '%s' incompatible with DataType" % (section_name, param)), None) + raise ValueError("Error on section '[%s]': '%s' incompatible with DataType" % (section_name, param)) from None # Function that generate the EDS file content for the current node in the manager diff --git a/src/objdictgen/gen_cfile.py b/src/objdictgen/gen_cfile.py index 06a7721..96f7a8d 100644 --- a/src/objdictgen/gen_cfile.py +++ b/src/objdictgen/gen_cfile.py @@ -1,30 +1,25 @@ -# -*- coding: utf-8 -*- +"""Generate C file from a object dictionary node for canfestival.""" # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import absolute_import -from builtins import range +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA -import re import os +import re from objdictgen.maps import OD @@ -42,7 +37,7 @@ FILE_HEADER = """\n/* File generated by gen_cfile.py. Should not be modified. */\n""" -class CFileContext(object): +class CFileContext: def __init__(self): self.internal_types = {} self.default_string_size = 10 diff --git a/src/objdictgen/jsonod.py b/src/objdictgen/jsonod.py index 6b3589e..7cbb0a2 100644 --- a/src/objdictgen/jsonod.py +++ b/src/objdictgen/jsonod.py @@ -1,43 +1,35 @@ """ OD dict/json serialization and deserialization functions """ # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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. +# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA -from datetime import datetime -import sys +import json +import logging import os import re -from collections import OrderedDict -import logging -import json -import jsonschema +from datetime import datetime + import deepdiff +import jsonschema import objdictgen from objdictgen import maps from objdictgen.maps import OD -if sys.version_info[0] >= 3: - unicode = str # pylint: disable=invalid-name - long = int # pylint: disable=invalid-name - ODict = dict -else: - ODict = OrderedDict - log = logging.getLogger('objdictgen') @@ -188,23 +180,9 @@ def exc_amend(exc, text): return exc -def ordereddict_hook(pairs): - """ json convert helper for py2, where OrderedDict is used to preserve - dict order - """ - new_pairs = [] - for key, value in pairs: - if isinstance(value, unicode): - value = value.encode('utf-8') - if isinstance(key, unicode): - key = key.encode('utf-8') - new_pairs.append((key, value)) - return OrderedDict(new_pairs) - - def str_to_number(string): """ Convert string to a number, otherwise pass it through """ - if string is None or isinstance(string, (int, float, long)): + if string is None or isinstance(string, (int, float)): return string s = string.strip() if s.startswith('0x') or s.startswith('-0x'): @@ -216,16 +194,16 @@ def str_to_number(string): def copy_in_order(d, order): """ Remake dict d with keys in order """ - out = ODict( - (k, d[k]) + out = { + k: d[k] for k in order if k in d - ) - out.update(ODict( - (k, v) + } + out.update({ + k: v for k, v in d.items() if k not in out - )) + }) return out @@ -398,11 +376,8 @@ def GenerateNode(contents): # Remove jsonc annotations jsontext = remove_jasonc(contents) - # Load the json, with awareness on ordering in py2 - if sys.version_info[0] < 3: - jd = json.loads(jsontext, object_pairs_hook=ordereddict_hook) - else: - jd = json.loads(jsontext) + # Load the json + jd = json.loads(jsontext) # Remove any __ in the file jd = remove_underscore(jd) @@ -413,7 +388,7 @@ def GenerateNode(contents): # than the json validator. However the type checking of the json # validator is better. global SCHEMA # pylint: disable=global-statement - if not SCHEMA and sys.version_info[0] >= 3: + if not SCHEMA: with open(os.path.join(objdictgen.JSON_SCHEMA), 'r') as f: SCHEMA = json.loads(remove_jasonc(f.read())) @@ -924,13 +899,9 @@ def node_fromdict(jd, internal=False): diff = deepdiff.DeepDiff(baseobj, obj['built-in'], view='tree') if diff: - if sys.version_info[0] >= 3: - log.debug("Index 0x{0:04x} ({0}) Difference between built-in object and imported:".format(index)) - for line in diff.pretty().splitlines(): - log.debug(' ' + line) - else: - # FIXME: No print - print("WARNING: Py2 cannot print difference of objects") + log.debug("Index 0x{0:04x} ({0}) Difference between built-in object and imported:".format(index)) + for line in diff.pretty().splitlines(): + log.debug(' ' + line) raise ValidationError("Built-in parameter index 0x{0:04x} ({0}) does not match against system parameters".format(index)) # There is a weakness to the Node implementation: There is no store diff --git a/src/objdictgen/maps.py b/src/objdictgen/maps.py index 70c4e6f..60039f9 100644 --- a/src/objdictgen/maps.py +++ b/src/objdictgen/maps.py @@ -1,24 +1,22 @@ """ Object mappings """ # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA # # Dictionary of translation between access symbol and their signification diff --git a/src/objdictgen/node.py b/src/objdictgen/node.py index c5b2911..1a7aa0f 100644 --- a/src/objdictgen/node.py +++ b/src/objdictgen/node.py @@ -1,53 +1,34 @@ -# -*- coding: utf-8 -*- # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import print_function -from builtins import chr -from builtins import object -from builtins import range +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA -import os -import sys -import re +import ast import copy import logging -from collections import OrderedDict +import os +import re import traceback -from past.builtins import execfile -from future.utils import raise_from + import colorama -import ast import objdictgen -from objdictgen.nosis import pickle as nosis -from objdictgen import maps -from objdictgen.maps import OD, MAPPING_DICTIONARY -from objdictgen import jsonod, eds_utils, gen_cfile - -if sys.version_info[0] >= 3: - unicode = str # pylint: disable=invalid-name - ODict = dict -else: - ODict = OrderedDict +from objdictgen import eds_utils, gen_cfile, jsonod, maps, nosis +from objdictgen.maps import MAPPING_DICTIONARY, OD log = logging.getLogger('objdictgen') @@ -120,11 +101,11 @@ def EvaluateNode(node: ast.AST): ''' if isinstance(node, ast.BinOp): if isinstance(node.op, ast.Add): - return EvaluateNode(node.left) + EvaluateNode(node.right) + return EvaluateNode(node.left) + EvaluateNode(node.right) elif isinstance(node.op, ast.Sub): - return EvaluateNode(node.left) - EvaluateNode(node.right) + return EvaluateNode(node.left) - EvaluateNode(node.right) else: - raise SyntaxError("Unhandled arithmatic operation %s" % type(node.op)) + raise SyntaxError("Unhandled arithmatic operation %s" % type(node.op)) elif isinstance(node, ast.Constant): if isinstance(node.value, int | float | complex): return node.value @@ -134,8 +115,7 @@ def EvaluateNode(node: ast.AST): raise TypeError("Unhandled ast node class %s" % type(node)) else: raise TypeError("Invalid argument type %s" % type(node) ) - - + def GetIndexRange(index): for irange in maps.INDEX_RANGES: @@ -152,6 +132,11 @@ def BE_to_LE(value): @return: a string containing the value converted """ + # FIXME: This function is used in assosciation with DCF files, but have + # not been able to figure out how that work. It is very likely that this + # function is not working properly after the py2 -> py3 conversion + raise NotImplementedError("BE_to_LE() may be broken in py3") + # FIXME: The function title is confusing as the input data type (str) is # different than the output (int) return int("".join(["%2.2X" % ord(char) for char in reversed(value)]), 16) @@ -165,6 +150,12 @@ def LE_to_BE(value, size): @return: a string containing the value converted """ + # FIXME: This function is used in assosciation with DCF files, but have + # not been able to figure out how that work. It is very likely that this + # function is not working properly after the py2 -> py3 conversion due to + # the change of chr() behavior + raise NotImplementedError("LE_to_BE() is broken in py3") + # FIXME: The function title is confusing as the input data type (int) is # different than the output (str) data = ("%" + str(size * 2) + "." + str(size * 2) + "X") % value @@ -192,21 +183,22 @@ def ImportProfile(profilename): if os.path.exists(os.path.join(base, fname)) ) except StopIteration: - raise_from(ValueError("Unable to load profile '%s': '%s': No such file or directory" % (profilename, fname)), None) + raise ValueError("Unable to load profile '%s': '%s': No such file or directory" % (profilename, fname)) from None # Mapping and AddMenuEntries are expected to be defined by the execfile # The profiles requires some vars to be set # pylint: disable=unused-variable try: - log.debug("EXECFILE %s" % (profilepath,)) - execfile(profilepath) # FIXME: Using execfile is unsafe - # pylint: disable=undefined-variable - return Mapping, AddMenuEntries # pyright: ignore # noqa: F821 + with open(profilepath, "r") as f: + log.debug("EXECFILE %s" % (profilepath,)) + code = compile(f.read(), profilepath, 'exec') + exec(code, globals(), locals()) # FIXME: Using exec is unsafe + # pylint: disable=undefined-variable + return Mapping, AddMenuEntries # pyright: ignore # noqa: F821 except Exception as exc: # pylint: disable=broad-except log.debug("EXECFILE FAILED: %s" % exc) log.debug(traceback.format_exc()) - raise_from(ValueError("Loading profile '%s' failed: %s" % (profilepath, exc)), exc) - return None # To satisfy linter only + raise ValueError("Loading profile '%s' failed: %s" % (profilepath, exc)) from exc # ------------------------------------------------------------------------------ @@ -383,7 +375,7 @@ def Index(index, mappingdictionary): # Definition of Node Object # ------------------------------------------------------------------------------ -class Node(object): +class Node: """ Class recording the Object Dictionary entries. It checks at each modification that the structure of the Object Dictionary stay coherent @@ -397,12 +389,12 @@ def __init__(self, name="", type="slave", id=0, description="", profilename="DS- self.ID = id self.Description = description self.ProfileName = profilename - self.Profile = profile or ODict() + self.Profile = profile or {} self.SpecificMenu = specificmenu or [] - self.Dictionary = ODict() - self.ParamsDictionary = ODict() - self.DS302 = ODict() - self.UserMapping = ODict() + self.Dictionary = {} + self.ParamsDictionary = {} + self.DS302 = {} + self.UserMapping = {} self.IndexOrder = [] # -------------------------------------------------------------------------- @@ -821,13 +813,13 @@ def GetIndexes(self): return list(sorted(self.Dictionary)) def CompileValue(self, value, index, compute=True): - if isinstance(value, (str, unicode)) and '$NODEID' in value.upper(): + if isinstance(value, str) and '$NODEID' in value.upper(): # NOTE: Don't change base, as the eval() use this base = self.GetBaseIndexNumber(index) # noqa: F841 pylint: disable=unused-variable try: log.debug("EVAL CompileValue() #1: '%s'" % (value,)) raw = eval(value) # FIXME: Using eval is not safe - if compute and isinstance(raw, (str, unicode)): + if compute and isinstance(raw, str): raw = raw.upper().replace("$NODEID", "self.ID") log.debug("EVAL CompileValue() #2: '%s'" % (raw,)) return eval(raw) # FIXME: Using eval is not safe @@ -838,8 +830,7 @@ def CompileValue(self, value, index, compute=True): return raw except Exception as exc: # pylint: disable=broad-except log.debug("EVAL FAILED: %s" % exc) - raise_from(ValueError("CompileValue failed for '%s'" % (value,)), exc) - return 0 # FIXME: Why ignore this exception? + raise ValueError("CompileValue failed for '%s'" % (value,)) from exc else: return value @@ -1052,7 +1043,7 @@ def GetMapValue(self, mapname): return (index << 16) + (subindex << 8) + size * int(self.ParamsDictionary[index][subindex]["buffer_size"]) raise ValueError("String size too big to fit in a PDO") except KeyError: - raise_from(ValueError("No string length found and default string size too big to fit in a PDO"), None) + raise ValueError("No string length found and default string size too big to fit in a PDO") from None else: if self.IsStringType(self.UserMapping[index]["values"][subindex]["type"]): try: @@ -1060,7 +1051,7 @@ def GetMapValue(self, mapname): return (index << 16) + (subindex << 8) + size * int(self.ParamsDictionary[index][subindex]["buffer_size"]) raise ValueError("String size too big to fit in a PDO") except KeyError: - raise_from(ValueError("No string length found and default string size too big to fit in a PDO"), None) + raise ValueError("No string length found and default string size too big to fit in a PDO") from None return (index << 16) + (subindex << 8) + size return None diff --git a/src/objdictgen/nodelist.py b/src/objdictgen/nodelist.py index c1714cf..1c9d918 100644 --- a/src/objdictgen/nodelist.py +++ b/src/objdictgen/nodelist.py @@ -1,43 +1,34 @@ -# -*- coding: utf-8 -*- # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import print_function -from __future__ import absolute_import -from builtins import object +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA +import errno import os import shutil -import errno -from future.utils import raise_from from objdictgen import eds_utils - # ------------------------------------------------------------------------------ # Definition of NodeList Object # ------------------------------------------------------------------------------ -class NodeList(object): +class NodeList: """ Class recording a node list for a CANOpen network. """ @@ -148,7 +139,7 @@ def SaveMasterNode(self, netname=None): try: self.Manager.SaveCurrentInFile(masterpath) except Exception as exc: # pylint: disable=broad-except - raise_from(ValueError("Fail to save master node in '%s'" % (masterpath, )), exc) + raise ValueError("Fail to save master node in '%s'" % (masterpath, )) from exc def LoadSlaveNodes(self, netname=None): cpjpath = os.path.join(self.Root, "nodelist.cpj") @@ -170,7 +161,7 @@ def LoadSlaveNodes(self, netname=None): self.AddSlaveNode(node["Name"], nodeid, node["DCFName"]) self.Changed = False except Exception as exc: # pylint: disable=broad-except - raise_from(ValueError("Unable to load CPJ file '%s'" % (cpjpath, )), exc) + raise ValueError("Unable to load CPJ file '%s'" % (cpjpath, )) from exc def SaveNodeList(self, netname=None): cpjpath = '' # For linting @@ -185,7 +176,7 @@ def SaveNodeList(self, netname=None): f.write(content) self.Changed = False except Exception as exc: # pylint: disable=broad-except - raise_from(ValueError("Fail to save node list in '%s'" % (cpjpath)), exc) + raise ValueError("Fail to save node list in '%s'" % (cpjpath)) from exc def GetOrderNumber(self, nodeid): nodeindexes = list(sorted(self.SlaveNodes)) diff --git a/src/objdictgen/nodemanager.py b/src/objdictgen/nodemanager.py index 620c338..c3a9647 100644 --- a/src/objdictgen/nodemanager.py +++ b/src/objdictgen/nodemanager.py @@ -1,38 +1,32 @@ -# -*- coding: utf-8 -*- # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import absolute_import -from builtins import object -from builtins import range +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA -import os -import re import codecs import logging +import os +import re + import colorama -from objdictgen.node import Node, Find, ImportProfile, BE_to_LE, LE_to_BE from objdictgen import maps -from objdictgen.maps import OD, MAPPING_DICTIONARY +from objdictgen.maps import MAPPING_DICTIONARY, OD +from objdictgen.node import BE_to_LE, Find, ImportProfile, LE_to_BE, Node log = logging.getLogger('objdictgen') @@ -55,7 +49,7 @@ def GetNewId(): return CURRENTID -class UndoBuffer(object): +class UndoBuffer: """ Class implementing a buffer of changes made on the current editing Object Dictionary """ @@ -146,7 +140,7 @@ def IsCurrentSaved(self): return self.LastSave == self.CurrentIndex -class NodeManager(object): +class NodeManager: """ Class which control the operations made on the node and answer to view requests """ @@ -1050,7 +1044,7 @@ def GetNodeEntryValues(self, node, index): dic["value"] = fmt % dic["value"] except TypeError as exc: log.debug("ValueError: '%s': %s" % (dic["value"], exc)) - pass # FIXME: dict["value"] can contain $NODEID for PDOs which is not an int i.e. $NODEID+0x200 + # FIXME: dict["value"] can contain $NODEID for PDOs which is not an int i.e. $NODEID+0x200 editor["value"] = "string" if values[0] == "INTEGER": editor["value"] = "number" diff --git a/src/objdictgen/nosis/pickle.py b/src/objdictgen/nosis.py similarity index 79% rename from src/objdictgen/nosis/pickle.py rename to src/objdictgen/nosis.py index afeb88a..33a8245 100644 --- a/src/objdictgen/nosis/pickle.py +++ b/src/objdictgen/nosis.py @@ -1,52 +1,192 @@ -from __future__ import print_function -from __future__ import absolute_import -from builtins import object +"""Nosis - XML Pickling.""" +# This a stripped down version of legacy tool "gnosis", which is +# central to the "OD" format. This is basically a XML pickler for +# python objects. # The original tool was written for very old +# python and this is an updated extract of the original to be able +# to use it with python 3 +# +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): +# +# This library 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 2.1 of the License, or (at your option) any later version. +# +# This library 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 this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA +import logging +import re import sys from io import StringIO -from collections import OrderedDict from xml.dom import minidom -from past.builtins import long -from future.utils import raise_from -from .xtoy import ( - aton, ntoa, - unsafe_string, unsafe_content, - safe_string, safe_content, -) - -if sys.version_info[0] >= 3: - unicode = str # pylint: disable=invalid-name - ODict = dict -else: - ODict = OrderedDict +log = logging.getLogger('objdictgen.nosis') -class _EmptyClass(object): +class _EmptyClass: """ Do-nohting empty class """ TYPE_IN_BODY = { int: 0, - long: 0, float: 0, complex: 0, - str: 0, + str: 1, } -if sys.version_info[0] < 3: - # our unicode vs. "regular string" scheme relies on unicode - # strings only being in the body, so this is hardcoded. - TYPE_IN_BODY[unicode] = 1 -else: - # py3 doesn't have unicode, and unicode here is str. This implies that - # on py3 all strings will be added as body elements instead of tag value="..." - TYPE_IN_BODY[unicode] = 1 - - def getInBody(typename): return TYPE_IN_BODY.get(typename) or 0 +# pylint: disable=invalid-name +pat_fl = r'[-+]?(((((\d+)?[.]\d+|\d+[.])|\d+)[eE][+-]?\d+)|((\d+)?[.]\d+|\d+[.]))' +re_float = re.compile(pat_fl + r'$') +re_zero = r'[+-]?0$' +pat_int = r'[-+]?[1-9]\d*' +re_int = re.compile(pat_int + r'$') +pat_flint = r'(%s|%s)' % (pat_fl, pat_int) # float or int +re_long = re.compile(r'[-+]?\d+[lL]' + r'$') +re_hex = re.compile(r'([-+]?)(0[xX])([0-9a-fA-F]+)' + r'$') +re_oct = re.compile(r'([-+]?)(0)([0-7]+)' + r'$') +pat_complex = r'(%s)?[-+]%s[jJ]' % (pat_flint, pat_flint) +re_complex = re.compile(pat_complex + r'$') +pat_complex2 = r'(%s):(%s)' % (pat_flint, pat_flint) +re_complex2 = re.compile(pat_complex2 + r'$') + + +def aton(s): + # -- massage the string slightly + s = s.strip() + while s[0] == '(' and s[-1] == ')': # remove optional parens + s = s[1:-1] + + # -- test for cases + if re.match(re_zero, s): + return 0 + + if re.match(re_float, s): + return float(s) + + if re.match(re_long, s): + return int(s.rstrip('lL')) + + if re.match(re_int, s): + return int(s) + + m = re.match(re_hex, s) + if m: + n = int(m.group(3), 16) + if n < sys.maxsize: + n = int(n) + if m.group(1) == '-': + n = n * (-1) + return n + + m = re.match(re_oct, s) + if m: + n = int(m.group(3), 8) + if n < sys.maxsize: + n = int(n) + if m.group(1) == '-': + n = n * (-1) + return n + + if re.match(re_complex, s): + return complex(s) + + if re.match(re_complex2, s): + r, i = s.split(':') + return complex(float(r), float(i)) + + raise ValueError("Invalid string '%s' passed to to_number()'d" % s) + + +# we use ntoa() instead of repr() to ensure we have a known output format +def ntoa(num: int|float|complex) -> str: + """Convert a number to a string without calling repr()""" + if isinstance(num, int): + s = "%d" % num + elif isinstance(num, float): + s = "%.17g" % num + # ensure a '.', adding if needed (unless in scientific notation) + if '.' not in s and 'e' not in s: + s = s + '.' + elif isinstance(num, complex): + # these are always used as doubles, so it doesn't + # matter if the '.' shows up + s = "%.17g+%.17gj" % (num.real, num.imag) + else: + raise ValueError("Unknown numeric type: %s" % repr(num)) + return s + + +XML_QUOTES = ( + ('&', '&'), + ('<', '<'), + ('>', '>'), + ('"', '"'), + ("'", '''), +) + + +def safe_string(s): + # markup XML entities + s = s.replace('&', '&') + s = s.replace('<', '<') + s = s.replace('>', '>') + s = s.replace('"', '"') + s = s.replace("'", ''') + # for others, use Python style escapes + s = repr(s) + return s[1:-1] # without the extra single-quotes + + +def unsafe_string(s): + # for Python escapes, exec the string + # (niggle w/ literalizing apostrophe) + _s = s = s.replace("'", "\\x27") + # log.debug("EXEC in unsafe_string(): '%s'" % ("s='" + s + "'",)) + exec("s='" + s + "'") + if s != _s: + log.debug("EXEC in unsafe_string(): '%s' -> '%s'" % (_s, s)) + # XML entities (DOM does it for us) + return s + + +def safe_content(s): + """Markup XML entities and strings so they're XML & unicode-safe""" + s = s.replace('&', '&') + s = s.replace('<', '<') + s = s.replace('>', '>') + + return s # To be able to be used with py3 + + # # wrap "regular" python strings as unicode + # if isinstance(s, str): + # s = u"\xbb\xbb%s\xab\xab" % s + + # return s.encode('utf-8') + + +def unsafe_content(s): + """Take the string returned by safe_content() and recreate the + original string.""" + # don't have to "unescape" XML entities (parser does it for us) + + # # unwrap python strings from unicode wrapper + # if s[:2] == chr(187) * 2 and s[-2:] == chr(171) * 2: + # s = s[2:-2].encode('us-ascii') + + return s + # Maintain list of object identities for multiple and cyclical references # (also to keep temporary objects alive) @@ -72,7 +212,7 @@ def _save_obj_with_id(node, obj): def add_class_to_store(classname='', klass=None): - "Put the class in the store (as 'classname'), return CLASS_STORE" + """Put the class in the store (as 'classname'), return CLASS_STORE""" if classname and klass: CLASS_STORE[classname] = klass return CLASS_STORE @@ -102,7 +242,7 @@ def obj_from_node(node): def get_node_valuetext(node): - "Get text from node, whether in value=, or in element body." + """Get text from node, whether in value=, or in element body.""" # we know where the text is, based on whether there is # a value= attribute. ie. pickler can place it in either @@ -129,7 +269,7 @@ def get_node_valuetext(node): # (it's not based on UserList so that (a) we don't have to pull in UserList, # and (b) it will break if someone accesses StreamWriter in an unexpected way # rather than failing silently for some cases) -class StreamWriter(object): +class StreamWriter: """A multipurpose stream object. Four styles: - write an uncompressed file @@ -174,7 +314,7 @@ def StreamReader(stream): appropriate for reading the stream.""" # turn strings into stream - if isinstance(stream, (str, unicode)): + if isinstance(stream, str): stream = StringIO(stream) # determine if we have a gzipped stream by checking magic @@ -190,14 +330,14 @@ def StreamReader(stream): def xmldump(iohandle=None, obj=None, binary=0, deepcopy=None, omit=None): - "Create the XML representation as a string." + """Create the XML representation as a string.""" if deepcopy is None: deepcopy = 0 return _pickle_toplevel_obj(StreamWriter(iohandle, binary), obj, deepcopy, omit) def xmlload(filehandle): - "Load pickled object from file fh." + """Load pickled object from file fh.""" return thing_from_dom(StreamReader(filehandle)) @@ -205,7 +345,7 @@ def xmlload(filehandle): def _pickle_toplevel_obj(xml_list, py_obj, deepcopy, omit=None): - "handle the top object -- add XML header, etc." + """handle the top object -- add XML header, etc.""" # Store the ref id to the pickling object (if not deepcopying) global VISITED # pylint: disable=global-statement @@ -453,7 +593,7 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): start_tag = start_tag + '%s value="%s" />\n' % ( _family_type('uniq', typestr, mtag, mextra), '') close_tag = '' - elif isinstance(thing, (int, long, float, complex)): + elif isinstance(thing, (int, float, complex)): # thing_str = repr(thing) thing_str = ntoa(thing) @@ -469,38 +609,7 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): start_tag = start_tag + '%s value="%s" />\n' % ( _family_type('atom', 'numeric', mtag, mextra), thing_str) close_tag = '' - elif isinstance(thing, (str, unicode)): - # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - # special check for now - this will be fixed in the next major - # gnosis release, so I don't care that the code is inline & gross - # for now - # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - if sys.version_info[0] < 3 and isinstance(thing, unicode): - # can't pickle unicode containing the special "escape" sequence - # we use for putting strings in the XML body (they'll be unpickled - # as strings, not unicode, if we do!) - # pylint: disable=redundant-u-string-prefix - if thing[0:2] == u'\xbb\xbb' and thing[-2:] == u'\xab\xab': - raise ValueError("Unpickleable Unicode value") - - # see if it contains any XML-illegal values - # if not is_legal_xml(thing): - # raise ValueError("Unpickleable Unicode value") - - if isinstance(thing, str) and getInBody(str): - # technically, this will crash safe_content(), but I prefer to - # have the test here for clarity - try: - # safe_content assumes it can always convert the string - # to unicode, which isn't true (eg. pickle a UTF-8 value) - _ = unicode(thing) - except ValueError as exc: - raise_from(ValueError("Unpickleable string value (%s): %s" % (repr(thing), exc)), None) - - # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - # End of temporary hack code - # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - + elif isinstance(thing, str): if in_body: start_tag = start_tag + '%s>%s' % ( _family_type('atom', 'string', mtag, mextra), safe_content(thing)) @@ -564,7 +673,7 @@ def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): def _thing_from_dom(dom_node, container=None): - "Converts an [xml_pickle] DOM tree to a 'native' Python object" + """Converts an [xml_pickle] DOM tree to a 'native' Python object""" for node in dom_node.childNodes: if not hasattr(node, '_attrs') or not node.nodeName != '#text': continue @@ -614,7 +723,7 @@ def _thing_from_dom(dom_node, container=None): elif node_family == 'map': # map must exist in VISITED{} before we unpickle subitems, # in order to handle self-references - mapping = ODict() + mapping = {} _save_obj_with_id(node, mapping) node_val = _thing_from_dom(node, mapping) elif node_family == 'uniq': diff --git a/src/objdictgen/nosis/__init__.py b/src/objdictgen/nosis/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/objdictgen/nosis/xtoy.py b/src/objdictgen/nosis/xtoy.py deleted file mode 100644 index 32a8771..0000000 --- a/src/objdictgen/nosis/xtoy.py +++ /dev/null @@ -1,144 +0,0 @@ -import sys -import re -import logging -from past.builtins import long - -log = logging.getLogger('objdictgen.nosis') - -# pylint: disable=invalid-name - -pat_fl = r'[-+]?(((((\d+)?[.]\d+|\d+[.])|\d+)[eE][+-]?\d+)|((\d+)?[.]\d+|\d+[.]))' -re_float = re.compile(pat_fl + r'$') -re_zero = r'[+-]?0$' -pat_int = r'[-+]?[1-9]\d*' -re_int = re.compile(pat_int + r'$') -pat_flint = r'(%s|%s)' % (pat_fl, pat_int) # float or int -re_long = re.compile(r'[-+]?\d+[lL]' + r'$') -re_hex = re.compile(r'([-+]?)(0[xX])([0-9a-fA-F]+)' + r'$') -re_oct = re.compile(r'([-+]?)(0)([0-7]+)' + r'$') -pat_complex = r'(%s)?[-+]%s[jJ]' % (pat_flint, pat_flint) -re_complex = re.compile(pat_complex + r'$') -pat_complex2 = r'(%s):(%s)' % (pat_flint, pat_flint) -re_complex2 = re.compile(pat_complex2 + r'$') - - -def aton(s): - # -- massage the string slightly - s = s.strip() - while s[0] == '(' and s[-1] == ')': # remove optional parens - s = s[1:-1] - - # -- test for cases - if re.match(re_zero, s): - return 0 - - if re.match(re_float, s): - return float(s) - - if re.match(re_long, s): - return long(s.rstrip('lL')) - - if re.match(re_int, s): - return int(s) - - m = re.match(re_hex, s) - if m: - n = long(m.group(3), 16) - if n < sys.maxsize: - n = int(n) - if m.group(1) == '-': - n = n * (-1) - return n - - m = re.match(re_oct, s) - if m: - n = long(m.group(3), 8) - if n < sys.maxsize: - n = int(n) - if m.group(1) == '-': - n = n * (-1) - return n - - if re.match(re_complex, s): - return complex(s) - - if re.match(re_complex2, s): - r, i = s.split(':') - return complex(float(r), float(i)) - - raise ValueError("Invalid string '%s' passed to to_number()'d" % s) - - -# we use ntoa() instead of repr() to ensure we have a known output format -def ntoa(num): - "Convert a number to a string without calling repr()" - if isinstance(num, int): - s = "%d" % num - elif isinstance(num, long): - s = "%ldL" % num - elif isinstance(num, float): - s = "%.17g" % num - # ensure a '.', adding if needed (unless in scientific notation) - if '.' not in s and 'e' not in s: - s = s + '.' - elif isinstance(num, complex): - # these are always used as doubles, so it doesn't - # matter if the '.' shows up - s = "%.17g:%.17g" % (num.real, num.imag) - else: - raise ValueError("Unknown numeric type: %s" % repr(num)) - return s - - -def safe_string(s): - # if isinstance(s, unicode): - # raise TypeError("Unicode strings may not be stored in XML attributes") - - # markup XML entities - s = s.replace('&', '&') - s = s.replace('<', '<') - s = s.replace('>', '>') - s = s.replace('"', '"') - s = s.replace("'", ''') - # for others, use Python style escapes - s = repr(s) - return s[1:-1] # without the extra single-quotes - - -def unsafe_string(s): - # for Python escapes, exec the string - # (niggle w/ literalizing apostrophe) - _s = s = s.replace("'", r"\047") - # log.debug("EXEC in unsafe_string(): '%s'" % ("s='" + s + "'",)) - exec("s='" + s + "'") - if s != _s: - log.debug("EXEC in unsafe_string(): '%s' -> '%s'" % (_s, s)) - # XML entities (DOM does it for us) - return s - - -def safe_content(s): - "Markup XML entities and strings so they're XML & unicode-safe" - s = s.replace('&', '&') - s = s.replace('<', '<') - s = s.replace('>', '>') - - return s # To be able to be used with py3 - - # # wrap "regular" python strings as unicode - # if isinstance(s, str): - # s = u"\xbb\xbb%s\xab\xab" % s - - # return s.encode('utf-8') - - -def unsafe_content(s): - """Take the string returned by safe_content() and recreate the - original string.""" - # don't have to "unescape" XML entities (parser does it for us) - - # # unwrap python strings from unicode wrapper - # if s[:2] == chr(187) * 2 and s[-2:] == chr(171) * 2: - # s = s[2:-2].encode('us-ascii') - - return s diff --git a/src/objdictgen/ui/commondialogs.py b/src/objdictgen/ui/commondialogs.py index da873e0..ec90610 100644 --- a/src/objdictgen/ui/commondialogs.py +++ b/src/objdictgen/ui/commondialogs.py @@ -1,37 +1,31 @@ -# -*- coding: utf-8 -*- # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import absolute_import -from builtins import range +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA -import os import logging +import os import wx import wx.grid import objdictgen -from objdictgen.node import BE_to_LE, LE_to_BE from objdictgen.maps import OD +from objdictgen.node import BE_to_LE, LE_to_BE log = logging.getLogger('objdictgen') diff --git a/src/objdictgen/ui/exception.py b/src/objdictgen/ui/exception.py index 2945a5f..b4bd8c7 100644 --- a/src/objdictgen/ui/exception.py +++ b/src/objdictgen/ui/exception.py @@ -1,34 +1,30 @@ # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -import traceback +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA import os -import sys import platform +import sys import time +import traceback import wx - # ------------------------------------------------------------------------------ # Exception Handler # ------------------------------------------------------------------------------ diff --git a/src/objdictgen/ui/networkedit.py b/src/objdictgen/ui/networkedit.py index f7b2112..85b9e4e 100644 --- a/src/objdictgen/ui/networkedit.py +++ b/src/objdictgen/ui/networkedit.py @@ -1,33 +1,26 @@ -# -*- coding: utf-8 -*- # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import print_function -from __future__ import absolute_import -from builtins import range +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA -import os -import sys import getopt import logging +import os +import sys import wx diff --git a/src/objdictgen/ui/networkeditortemplate.py b/src/objdictgen/ui/networkeditortemplate.py index 2c2c20e..57f6d1d 100644 --- a/src/objdictgen/ui/networkeditortemplate.py +++ b/src/objdictgen/ui/networkeditortemplate.py @@ -1,32 +1,27 @@ # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import absolute_import -from builtins import range +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA import wx +from objdictgen.ui import commondialogs as cdia from objdictgen.ui import nodeeditortemplate as net from objdictgen.ui import subindextable as sit -from objdictgen.ui import commondialogs as cdia [ ID_NETWORKEDITNETWORKNODES, diff --git a/src/objdictgen/ui/nodeeditortemplate.py b/src/objdictgen/ui/nodeeditortemplate.py index d3ff1bf..64c0912 100644 --- a/src/objdictgen/ui/nodeeditortemplate.py +++ b/src/objdictgen/ui/nodeeditortemplate.py @@ -1,40 +1,29 @@ # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import absolute_import -from builtins import object -from builtins import range - -import sys +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA import wx -from objdictgen.ui import commondialogs as cdia from objdictgen.maps import OD - -if sys.version_info[0] >= 3: - unicode = str # pylint: disable=invalid-name +from objdictgen.ui import commondialogs as cdia -class NodeEditorTemplate(object): +class NodeEditorTemplate: EDITMENU_ID = None diff --git a/src/objdictgen/ui/objdictedit.py b/src/objdictgen/ui/objdictedit.py index ff488e0..1204847 100644 --- a/src/objdictgen/ui/objdictedit.py +++ b/src/objdictgen/ui/objdictedit.py @@ -1,44 +1,34 @@ -# -*- coding: utf-8 -*- # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldaleldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldaleldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import print_function -from __future__ import absolute_import -from builtins import range +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA -import os -import sys import getopt import logging +import os +import sys import wx -from objdictgen.ui.exception import AddExceptHook +import objdictgen +from objdictgen.ui import commondialogs as cdia from objdictgen.ui import nodeeditortemplate as net from objdictgen.ui import subindextable as sit -from objdictgen.ui import commondialogs as cdia -import objdictgen - -if sys.version_info[0] >= 3: - unicode = str # pylint: disable=invalid-name +from objdictgen.ui.exception import AddExceptHook log = logging.getLogger('objdictgen') diff --git a/src/objdictgen/ui/subindextable.py b/src/objdictgen/ui/subindextable.py index 7591d8b..27c769e 100644 --- a/src/objdictgen/ui/subindextable.py +++ b/src/objdictgen/ui/subindextable.py @@ -1,38 +1,30 @@ -# -*- coding: utf-8 -*- # -# This file is based on objdictgen from CanFestival +# Copyright (C) 2022-2024 Svein Seldal, Laerdal Medical AS +# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD # -# Copyright (C) 2022-2023 Svein Seldal, Laerdal Medical AS -# Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD +# This library 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 2.1 of the License, or (at your option) any later version. # -# This library 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 2.1 of the License, or (at your option) any later version. +# This library 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. # -# This library 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 this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# USA - -from __future__ import absolute_import -from builtins import map -from builtins import range +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA import codecs import wx import wx.grid -from objdictgen.ui import commondialogs as cdia from objdictgen import maps from objdictgen.maps import OD - +from objdictgen.ui import commondialogs as cdia COL_SIZES = [75, 250, 150, 125, 100, 60, 250, 60] COL_ALIGNMENTS = [wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_CENTER, wx.ALIGN_RIGHT, wx.ALIGN_CENTER, wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_LEFT] diff --git a/tests/test_nodemanager.py b/tests/test_nodemanager.py index d761195..656af88 100644 --- a/tests/test_nodemanager.py +++ b/tests/test_nodemanager.py @@ -1,5 +1,5 @@ -from pprint import pprint import os + from objdictgen.nodemanager import NodeManager diff --git a/tests/test_nosis.py b/tests/test_nosis.py new file mode 100644 index 0000000..b804328 --- /dev/null +++ b/tests/test_nosis.py @@ -0,0 +1,154 @@ +from dataclasses import dataclass +import pytest + +from objdictgen import nosis + + +def test_aton(): + + aton = nosis.aton + assert aton("(1)") == 1 + assert aton("0") == 0 + assert aton("3") == 3 + assert aton("1.") == 1. + assert aton("2l") == 2 + assert aton("0x10") == 16 + assert aton("-0x04") == -4 + assert aton("010") == 8 + assert aton("-07") == -7 + assert aton("1+2j") == 1+2j + assert aton("1:2") == 1+2j + + with pytest.raises(ValueError): + aton("1.2.3") + + +def test_ntoa(): + + ntoa = nosis.ntoa + assert ntoa(1) == "1" + assert ntoa(0.) == "0." + assert ntoa(1.5) == "1.5" + assert ntoa(1+2j) == "1+2j" + + with pytest.raises(ValueError): + ntoa("foo") + + +SAFE_TESTS = [ + ("", ""), + ("&", "&"), + ("<", "<"), + (">", ">"), + ('"', '"'), + ("'", "'"), + ("\x3f", "?"), + ("\x00", "\\x00"), + ("foo", "foo"), + ("fu's", "fu's"), + ("fus")) + cmp_xml(Dut("m&m")) + # cmp_xml(Data("\x00\x00\x00\x00")) + + +def test_nosis_xml_variants(): + + @dataclass + class Dut: + s: str + + nosis.add_class_to_store('Dut', Dut) + + # Attribute in body + xml = """ + + +hello +""" + + data = nosis.xmlload(xml) + assert data.s == "hello" + + # Attribute in tag + xml = """ + + + +""" + + data = nosis.xmlload(xml) + assert data.s == "world" + + +def test_nosis_all_datatypes(): + '''Test all datatypes''' + + # @dataclass + # class C: + # s: str + + @dataclass + class Dut: + s: str + i: int + l: list + d: dict + t: tuple + n: None + f: float + c: complex + T: bool + F: bool + # o: C + # b: bytes + + nosis.add_class_to_store('Dut', Dut) + # nosis.add_class_to_store('C', C) + + cmp_xml(Dut( + s="foo", i=1, l=[1, 2, 3], d={'a': 1, 'b': 2}, t=(1, 2, 3), + n=None, f=1.5, c=1+2j, T=True, F=False #, o=C("foo"), b=b'\x00\x01\x02' + )) diff --git a/tests/test_objdictgen.py b/tests/test_objdictgen.py index f9ae581..c6a4373 100644 --- a/tests/test_objdictgen.py +++ b/tests/test_objdictgen.py @@ -1,4 +1,5 @@ import os + import objdictgen.__main__ diff --git a/tests/test_odcompare.py b/tests/test_odcompare.py index a3d1eaf..9a71a7d 100644 --- a/tests/test_odcompare.py +++ b/tests/test_odcompare.py @@ -2,20 +2,13 @@ import shutil import re import os -import sys -from collections import OrderedDict import pytest from objdictgen import Node -if sys.version_info[0] >= 3: - ODict = dict -else: - ODict = OrderedDict - def shave_dict(a, b): - if isinstance(a, (ODict, dict)) and isinstance(b, (ODict, dict)): + if isinstance(a, dict) and isinstance(b, dict): for k in set(a.keys()) | set(b.keys()): if k in a and k in b: a[k], b[k] = shave_dict(a[k], b[k]) @@ -68,18 +61,18 @@ def shave_equal(a, b, ignore=None): # delattr(obj, 'IndexOrder') -@pytest.mark.parametrize("suffix", ['.od', '.json', '.eds']) +@pytest.mark.parametrize("suffix", ['od', 'json', 'eds']) def test_load_compare(odfile, suffix): ''' Tests that the file can be loaded twice without different. L(od) == L(od) ''' - if not os.path.exists(odfile + suffix): + if not os.path.exists(odfile + '.' + suffix): pytest.skip("File not found") # Load the OD - m1 = Node.LoadFile(odfile + suffix) - m2 = Node.LoadFile(odfile + suffix) + m1 = Node.LoadFile(odfile + '.' + suffix) + m2 = Node.LoadFile(odfile + '.' + suffix) assert m1.__dict__ == m2.__dict__ @@ -119,12 +112,11 @@ def test_odexport(wd, odfile, fn): m2 = Node.LoadFile(od + '.od') # Compare the OD master and the OD2 objects + if m1.__dict__ != m2.__dict__: + a, b = shave_equal(m1, m2) + assert a == b assert m1.__dict__ == m2.__dict__ - # Compare the files - The py3 ones are by guarantee different, as the str handling is different - if sys.version_info[0] < 3: - assert fn.diff(od + '.od.orig', od + '.od', n=0) - def test_jsonexport(wd, odfile): ''' Test that the file can be exported to json and that the loaded file diff --git a/tests/test_odg.py b/tests/test_odg.py index 91bc6e5..abb8a76 100644 --- a/tests/test_odg.py +++ b/tests/test_odg.py @@ -1,5 +1,6 @@ import os import pytest + from objdictgen.__main__ import main