def test_with_separator_identity(self): one = Characters() other = Characters() # a copy of a characters object must be equal, but not the same self.assertTrue(one == other, 'Objects differ: "{}", "{}"'.format(one, other)) self.assertFalse(one is other)
def setup(): setup = Setup() unb_segment = "UNB+UNOA:4+APIS*ABE+USADHS+070429:0900+000000001++USADHS'" cc = Characters() setup.cc = cc.with_control_character("decimal_point", ".") setup.collection = SegmentCollection.from_str(unb_segment) return setup
def get_control_characters(message, characters=None): u"""Read the UNA segment from the passed string. :param message: a valid EDI message string, or UNA segment string, to extract the control characters from :param characters: the control characters to use, if none found in the message :rtype: str or None :return: the control characters """ if not characters: characters = Characters() # First, try to read the UNA segment ("Service String Advice", # conditional). This segment and the UNB segment (Interchange Header) # must always be written in ASCII, even if after the BGM the files # continues with cyrillic or UTF-16. # If it does not exist, return a default. if not message[:3] == u"UNA": return characters # Get the character definitions chars = message[3:9] characters.is_extracted_from_message = True characters.component_separator = chars[0] characters.data_separator = chars[1] characters.decimal_point = chars[2] characters.escape_character = chars[3] characters.reserved_character = chars[4] characters.segment_terminator = chars[5] return characters
def parse(self, message, characters=None): u"""Parse the message into a list of segments. :param characters: the control characters to use, if there is no UNA segment present :param message: The EDI message :rtype: """ # FIXME: DRY: use get_control_characters here? tokens = [] # If there is a UNA token, take the following 6 characters # unconditionally, save them as token and use it as control characters # for further parsing if message[0:3] == u'UNA': control_chars = message[3:9] tokens.append(Token(Token.Type.CONTENT, u'UNA')) tokens.append(Token(Token.Type.CTRL_CHARS, control_chars)) # remove the UNA segment from the string message = message[9:].lstrip(u"\r\n") self.characters = Characters.from_str(u'UNA' + control_chars) else: # if no UNA header present, use default control characters if characters is not None: self.characters = characters tokenizer = Tokenizer() tokens += tokenizer.get_tokens(message, self.characters) segments = self.convert_tokens_to_segments(tokens, self.characters) return segments
def parse(self, message: str, characters: Characters = None) -> Generator[Segment, Any, None]: """Parse the message into a list of segments. :param characters: the control characters to use, if there is no UNA segment present :param message: The EDI message :rtype: """ # If there is a UNA, take the following 6 characters # unconditionally, save them, strip them, and make control Characters() # for further parsing if message[0:3] == "UNA": self.characters = Characters.from_str("UNA" + message[3:9]) # remove the UNA segment from the string message = message[9:].lstrip("\r\n") else: # if no UNA header present, use default control characters if characters is not None: self.characters = characters tokenizer = Tokenizer() return self.convert_tokens_to_segments( tokenizer.get_tokens(message, self.characters), self.characters)
def from_segments(cls, segments: list or collections.Iterable) -> "Interchange": segments = iter(segments) first_segment = next(segments) if first_segment.tag == 'UNA': unb = next(segments) elif first_segment.tag == 'UNB': unb = first_segment else: raise EDISyntaxError( 'An interchange must start with UNB or UNA and UNB') # Loosy syntax check : if len(unb.elements) < 4: raise EDISyntaxError('Missing elements in UNB header') datetime_str = '-'.join(unb.elements[3]) timestamp = datetime.datetime.strptime(datetime_str, '%y%m%d-%H%M') interchange = Interchange( syntax_identifier=unb.elements[0], sender=unb.elements[1], recipient=unb.elements[2], timestamp=timestamp, control_reference=unb.elements[4], ) if first_segment.tag == 'UNA': interchange.has_una_segment = True interchange.characters = Characters.from_str( first_segment.elements[0]) return interchange.add_segments(segment for segment in segments if segment.tag != 'UNZ')
def from_segments(cls, segments: Union[list, Iterable]) -> "Interchange": segments = iter(segments) first_segment = next(segments) if first_segment.tag == "UNA": unb = next(segments) elif first_segment.tag == "UNB": unb = first_segment else: raise EDISyntaxError( "An interchange must start with UNB or UNA and UNB") # Loosy syntax check : if len(unb.elements) < 4: raise EDISyntaxError("Missing elements in UNB header") datetime_str = "-".join(unb.elements[3]) timestamp = datetime.datetime.strptime(datetime_str, "%y%m%d-%H%M") interchange = Interchange( syntax_identifier=unb.elements[0], sender=unb.elements[1], recipient=unb.elements[2], timestamp=timestamp, control_reference=unb.elements[4], ) if first_segment.tag == "UNA": interchange.has_una_segment = True interchange.characters = Characters.from_str( first_segment.elements[0]) return interchange.add_segments(segments)
def __init__(self): # The segments that make up this message self.segments = [] self.characters = Characters() # Flag whether the UNA header is present self.has_una_segment = False
class TestControlCharacters(unittest.TestCase): def setUp(self): self.cc = Characters() def test_wrong_attribute(self): self.assertRaises(AttributeError, self.cc.with_control_character, 'wrongtype', '+') def test_wrong_character_size(self): self.assertRaises(ValueError, self.cc.with_control_character, 'decimal_point', ',.') def test_correct_parameters(self): d = self.cc.with_control_character('component_separator', '/') self.assertEqual(self.cc.with_control_character( 'component_separator', '/').component_separator, '/') self.assertEqual(self.cc.with_control_character( 'data_separator', '/').data_separator, '/') self.assertEqual(self.cc.with_control_character( 'decimal_point', '/').decimal_point, '/') self.assertEqual(self.cc.with_control_character( 'escape_character', '/').escape_character, '/') self.assertEqual(self.cc.with_control_character( 'reserved_character', '/').reserved_character, '/') self.assertEqual(self.cc.with_control_character( 'segment_terminator', '/').segment_terminator, '/')
def add_segment(self, segment: Segment) -> "Message": """Append a segment to the message. :param segment: The segment to add """ if segment.tag == "UNA": self.has_una_segment = True self.characters = Characters.from_str(segment.elements[0]) self.segments.append(segment) return self
def create_segment(name: str, *elements: list) -> Segment: """Create a new instance of the relevant class type. :param name: The name of the segment :param elements: The data elements for this segment """ if not SegmentFactory.characters: SegmentFactory.characters = Characters() # FIXME: characters is not used! return Segment(name, *elements)
def add_segment(self, segment: Segment) -> "UNAHandlingMixin": """Append a segment to the collection. Passing a UNA segment means setting/overriding the control characters and setting the serializer to output the Service String Advice. If you wish to change the control characters from the default and not output the Service String Advice, change self.characters instead, without passing a UNA Segment. :param segment: The segment to add """ if segment.tag == "UNA": self.has_una_segment = True self.characters = Characters.from_str(segment.elements[0]) return self return super().add_segment(segment)
def __init__(self, extra_header_elements: List[Union[str, List[str]]] = []): """ :param extra_header_elements: a list of elements to be appended at the end of the header segment (same format as Segment() constructor *elements). """ # The segments that make up this message self.segments = [] self.characters = Characters() self.extra_header_elements = extra_header_elements # Flag whether the UNA header is present self.has_una_segment = False
def __init__(self, sender: str, recipient: str, control_reference: str, syntax_identifier: Tuple[str, int], delimiters: Characters = Characters(), timestamp: datetime.datetime = None, *args, **kwargs): super().__init__(*args, **kwargs) self.sender = sender self.recipient = recipient self.control_reference = control_reference self.syntax_identifier = syntax_identifier self.delimiters = delimiters self.timestamp = timestamp or datetime.datetime.now()
class TestControlCharacters(unittest.TestCase): def setUp(self): self.cc = Characters() def test_wrong_attribute(self): self.assertRaises(AttributeError, self.cc.with_control_character, "wrongtype", "+") def test_wrong_character_size(self): self.assertRaises(ValueError, self.cc.with_control_character, "decimal_point", ",.") def test_correct_parameters(self): d = self.cc.with_control_character("component_separator", "/") self.assertEqual( self.cc.with_control_character("component_separator", "/").component_separator, "/", ) self.assertEqual( self.cc.with_control_character("data_separator", "/").data_separator, "/") self.assertEqual( self.cc.with_control_character("decimal_point", "/").decimal_point, "/") self.assertEqual( self.cc.with_control_character("escape_character", "/").escape_character, "/", ) self.assertEqual( self.cc.with_control_character("reserved_character", "/").reserved_character, "/", ) self.assertEqual( self.cc.with_control_character("segment_terminator", "/").segment_terminator, "/", )
def create_segment(name: str, *elements: Union[str, List[str]], validate: bool = True) -> Segment: """Create a new instance of the relevant class type. :param name: The name of the segment :param elements: The data elements for this segment :param validate: bool if True, the created segment is validated before return """ if not SegmentFactory.characters: SegmentFactory.characters = Characters() # Basic segment type validation is done here. # The more special validation must be done in the corresponding Segment if not name: raise EDISyntaxError("The tag of a segment must not be empty.") if type(name) != str: raise EDISyntaxError( "The tag name of a segment must be a str, but is a {}: {}". format(type(name), name)) if not name.isalnum(): raise EDISyntaxError( "Tag '{}': A tag name must only contain alphanumeric characters." .format(name)) for Plugin in SegmentProvider.plugins: if Plugin().tag == name: s = Plugin(name, *elements) else: # we don't support this kind of EDIFACT segment (yet), so # just create a generic Segment() s = Segment(name, *elements) if validate: if not s.validate(): raise EDISyntaxError( "could not create '{}' Segment. Validation failed.".format( name)) # FIXME: characters is not used! return Segment(name, *elements)
def __init__(self, factory: SegmentFactory = None): if factory is None: factory = SegmentFactory() self.factory = factory self.characters = Characters()
def test_with_separator_identity(): one = Characters() other = Characters() # a copy of a characters object must be equal, but not the same assert one == other, 'Objects differ: "{}", "{}"'.format(one, other) assert one is not other
def setUp(self): self.cc = Characters()
def test_cc_assigning(): one = Characters() one.component_separator = "x" assert one.component_separator == "x" assert one == "x+,? '"
def test_wrong_cc_assigning(): with pytest.raises(ValueError): Characters().with_control_character("component_separator", "xd") with pytest.raises(AttributeError): Characters().with_control_character("notexisting", ":")
def setup(): setup = Setup() una_segment = "UNA:+.? '" setup.cc = Characters.from_str(una_segment) return setup
def test_cc_assigning(self): one = Characters() one.component_separator = 'x' self.assertEqual(one.component_separator, 'x') self.assertEqual(str(one), "x+,? '")
def test_no_terminator(self): with self.assertRaises(RuntimeError) as cm: self._tokenizer.get_tokens("TEST", Characters()) self.assertEqual(str(cm.exception), "Unexpected end of EDI message")
def _assert_tokens(self, message, expected=None): if expected is None: expected = [] tokens = self._tokenizer.get_tokens("{}'".format(message), Characters()) expected.append(Token(Token.Type.TERMINATOR, "'")) self.assertEqual(expected, tokens)
def test_no_terminator(): with pytest.raises(RuntimeError): Tokenizer().get_tokens("TEST", Characters()) pytest.fail("Unexpected end of EDI message")