Skip to content

Commit d9bb320

Browse files
authored
Merge pull request #74 from aptiko/issue71-documentation
Partially refactor the documentation (partial fix for #71)
2 parents 2986272 + 954f2a7 commit d9bb320

File tree

3 files changed

+95
-81
lines changed

3 files changed

+95
-81
lines changed

docs/API.rst

+3-7
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ The base meta class is a PluginMount:
4343
.. autoclass:: pydifact.api.PluginMount
4444

4545

46-
Available entry points for plugins are:
47-
48-
.. autoclass:: pydifact.segments.SegmentProvider
49-
:members:
50-
51-
.. automethod:: SegmentProvider.__str__
52-
46+
:class:`~pydifact.segments.SegmentProvider` uses
47+
:class:`~pydifact.api.PluginMount` and can thus be
48+
extended with plugins.

docs/conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
3030
# ones.
3131
extensions = ["sphinx.ext.autodoc", "sphinx_rtd_theme"]
32+
autodoc_inherit_docstrings = False
3233

3334
# Add any paths that contain templates here, relative to this directory.
3435
templates_path = ["_templates"]
@@ -47,7 +48,6 @@
4748
html_theme = "sphinx_rtd_theme"
4849

4950
html_theme_options = {
50-
"canonical_url": "https://pydifact.readthedocs.io/",
5151
# "logo_only": False,
5252
"display_version": True,
5353
"prev_next_buttons_location": "both",

pydifact/segmentcollection.py

+91-73
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,30 @@
3434

3535

3636
class 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

353373
class 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

408428
class 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

Comments
 (0)