iiasa / ixmp4

A data warehouse for high-powered scenario analysis in the domain of integrated assessment of climate change and energy systems modeling
https://docs.ece.iiasa.ac.at/ixmp4
MIT License
10 stars 6 forks source link

All we need to do is listen. #99

Closed meksor closed 2 months ago

meksor commented 2 months ago

Hi I've replaced the current audit information setup (calling set_creation_info when creating an object) with a listener system in anticipation for the versioning change.

I've added the class SqlaEventHandler to be instantiated in each database backend. This handler listens to three events explained in the docstrings.

If the event targets inherit from the new mixins (by fridolin, moved and refactored by me here), special data will be injected into the queries that serves to set/update the relevant columns without an extra query dispatch. This is documented in the mixin docstrings.

The following test was added:

def test_audit_info(self, test_mp, request):
    test_mp = request.getfixturevalue(test_mp)
    run = test_mp.backend.runs.create("Model", "Scenario")
    test_mp.backend.runs.set_as_default_version(run.id)
    test_mp.backend.runs.create("Model", "Scenario")

    runs = test_mp.backend.runs.tabulate(default_only=False)
    assert (runs["created_by"] == "@unknown").all()
    # was updated by set_as_default_version
    assert runs["updated_by"][0] == "@unknown"
    assert runs["updated_by"][1] is None

As far as I can see this is the only place where the current rules apply. Run is the only model that can be updated directly (so i added HasUpdateInfo to its parents).

Note that this does not include the functionality to automatically update the UpdateInfo when related objects are changed. For example when using Run.iamc.add or Run.iamc.remove. This is on purpose as I think it will make these PRs easier to review. My plan is to implement some sort of "ownership" system that can be used for update information, orphan deletion and permission checking all at the same time. A short summary could be that we want to allow rows to own other rows along foreign keys. For example a Run owns multiple TimeSeries which in turn own multiple DataPoints. More to follow in a seperate PR.

More: