Double Context Entry

Double context entry is a pattern you will see throughout DFFML. It’s used almost everywhere for consistencies sake.

Note

This was covered in the 2021-02-016 Weekly Sync meeting: https://youtu.be/OY_OfqB7rkY?t=657 (until 19:12).

DFFML uses asyncio heavily, as such when we talk about entering or exiting contexts, we are always talking about the async context unless explicitly stated.

Rules of the double context entry pattern

  • All objects are comprised of two classes

    • The parent

      • Usually responsible for saving / loading files, creating initial connections to databases.

    • The context (Which always have the suffix, “Context”)

      • Usually responsible for transactions within a database, locks on sub resources, etc.

      • The methods of an object should reside in it’s context.

Benefits of using this pattern

Many network attached resources will fall into this pattern. By applying it uniformly, we make it so all classes have a future proof place to load network resources when / if needed.

Example - Model

You can also keep model or sources loaded by passing in their contexts. The advantage of this method is that we as the caller can control when models are saved and loaded, and when sources are opened / closed (for example if they are backed by a file).

model.py

import asyncio
from dffml import *

model = SLRModel(
    features=Features(Feature("Years", int, 1),),
    predict=Feature("Salary", int, 1),
    location="tempdir",
)


async def main():
    # Load model and source using double context entry pattern
    # Here we create a memory backed source. Equivlant to what the above example
    # does behind the scenes when passing in dict objects.
    async with model, MemorySource(
        records=[
            Record(i, data={"features": {"Years": i}}) for i in range(6, 8)
        ]
    ) as source:
        async with model() as mctx, source() as sctx:
            # Train the model
            await train(
                mctx,
                {"Years": 0, "Salary": 10},
                {"Years": 1, "Salary": 20},
                {"Years": 2, "Salary": 30},
                {"Years": 3, "Salary": 40},
            )
            # Make predictions
            async for i, features, prediction in predict(mctx, sctx):
                features["Salary"] = round(prediction["Salary"]["value"])
                print(features)


asyncio.run(main())
$ python model.py
{'Years': 6, 'Salary': 70}
{'Years': 7, 'Salary': 80}