From f98753abedae5f9a89e3e702744ef515bebc5d59 Mon Sep 17 00:00:00 2001 From: Hoss Date: Tue, 3 May 2022 19:54:04 +0800 Subject: [PATCH 1/8] feat(core): drop support to Python 2.7 --- pyx12lib/common/interchange/__init__.py | 2 -- pyx12lib/common/interchange/functional_group.py | 5 ----- pyx12lib/common/interchange/grammar/functional_group.py | 3 --- pyx12lib/common/interchange/grammar/interchange.py | 3 --- pyx12lib/common/interchange/grammar/transaction_set.py | 3 --- pyx12lib/common/interchange/interchange.py | 5 ----- pyx12lib/common/interchange/transaction_set.py | 5 ----- pyx12lib/core/exceptions.py | 4 ---- pyx12lib/core/grammar/element.py | 5 ----- pyx12lib/core/grammar/segment.py | 5 ----- pyx12lib/core/renderer.py | 4 ---- 11 files changed, 44 deletions(-) diff --git a/pyx12lib/common/interchange/__init__.py b/pyx12lib/common/interchange/__init__.py index 6685d79..f64db52 100644 --- a/pyx12lib/common/interchange/__init__.py +++ b/pyx12lib/common/interchange/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from .functional_group import GeRenderer, GsRenderer from .interchange import IeaRenderer, IsaRenderer from .transaction_set import SeRenderer, StRenderer diff --git a/pyx12lib/common/interchange/functional_group.py b/pyx12lib/common/interchange/functional_group.py index 88ab5de..f3ec59d 100644 --- a/pyx12lib/common/interchange/functional_group.py +++ b/pyx12lib/common/interchange/functional_group.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from builtins import str - import dateutil.parser from pyx12lib.core.renderer import SegmentRenderer diff --git a/pyx12lib/common/interchange/grammar/functional_group.py b/pyx12lib/common/interchange/grammar/functional_group.py index cf18363..d5bb658 100644 --- a/pyx12lib/common/interchange/grammar/functional_group.py +++ b/pyx12lib/common/interchange/grammar/functional_group.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from pyx12lib.core.grammar import Element from pyx12lib.core.grammar.segment import USAGE_MANDATORY, BaseSegment diff --git a/pyx12lib/common/interchange/grammar/interchange.py b/pyx12lib/common/interchange/grammar/interchange.py index 44c17b7..342dd11 100644 --- a/pyx12lib/common/interchange/grammar/interchange.py +++ b/pyx12lib/common/interchange/grammar/interchange.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from pyx12lib.core.grammar import Element from pyx12lib.core.grammar.segment import USAGE_MANDATORY, BaseSegment diff --git a/pyx12lib/common/interchange/grammar/transaction_set.py b/pyx12lib/common/interchange/grammar/transaction_set.py index cb38481..a5c5c82 100644 --- a/pyx12lib/common/interchange/grammar/transaction_set.py +++ b/pyx12lib/common/interchange/grammar/transaction_set.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from pyx12lib.core.grammar import Element from pyx12lib.core.grammar.segment import USAGE_MANDATORY, BaseSegment diff --git a/pyx12lib/common/interchange/interchange.py b/pyx12lib/common/interchange/interchange.py index 6b4c0bf..339c27b 100644 --- a/pyx12lib/common/interchange/interchange.py +++ b/pyx12lib/common/interchange/interchange.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from builtins import str - import dateutil.parser from pyx12lib.core.grammar.element import COMPONENT_DELIMITER diff --git a/pyx12lib/common/interchange/transaction_set.py b/pyx12lib/common/interchange/transaction_set.py index fcbaaed..bb5e35d 100644 --- a/pyx12lib/common/interchange/transaction_set.py +++ b/pyx12lib/common/interchange/transaction_set.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from builtins import str - from pyx12lib.core.renderer import SegmentRenderer from .grammar import SeSegment, StSegment diff --git a/pyx12lib/core/exceptions.py b/pyx12lib/core/exceptions.py index 2fca9ad..08aa13d 100644 --- a/pyx12lib/core/exceptions.py +++ b/pyx12lib/core/exceptions.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - - class BaseX12Exception(ValueError): pass diff --git a/pyx12lib/core/grammar/element.py b/pyx12lib/core/grammar/element.py index 34a498b..f13a723 100644 --- a/pyx12lib/core/grammar/element.py +++ b/pyx12lib/core/grammar/element.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from builtins import object - COMPONENT_DELIMITER = '^' USAGE_MANDATORY = 'M' diff --git a/pyx12lib/core/grammar/segment.py b/pyx12lib/core/grammar/segment.py index dbfb95a..d8d463f 100644 --- a/pyx12lib/core/grammar/segment.py +++ b/pyx12lib/core/grammar/segment.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from builtins import object - ELEMENT_DELIMITER = '*' SEGMENT_TERMINATOR = '~' diff --git a/pyx12lib/core/renderer.py b/pyx12lib/core/renderer.py index 08bdc70..dfc40ae 100644 --- a/pyx12lib/core/renderer.py +++ b/pyx12lib/core/renderer.py @@ -1,9 +1,5 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import collections import copy -from builtins import object, zip import six From 460b7b446c492956ebb763fba7eed1f79d16e73a Mon Sep 17 00:00:00 2001 From: Hoss Date: Tue, 3 May 2022 20:27:57 +0800 Subject: [PATCH 2/8] refactor(common): remove unused common renderer --- pyx12lib/common/interchange/__init__.py | 3 - .../common/interchange/functional_group.py | 49 --------------- pyx12lib/common/interchange/interchange.py | 63 ------------------- .../common/interchange/transaction_set.py | 25 -------- 4 files changed, 140 deletions(-) delete mode 100644 pyx12lib/common/interchange/functional_group.py delete mode 100644 pyx12lib/common/interchange/interchange.py delete mode 100644 pyx12lib/common/interchange/transaction_set.py diff --git a/pyx12lib/common/interchange/__init__.py b/pyx12lib/common/interchange/__init__.py index f64db52..e69de29 100644 --- a/pyx12lib/common/interchange/__init__.py +++ b/pyx12lib/common/interchange/__init__.py @@ -1,3 +0,0 @@ -from .functional_group import GeRenderer, GsRenderer -from .interchange import IeaRenderer, IsaRenderer -from .transaction_set import SeRenderer, StRenderer diff --git a/pyx12lib/common/interchange/functional_group.py b/pyx12lib/common/interchange/functional_group.py deleted file mode 100644 index f3ec59d..0000000 --- a/pyx12lib/common/interchange/functional_group.py +++ /dev/null @@ -1,49 +0,0 @@ -import dateutil.parser - -from pyx12lib.core.renderer import SegmentRenderer - -from .grammar import GeSegment, GsSegment - - -class GsRenderer(SegmentRenderer): - grammar = GsSegment - - @property - def element_value_getters(self): - return { - 'GS01': lambda ele, data, stat: 'SO', # Shipping Instruction - 'GS02': lambda ele, data, stat: data.sender_id, - 'GS03': lambda ele, data, stat: data.vendor_id, - 'GS04': self.gs04, - 'GS05': self.gs05, - 'GS06': lambda ele, data, stat: '{:d}'.format(data.functional_group_no), - 'GS07': lambda ele, data, stat: 'X', # ANSI X12 - 'GS08': lambda ele, data, stat: '004010', # Version 4010 - } - - @staticmethod - def gs04(ele, data, stat): - if data.submit_datetime: - datetime = dateutil.parser.parse(data.submit_datetime) - return datetime.strftime("%Y%m%d") - - return '' - - @staticmethod - def gs05(ele, data, stat): - if data.submit_datetime: - datetime = dateutil.parser.parse(data.submit_datetime) - return datetime.strftime('%H%M') - - return '' - - -class GeRenderer(SegmentRenderer): - grammar = GeSegment - - @property - def element_value_getters(self): - return { - 'GE01': lambda ele, data, stat: str(data.transaction_count), - 'GE02': lambda ele, data, stat: '{:d}'.format(data.functional_group_no), - } diff --git a/pyx12lib/common/interchange/interchange.py b/pyx12lib/common/interchange/interchange.py deleted file mode 100644 index 339c27b..0000000 --- a/pyx12lib/common/interchange/interchange.py +++ /dev/null @@ -1,63 +0,0 @@ -import dateutil.parser - -from pyx12lib.core.grammar.element import COMPONENT_DELIMITER -from pyx12lib.core.renderer import SegmentRenderer - -from .grammar import IeaSegment, IsaSegment - - -class IsaRenderer(SegmentRenderer): - grammar = IsaSegment - - @property - def element_value_getters(self): - return { - 'ISA01': lambda ele, data, stat: '00', # No Authorization Info Present - 'ISA02': lambda ele, data, stat: '{: <10}'.format(' '), - 'ISA03': lambda ele, data, stat: '00', # No Security Info Present - 'ISA04': lambda ele, data, stat: '{: <10}'.format(' '), - 'ISA05': lambda ele, data, stat: 'ZZ', # Sender ID Mutually Defined - 'ISA06': lambda ele, data, stat: '{: <{width}}'.format(data.sender_id, width=ele.minimum), - 'ISA07': lambda ele, data, stat: 'ZZ', # Receiver ID Mutually Defined - 'ISA08': lambda ele, data, stat: '{: <{width}}'.format(data.vendor_id, width=ele.minimum), - 'ISA09': self.isa09, - 'ISA10': self.isa10, - 'ISA11': lambda ele, data, stat: 'U', - 'ISA12': lambda ele, data, stat: '00401', - 'ISA13': self.isa13, - 'ISA14': lambda ele, data, stat: '1', # to request an interchange ack - 'ISA15': lambda ele, data, stat: 'P', # Production Data - 'ISA16': lambda ele, data, stat: COMPONENT_DELIMITER, - } - - @staticmethod - def isa09(ele, data, stat): - if data.submit_datetime: - datetime = dateutil.parser.parse(data.submit_datetime) - return datetime.strftime("%y%m%d") - - return '' - - @staticmethod - def isa10(ele, data, stat): - if data.submit_datetime: - datetime = dateutil.parser.parse(data.submit_datetime) - return datetime.strftime("%H%M") - - return '' - - def isa13(self, ele, data, stat): - div, mod = divmod(data.interchange_no, 999999999) # ISA Control Number is a 9 digits integer - control_no = mod if not div else mod + 1 - return '{:09d}'.format(control_no) - - -class IeaRenderer(SegmentRenderer): - grammar = IeaSegment - - @property - def element_value_getters(self): - return { - 'IEA01': lambda ele, data, stat: str(data.functional_group_count), - 'IEA02': lambda ele, data, stat: '{:09d}'.format(data.interchange_no), - } diff --git a/pyx12lib/common/interchange/transaction_set.py b/pyx12lib/common/interchange/transaction_set.py deleted file mode 100644 index bb5e35d..0000000 --- a/pyx12lib/common/interchange/transaction_set.py +++ /dev/null @@ -1,25 +0,0 @@ -from pyx12lib.core.renderer import SegmentRenderer - -from .grammar import SeSegment, StSegment - - -class StRenderer(SegmentRenderer): - grammar = StSegment - - @property - def element_value_getters(self): - return { - 'ST01': lambda ele, data, stat: '304', # Shipping Instruction - 'ST02': lambda ele, data, stat: '{:04d}'.format(data.transaction_set_no), - } - - -class SeRenderer(SegmentRenderer): - grammar = SeSegment - - @property - def element_value_getters(self): - return { - 'SE01': lambda ele, data, stat: str(data.segment_counts), - 'SE02': lambda ele, data, stat: '{:04d}'.format(data.transaction_set_no), - } From 07a6db0d1edb49ee0b6348ebbea2c30389d7ec19 Mon Sep 17 00:00:00 2001 From: Hoss Date: Tue, 3 May 2022 20:28:12 +0800 Subject: [PATCH 3/8] refactor(common): replace strings by constants --- .../interchange/grammar/functional_group.py | 91 ++++++----- .../common/interchange/grammar/interchange.py | 153 +++++++++--------- .../interchange/grammar/transaction_set.py | 21 ++- 3 files changed, 131 insertions(+), 134 deletions(-) diff --git a/pyx12lib/common/interchange/grammar/functional_group.py b/pyx12lib/common/interchange/grammar/functional_group.py index d5bb658..3002ea6 100644 --- a/pyx12lib/common/interchange/grammar/functional_group.py +++ b/pyx12lib/common/interchange/grammar/functional_group.py @@ -1,96 +1,95 @@ -from pyx12lib.core.grammar import Element -from pyx12lib.core.grammar.segment import USAGE_MANDATORY, BaseSegment +from pyx12lib.core.grammar import BaseSegment, Element, element, segment class GsSegment(BaseSegment): - segment_id = 'GS' - usage = 'M' + segment_id = "GS" + usage = segment.USAGE_MANDATORY max_use = 1 elements = ( Element( - reference_designator='GS01', - name='Functional Identifier Code', - usage=USAGE_MANDATORY, - element_type='ID', + reference_designator="GS01", + name="Functional Identifier Code", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=2, maximum=2, ), Element( - reference_designator='GS02', - name='Application Sender\'s Code', - usage=USAGE_MANDATORY, - element_type='AN', + reference_designator="GS02", + name="Application Sender's Code", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, minimum=2, maximum=15, ), Element( - reference_designator='GS03', - name='Application Receiver\'s Code', - usage=USAGE_MANDATORY, - element_type='AN', + reference_designator="GS03", + name="Application Receiver's Code", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, minimum=2, maximum=15, ), Element( - reference_designator='GS04', - name='Date', - usage=USAGE_MANDATORY, - element_type='DT', + reference_designator="GS04", + name="Date", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_DATE, minimum=8, maximum=8, ), Element( - reference_designator='GS05', - name='Time', - usage=USAGE_MANDATORY, - element_type='TM', + reference_designator="GS05", + name="Time", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_TIME, minimum=4, maximum=8, ), Element( - reference_designator='GS06', - name='Group Control Number', - usage=USAGE_MANDATORY, - element_type='N0', + reference_designator="GS06", + name="Group Control Number", + usage=element.USAGE_MANDATORY, + element_type=element.get_numeric_type(0), minimum=1, maximum=9, ), Element( - reference_designator='GS07', - name='Responsible Agency Code', - usage=USAGE_MANDATORY, - element_type='ID', + reference_designator="GS07", + name="Responsible Agency Code", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=1, maximum=2, ), Element( - reference_designator='GS08', - name='Version / Release / Industry Identifier Code', - usage=USAGE_MANDATORY, - element_type='AN', + reference_designator="GS08", + name="Version / Release / Industry Identifier Code", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, minimum=1, maximum=12, ), ) -class GeSegment(BaseSegment): - segment_id = 'GE' +class GeSegment(segment.BaseSegment): + segment_id = "GE" max_use = 1 elements = ( Element( - reference_designator='GE01', - name='VNumber of Transaction Sets Included', - usage=USAGE_MANDATORY, - element_type='N0', + reference_designator="GE01", + name="Number of Transaction Sets Included", + usage=element.USAGE_MANDATORY, + element_type=element.get_numeric_type(0), minimum=1, maximum=6, ), Element( - reference_designator='GE02', - name='Group Control Number', - usage=USAGE_MANDATORY, - element_type='N0', + reference_designator="GE02", + name="Group Control Number", + usage=element.USAGE_MANDATORY, + element_type=element.get_numeric_type(0), minimum=1, maximum=9, ), diff --git a/pyx12lib/common/interchange/grammar/interchange.py b/pyx12lib/common/interchange/grammar/interchange.py index 342dd11..498d15e 100644 --- a/pyx12lib/common/interchange/grammar/interchange.py +++ b/pyx12lib/common/interchange/grammar/interchange.py @@ -1,137 +1,136 @@ -from pyx12lib.core.grammar import Element -from pyx12lib.core.grammar.segment import USAGE_MANDATORY, BaseSegment +from pyx12lib.core.grammar import BaseSegment, Element, element, segment class IsaSegment(BaseSegment): - segment_id = 'ISA' - usage = 'M' + segment_id = "ISA" + usage = segment.USAGE_MANDATORY max_use = 1 elements = ( Element( - reference_designator='ISA01', - name='Authorization Information Qualifier', - usage=USAGE_MANDATORY, - element_type='ID', + reference_designator="ISA01", + name="Authorization Information Qualifier", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=2, maximum=2, ), Element( - reference_designator='ISA02', - name='Authorization Information', - usage=USAGE_MANDATORY, - element_type='AN', + reference_designator="ISA02", + name="Authorization Information", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, minimum=10, maximum=10, ), Element( - reference_designator='ISA03', - name='Security Information Qualifier', - usage=USAGE_MANDATORY, - element_type='ID', + reference_designator="ISA03", + name="Security Information Qualifier", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=2, maximum=2, ), Element( - reference_designator='ISA04', - name='Security Information', - usage=USAGE_MANDATORY, - element_type='AN', + reference_designator="ISA04", + name="Security Information", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, minimum=10, maximum=10, ), Element( - reference_designator='ISA05', - name='Interchange ID Qualifier', - usage=USAGE_MANDATORY, - element_type='ID', + reference_designator="ISA05", + name="Interchange ID Qualifier", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=2, maximum=2, ), Element( - reference_designator='ISA06', - name='Interchange Sender ID', - usage=USAGE_MANDATORY, - element_type='AN', + reference_designator="ISA06", + name="Interchange Sender ID", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, minimum=15, maximum=15, ), Element( - reference_designator='ISA07', - name='Interchange ID Qualifier', - usage=USAGE_MANDATORY, - element_type='ID', + reference_designator="ISA07", + name="Interchange ID Qualifier", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=2, maximum=2, ), Element( - reference_designator='ISA08', - name='Interchange Receiver ID', - usage=USAGE_MANDATORY, - element_type='AN', + reference_designator="ISA08", + name="Interchange Receiver ID", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, minimum=15, maximum=15, ), Element( - reference_designator='ISA09', - name='Interchange Date', - usage=USAGE_MANDATORY, - element_type='DT', + reference_designator="ISA09", + name="Interchange Date", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_DATE, minimum=6, maximum=6, ), Element( - reference_designator='ISA10', - name='Interchange Time', - usage=USAGE_MANDATORY, - element_type='TM', + reference_designator="ISA10", + name="Interchange Time", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_TIME, minimum=4, maximum=4, ), Element( - reference_designator='ISA11', - name='Interchange Control Standards Identifier', - usage=USAGE_MANDATORY, - element_type='ID', + reference_designator="ISA11", + name="Interchange Control Standards Identifier", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=1, maximum=1, ), Element( - reference_designator='ISA12', - name='Interchange Control Version Number', - usage=USAGE_MANDATORY, - element_type='ID', + reference_designator="ISA12", + name="Interchange Control Version Number", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=5, maximum=5, ), Element( - reference_designator='ISA13', - name='Interchange Control Number', - usage=USAGE_MANDATORY, - element_type='N0', + reference_designator="ISA13", + name="Interchange Control Number", + usage=element.USAGE_MANDATORY, + element_type=element.get_numeric_type(0), minimum=9, maximum=9, ), Element( - reference_designator='ISA14', - name='Acknowledgment Requested', - usage=USAGE_MANDATORY, - element_type='ID', + reference_designator="ISA14", + name="Acknowledgment Requested", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=1, maximum=1, ), Element( - reference_designator='ISA15', - name='Usage Indicator', - usage=USAGE_MANDATORY, - element_type='ID', + reference_designator="ISA15", + name="Usage Indicator", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=1, maximum=1, ), Element( - reference_designator='ISA16', - name='Component Element Separator', - usage=USAGE_MANDATORY, - element_type='AN', + reference_designator="ISA16", + name="Component Element Separator", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, minimum=1, maximum=1, ), @@ -139,22 +138,22 @@ class IsaSegment(BaseSegment): class IeaSegment(BaseSegment): - segment_id = 'IEA' + segment_id = "IEA" max_use = 1 elements = ( Element( - reference_designator='IEA01', - name='Number of Included Functional Groups', - usage=USAGE_MANDATORY, - element_type='N0', + reference_designator="IEA01", + name="Number of Included Functional Groups", + usage=element.USAGE_MANDATORY, + element_type=element.get_numeric_type(0), minimum=1, maximum=5, ), Element( - reference_designator='IEA02', - name='Interchange Control Number', - usage=USAGE_MANDATORY, - element_type='N0', + reference_designator="IEA02", + name="Interchange Control Number", + usage=element.USAGE_MANDATORY, + element_type=element.get_numeric_type(0), minimum=9, maximum=9, ), diff --git a/pyx12lib/common/interchange/grammar/transaction_set.py b/pyx12lib/common/interchange/grammar/transaction_set.py index a5c5c82..151d9f1 100644 --- a/pyx12lib/common/interchange/grammar/transaction_set.py +++ b/pyx12lib/common/interchange/grammar/transaction_set.py @@ -1,25 +1,24 @@ -from pyx12lib.core.grammar import Element -from pyx12lib.core.grammar.segment import USAGE_MANDATORY, BaseSegment +from pyx12lib.core.grammar import Element, BaseSegment, segment, element class StSegment(BaseSegment): segment_id = 'ST' - usage = 'M' + usage = segment.USAGE_MANDATORY max_use = 1 elements = ( Element( reference_designator='ST01', name='Transaction Set Identifier Code', - usage=USAGE_MANDATORY, - element_type='ID', + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, minimum=3, maximum=3, ), Element( reference_designator='ST02', name='Transaction Set Control Number', - usage=USAGE_MANDATORY, - element_type='AN', + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, minimum=4, maximum=9, ), @@ -33,16 +32,16 @@ class SeSegment(BaseSegment): Element( reference_designator='SE01', name='Number of Included Segments', - usage=USAGE_MANDATORY, - element_type='N0', + usage=element.USAGE_MANDATORY, + element_type=element.get_numeric_type(0), minimum=1, maximum=10, ), Element( reference_designator='SE02', name='Transaction Set Control Number', - usage=USAGE_MANDATORY, - element_type='AN', + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, minimum=4, maximum=9, ), From 70acd99ce8583d3218b1ff990ca49dedaaa9d2d5 Mon Sep 17 00:00:00 2001 From: Hoss Date: Tue, 3 May 2022 20:47:22 +0800 Subject: [PATCH 4/8] test(core): add test case for renderers --- pyx12lib/core/grammar/element.py | 1 + pyx12lib/core/tests/__init__.py | 0 .../tests/test_composite_segment_renderer.py | 144 ++++++++++++++++ pyx12lib/core/tests/test_segment_renderer.py | 158 ++++++++++++++++++ .../core/tests/test_segment_renderer_loop.py | 139 +++++++++++++++ 5 files changed, 442 insertions(+) create mode 100644 pyx12lib/core/tests/__init__.py create mode 100644 pyx12lib/core/tests/test_composite_segment_renderer.py create mode 100644 pyx12lib/core/tests/test_segment_renderer.py create mode 100644 pyx12lib/core/tests/test_segment_renderer_loop.py diff --git a/pyx12lib/core/grammar/element.py b/pyx12lib/core/grammar/element.py index f13a723..cafffd2 100644 --- a/pyx12lib/core/grammar/element.py +++ b/pyx12lib/core/grammar/element.py @@ -10,6 +10,7 @@ ELEMENT_TYPE_TIME = 'TM' ELEMENT_TYPE_NUMERIC = 'N' ELEMENT_TYPE_DECIMAL = 'R' +ELEMENT_TYPE_COMPOSITE = '' def get_numeric_type(max_digits): diff --git a/pyx12lib/core/tests/__init__.py b/pyx12lib/core/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyx12lib/core/tests/test_composite_segment_renderer.py b/pyx12lib/core/tests/test_composite_segment_renderer.py new file mode 100644 index 0000000..7420e0a --- /dev/null +++ b/pyx12lib/core/tests/test_composite_segment_renderer.py @@ -0,0 +1,144 @@ +from unittest import TestCase + +from pyx12lib.core import exceptions +from pyx12lib.core.grammar import BaseSegment, Element, NotUsedElement, element, segment, CompositeElement, Component +from pyx12lib.core.renderer import ComponentSegmentRenderer, SegmentRenderer + + +class _TestCompositeSegment(BaseSegment): + segment_id = "TEST" + usage = segment.USAGE_MANDATORY + max_use = 1 + elements = ( + NotUsedElement(reference_designator="TEST01"), + Element( + reference_designator="TEST02", + name="Test Element", + usage=element.USAGE_OPTIONAL, + element_type=element.ELEMENT_TYPE_STRING, + minimum=2, + maximum=3, + ), + CompositeElement( + reference_designator='TEST03', + name='Composite Element', + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_COMPOSITE, + minimum=1, + maximum=35, + components=( + Component( + reference_designator='C04001', + name='Composite Test 1', + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, + minimum=1, + maximum=1, + ), + Component( + reference_designator='C04002', + name='Composite Test 1', + usage=element.USAGE_OPTIONAL, + element_type=element.ELEMENT_TYPE_STRING, + minimum=1, + maximum=5, + ), + ), + ), + Element( + reference_designator="TEST04", + name="Test Element", + usage=element.USAGE_OPTIONAL, + element_type=element.ELEMENT_TYPE_STRING, + minimum=1, + maximum=30, + ), + ) + + +class TestComponentSegmentRenderer(TestCase): + def test_test_mandatory_composite_element_raises_error(self): + # arrange + data = {} + + class _TestComponentSegmentRenderer(ComponentSegmentRenderer): + grammar = _TestCompositeSegment + + @property + def element_value_getters(self): + return { + 'TEST03': self.test03, + } + + def test03(self, ele, data, stat): + value_getters = { + 'C04001': lambda _comp, _data, _stat: '', + 'C04002': lambda _comp, _data, _stat: 'TEST', + } + + return self.get_component_values(ele, data, stat, value_getters) + + # action & assert + renderer = _TestComponentSegmentRenderer(data) + with self.assertRaises(exceptions.MandatoryComponentException): + renderer.render() + + def test_test_mandatory_composite_component_raises_error(self): + # arrange + data = {} + + class _TestComponentSegmentRenderer(ComponentSegmentRenderer): + grammar = _TestCompositeSegment + + @property + def element_value_getters(self): + return { + 'TEST03': self.test03, + } + + def test03(self, ele, data, stat): + value_getters = { + 'C04001': lambda _comp, _data, _stat: '', + 'C04002': lambda _comp, _data, _stat: '', + } + + return self.get_component_values(ele, data, stat, value_getters) + + # action & assert + renderer = _TestComponentSegmentRenderer(data) + with self.assertRaises(exceptions.MandatoryCompositeElementException): + renderer.render() + + def test_composite_element(self): + # arrange + expect_data = ( + 'TEST***A^BCD*TRAILING ELE~' + ) + data = { + "test_data_composite": ['A', 'BCD'], + } + + class _TestComponentSegmentRenderer(ComponentSegmentRenderer): + grammar = _TestCompositeSegment + + @property + def element_value_getters(self): + return { + 'TEST03': self.test03, + 'TEST04': lambda ele, data, stat: 'TRAILING ELE', + } + + def test03(self, ele, data, stat): + value_getters = { + 'C04001': lambda _comp, _data, _stat: _data['test_data_composite'][0], + 'C04002': lambda _comp, _data, _stat: _data['test_data_composite'][1], + } + + return self.get_component_values(ele, data, stat, value_getters) + + # action + renderer = _TestComponentSegmentRenderer(data) + result = renderer.render() + + # assert + self.assertEqual(expect_data, result) diff --git a/pyx12lib/core/tests/test_segment_renderer.py b/pyx12lib/core/tests/test_segment_renderer.py new file mode 100644 index 0000000..22f4fe1 --- /dev/null +++ b/pyx12lib/core/tests/test_segment_renderer.py @@ -0,0 +1,158 @@ +from unittest import TestCase + +from pyx12lib.core import exceptions +from pyx12lib.core.grammar import BaseSegment, Element, NotUsedElement, element, segment +from pyx12lib.core.renderer import SegmentRenderer + + +class _TestSegment(BaseSegment): + segment_id = "TEST" + usage = segment.USAGE_MANDATORY + max_use = 1 + elements = ( + NotUsedElement(reference_designator="TEST01"), + Element( + reference_designator="TEST02", + name="Test Element", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, + minimum=2, + maximum=3, + ), + Element( + reference_designator="TEST03", + name="Test Element", + usage=element.USAGE_OPTIONAL, + element_type=element.ELEMENT_TYPE_STRING, + minimum=1, + maximum=1, + ), + Element( + reference_designator="TEST04", + name="Test Element", + usage=element.USAGE_OPTIONAL, + element_type=element.ELEMENT_TYPE_STRING, + minimum=1, + maximum=1, + ), + ) + + +class TestSegmentRenderer(TestCase): + def test_mandatory_segment_raises_error(self): + # arrange + data = { + "test_data": "", + } + + class _TestSegmentRenderer(SegmentRenderer): + grammar = _TestSegment + element_value_getters = { + "TEST02": lambda ele, data, stat: data["test_data"], + } + + # action & assert + renderer = _TestSegmentRenderer(data) + with self.assertRaises(exceptions.MandatorySegmentException): + renderer.render() + + def test_mandatory_element_raises_error(self): + # arrange + data = { + "test_data": "", + } + + class _TestSegmentRenderer(SegmentRenderer): + grammar = _TestSegment + element_value_getters = { + "TEST02": lambda ele, data, stat: data["test_data"], + "TEST03": lambda ele, data, stat: "TEST 03", + } + + # action & assert + renderer = _TestSegmentRenderer(data) + with self.assertRaises(exceptions.MandatoryElementException): + renderer.render() + + def test_element_too_short_raises_error(self): + # arrange + data = { + "test_data": "1", + } + + class _TestSegmentRenderer(SegmentRenderer): + grammar = _TestSegment + element_value_getters = { + "TEST02": lambda ele, data, stat: data["test_data"], + } + + # action & assert + renderer = _TestSegmentRenderer(data) + with self.assertRaises(exceptions.LengthException): + renderer.render() + + def test_element_too_long_raises_error(self): + # arrange + data = { + "test_data": "1234", + } + + class _TestSegmentRenderer(SegmentRenderer): + grammar = _TestSegment + element_value_getters = { + "TEST02": lambda ele, data, stat: data["test_data"], + } + + # action & assert + renderer = _TestSegmentRenderer(data) + with self.assertRaises(exceptions.LengthException): + renderer.render() + + def test_renderer_with_complex_value_getter(self): + # arrange + expect_result = "TEST**000~" + data = { + "test_data": 0, + } + + class _TestSegmentRenderer(SegmentRenderer): + grammar = _TestSegment + + @property + def element_value_getters(self): + return { + "TEST02": self.test002, + } + + @staticmethod + def test002(ele, data, stat): + return f"{data['test_data']:03d}" + + # action + renderer = _TestSegmentRenderer(data) + result = renderer.render() + + # assert + self.assertEqual(expect_result, result) + + def test_omit_empty_trailing_elements(self): + # arrange + expect_result = "TEST**12~" + data = { + "test_data": "12", + } + + class _TestSegmentRenderer(SegmentRenderer): + grammar = _TestSegment + element_value_getters = { + "TEST02": lambda ele, data, stat: data["test_data"], + "TEST03": lambda ele, data, stat: "", + "TEST04": lambda ele, data, stat: "", + } + + # action + renderer = _TestSegmentRenderer(data) + result = renderer.render() + + # assert + self.assertEqual(expect_result, result) diff --git a/pyx12lib/core/tests/test_segment_renderer_loop.py b/pyx12lib/core/tests/test_segment_renderer_loop.py new file mode 100644 index 0000000..6496da7 --- /dev/null +++ b/pyx12lib/core/tests/test_segment_renderer_loop.py @@ -0,0 +1,139 @@ +from unittest import TestCase + +from pyx12lib.core.grammar import BaseSegment, Element, NotUsedElement, element, segment +from pyx12lib.core.renderer import SegmentRenderer, SegmentRendererLoop + + +class _TestSegment(BaseSegment): + segment_id = "TEST" + usage = segment.USAGE_MANDATORY + max_use = 1 + elements = ( + NotUsedElement(reference_designator="TEST01"), + Element( + reference_designator="TEST02", + name="Test Element", + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, + minimum=2, + maximum=3, + ), + Element( + reference_designator="TEST03", + name="Test Element", + usage=element.USAGE_OPTIONAL, + element_type=element.ELEMENT_TYPE_STRING, + minimum=1, + maximum=1, + ), + Element( + reference_designator="TEST04", + name="Test Element", + usage=element.USAGE_OPTIONAL, + element_type=element.ELEMENT_TYPE_STRING, + minimum=1, + maximum=1, + ), + ) + + +class _Test2Segment(BaseSegment): + segment_id = "TEST2" + usage = segment.USAGE_MANDATORY + max_use = 1 + elements = ( + Element( + reference_designator="TEST201", + name="Test Element", + usage=element.USAGE_OPTIONAL, + element_type=element.ELEMENT_TYPE_STRING, + minimum=1, + maximum=1, + ), + ) + + +class TestSegmentRendererLoop(TestCase): + def test_default_data_list_for_renderer_loop(self): + # arrange + expect_result = "TEST**11~" "TEST2*2~" + data = { + "test_data_1": "11", + "test_data_2": "2", + } + + class _TestSegmentRenderer(SegmentRenderer): + grammar = _TestSegment + element_value_getters = { + "TEST02": lambda ele, data, stat: data["test_data_1"], + } + + class _Test2SegmentRenderer(SegmentRenderer): + grammar = _Test2Segment + element_value_getters = { + "TEST201": lambda ele, data, stat: data["test_data_2"], + } + + class _TestSegmentRendererLoop(SegmentRendererLoop): + loop_id = "TEST" + renderer_class_list = [ + _TestSegmentRenderer, + _Test2SegmentRenderer, + ] + + # action + renderer = _TestSegmentRendererLoop(data) + result = renderer.render() + + # assert + self.assertEqual(expect_result, result) + + def test_customized_data_list_for_renderer_loop(self): + # arrange + expect_result = "TEST**AA~" "TEST2*1~" "TEST**BB~" "TEST2*2~" + data = { + "test_data_1": ["AA", "1"], + "test_data_2": ["BB", "2"], + } + + class _TestSegmentRenderer(SegmentRenderer): + grammar = _TestSegment + element_value_getters = { + "TEST02": lambda ele, data, stat: data.test_data_1, + } + + class _Test2SegmentRenderer(SegmentRenderer): + grammar = _Test2Segment + element_value_getters = { + "TEST201": lambda ele, data, stat: data.test_data_2, + } + + class _TestSegmentRendererLoop(SegmentRendererLoop): + loop_id = "TEST" + renderer_class_list = [ + _TestSegmentRenderer, + _Test2SegmentRenderer, + ] + + def preprocess_data(self, data): + data_list = [] + data_list.append( + self.build_data( + test_data_1=data["test_data_1"][0], + test_data_2=data["test_data_1"][1], + ) + ) + data_list.append( + self.build_data( + test_data_1=data["test_data_2"][0], + test_data_2=data["test_data_2"][1], + ) + ) + return data_list + + # action + renderer = _TestSegmentRendererLoop(data) + result = renderer.render() + + # assert + self.assertEqual(expect_result, result) From d531a990eeffba6bfdc00b2d179ece24347b60a8 Mon Sep 17 00:00:00 2001 From: Hoss Date: Wed, 4 May 2022 21:48:08 +0800 Subject: [PATCH 5/8] feat(core): render to intermediate objects [why] prepare a medium to contain parsed data in the future --- pyx12lib/core/base.py | 153 ++++++++++++++++++++++++++++++++++++++ pyx12lib/core/renderer.py | 146 +++++++++--------------------------- 2 files changed, 188 insertions(+), 111 deletions(-) create mode 100644 pyx12lib/core/base.py diff --git a/pyx12lib/core/base.py b/pyx12lib/core/base.py new file mode 100644 index 0000000..6629346 --- /dev/null +++ b/pyx12lib/core/base.py @@ -0,0 +1,153 @@ +import six + +from pyx12lib.core import exceptions +from pyx12lib.core.grammar import element, segment + + +class RenderedSegment: + def __init__(self, grammar, elements, segment_terminator, element_delimiter): + self._grammar = grammar + self._elements = elements + self._segment_terminator = segment_terminator + self._element_delimiter = element_delimiter + + def is_valid(self): + # Check empty segment + if self.is_empty(): + if self._grammar.usage == segment.USAGE_MANDATORY: + raise exceptions.MandatorySegmentException(self._grammar) + if self._grammar.usage in (segment.USAGE_OPTIONAL, segment.USAGE_CONDITIONAL): + return True + + # Check each element + return all(ele.is_valid() for ele in self._elements) + + def is_empty(self): + return all(e.is_empty() for e in self._elements) + + def to_string(self): + ele_values = self._element_delimiter.join( + (self._grammar.segment_id, *(e.to_string() for e in self._elements)) + ) + return ele_values.rstrip(self._element_delimiter) + self._segment_terminator + + +class RenderedElement: + def __init__(self, ele, value): + self._element = ele + self._value = value + + def is_valid(self): + ele = self._element + value = self.to_string() + + # Skip NotUsedElement + if isinstance(ele, element.NotUsedElement): + if value != '': + raise exceptions.NotUsedElementException(ele, value) + return True + + # Check string type + if not isinstance(value, six.string_types): + raise exceptions.NotStringException(ele, value) + + # Check mandatory + if ele.usage == element.USAGE_MANDATORY and value == '': + raise exceptions.MandatoryElementException(ele, value) + + if value != '': + # Check width + if not (ele.minimum <= len(value) <= ele.maximum): + raise exceptions.LengthException(ele, value) + + # Check type + if ele.type == element.ELEMENT_TYPE_DECIMAL or ele.type.startswith(element.ELEMENT_TYPE_NUMERIC): + try: + float(value) + except ValueError: + raise exceptions.NotDecimalError(ele, value) + + if ele.type.startswith(element.ELEMENT_TYPE_NUMERIC): + decimal_places = int(ele.type[1]) + try: + if len(value.split('.')[1]) != decimal_places: + raise exceptions.DecimalPlaceNotMatchError(ele, value) + except IndexError: + if decimal_places > 0: + raise exceptions.DecimalPlaceNotMatchError(ele, value) + + return True + + def is_empty(self): + return not bool(self._value) + + def to_string(self): + return self._value + + +class RenderedCompositeElement: + def __init__(self, composite_ele, components, component_delimiter): + self._composite_element = composite_ele + self._components = components + self._component_delimiter = component_delimiter + + def is_valid(self): + # Check empty composite element + if self.is_empty(): + if self._composite_element.usage == element.USAGE_MANDATORY: + raise exceptions.MandatoryCompositeElementException( + self._composite_element + ) + if self._composite_element.usage in ( + element.USAGE_OPTIONAL, + element.USAGE_CONDITIONAL, + ): + return True + + # Check each component + return all(comp.is_valid() for comp in self._components) + + def is_empty(self): + return all(comp.is_empty() for comp in self._components) + + def to_string(self): + return self._component_delimiter.join( + (comp.to_string() for comp in self._components) + ).rstrip(self._component_delimiter) + + +class RenderedComponent: + def __init__(self, component, value): + self._component = component + self._value = value + + def is_valid(self): + component = self._component + value = self.to_string() + + # Skip NotUsedElement + if isinstance(component, element.NotUsedElement): + if value != '': + raise exceptions.NotUsedElementException(component, value) + return True + + # Check data type + if not isinstance(value, six.string_types): + raise exceptions.NotStringException(component, value) + + # Check mandatory + if component.usage == element.USAGE_MANDATORY and value == '': + raise exceptions.MandatoryComponentException(component, value) + + if value != '': + # Check width + if not (component.minimum <= len(value) <= component.maximum): + raise exceptions.LengthException(component, value) + + return True + + def is_empty(self): + return not bool(self._value) + + def to_string(self): + return self._value diff --git a/pyx12lib/core/renderer.py b/pyx12lib/core/renderer.py index dfc40ae..832f426 100644 --- a/pyx12lib/core/renderer.py +++ b/pyx12lib/core/renderer.py @@ -4,10 +4,9 @@ import six from pyx12lib.core import exceptions +from pyx12lib.core.base import RenderedElement, RenderedSegment, RenderedComponent, RenderedCompositeElement from pyx12lib.core.grammar.element import ( COMPONENT_DELIMITER, - ELEMENT_TYPE_DECIMAL, - ELEMENT_TYPE_NUMERIC, NotUsedElement, ) from pyx12lib.core.grammar.segment import ( @@ -44,7 +43,7 @@ class SegmentRenderer(BaseSegmentRenderer): grammar = None element_value_getters = None - _element_values = None + _rendered_segment = None def __init__(self, data, **kwargs): super(SegmentRenderer, self).__init__(**kwargs) @@ -56,138 +55,63 @@ def get_element_value_getter(self, ref_des): raise NotImplementedError('element_value_getters should be defined.') return self.element_value_getters.get(ref_des, NotUsedElement.value_getter) - @staticmethod - def _is_element_valid(element, value): - # Skip NotUsedElement - if isinstance(element, NotUsedElement): - if value != '': - raise exceptions.NotUsedElementException(element, value) - return True - - # Check string type - if not isinstance(value, six.string_types): - raise exceptions.NotStringException(element, value) - - # Check mandatory - if element.usage == USAGE_MANDATORY and value == '': - raise exceptions.MandatoryElementException(element, value) - - if value != '': - # Check width - if not (element.minimum <= len(value) <= element.maximum): - raise exceptions.LengthException(element, value) - - # Check type - if element.type == ELEMENT_TYPE_DECIMAL or element.type.startswith(ELEMENT_TYPE_NUMERIC): - try: - float(value) - except ValueError: - raise exceptions.NotDecimalError(element, value) - - if element.type.startswith(ELEMENT_TYPE_NUMERIC): - decimal_places = int(element.type[1]) - try: - if len(value.split('.')[1]) != decimal_places: - raise exceptions.DecimalPlaceNotMatchError(element, value) - except IndexError: - if decimal_places > 0: - raise exceptions.DecimalPlaceNotMatchError(element, value) - - return True - - def is_valid(self): - # Check empty segment - if not any(self._element_values.values()): - if self.grammar.usage == USAGE_MANDATORY: - raise exceptions.MandatorySegmentException(self.grammar) - if self.grammar.usage in (USAGE_OPTIONAL, USAGE_CONDITIONAL): - return True - - # Check each elements - return all( - self._is_element_valid(ele, self._element_values[ele.reference_designator]) for ele in self.grammar.elements - ) + def _get_rendered_segment(self): + if self._rendered_segment is None: + self.build() + return self._rendered_segment def count(self): - return 1 if any(self._element_values.values()) else 0 + return 1 if not self._get_rendered_segment().is_empty() else 0 def build(self): - self._element_values = collections.OrderedDict() + render_stat = collections.OrderedDict() + elements = [] for ele in self.grammar.elements: - ref_des = ele.reference_designator - value_getter = self.get_element_value_getter(ref_des) - self._element_values[ref_des] = value_getter(ele, self._data, self._element_values) - - return self._element_values + value = self.get_element_value_getter(ele.reference_designator)(ele, self._data, render_stat) + render_stat[ele.reference_designator] = value + elements.append(RenderedElement(ele=ele, value=value)) + + self._rendered_segment = RenderedSegment( + grammar=self.grammar, + elements=elements, + segment_terminator=self._segment_terminator, + element_delimiter=self._element_delimiter, + ) def render(self): - ele_values_dict = self._element_values if self._element_values is not None else self.build() + rendered_segment = self._get_rendered_segment() - self.is_valid() + rendered_segment.is_valid() - if not any(ele_values_dict.values()): + if rendered_segment.is_empty(): return '' - ele_values_list = list(ele_values_dict.values()) - ele_values_list.insert(0, self.grammar.segment_id) - segment_value = ( - self._element_delimiter.join(ele_values_list).rstrip(self._element_delimiter) + self._segment_terminator - ) - - return segment_value + return rendered_segment.to_string() class ComponentSegmentRenderer(SegmentRenderer): - def is_composite_element_valid(self, element, comp_values_tuple): - # Check empty composite element - if not any(val for ele, val in comp_values_tuple): - if element.usage == USAGE_MANDATORY: - raise exceptions.MandatoryCompositeElementException(element) - if element.usage in (USAGE_OPTIONAL, USAGE_CONDITIONAL): - return True - - # Check each components - return all(self.is_component_valid(element, value) for element, value in comp_values_tuple) - - @staticmethod - def is_component_valid(component, value): - # Skip NotUsedElement - if isinstance(component, NotUsedElement): - if value != '': - raise exceptions.NotUsedElementException(component, value) - return True - - # Check data type - if not isinstance(value, six.string_types): - raise exceptions.NotStringException(component, value) - - # Check mandatory - if component.usage == USAGE_MANDATORY and value == '': - raise exceptions.MandatoryComponentException(component, value) - - if value != '': - # Check width - if not (component.minimum <= len(value) <= component.maximum): - raise exceptions.LengthException(component, value) - - return True - def get_component_values(self, ele, data, stat, value_getters): local_stat = copy.deepcopy(stat) comp_values = {} + components = [] for comp in ele.components: - ref_des = comp.reference_designator - value_getter = value_getters.get(ref_des, NotUsedElement.value_getter) - comp_values[ref_des] = value_getter(comp, data, local_stat) + value = value_getters.get(comp.reference_designator, NotUsedElement.value_getter)(comp, data, local_stat) + comp_values[comp.reference_designator] = value + components.append(RenderedComponent(component=comp, value=value)) local_stat.update(comp_values) - comp_values_list = [comp_values[comp.reference_designator] for comp in ele.components] - self.is_composite_element_valid(ele, list(zip(ele.components, comp_values_list))) + composite_ele = RenderedCompositeElement( + composite_ele=ele, + components=components, + component_delimiter=self._component_delimiter, + ) + + composite_ele.is_valid() - return self._component_delimiter.join(comp_values_list).rstrip(self._component_delimiter) + return composite_ele.to_string() class SegmentRendererLoop(BaseSegmentRenderer): From 916416450d91f20d88a4d62ac9d30458972e4522 Mon Sep 17 00:00:00 2001 From: Hoss Date: Fri, 6 May 2022 18:29:00 +0800 Subject: [PATCH 6/8] refactor(core): rename the base renderer class [why] to make the name match its purpose --- pyx12lib/core/renderer.py | 2 +- .../tests/test_composite_segment_renderer.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyx12lib/core/renderer.py b/pyx12lib/core/renderer.py index 832f426..9924b37 100644 --- a/pyx12lib/core/renderer.py +++ b/pyx12lib/core/renderer.py @@ -90,7 +90,7 @@ def render(self): return rendered_segment.to_string() -class ComponentSegmentRenderer(SegmentRenderer): +class CompositeElementRenderer(SegmentRenderer): def get_component_values(self, ele, data, stat, value_getters): local_stat = copy.deepcopy(stat) diff --git a/pyx12lib/core/tests/test_composite_segment_renderer.py b/pyx12lib/core/tests/test_composite_segment_renderer.py index 7420e0a..6d49e0f 100644 --- a/pyx12lib/core/tests/test_composite_segment_renderer.py +++ b/pyx12lib/core/tests/test_composite_segment_renderer.py @@ -2,7 +2,7 @@ from pyx12lib.core import exceptions from pyx12lib.core.grammar import BaseSegment, Element, NotUsedElement, element, segment, CompositeElement, Component -from pyx12lib.core.renderer import ComponentSegmentRenderer, SegmentRenderer +from pyx12lib.core.renderer import CompositeElementRenderer, SegmentRenderer class _TestCompositeSegment(BaseSegment): @@ -56,12 +56,12 @@ class _TestCompositeSegment(BaseSegment): ) -class TestComponentSegmentRenderer(TestCase): +class TestCompositeElementRenderer(TestCase): def test_test_mandatory_composite_element_raises_error(self): # arrange data = {} - class _TestComponentSegmentRenderer(ComponentSegmentRenderer): + class _TestCompositeElementRenderer(CompositeElementRenderer): grammar = _TestCompositeSegment @property @@ -79,7 +79,7 @@ def test03(self, ele, data, stat): return self.get_component_values(ele, data, stat, value_getters) # action & assert - renderer = _TestComponentSegmentRenderer(data) + renderer = _TestCompositeElementRenderer(data) with self.assertRaises(exceptions.MandatoryComponentException): renderer.render() @@ -87,7 +87,7 @@ def test_test_mandatory_composite_component_raises_error(self): # arrange data = {} - class _TestComponentSegmentRenderer(ComponentSegmentRenderer): + class _TestCompositeElementRenderer(CompositeElementRenderer): grammar = _TestCompositeSegment @property @@ -105,7 +105,7 @@ def test03(self, ele, data, stat): return self.get_component_values(ele, data, stat, value_getters) # action & assert - renderer = _TestComponentSegmentRenderer(data) + renderer = _TestCompositeElementRenderer(data) with self.assertRaises(exceptions.MandatoryCompositeElementException): renderer.render() @@ -118,7 +118,7 @@ def test_composite_element(self): "test_data_composite": ['A', 'BCD'], } - class _TestComponentSegmentRenderer(ComponentSegmentRenderer): + class _TestCompositeElementRenderer(CompositeElementRenderer): grammar = _TestCompositeSegment @property @@ -137,7 +137,7 @@ def test03(self, ele, data, stat): return self.get_component_values(ele, data, stat, value_getters) # action - renderer = _TestComponentSegmentRenderer(data) + renderer = _TestCompositeElementRenderer(data) result = renderer.render() # assert From a36290845f647cbb17ec0cc543c3ff6ca3899eae Mon Sep 17 00:00:00 2001 From: Hoss Date: Fri, 6 May 2022 18:30:13 +0800 Subject: [PATCH 7/8] refactor(core): remove improper const def. --- pyx12lib/core/renderer.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pyx12lib/core/renderer.py b/pyx12lib/core/renderer.py index 9924b37..4bd0c11 100644 --- a/pyx12lib/core/renderer.py +++ b/pyx12lib/core/renderer.py @@ -1,9 +1,6 @@ import collections import copy -import six - -from pyx12lib.core import exceptions from pyx12lib.core.base import RenderedElement, RenderedSegment, RenderedComponent, RenderedCompositeElement from pyx12lib.core.grammar.element import ( COMPONENT_DELIMITER, @@ -11,15 +8,9 @@ ) from pyx12lib.core.grammar.segment import ( ELEMENT_DELIMITER, - SEGMENT_TERMINATOR, - USAGE_CONDITIONAL, - USAGE_MANDATORY, - USAGE_OPTIONAL, + SEGMENT_TERMINATOR ) -WEIGHT_MAX_DIGITS = 3 -MEASURE_MAX_DIGITS = 4 - class BaseSegmentRenderer(object): def __init__( From c4236396a0f86a9af128583427691ea9db9785e1 Mon Sep 17 00:00:00 2001 From: Hoss Date: Fri, 6 May 2022 18:31:30 +0800 Subject: [PATCH 8/8] doc: add sample code to README --- README.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/README.md b/README.md index 24e66e1..3cddef8 100644 --- a/README.md +++ b/README.md @@ -1 +1,77 @@ # pyx12-lib + +--- + +## Quick Example + +### Rendering +* Define the grammar for the segment. +```python +from pyx12lib.core.grammar import Element, BaseSegment, segment, element + + +class StSegment(BaseSegment): + segment_id = 'ST' + usage = segment.USAGE_MANDATORY + max_use = 1 + elements = ( + Element( + reference_designator='ST01', + name='Transaction Set Identifier Code', + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_ID, + minimum=3, + maximum=3, + ), + Element( + reference_designator='ST02', + name='Transaction Set Control Number', + usage=element.USAGE_MANDATORY, + element_type=element.ELEMENT_TYPE_STRING, + minimum=4, + maximum=9, + ), + ) +``` +* Render the segment from its grammar. +```python +from pyx12lib.common.interchange.grammar import StSegment +from pyx12lib.core.renderer import SegmentRenderer + + +class StRenderer(SegmentRenderer): + grammar = StSegment + + element_value_getters = { + 'ST01': lambda ele, data, stat: '997', + 'ST02': lambda ele, data, stat: '{:04d}'.format(data.transaction_set_no), + } +``` +* Advanced definition for element value getters +```python +from pyx12lib.common.interchange.grammar import StSegment +from pyx12lib.core.renderer import SegmentRenderer + + +class StRenderer(SegmentRenderer): + grammar = StSegment + + @property + def element_value_getters(self): + return { + 'ST01': lambda ele, data, stat: '997', + 'ST02': self.st02, + } + + @staticmethod + def st02(ele, data, stat): + assert ele is StSegment.elements[1] # the grammar for the element. + assert stat['ST01'] is '997' # stat consists of rendered data so far. + return '{:04d}'.format(data.transaction_set_no) # return value should always be strings +``` + +--- +## Test +```bash +python -m unittest discover +```