Changeset - c49371c87ed3
[Not reviewed]
0 0 1
Brett Smith - 7 years ago 2017-10-25 21:17:41
brettcsmith@brettcsmith.org
CODE: Add a high-level overview of the program structure.
1 file changed with 93 insertions and 0 deletions:
0 comments (0 inline, 0 general)
CODE.rst
Show inline comments
 
new file 100644
 
import2ledger code structure
 
============================
 

	
 
Concepts
 
--------
 

	
 
The main workflow of the program passes through three different types with different responsibilities.
 

	
 
Entry data
 
~~~~~~~~~~
 

	
 
Data for an output entry is kept and passed around in a dict with the following contents:
 

	
 
``date``
 
  A datetime.date object (if this is omitted, the ``default_date`` hook will fill in the default date from the user's configuration)
 

	
 
``payee``
 
  A string
 

	
 
``amount``
 
  A string or other object that can be safely converted to a decimal.Decimal
 

	
 
``currency``
 
  A string with a three-letter code, uppercase, identifying the transaction currency
 

	
 
It can optionally include additional keys for use as template variables.
 

	
 
Importers
 
~~~~~~~~~
 

	
 
At a high level, importers read a source file, and generate data for output entries.
 

	
 
Class method ``can_handle(source_file)``
 
  Returns true if the importer can generate entries from the given source file object, false otherwise.
 

	
 
``__init__(source_file)``
 
  Initializes an importer to generate entries from the given source file object.
 

	
 
``__iter__()``
 
  Returns a iterator of entry data dicts.
 

	
 
Class attribute ``TEMPLATE_KEY``
 
  A string with the full key to load the corresponding template from the user's configuration (e.g., ``'template patreon income'``).
 

	
 
Hooks
 
~~~~~
 

	
 
Hooks make arbitrary transformations to entry data dicts.  Every entry data dict generated by an importer is run through every hook before being output.
 

	
 
``__init__(config)``
 
  Initializes the hook with the user's configuration.
 

	
 
``run(entry_data)``
 
  This method makes the hook's transformations to the entry data dict, if any.  If this method clears the entry data dict, that entry will not be output.
 

	
 
Templates
 
~~~~~~~~~
 

	
 
Templates receive entry data dicts and format them into final output entries.
 

	
 
``__init__(template_str)``
 
  Initializes the template from a single string, as read from the user's configuration.
 

	
 
``render(payee, amount, currency, date=None, **template_vars)``
 
  Returns a string with the output entry, using the given entry data.  This is expected to be called as ``render(**entry_data)``; the arguments take the same types as entry data dicts.
 

	
 
Loading importers and hooks
 
---------------------------
 

	
 
Importers and hooks are both loaded and found dynamically when the program starts.  This makes it easy to extend the program: you just need to write the class following the established pattern, no registration needed.
 

	
 
import2ledger finds importers by looking at all ``.py`` files in the ``importers/`` directory, skipping files whose names start with ``.`` (hidden) or ``_`` (private).  It tries to import that file as a module.  If it succeeds, it looks for things in the module named ``*Importer``, and adds those to the list of importers.
 

	
 
Hooks follow the same pattern, searching the ``hooks/`` directory and looking for things named ``*Hook``.
 

	
 
Technically this is done by ``importers.load_all()`` and ``hooks.load_all()`` functions, but most of the code to do this is in the ``util`` module.
 

	
 
Main loop
 
---------
 

	
 
At a high level, import2ledger handles each input file this way::
 

	
 
  usable_importers = importers where can_handle(input_file) returns true
 
  for importer_class in usable_importers:
 
    template = built from importer_class.TEMPLATE_KEY
 
    input_file.seek(0)
 
    for entry_data in importer_class(input_file):
 
      for hook in all_hooks:
 
        hook.run(entry_data)
 
      if entry_data:
 
        template.render(**entry_data)
 

	
 
Note in particular that multiple importers can handle the same input file.  This helps support inputs like Patreon's earnings CSV, where completely different transactions are generated from the same source.
0 comments (0 inline, 0 general)