--- /dev/null
+.. _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:
+
+
# 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, }
+++ /dev/null
-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
-
-
--- /dev/null
+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
--- /dev/null
+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.
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
+
--- /dev/null
+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
+
+
--- /dev/null
+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,
+ ),
+ ],
+ )
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
.. autoclass:: fints.client.FinTS3Client
:noindex:
:members: get_sepa_accounts
+ :noindex:
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
-------------------------
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
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
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
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)
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': {},
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(...)