From 988ee9db94dcadda6b7cc21eaaecf834c1686dc1 Mon Sep 17 00:00:00 2001 From: Peter Boin Date: Sun, 9 Jul 2017 11:32:37 +1000 Subject: [PATCH] fixed over-usage of assert --- pygcode/exceptions.py | 15 +++++++++++++-- pygcode/gcodes.py | 11 ++++++++--- pygcode/machine.py | 45 +++++++++++++++++++++++++++++-------------- pygcode/words.py | 14 +++++++++----- tests/test_machine.py | 7 ++++--- 5 files changed, 65 insertions(+), 27 deletions(-) diff --git a/pygcode/exceptions.py b/pygcode/exceptions.py index 0249ff7..d445e6b 100644 --- a/pygcode/exceptions.py +++ b/pygcode/exceptions.py @@ -2,6 +2,17 @@ # ===================== Parsing Exceptions ===================== class GCodeBlockFormatError(Exception): """Raised when errors encountered while parsing block text""" - pass -# ===================== Parsing Exceptions ===================== +class GCodeParameterError(Exception): + """Raised for conflicting / invalid / badly formed parameters""" + +class GCodeWordStrError(Exception): + """Raised when issues found while parsing a word string""" + +# ===================== Machine Exceptions ===================== +class MachineInvalidAxis(Exception): + """Raised if an axis is invalid""" + # For example: for axes X/Y/Z, set the value of "Q"; wtf? + +class MachineInvalidState(Exception): + """Raised if a machine state is set incorrectly, or in conflict""" diff --git a/pygcode/gcodes.py b/pygcode/gcodes.py index 5f70312..6b20d9e 100644 --- a/pygcode/gcodes.py +++ b/pygcode/gcodes.py @@ -3,6 +3,8 @@ from copy import copy from .words import Word +from .exceptions import GCodeParameterError + # Terminology of a "G-Code" # For the purposes of this library, so-called "G" codes do not necessarily # use the letter "G" in their word; other letters include M, F, S, and T @@ -196,8 +198,11 @@ class GCode(object): :param word: Word instance """ assert isinstance(word, Word), "invalid parameter class: %r" % word - assert word.letter in self.param_letters, "invalid parameter for %s: %s" % (self.__class__.__name__, str(word)) - assert word.letter not in self.params, "parameter defined twice: %s -> %s" % (self.params[word.letter], word) + if word.letter not in self.param_letters: + raise GCodeParameterError("invalid parameter for %s: %s" % (self.__class__.__name__, str(word))) + if word.letter in self.params: + raise GCodeParameterError("parameter defined twice: %s -> %s" % (self.params[word.letter], word)) + self.params[word.letter] = word def __getattr__(self, key): @@ -243,7 +248,7 @@ class GCode(object): :param machine: Machine instance, to change state :return: GCodeEffect instance; effect the gcode just had on machine """ - from .machine import Machine # importing high-level state + from .machine import Machine # importing up (done live to avoid dependency loop) assert isinstance(machine, Machine), "invalid parameter" # Set mode diff --git a/pygcode/machine.py b/pygcode/machine.py index 61a1b5c..8dc1db8 100644 --- a/pygcode/machine.py +++ b/pygcode/machine.py @@ -11,6 +11,7 @@ from .block import Block from .line import Line from .words import Word +from .exceptions import MachineInvalidAxis, MachineInvalidState UNIT_IMPERIAL = GCodeUseInches.unit_id # G20 UNIT_METRIC = GCodeUseMillimeters.unit_id # G21 @@ -37,7 +38,8 @@ class Position(object): axes = self.__class__.default_axes else: invalid_axes = set(axes) - self.POSSIBLE_AXES - assert not invalid_axes, "invalid axes proposed %s" % invalid_axes + if invalid_axes: + raise MachineInvalidAxis("invalid axes proposed %s" % invalid_axes) self.__dict__['axes'] = set(axes) & self.POSSIBLE_AXES # Unit @@ -61,7 +63,7 @@ class Position(object): if key in self.axes: self._value[key] = value elif key in self.POSSIBLE_AXES: - raise AssertionError("'%s' axis is not defined to be set" % key) + raise MachineInvalidAxis("'%s' axis is not defined to be set" % key) else: self.__dict__[key] = value @@ -86,14 +88,16 @@ class Position(object): # Arithmetic def __add__(self, other): - assert not (self.axes ^ other.axes), "axes: %r != %r" % (self.axes, other.axes) + if self.axes ^ other.axes: + raise MachineInvalidAxis("axes: %r != %r" % (self.axes, other.axes)) new_obj = copy(self) for k in new_obj._value: new_obj._value[k] += other._value[k] return new_obj def __sub__(self, other): - assert not (other.axes - self.axes), "for a - b: axes in b, that are not in a: %r" % (other.axes - self.axes) + if other.axes - self.axes: + raise MachineInvalidAxis("for a - b: axes in b, that are not in a: %r" % (other.axes - self.axes)) new_obj = copy(self) for k in other._value: new_obj._value[k] -= other._value[k] @@ -277,11 +281,12 @@ class Mode(object): # clear mode group self.modal_groups[MODAL_GROUP_MAP[key]] = None else: - # set mode group explicitly + # set mode group explicitly, not advisable # (recommended to use self.set_mode(value) instead) - assert isinstance(value, GCode), "invalid value type: %r" % value - assert value.modal_group == MODAL_GROUP_MAP[key], \ - "cannot set '%s' mode as %r, wrong group" % (key, value) + if not isinstance(value, GCode): + raise MachineInvalidState("invalid mode value: %r" % value) + if value.modal_group != MODAL_GROUP_MAP[key]: + raise MachineInvalidState("cannot set '%s' mode as %r, wrong group" % (key, value)) self.modal_groups[MODAL_GROUP_MAP[key]] = value.modal_copy() else: self.__dict__[key] = value @@ -329,6 +334,21 @@ class Machine(object): if coord_sys_mode: self.state.cur_coord_sys = coord_sys_mode.coord_system_id + def modal_gcode(self, modal_params): + if not modal_params: + return None + if self.mode.motion is None: + raise MachineInvalidState("unable to assign modal parameters when no motion mode is set") + (modal_gcodes, unasigned_words) = words2gcodes([self.mode.motion.word] + modal_params) + if unasigned_words: + raise MachineInvalidState("modal parameters '%s' cannot be assigned when in mode: %r" % ( + ' '.join(str(x) for x in unasigned_words), self.mode + )) + if modal_gcodes: + assert len(modal_gcodes) == 1, "more than 1 modal code found" + return modal_gcodes[0] + return None + def process(self, *gcode_list, **kwargs): """ Process gcodes @@ -338,12 +358,9 @@ class Machine(object): # Add modal gcode to list of given gcodes modal_params = kwargs.get('modal_params', []) if modal_params: - assert self.mode.motion is not None, "unable to assign modal parameters when no motion mode is set" - (modal_gcodes, unasigned_words) = words2gcodes([self.mode.motion.word] + modal_params) - assert unasigned_words == [], "modal parameters '%s' unknown when in mode: %r" % ( - ' '.join(str(x) for x in unasigned_words), self.mode - ) - gcode_list += modal_gcodes + modal_gcode = self.modal_gcode(modal_params) + if modal_gcode: + gcode_list.append(modal_gcode) for gcode in sorted(gcode_list): gcode.process(self) # shifts ownership of what happens now to GCode class diff --git a/pygcode/words.py b/pygcode/words.py index f4ead70..059dc42 100644 --- a/pygcode/words.py +++ b/pygcode/words.py @@ -2,7 +2,7 @@ import re import itertools import six -from .exceptions import GCodeBlockFormatError +from .exceptions import GCodeBlockFormatError, GCodeWordStrError REGEX_FLOAT = re.compile(r'^-?(\d+\.?\d*|\.\d+)') # testcase: ..tests.test_words.WordValueMatchTests.test_float REGEX_INT = re.compile(r'^-?\d+') @@ -201,10 +201,13 @@ WORD_MAP = { class Word(object): def __init__(self, *args): - assert len(args) in [1, 2], "input arguments either: (letter, value) or (word_str)" + if len(args) not in (1, 2): + raise AssertionError("input arguments either: (letter, value) or (word_str)") if len(args) == 2: + # Word('G', 90) (letter, value) = args else: + # Word('G90') word_str = args[0] letter = word_str[0] # first letter value = word_str[1:] # rest of string @@ -300,7 +303,7 @@ def text2words(block_text): value_regex = WORD_MAP[letter]['value_regex'] value_match = value_regex.search(block_text[index:]) if value_match is None: - raise GCodeBlockFormatError("word '%s' value invalid" % letter) + raise GCodeWordStrError("word '%s' value invalid" % letter) value = value_match.group() # matched text yield Word(letter, value) @@ -311,13 +314,14 @@ def text2words(block_text): remainder = block_text[index:] if remainder and re.search(r'\S', remainder): - raise GCodeBlockFormatError("block code remaining '%s'" % remainder) + raise GCodeWordStrError("block code remaining '%s'" % remainder) def str2word(word_str): words = list(text2words(word_str)) if words: - assert len(words) <= 1, "more than one word given" + if len(words) > 1: + raise GCodeWordStrError("more than one word given") return words[0] return None diff --git a/tests/test_machine.py b/tests/test_machine.py index c77a3f8..5f52aa6 100644 --- a/tests/test_machine.py +++ b/tests/test_machine.py @@ -7,6 +7,7 @@ add_pygcode_to_path() # Units under test from pygcode.machine import Position, Machine from pygcode.line import Line +from pygcode.exceptions import MachineInvalidAxis class PositionTests(unittest.TestCase): @@ -56,9 +57,9 @@ class PositionTests(unittest.TestCase): self.assertEqual(p1 + p2, Position(axes='XYZ', X=1, Y=12, Z=-20)) p3 = Position(axes='XYZA') - with self.assertRaises(AssertionError): + with self.assertRaises(MachineInvalidAxis): p1 + p3 # mismatched axes - with self.assertRaises(AssertionError): + with self.assertRaises(MachineInvalidAxis): p3 + p1 # mismatched axes def test_arithmetic_sub(self): @@ -68,7 +69,7 @@ class PositionTests(unittest.TestCase): p3 = Position(axes='XYZA') p3 - p1 # fine - with self.assertRaises(AssertionError): + with self.assertRaises(MachineInvalidAxis): p1 - p3 # mismatched axes def test_arithmetic_multiply(self):