3434
3535
3636class AbstractSegmentsContainer :
37- """Represent a collection of EDI Segments for both reading and writing .
37+ """Abstract base class of subclasses containing collection of segments .
3838
39- You should not instantiate AbstractSegmentsContainer itself, but subclass it use that.
39+ :class:`AbstractSegmentsContainer` is the superclass of several classes such as
40+ :class:`RawSegmentCollection` and :class:`Interchange` and contains methods common
41+ to them.
4042
41- The segments list in AbstractSegmentsContainer includes header and footer segments too.
42- Inheriting envelopes must NOT include these elements in .segments, as get_header_element() and
43- get_footer_element() should provide these elements on-the-fly.
43+ **Implementation detail:** Subclasses must set :attr:`HEADER_TAG` and
44+ :attr:`FOOTER_TAG`.
4445
45- Inheriting classes must set HEADER_TAG and FOOTER_TAG
46+ :param extra_header_elements: A list of elements to be appended at the end
47+ of the header segment (same format as :class:`~pydifact.segments.Segment`
48+ constructor elements).
49+
50+ :param characters: The set of control characters
51+
52+ .. attribute:: segments
53+
54+ The segments that comprise the container. This does not include the envelope
55+ (that is, the header and footer) segments. To get the envolope segments, use
56+ as :meth:`get_header_segment` and :meth:`get_footer_segment`.
57+
58+ .. attribute:: characters
59+
60+ The control characters (a :class:`~pydifact.control.Characters` object).
4661 """
4762
4863 HEADER_TAG : str = None
@@ -53,13 +68,6 @@ def __init__(
5368 extra_header_elements : List [Union [str , List [str ]]] = None ,
5469 characters : Optional [Characters ] = None ,
5570 ):
56- """
57- :param extra_header_elements: a list of elements to be appended at the end
58- of the header segment (same format as Segment() constructor *elements).
59- :param characters: the set of control characters
60- """
61-
62- # The segments that make up this message
6371 self .segments = []
6472
6573 # set of control characters
@@ -81,10 +89,9 @@ def from_str(
8189 ) -> "AbstractSegmentsContainer" :
8290 """Create an instance from a string.
8391
84- This method is intended for usage in inheriting classes, not it AbstractSegmentsContainer itself.
85- :param string: The EDI content
86- :param parser: A parser to convert the tokens to segments, defaults to `Parser`
87- :param characters: the set of control characters
92+ :param string: The EDI content.
93+ :param parser: A parser to convert the tokens to segments; defaults to `Parser`.
94+ :param characters: The set of control characters.
8895 """
8996 if parser is None :
9097 parser = Parser (characters = characters )
@@ -99,11 +106,12 @@ def from_segments(
99106 segments : Union [List , Iterable ],
100107 characters : Optional [Characters ] = None ,
101108 ) -> "AbstractSegmentsContainer" :
102- """Create a new AbstractSegmentsContainer instance from a iterable list of segments.
109+ """Create an instance from a list of segments.
103110
104- :param segments: The segments of the EDI interchange
105- :param characters: the set of control characters
111+ :param segments: The segments of the EDI interchange.
106112 :type segments: list/iterable of Segment
113+
114+ :param characters: The set of control characters.
107115 """
108116
109117 # create a new instance of AbstractSegmentsContainer and return it
@@ -115,11 +123,13 @@ def get_segments(
115123 name : str ,
116124 predicate : Callable = None , # Python3.9+ Callable[[Segment], bool]
117125 ) -> list :
118- """Get all the segments that match the requested name.
126+ """Get all segments that match the requested name.
119127
120- :param name: The name of the segments to return
121- :param predicate: Optional predicate callable that returns True if the given segment matches a condition
122- :rtype: list of Segment
128+ :param name: The name of the segments to return.
129+ :param predicate: Optional callable that accepts a segment as argument.
130+ Only segments for which the returned value is ``True'' are returned.
131+
132+ :rtype: list of :class:`Segment` objects.
123133 """
124134 for segment in self .segments :
125135 if segment .tag == name and (predicate is None or predicate (segment )):
@@ -132,10 +142,11 @@ def get_segment(
132142 ) -> Optional [Segment ]:
133143 """Get the first segment that matches the requested name.
134144
135- :return: The requested segment, or None if not found
136- :param name: The name of the segment to return
145+ :param name: The name of the segment to return.
137146 :param predicate: Optional predicate that must match on the segments
138- to return
147+ to return.
148+
149+ :return: The requested segment, or None if not found.
139150 """
140151 for segment in self .get_segments (name , predicate ):
141152 return segment
@@ -146,17 +157,18 @@ def split_by(
146157 self ,
147158 start_segment_tag : str ,
148159 ) -> Iterable : # Python3.9+ Iterable["RawSegmentCollection"]
149- """Split a segment collection by tag.
150-
151- Everything before the first start segment is ignored, so if no matching
152- start segment is found at all, returned result is empty.
160+ """Split the segment collection by tag.
153161
162+ Assuming the collection contains tags ``["A", "B", "A", "A", "B", "D"]``,
163+ ``split_by("A")`` would return ``[["A", "B"], ["A"], ["A", "B", "D"]]``.
164+ Everything before the first start segment is ignored, so if no matching start
165+ segment is found at all, the returned result is empty.
154166
155167 :param start_segment_tag:
156168 the segment tag we want to use as separator
157169
158- :return: generator of segment collections. The start tag is included in
159- each yielded collection
170+ :return: Generator of segment collections. The start tag is included in
171+ each yielded collection.
160172 """
161173 current_list = None
162174
@@ -176,13 +188,16 @@ def split_by(
176188 def add_segments (
177189 self , segments : Union [List [Segment ], Iterable ]
178190 ) -> "AbstractSegmentsContainer" :
179- """Add multiple segments to the collection. Passing a UNA segment means setting/overriding the control
180- characters and setting the serializer to output the Service String Advice. If you wish to change the control
181- characters from the default and not output the Service String Advice, change self.characters instead,
182- without passing a UNA Segment.
191+ """Append a list of segments to the collection.
183192
184- :param segments: The segments to add
185- :type segments: list or iterable of Segments
193+ For the :class:`Interchange` subclass, passing a ``UNA`` segment means
194+ setting/overriding the control characters and setting the serializer to output
195+ the Service String Advice. If you wish to change the control characters from the
196+ default and not output the Service String Advice, change :attr:`characters`
197+ instead, without passing a ``UNA`` Segment.
198+
199+ :param segments: The segments to add.
200+ :type segments: List or iterable of :class:`~pydifact.segments.Segment` objects.
186201 """
187202 for segment in segments :
188203 self .add_segment (segment )
@@ -192,31 +207,41 @@ def add_segments(
192207 def add_segment (self , segment : Segment ) -> "AbstractSegmentsContainer" :
193208 """Append a segment to the collection.
194209
195- Note: skips segments that are header or footer tags of this segment container type.
210+ Note: skips segments that are header or footer tags of this segment container
211+ type.
212+
196213 :param segment: The segment to add
197214 """
198215 if not segment .tag in (self .HEADER_TAG , self .FOOTER_TAG ):
199216 self .segments .append (segment )
200217 return self
201218
202219 def get_header_segment (self ) -> Optional [Segment ]:
203- """Craft and return this container header segment (if any)
220+ """Craft and return a header segment.
221+
222+ :meth:`get_header_segment` creates and returns an appropriate
223+ :class:`~pydifact.segments.Segment` object that can serve as a header of the
224+ current object. This is useful, for example, when serializing the current object.
204225
205- :returns: None if there is no header for that container
226+ Although the current object may have been created by reading a string (e.g.
227+ with :meth:`from_str`), :meth:`get_header_segment` does not return the header
228+ segment that was read by the string; that segment would have been useful only
229+ during reading and it is the job of :meth:`from_str` to check it.
206230 """
207231 return None
208232
209233 def get_footer_segment (self ) -> Optional [Segment ]:
210- """Craft and return this container footer segment (if any)
234+ """Craft and return a footer segment.
211235
212- :returns: None if there is no footer for that container
236+ This is similar to :meth:`get_header_segment`, but for the footer segment.
213237 """
214238 return None
215239
216240 def serialize (self , break_lines : bool = False ) -> str :
217- """Serialize all the segments added to this object.
241+ """Return the string representation of the object.
218242
219- :param break_lines: if True, insert line break after each segment terminator.
243+ :param break_lines: If ``True``, inserts line break after each segment
244+ terminator.
220245 """
221246 header = self .get_header_segment ()
222247 footer = self .get_footer_segment ()
@@ -235,18 +260,13 @@ def serialize(self, break_lines: bool = False) -> str:
235260 )
236261
237262 def validate (self ):
238- """Validates this container .
263+ """Validate the object .
239264
240- This method must be overridden in implementing subclasses, and should make sure that
241- the container is implemented correctly.
242-
243- It does not return anything and should raise an Exception in case of errors.
265+ Raises an exception if the object is invalid.
244266 """
245267 raise NotImplementedError
246268
247269 def __str__ (self ) -> str :
248- """Allow the object to be serialized by casting to a string."""
249-
250270 return self .serialize ()
251271
252272
@@ -352,12 +372,12 @@ def validate(self):
352372
353373class Message (AbstractSegmentsContainer ):
354374 """
355- A message (started by UNH segment, ended by UNT segment)
375+ A message (started by UNH_ segment, ended by UNT_ segment)
356376
357377 Optional features of UNH are not yet supported.
358378
359- https://www.stylusstudio.com/edifact/40100/UNH_.htm
360- https://www.stylusstudio.com/edifact/40100/UNT_.htm
379+ .. _UNH: https://www.stylusstudio.com/edifact/40100/UNH_.htm
380+ .. _UNT: https://www.stylusstudio.com/edifact/40100/UNT_.htm
361381 """
362382
363383 HEADER_TAG = "UNH"
@@ -406,19 +426,20 @@ def validate(self):
406426
407427
408428class Interchange (FileSourcableMixin , UNAHandlingMixin , AbstractSegmentsContainer ):
409- """
410- An interchange (started by UNB segment, ended by UNZ segment)
429+ """An EDIFACT interchange.
411430
412- Optional features of UNB are not yet supported.
431+ In EDIFACT, the **interchange** is the entire document at the highest level. Except
432+ for its header (a UNB_ segment) and footer (a UNZ_ segment), it consists of one or
433+ more **messages**.
413434
414- Functional groups are not yet supported
435+ :class:`Interchange` currently does not support functional groups and optional
436+ features of UNB.
415437
416- Messages are supported, see get_message() and get_message(), but are
417- optional: interchange segments can be accessed without going through
418- messages.
438+ :class:`Interchange` supports all methods of :class:`AbstractSegmentsContainer` plus
439+ some additional methods.
419440
420- https://www.stylusstudio.com/edifact/40100/UNB_.htm
421- https://www.stylusstudio.com/edifact/40100/UNZ_.htm
441+ .. _UNB: https://www.stylusstudio.com/edifact/40100/UNB_.htm
442+ .. _UNZ: https://www.stylusstudio.com/edifact/40100/UNZ_.htm
422443 """
423444
424445 HEADER_TAG = "UNB"
@@ -455,11 +476,6 @@ def get_header_segment(self) -> Segment:
455476 )
456477
457478 def get_footer_segment (self ) -> Segment :
458- """:returns a (UNZ) footer segment with correct segment count and control reference.
459-
460- It counts either of the number of messages or, if used, of the number of functional groups
461- in an interchange (TODO)."""
462-
463479 # FIXME: count functional groups (UNG/UNE) correctly
464480 cnt = 0
465481 for segment in self .segments :
@@ -475,11 +491,12 @@ def get_footer_segment(self) -> Segment:
475491 )
476492
477493 def get_messages (self ) -> List [Message ]:
478- """parses a list of messages out of the internal segments .
494+ """Get list of messages in the interchange .
479495
480- :raises EDISyntaxError if constraints are not met (e.g. UNH/UNT both correct)
496+ Using :meth:`get_messages` is optional; interchange segments can be accessed
497+ directly without going through messages.
481498
482- TODO: parts of this here are better done in the validate() method
499+ :raises: :class:`EDISyntaxError` if the interchange contents are not correct.
483500 """
484501
485502 message = None
@@ -511,6 +528,7 @@ def get_messages(self) -> List[Message]:
511528 raise EDISyntaxError ("UNH segment was not closed with a UNT segment." )
512529
513530 def add_message (self , message : Message ) -> "Interchange" :
531+ """Append a message to the interchange."""
514532 segments = (
515533 [message .get_header_segment ()]
516534 + message .segments
0 commit comments