Getting Started with an Existing Codebase

Depending on your requirements, there are a few ways to make your existing code Scooch configurable. In most cases the scooch.configurize will be a key piece of functionality.

Configurizing Classes that You Own

If you already have class definitions in your code that you want to make configurable, you can use the configurize decorator:

from scooch import configurize

@configurize
class PreexistingClass(object):

    def __init__(self, arg1, arg2=5)
        ...

Scooch will create a derived class that has arg1 and arg2 as configurable parameters, with a default value of 5 for arg2. The derived class will take a Scooch Config dictionary in the constructor argument, like any other Scooch Configurable.

Note that Scooch will prefix Scooch to the class name to differentiate it from the non-configurable version of the class. For example, in your Scooch config.yaml file, the type of the configurized class above will be ScoochPreexistingClass

You can also add a class to an existing Scooch Configurable hierarchy, by specifying a base_class that is also a Configurable.

@configurize(base_class=ScoochConfigurableBaseClass)
class PreexistingClass(object):

    def __init__(self, arg1, arg2=5)

Configurizing Third-Party Classes

If the class you want to configure with Scooch belongs to a code base you are not a contributor for, you will have no access to modify the class definition. One way to make a third party class Scooch configurable is to use scooch.configurize.

For example, if you are developing an ML training pipeline, you may want to use the classes in tensorflow.keras.losses, and parameterize them in your Scooch config file. Making a single third party class configurable is as easy as:

from scooch import configurize
import tensorflow as tf

configurable_class = configurize(tf.keras.losses.BinaryCrossEntropy)

We can now write a ./config.yaml for this new class:

ScoochBinaryCrossEntropy:
    from_logits: True
    label_smoothing: 0.5

The new configurable loss may be instantiated and used like any other keras loss function.

...

from scooch import Config

loss_func = configurable_class(Config('./config.yaml'))

...

This will create a single configurable class that is currently isolated to it’s own inheritance hierarchy. Perhaps you want to create a range of Loss functions, some custom and some derived from a third party, that will be selectable in your config.yaml like any other Scooch type. Scooch can do this through multiple inheritence, by adding a user defined Scooch base class to each congigurized third party class:

For example, to add all keras loss functions to our Scooch hierarchy we could do the following:

from scooch import Configurable
from scooch import configurize
import sys
import inspect

class Loss(Configurable):
    """
    Base class for all Scooch Configurable loss functions.
    """

    pass

clsmembers = inspect.getmembers(sys.modules[tf.keras.losses.__name__], inspect.isclass)
configurable_tf_losses = [configurize(mem[0], Loss) for mem in clsmembers if mem[0] != 'Loss']

With the above code, classes can now be defined with a Param of type Loss, which will now be able to use all keras loss functions in your config.yaml file:

...

from scooch import Param

class Experiment(Configurable):

    Param(Loss, doc="A Loss function to train a model with.")
    ...

Configurizing Code that is not Object Oriented

We are currently working on extending Scooch to configure functional code.

If you’re convinced by the arguments in Why Object Oriented Configs? section, you may want to start trying to structure your code using object oriented programming.

An easy start to transforming your code into an object oriented structure, can be to first place it in a run() method of a Scooch Configurable. For example,

from scooch import Configurable
from scooch import Config

class Experiment(Configurable):
    """
    The class that encapsulates my ML task.
    """

    def run():
        # Your code here.

experiment = Experiment(Config('./config.yaml'))
experiment.run()

From here, you might want to start breaking out configurable parameters as scooch.Params, separate functionality into separate methods, and create classes used within the Experiment class to modularize some of its processes.

If Scooch looks like something you want to use, but it does not meet your needs, you can file a feature request.