The circulation module is going to be rewritten from scratch in Invenio 2, making it more flexible and covering more library use cases. The purpose of this RFC is to discuss current development stages regarding the programming approach.
While discussing the possible use cases for the circulation module, it became apparent that the module is basically about managing states of different entities and triggering events based on those changes. Along with the fact that the statuses of items vary among the instances managing a library, the following approach is proposed:
The two entities having varying states, Item and Request (or LoanCycle, naming to be discussed), would be managed by using a configuration file using the following pattern:
This configuration file defines every possible transition between statuses and defines what is supposed to happen during those changes.
Example: Switching from "on_shelf" to "on_loan"
While being in status "on_shelf", one possible transition is to go to the status "on_loan". When this transition is triggered, the following functions are executed:
"on_shelf" -> "to" -> "attempt_leave" -> "function" will check the needed parameters to leave "on_shelf" and go to "on_loan"
"on_shelf" -> "to" -> "on_leave" will trigger every action associated with leaving "on_shelf" to "on_loan"
"on_loan" -> "from" -> "attempt_enter" -> "function" will check the needed parameters to enter the "on_loan" status coming from "on_shelf"
"on_loan" -> "from" -> "on_leave" will trigger every action associated with with entering "on_loan" from "on_shelf"
The classes Item and Request would then provide a very generic interface like the following:
class Item/Request(object):
@classmethod
def new(cls, ...):
"""Creates a new entity."""
@classmethod
def get(cls, id):
"""Gets and entity by ID."""
@classmethod
def get_all_functions(cls):
"""Gets all available functions triggering a transition."""
@classmethod
def get_all_statuses(cls):
"""Gets all available statuses."""
def run(self, function_name, **kwargs):
"""Runs a transition."""
def get_available_functions(self):
"""Gets the available functions triggering a transition from the current status."""
Following this approach the needed statuses and workflows can be configured and the demands for logging events can be satisfied.
UPDATE 18.05.2015
After playing around with that approach and the presented configuration file it became apparent that the config approach is a little to elaborate. It basically led to a lot of redundancy and was therefore hard to read. For example the loan action, would require a section in "on_shelf" -> "to" -> "on_loan" as well as a section in "on_loan" -> "from" -> "on_shelf". Additionally, this redundancy provided pretty little value. Sticking to the loan action example: The approach would trigger four function calls, and the question arose: Is this really necessary?
So, even though it moves a little bit away from the "state machine approach", this new configuration file style is proposed:
It's a more direct mapping from a given status to the available actions related to it. The _validatefunc and func parameter work the same way as the "attempt" -> "function" and "on" functions in the previous configuration, whereas parameters corresponds to "needed_attributes", but moved up one level, since they are needed for both function calls anyways.
Also the interface changed a bit (Should become more similar to the Invenio 2 way):
class CirculationObject(object):
@classmethod
def new(cls, status='new'):
"""Creates a new entity."""
@classmethod
def get(cls, id):
"""Gets an entity by ID."""
@classmethod
def query(cls, **kwargs):
"""Gets entities by utilizing the provides search capabilities."""
@classmethod
def get_all_functions(cls):
"""Gets all available functions triggering a transition."""
@classmethod
def get_all_statuses(cls):
"""Gets all available statuses."""
def get_function_parameters(self, function_name, status=None):
"""Returns the required parameters for a given function."""
def validate_run(self, function_name, **kwargs):
"""Runs the *validate_func* function."""
def run(self, function_name, **kwargs):
"""Runs a transition."""
def get_available_functions(self):
"""Gets the available functions triggering a transition from the current status."""
UPDATE 17.07.2015
As it turned out, a function based approach causes some difficulties regarding the validation, since some of the information gathered during the validation step can be reused in the actual run. Therefore, objects are proposed to store those kind of information.
class ReturnConfig(ConfigBase):
new_status = 'on_shelf'
parameters = ['item']
def run(self, _storage, item=None, **kwargs):
clc = CirculationLoanCycle.search(item=item,
current_status='on_loan')[0]
clc.run('return', _storage=_storage)
def check_item(self, item=None, **kwargs):
if not item:
raise Exception('There needs to be an item')
The _newstatus attributes triggers the status change after execution of the script, while parameters is supposed to make the interaction easier.
There is currently also some magic happening, which executes every method starting with 'check' during the validation step and then gathers the exceptions. This is supposed to be utilized later on in the user interface to provide information for the user about incorrect settings. But there should be a debug mode for developers, since this exception gathering removes some information currently.
RFC Programming approach
The circulation module is going to be rewritten from scratch in Invenio 2, making it more flexible and covering more library use cases. The purpose of this RFC is to discuss current development stages regarding the programming approach.
While discussing the possible use cases for the circulation module, it became apparent that the module is basically about managing states of different entities and triggering events based on those changes. Along with the fact that the statuses of items vary among the instances managing a library, the following approach is proposed:
The two entities having varying states, Item and Request (or LoanCycle, naming to be discussed), would be managed by using a configuration file using the following pattern:
This configuration file defines every possible transition between statuses and defines what is supposed to happen during those changes.
Example: Switching from "on_shelf" to "on_loan" While being in status "on_shelf", one possible transition is to go to the status "on_loan". When this transition is triggered, the following functions are executed:
The classes Item and Request would then provide a very generic interface like the following:
Following this approach the needed statuses and workflows can be configured and the demands for logging events can be satisfied.
UPDATE 18.05.2015
After playing around with that approach and the presented configuration file it became apparent that the config approach is a little to elaborate. It basically led to a lot of redundancy and was therefore hard to read. For example the loan action, would require a section in "on_shelf" -> "to" -> "on_loan" as well as a section in "on_loan" -> "from" -> "on_shelf". Additionally, this redundancy provided pretty little value. Sticking to the loan action example: The approach would trigger four function calls, and the question arose: Is this really necessary?
So, even though it moves a little bit away from the "state machine approach", this new configuration file style is proposed:
It's a more direct mapping from a given status to the available actions related to it. The _validatefunc and func parameter work the same way as the "attempt" -> "function" and "on" functions in the previous configuration, whereas parameters corresponds to "needed_attributes", but moved up one level, since they are needed for both function calls anyways.
Also the interface changed a bit (Should become more similar to the Invenio 2 way):
UPDATE 17.07.2015
As it turned out, a function based approach causes some difficulties regarding the validation, since some of the information gathered during the validation step can be reused in the actual run. Therefore, objects are proposed to store those kind of information.
An example Config object looks like this:
The _newstatus attributes triggers the status change after execution of the script, while parameters is supposed to make the interaction easier.
There is currently also some magic happening, which executes every method starting with 'check' during the validation step and then gathers the exceptions. This is supposed to be utilized later on in the user interface to provide information for the user about incorrect settings. But there should be a debug mode for developers, since this exception gathering removes some information currently.