]> git.ipfire.org Git - thirdparty/python-fints.git/commitdiff
Re-shuffle docs, document new client stuff
authorHenryk Plötz <henryk@ploetzli.ch>
Sun, 16 Sep 2018 00:25:02 +0000 (02:25 +0200)
committerRaphael Michel <mail@raphaelmichel.de>
Mon, 3 Dec 2018 18:34:46 +0000 (19:34 +0100)
14 files changed:
docs/client.rst [new file with mode: 0644]
docs/conf.py
docs/developer.rst [deleted file]
docs/developer/index.rst [new file with mode: 0644]
docs/developer/parsing.rst [new file with mode: 0644]
docs/developer/segments/all.rst [new file with mode: 0644]
docs/developer/segments/index.rst [new file with mode: 0644]
docs/developer/sequence.rst [new file with mode: 0644]
docs/developer/types.rst [new file with mode: 0644]
docs/developer/working.rst [new file with mode: 0644]
docs/index.rst
docs/reading.rst
docs/tans.rst
fints/client.py

diff --git a/docs/client.rst b/docs/client.rst
new file mode 100644 (file)
index 0000000..9253c38
--- /dev/null
@@ -0,0 +1,78 @@
+.. _client:
+
+The client object
+=================
+
+Storing and restoring client state
+----------------------------------
+
+The :class:`~fints.client.FinTS3Client` object keeps some internal state that's beneficial to keep
+across invocations. This includes
+
+ * A system identifier that uniquely identifies this particular FinTS endpoint
+ * The Bank Parameter Data (BPD) with information about the bank and its advertised capabilities
+ * The User Parameter Data (UPD) with information about the user account and allowed actions
+
+.. autoclass:: fints.client.FinTS3Client
+   :members: __init__, get_data, set_data
+   :noindex:
+   :undoc-members:
+
+Using the :func:`~fints.client.FinTS3Client.get_data`/:func:`~fints.client.FinTS3Client.set_data`
+facility is purely optional for reading operations, but may speed up the process because the BPD/UPD
+can be cached and need not be transmitted again.
+
+It may be required to use the facility for transaction operations if both parts of a two-step transaction
+cannot be completed with the same :class:`~fints.client.FinTS3Client` object.
+
+The :func:`~fints.client.FinTS3Client.get_data` parameter `include_private` (defaults to `False`) enables
+including the User Parameter Data in the datablob. Set this to `True` if you can sufficiently ensure the
+privacy of the returned datablob (mostly: user name and account numbers).
+
+If your system manages multiple users/identity contexts, you SHOULD keep distinct datablobs per
+user or context.
+
+You SHOULD NOT call any other methods on the :class:`~fints.client.FinTS3Client` object
+after calling :func:`~fints.client.FinTS3Client.get_data`.
+
+
+Keeping the dialog open
+-----------------------
+
+All FinTS operations happen in the context of a so-called "dialog". The simple reading operations of this
+library will automatically open and close the dialog when necessary, but each opening and each closing
+takes one FinTS roundtrip.
+
+For the case where multiple operations are to be performed one after the other you can indicate to the library
+that you want to open a standing dialog and keep it open explicitly by entering the
+:class:`~fints.client.FinTS3Client` as a context handler.
+
+This can, and should be, complemented with the client state facility as follows:
+
+.. code-block:: python
+
+    datablob = ... # get from backend storage, or set to None
+    client = FinTS3PinTanClient(..., set_data=datablob)
+
+    with client:
+        accounts = client.get_sepa_accounts()
+        balance = client.get_balance(accounts[0])
+        transactions = client.get_transactions(accounts[0])
+
+    datablob = client.get_data()
+    # Store datablob to backend storage
+
+For transactions involving TANs it may be required by the bank to issue both steps for one transaction
+within the same dialog. In this case it's mandatory to use a standing dialog, because otherwise each
+step would be issued in its own, implicit, dialog.
+
+
+Storing and restoring dialog state
+----------------------------------
+
+.. autoclass:: fints.client.FinTS3Client
+   :members: pause_dialog, resume_dialog
+   :noindex:
+   :undoc-members:
+
+
index d3afa1467fdcba60ce6f554a8a29efc6a0b6e64e..31bd5bf3dad4148944679bdba0c917fe71082251 100644 (file)
@@ -385,4 +385,4 @@ epub_exclude_files = ['search.html']
 
 
 # Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'https://docs.python.org/': None}
+intersphinx_mapping = {'https://docs.python.org/': None, 'https://mt940.readthedocs.io/en/latest/': None, }
diff --git a/docs/developer.rst b/docs/developer.rst
deleted file mode 100644 (file)
index e887237..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-Developer documentation/API
-===========================
-
-Parsing and serialization
--------------------------
-
-.. autoclass:: fints.parser.FinTS3Parser
-   :members:
-
-.. autoclass:: fints.parser.FinTS3Serializer
-   :members:
-
-Example usage:
-
-.. code-block:: python
-
-   >>> message = (b'HNHBK:1:3+000000000428+300+430711670077=043999659571CN9D=+2+430711670077=043'
-   ...            b"999659571CN9D=:2'HNVSK:998:3+PIN:1+998+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+"
-   ...            b"2:2:13:@8@00000000:5:1+280:15050500:hermes:S:0:0+0'HNVSD:999:1+@195@HNSHK:2:"
-   ...            b'4+PIN:1+999+9166926+1+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+1+1:999:1+6:10:16'
-   ...            b"+280:15050500:hermes:S:0:0'HIRMG:3:2+0010::Nachricht entgegengenommen.+0100:"
-   ...            b":Dialog beendet.'HNSHA:4:2+9166926''HNHBS:5:1+2'")
-   >>> from fints.parser import FinTS3Parser
-   >>> s = FinTS3Parser().parse_message(message)
-   >>> s
-   SegmentSequence([fints.segments.HNHBK3(header=fints.formals.SegmentHeader('HNHBK', 1, 3), message_size='000000000428', hbci_version=300, dialogue_id='430711670077=043999659571CN9D=', message_number=2, reference_message=fints.formals.ReferenceMessage(dialogue_id='430711670077=043999659571CN9D=', message_number=2)), fints.segments.HNVSK3(header=fints.formals.SegmentHeader('HNVSK', 998, 3), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='998', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(name_party='2', cid=None, identifier_party='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_datetime=fints.formals.SecurityDateTime(datetime_type='1'), encryption_algorithm=fints.formals.EncryptionAlgorithm(usage_encryption='2', operation_mode='2', encryption_algorithm='13', algorithm_parameter_value=b'00000000', algorithm_parameter_name='5', algorithm_parameter_iv_name='1'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0), compression_function='0'), fints.segments.HNVSD1(header=fints.formals.SegmentHeader('HNVSD', 999, 1), data=SegmentSequence([fints.segments.HNSHK4(header=fints.formals.SegmentHeader('HNSHK', 2, 4), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='999', security_reference='9166926', security_application_area='1', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(name_party='2', cid=None, identifier_party='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_reference_number=1, security_datetime=fints.formals.SecurityDateTime(datetime_type='1'), hash_algorithm=fints.formals.HashAlgorithm(usage_hash='1', hash_algorithm='999', algorithm_parameter_name='1'), signature_algorithm=fints.formals.SignatureAlgorithm(usage_signature='6', signature_algorithm='10', operation_mode='16'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0)), fints.segments.HIRMG2(header=fints.formals.SegmentHeader('HIRMG', 3, 2), responses=[fints.formals.Response(code='0010', reference_element=None, text='Nachricht entgegengenommen.'), fints.formals.Response(code='0100', reference_element=None, text='Dialog beendet.')]), fints.segments.HNSHA2(header=fints.formals.SegmentHeader('HNSHA', 4, 2), security_reference='9166926')])), fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', 5, 1), message_number=2)])
-   >>> from fints.parser import FinTS3Serializer
-   >>> FinTS3Serializer().serialize_message(s)
-   b"HNHBK:1:3+000000000428+300+430711670077=043999659571CN9D=+2+430711670077=043999659571CN9D=:2'HNVSK:998:3+PIN:1+998+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+2:2:13:@8@00000000:5:1+280:15050500:hermes:S:0:0+0'HNVSD:999:1+@195@HNSHK:2:4+PIN:1+999+9166926+1+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+1+1:999:1+6:10:16+280:15050500:hermes:S:0:0'HIRMG:3:2+0010::Nachricht entgegengenommen.+0100::Dialog beendet.'HNSHA:4:2+9166926''HNHBS:5:1+2'"
-
-.. note::
-
-  In general parsing followed by serialization is not idempotent: A message may contain empty list elements at the end, but our serializer will never generate them.
-
-FinTS Segment Sequence
-----------------------
-
-A message is a sequence of segments. The :class:`~fints.formals.SegmentSequence` object allows searching for segments by type and version, by default recursing into nested sequences.
-
-.. autoclass:: fints.types.SegmentSequence
-   :members:
-   :undoc-members: print_nested
-
-
-FinTS Segments
---------------
-A segment is the core communication workhorse in FinTS. Each segment has a header of fixed format, which includes the segment type ("Segmentkennung"), number within the message, version, and, optionally, the number of the segment of another message it is in response or relation to ("Bezugssegment").
-
-The header is followed by a nested structure of fields and groups of fields, the exact specification of which depends on the segment type and version.
-
-All segment classes derive from  :class:`~fints.segments.FinTS3Segment`, which specifies the ``header`` attribute of :class:`~fints.formals.SegmentHeader` type.
-
-.. autoclass:: fints.segments.FinTS3Segment
-   :members:
-   :inherited-members: print_nested
-   :member-order: bysource
-
-   .. attribute:: TYPE
-
-      Segment type. Will be determined from the class name in subclasses, if the class name consists only of uppercase characters followed by decimal digits. Subclasses may explicitly set a class attribute instead.
-
-   .. attribute:: VERSION
-
-      Segment version. Will be determined from the class name in subclasses, if the class name consists only of uppercase characters followed by decimal digits. Subclasses may explicitly set a class attribute instead.
-
-   .. classmethod:: find_subclass(segment: list)
-
-      Parse the given ``segment`` parameter as a :class:`~fints.formals.SegmentHeader` and return a subclass with matching type and version class attributes.
-
-.. autoclass:: fints.formals.SegmentHeader
-   :members:
-   :member-order: bysource
-
-The :class:`~fints.segments.FinTS3Segment` class and its base classes employ a number of dynamic programming techniques so that derived classes need only specify the name, order and type of fields and all type conversion, construction etc. will take place automatically. All derived classes basically should behave "as expected", returning only native Python datatypes.
-
-Consider this example segment class:
-
-.. code-block:: python
-
-    class HNHBS1(FinTS3Segment):
-        message_number = DataElementField(type='num', max_length=4)
-
-Calling ``print_nested`` on an instance of this class might yield:
-
-.. code-block:: python
-
-    fints.segments.HNHBS1(
-        header = fints.formals.SegmentHeader('HNHBS', 4, 1),
-        message_number = 1,
-    )
-
-Working with Segments
-~~~~~~~~~~~~~~~~~~~~~
-
-Objects of :class:`~fints.segments.FinTS3Segment` or a subclass can be created by calling their constructor. The constructor takes optional arguments for all fields of the class. Setting and getting fields and subfields works, and consumes and returns Python objects as appropriate:
-
-.. code-block:: python
-
-    >>> from fints.segments import HNHBS1
-    >>> s = HNHBS1()
-    >>> s
-    fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', None, 1), message_number=None)
-    >>> s.header.number = 3
-    >>> s.header
-    fints.formals.SegmentHeader('HNHBS', 3, 1)
-
-When setting a value, format and length restrictions will be checked, if possible:
-
-.. code-block:: python
-
-    >>> s.message_number = 'abc'
-    ValueError: invalid literal for int() with base 10: 'abc'
-    >>> s.message_number = 12345
-    ValueError: Value '12345' cannot be rendered: max_length=4 exceeded
-
-The only exception is: Every field can be set to ``None`` in order to clear the field and make it unset, recursively. No checking is performed whether all fields that are required (or conditionally required) by the specification are set. For convenience, an unset constructed field will still be filled with an instance of the field's value type, so that subfield accessing will always work, without encountering ``None`` values on the way.
-
-.. code-block:: python
-
-    >>> s.header = None
-    >>> s
-    fints.segments.HNHBS1(header=fints.formals.SegmentHeader(None, None, None), message_number=None)
-
-When calling the constructor with non-keyword arguments, fields are assigned in order, with the exception of ``header`` in :class:`~fints.segments.FinTS3Segment` subclasses, which can only be given as a keyword argument. When no ``header`` argument is present, a :class:`~fints.formals.SegmentHeader` is automatically constructed with default values (and no ``number``). It's generally not required to construct the ``header`` parameter manually.
-
-**FIXME** The ``number`` should in the future be generated automatically within in sequence (at least before serializing).
-
-.. code-block:: python
-
-    >>> HNHBS1(42)
-    fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', None, 1), message_number=42)
-    >>> HNHBS1(42, header=SegmentHeader('FOO'))
-    fints.segments.HNHBS1(header=fints.formals.SegmentHeader('FOO', None, None), message_number=42)
-
-
-Some segment fields have a variable number of values. These are always treated as a list, and minimum/maximum list length is obeyed. Setting a value beyond the end of the list results in an exception. Empty values are added to maintain the correct minimum number of values.
-
-.. code-block:: python
-
-    >>> from fints.segments import HIRMG2
-    >>> s = HIRMG2()
-    >>> s
-    fints.segments.HIRMG2(header=fints.formals.SegmentHeader('HIRMG', None, 2), responses=[fints.formals.Response(code=None, reference_element=None, text=None)])
-    >>> s.responses[0].code = '0010'
-    >>> s.responses[1].code = '0100'
-    >>> s.print_nested()
-    fints.segments.HIRMG2(
-        header = fints.formals.SegmentHeader('HIRMG', None, 2),
-        responses = [
-                    fints.formals.Response(
-                        code = '0010',
-                        reference_element = None,
-                        text = None,
-                    ),
-                    fints.formals.Response(
-                        code = '0100',
-                        reference_element = None,
-                        text = None,
-                    ),
-            ],
-    )
-    >>> HIRMG2(responses=[fints.formals.Response('2342')]).print_nested()
-    fints.segments.HIRMG2(
-        header = fints.formals.SegmentHeader('HIRMG', None, 2),
-        responses = [
-                    fints.formals.Response(
-                        code = '2342',
-                        reference_element = None,
-                        text = None,
-                    ),
-            ],
-    )
-
-
-All Segments
-____________
-
-.. automodule:: fints.segments
-    :members:
-    :inherited-members:
-    :undoc-members:
-    :show-inheritance:
-    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
-    :member-order: bysource
-
-
-Defining new Segment classes
-----------------------------
-
-Field types
-~~~~~~~~~~~
-
-.. automodule:: fints.fields
-    :members:
-    :undoc-members:
-    :exclude-members: print_nested
-    :member-order: bysource
-
-
-Constructed and helper types
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. automodule:: fints.formals
-    :members:
-    :undoc-members:
-    :exclude-members: print_nested
-    :member-order: bysource
-
-
diff --git a/docs/developer/index.rst b/docs/developer/index.rst
new file mode 100644 (file)
index 0000000..6404e90
--- /dev/null
@@ -0,0 +1,14 @@
+Developer documentation/API
+===========================
+
+This part of the documentation is for you if you want to improve python-fints, but also if you just want to look behind the curtain.
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+
+   parsing
+   segments/index
+   sequence
+   working
+   types
diff --git a/docs/developer/parsing.rst b/docs/developer/parsing.rst
new file mode 100644 (file)
index 0000000..74fc71b
--- /dev/null
@@ -0,0 +1,30 @@
+Parsing and serialization
+-------------------------
+
+.. autoclass:: fints.parser.FinTS3Parser
+   :members:
+
+.. autoclass:: fints.parser.FinTS3Serializer
+   :members:
+
+Example usage:
+
+.. code-block:: python
+
+   >>> message = (b'HNHBK:1:3+000000000428+300+430711670077=043999659571CN9D=+2+430711670077=043'
+   ...            b"999659571CN9D=:2'HNVSK:998:3+PIN:1+998+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+"
+   ...            b"2:2:13:@8@00000000:5:1+280:15050500:hermes:S:0:0+0'HNVSD:999:1+@195@HNSHK:2:"
+   ...            b'4+PIN:1+999+9166926+1+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+1+1:999:1+6:10:16'
+   ...            b"+280:15050500:hermes:S:0:0'HIRMG:3:2+0010::Nachricht entgegengenommen.+0100:"
+   ...            b":Dialog beendet.'HNSHA:4:2+9166926''HNHBS:5:1+2'")
+   >>> from fints.parser import FinTS3Parser
+   >>> s = FinTS3Parser().parse_message(message)
+   >>> s
+   SegmentSequence([fints.segments.HNHBK3(header=fints.formals.SegmentHeader('HNHBK', 1, 3), message_size='000000000428', hbci_version=300, dialogue_id='430711670077=043999659571CN9D=', message_number=2, reference_message=fints.formals.ReferenceMessage(dialogue_id='430711670077=043999659571CN9D=', message_number=2)), fints.segments.HNVSK3(header=fints.formals.SegmentHeader('HNVSK', 998, 3), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='998', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(name_party='2', cid=None, identifier_party='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_datetime=fints.formals.SecurityDateTime(datetime_type='1'), encryption_algorithm=fints.formals.EncryptionAlgorithm(usage_encryption='2', operation_mode='2', encryption_algorithm='13', algorithm_parameter_value=b'00000000', algorithm_parameter_name='5', algorithm_parameter_iv_name='1'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0), compression_function='0'), fints.segments.HNVSD1(header=fints.formals.SegmentHeader('HNVSD', 999, 1), data=SegmentSequence([fints.segments.HNSHK4(header=fints.formals.SegmentHeader('HNSHK', 2, 4), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='999', security_reference='9166926', security_application_area='1', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(name_party='2', cid=None, identifier_party='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_reference_number=1, security_datetime=fints.formals.SecurityDateTime(datetime_type='1'), hash_algorithm=fints.formals.HashAlgorithm(usage_hash='1', hash_algorithm='999', algorithm_parameter_name='1'), signature_algorithm=fints.formals.SignatureAlgorithm(usage_signature='6', signature_algorithm='10', operation_mode='16'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0)), fints.segments.HIRMG2(header=fints.formals.SegmentHeader('HIRMG', 3, 2), responses=[fints.formals.Response(code='0010', reference_element=None, text='Nachricht entgegengenommen.'), fints.formals.Response(code='0100', reference_element=None, text='Dialog beendet.')]), fints.segments.HNSHA2(header=fints.formals.SegmentHeader('HNSHA', 4, 2), security_reference='9166926')])), fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', 5, 1), message_number=2)])
+   >>> from fints.parser import FinTS3Serializer
+   >>> FinTS3Serializer().serialize_message(s)
+   b"HNHBK:1:3+000000000428+300+430711670077=043999659571CN9D=+2+430711670077=043999659571CN9D=:2'HNVSK:998:3+PIN:1+998+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+2:2:13:@8@00000000:5:1+280:15050500:hermes:S:0:0+0'HNVSD:999:1+@195@HNSHK:2:4+PIN:1+999+9166926+1+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+1+1:999:1+6:10:16+280:15050500:hermes:S:0:0'HIRMG:3:2+0010::Nachricht entgegengenommen.+0100::Dialog beendet.'HNSHA:4:2+9166926''HNHBS:5:1+2'"
+
+.. note::
+
+  In general parsing followed by serialization is not idempotent: A message may contain empty list elements at the end, but our serializer will never generate them.
diff --git a/docs/developer/segments/all.rst b/docs/developer/segments/all.rst
new file mode 100644 (file)
index 0000000..c5ff5bf
--- /dev/null
@@ -0,0 +1,123 @@
+All Segments
+____________
+
+
+fints.segments.accounts module
+------------------------------
+
+.. automodule:: fints.segments.accounts
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
+
+fints.segments.auth module
+--------------------------
+
+.. automodule:: fints.segments.auth
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
+
+fints.segments.bank module
+--------------------------
+
+.. automodule:: fints.segments.bank
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
+
+fints.segments.base module
+--------------------------
+
+.. automodule:: fints.segments.base
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset, FinTS3Segment
+
+fints.segments.debit module
+---------------------------
+
+.. automodule:: fints.segments.debit
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
+
+fints.segments.depot module
+---------------------------
+
+.. automodule:: fints.segments.depot
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
+
+fints.segments.dialog module
+----------------------------
+
+.. automodule:: fints.segments.dialog
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
+
+fints.segments.journal module
+-----------------------------
+
+.. automodule:: fints.segments.journal
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
+
+fints.segments.message module
+-----------------------------
+
+.. automodule:: fints.segments.message
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
+
+fints.segments.saldo module
+---------------------------
+
+.. automodule:: fints.segments.saldo
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
+
+fints.segments.statement module
+-------------------------------
+
+.. automodule:: fints.segments.statement
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
+
+fints.segments.transfer module
+------------------------------
+
+.. automodule:: fints.segments.transfer
+    :members:
+    :inherited-members:
+    :undoc-members:
+    :show-inheritance:
+    :exclude-members: print_nested, naive_parse, find_subclass, is_unset
diff --git a/docs/developer/segments/index.rst b/docs/developer/segments/index.rst
new file mode 100644 (file)
index 0000000..102769a
--- /dev/null
@@ -0,0 +1,47 @@
+FinTS Segments
+--------------
+A segment is the core communication workhorse in FinTS. Each segment has a header of fixed format, which includes the segment type ("Segmentkennung"), number within the message, version, and, optionally, the number of the segment of another message it is in response or relation to ("Bezugssegment").
+
+The header is followed by a nested structure of fields and groups of fields, the exact specification of which depends on the segment type and version.
+
+All segment classes derive from  :class:`~fints.segments.base.FinTS3Segment`, which specifies the ``header`` attribute of :class:`~fints.formals.SegmentHeader` type.
+
+.. autoclass:: fints.segments.base.FinTS3Segment
+   :members:
+   :inherited-members: print_nested
+   :member-order: bysource
+
+   .. attribute:: TYPE
+
+      Segment type. Will be determined from the class name in subclasses, if the class name consists only of uppercase characters followed by decimal digits. Subclasses may explicitly set a class attribute instead.
+
+   .. attribute:: VERSION
+
+      Segment version. Will be determined from the class name in subclasses, if the class name consists only of uppercase characters followed by decimal digits. Subclasses may explicitly set a class attribute instead.
+
+   .. classmethod:: find_subclass(segment: list)
+
+      Parse the given ``segment`` parameter as a :class:`~fints.formals.SegmentHeader` and return a subclass with matching type and version class attributes.
+
+The :class:`~fints.segments.base.FinTS3Segment` class and its base classes employ a number of dynamic programming techniques so that derived classes need only specify the name, order and type of fields. All type conversion, construction etc. will take place automatically. All derived classes basically should behave "as expected", returning only native Python datatypes.
+
+Consider this example segment class:
+
+.. code-block:: python
+
+    class HNHBS1(FinTS3Segment):
+        message_number = DataElementField(type='num', max_length=4)
+
+Calling ``print_nested`` on an instance of this class might output:
+
+.. code-block:: python
+
+    fints.segments.HNHBS1(
+        header = fints.formals.SegmentHeader('HNHBS', 4, 1),
+        message_number = 1,
+    )
+
+.. toctree::
+   :maxdepth: 2
+
+   all
diff --git a/docs/developer/sequence.rst b/docs/developer/sequence.rst
new file mode 100644 (file)
index 0000000..318de0b
--- /dev/null
@@ -0,0 +1,9 @@
+FinTS Segment Sequence
+----------------------
+
+A message is a sequence of segments. The :class:`~fints.formals.SegmentSequence` object allows searching for segments by type and version, by default recursing into nested sequences.
+
+.. autoclass:: fints.types.SegmentSequence
+   :members:
+   :undoc-members: print_nested
+
diff --git a/docs/developer/types.rst b/docs/developer/types.rst
new file mode 100644 (file)
index 0000000..419567d
--- /dev/null
@@ -0,0 +1,33 @@
+Defining new Segment classes
+----------------------------
+
+Base types
+~~~~~~~~~~~
+
+.. automodule:: fints.types
+    :members:
+    :undoc-members:
+    :exclude-members: print_nested, SegmentSequence
+    :member-order: bysource
+
+
+Field types
+~~~~~~~~~~~
+
+.. automodule:: fints.fields
+    :members:
+    :undoc-members:
+    :exclude-members: print_nested
+    :member-order: bysource
+
+
+Constructed and helper types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. automodule:: fints.formals
+    :members:
+    :undoc-members:
+    :exclude-members: print_nested SegmentHeader
+    :member-order: bysource
+
+
diff --git a/docs/developer/working.rst b/docs/developer/working.rst
new file mode 100644 (file)
index 0000000..ac521ec
--- /dev/null
@@ -0,0 +1,81 @@
+Working with Segments
+~~~~~~~~~~~~~~~~~~~~~
+
+Objects of :class:`~fints.segments.base.FinTS3Segment` or a subclass can be created by calling their constructor. The constructor takes optional arguments for all fields of the class. Setting and getting fields and subfields works, and consumes and returns Python objects as appropriate:
+
+.. code-block:: python
+
+    >>> from fints.segments import HNHBS1
+    >>> s = HNHBS1()
+    >>> s
+    fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', None, 1), message_number=None)
+    >>> s.header.number = 3
+    >>> s.header
+    fints.formals.SegmentHeader('HNHBS', 3, 1)
+
+When setting a value, format and length restrictions will be checked, if possible:
+
+.. code-block:: python
+
+    >>> s.message_number = 'abc'
+    ValueError: invalid literal for int() with base 10: 'abc'
+    >>> s.message_number = 12345
+    ValueError: Value '12345' cannot be rendered: max_length=4 exceeded
+
+The only exception is: Every field can be set to ``None`` in order to clear the field and make it unset, recursively. No checking is performed whether all fields that are required (or conditionally required) by the specification are set. For convenience, an unset constructed field will still be filled with an instance of the field's value type, so that subfield accessing will always work, without encountering ``None`` values on the way.
+
+.. code-block:: python
+
+    >>> s.header = None
+    >>> s
+    fints.segments.HNHBS1(header=fints.formals.SegmentHeader(None, None, None), message_number=None)
+
+When calling the constructor with non-keyword arguments, fields are assigned in order, with the exception of ``header`` in :class:`~fints.segments.base.FinTS3Segment` subclasses, which can only be given as a keyword argument. When no ``header`` argument is present, a :class:`~fints.formals.SegmentHeader` is automatically constructed with default values (and no ``number``). It's generally not required to construct the ``header`` parameter manually.
+
+**FIXME** The ``number`` should in the future be generated automatically within in sequence (at least before serializing).
+
+.. code-block:: python
+
+    >>> HNHBS1(42)
+    fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', None, 1), message_number=42)
+    >>> HNHBS1(42, header=SegmentHeader('FOO'))
+    fints.segments.HNHBS1(header=fints.formals.SegmentHeader('FOO', None, None), message_number=42)
+
+
+Some segment fields have a variable number of values. These are always treated as a list, and minimum/maximum list length is obeyed. Setting a value beyond the end of the list results in an exception. Empty values are added to maintain the correct minimum number of values.
+
+.. code-block:: python
+
+    >>> from fints.segments import HIRMG2
+    >>> s = HIRMG2()
+    >>> s
+    fints.segments.HIRMG2(header=fints.formals.SegmentHeader('HIRMG', None, 2), responses=[fints.formals.Response(code=None, reference_element=None, text=None)])
+    >>> s.responses[0].code = '0010'
+    >>> s.responses[1].code = '0100'
+    >>> s.print_nested()
+    fints.segments.HIRMG2(
+        header = fints.formals.SegmentHeader('HIRMG', None, 2),
+        responses = [
+                    fints.formals.Response(
+                        code = '0010',
+                        reference_element = None,
+                        text = None,
+                    ),
+                    fints.formals.Response(
+                        code = '0100',
+                        reference_element = None,
+                        text = None,
+                    ),
+            ],
+    )
+    >>> HIRMG2(responses=[fints.formals.Response('2342')]).print_nested()
+    fints.segments.HIRMG2(
+        header = fints.formals.SegmentHeader('HIRMG', None, 2),
+        responses = [
+                    fints.formals.Response(
+                        code = '2342',
+                        reference_element = None,
+                        text = None,
+                    ),
+            ],
+    )
index 165e5dcc085fb6fb020b6914e0eff39d088503dc..12c466edd5f644319c3c4891e752155d43185fd4 100644 (file)
@@ -7,16 +7,24 @@ FinTS client library
 This is a pure-python implementation of FinTS (formerly known as HBCI), a
 online-banking protocol commonly supported by German banks.
 
-Documentation content
----------------------
+Library user documentation content
+----------------------------------
 
 .. toctree::
    :maxdepth: 2
 
    quickstart
    reading
+   client
    tans
    transfers
    debits
    tested
-   developer
+
+
+Library developer documentation content
+---------------------------------------
+.. toctree::
+   :maxdepth: 2
+
+   developer/index
index 9c373f55d2465a16390095159b22e64828155288..c37982ff57de0c1d00aaa480631b31f783e6db7b 100644 (file)
@@ -9,6 +9,7 @@ The most simple method allows you to get all bank accounts that your user has ac
 .. autoclass:: fints.client.FinTS3Client
    :noindex:
    :members: get_sepa_accounts
+   :noindex:
 
 This method will return a list of named tuples of the following type:
 
@@ -16,6 +17,16 @@ This method will return a list of named tuples of the following type:
 
 You will need this account object for many further operations to show which account you want to operate on.
 
+Fetching bank information
+-------------------------
+
+During the first interaction with the bank some meta information about the bank and your user is transmitted
+from the bank.
+
+.. autoclass:: fints.client.FinTS3Client
+   :members: get_information
+   :noindex:
+
 
 Fetching account balances
 -------------------------
index df9f9ffc42e18439e15f1ba152e29936844904cd..83a3602584a5a0c42e8b96b2e1b1fdbc1da00329 100644 (file)
@@ -17,22 +17,6 @@ you want to use:
 The returned values have a subtype ``fints.models.TANMethod``, with varying parameters depending on the version
 used by the bank:
 
-.. autoclass:: fints.client.FinTS3Client
-   :noindex:
-   :members: get_tan_methods
-
-.. autoclass:: fints.models.TANMethod1
-
-.. autoclass:: fints.models.TANMethod2
-
-.. autoclass:: fints.models.TANMethod3
-
-.. autoclass:: fints.models.TANMethod4
-
-.. autoclass:: fints.models.TANMethod5
-
-.. autoclass:: fints.models.TANMethod6
-
 .. warning:: If the ``description_required`` attribute is ``2``, you will need to get the description of the TAN medium
              you want to use and pass it as ``tan_description`` to some operations. You can send a request for this
              information with the ``client.get_tan_description()`` method call. Currently, this returns an unparsed
@@ -44,14 +28,6 @@ TAN challenges
 You should then pass the chosen ``TANMethod`` object to your operation, e.g. ``start_simple_sepa_transfer``.
 If a TAN is required, this operation will return a ``TANChallenge``, again depending on the version used by the bank.
 
-.. autoclass:: fints.models.TANChallenge3
-
-.. autoclass:: fints.models.TANChallenge4
-
-.. autoclass:: fints.models.TANChallenge5
-
-.. autoclass:: fints.models.TANChallenge6
-
 The ``challenge`` attribute will contain human-readable instructions on how to proceed.
 
 Flicker-Code / optiTAN
index 4446071876de2619faec0c28277c10d9578c4c00..9aeac90ae8af7177bfc5af038bd71df1fcaed329 100644 (file)
@@ -114,7 +114,7 @@ class TransactionResponse:
         return "<{o.__class__.__name__}(status={o.status!r}, responses={o.responses!r}, data={o.data!r})>".format(o=self)
 
 class FinTS3Client:
-    def __init__(self, bank_identifier, user_id, customer_id=None, set_data=None):
+    def __init__(self, bank_identifier, user_id, customer_id=None, set_data:bytes=None):
         self.accounts = []
         if isinstance(bank_identifier, BankIdentifier):
             self.bank_identifier = bank_identifier
@@ -249,12 +249,12 @@ class FinTS3Client:
 
         return data
 
-    def get_data(self, including_private=False):
+    def get_data(self, including_private:bool=False) -> bytes:
         # FIXME Test, document
         data = self._get_data_v1(including_private=including_private)
         return compress_datablob(DATA_BLOB_MAGIC, 1, data)
 
-    def set_data(self, blob):
+    def set_data(self, blob: bytes):
         # FIXME Test, document
         decompress_datablob(DATA_BLOB_MAGIC, blob, self)
 
@@ -277,31 +277,30 @@ class FinTS3Client:
         Return information about the connected bank.
 
         Note: Can only be filled after the first communication with the bank.
-        If in doubt, use a construction like:
-        ````
-        f = FinTS3Client(...)
-        with f:
-            info = f.get_information()
-        ````
-
-        Returns a nested dictionary:
-        ````
-        bank:
-            name: Bank Name
-            supported_operations: dict(FinTSOperations -> boolean)
-        accounts:
-            - iban: IBAN
-              account_number: Account Number
-              subaccount_number: Sub-Account Number
-              bank_identifier: fints.formals.BankIdentifier(...)
-              customer_id: Customer ID
-              type: Account type
-              currency: Currency
-              owner_name: ['Owner Name 1', 'Owner Name 2 (optional)']
-              product_name: Account product name
-              supported_operations: dict(FinTSOperations -> boolean)
-            - ...
-        ````
+        If in doubt, use a construction like::
+        
+            f = FinTS3Client(...)
+            with f:
+                info = f.get_information()
+
+        Returns a nested dictionary::
+
+            bank:
+                name: Bank Name
+                supported_operations: dict(FinTSOperations -> boolean)
+            accounts:
+                - iban: IBAN
+                  account_number: Account Number
+                  subaccount_number: Sub-Account Number
+                  bank_identifier: fints.formals.BankIdentifier(...)
+                  customer_id: Customer ID
+                  type: Account type
+                  currency: Currency
+                  owner_name: ['Owner Name 1', 'Owner Name 2 (optional)']
+                  product_name: Account product name
+                  supported_operations: dict(FinTSOperations -> boolean)
+                - ...
+
         """
         retval = {
             'bank': {},
@@ -779,6 +778,9 @@ class FinTS3Client:
         Caller SHOULD ensure that the dialog is resumed (and properly ended) within a reasonable amount of time.
 
         :Example:
+        
+        ::
+
             client = FinTS3PinTanClient(..., set_data=None)
             with client:
                 challenge = client.sepa_transfer(...)