When collecting user input, we often define the kind of input we expect using [[schemas|Schemas]]. These schemas can use certain Python base types, but they can also use custom types, called objects, that we define. For example, consider this schema:
{
"type": "dict",
"properties": [
{
"name": "length",
"schema": {
"type": "custom"
"obj_type": "Real",
}
}
]
}
The schema would match a dictionary like this:
{"length": 34.5}
This schema describes a dictionary with a length
key whose value is of custom type Real
, and the Real
type is one of our objects. We define these objects as classes in extensions/objects/models/objects.py
and components in extensions/objects/templates
.
The Python classes for objects are defined in objects.py
. For example, here's the class for the int
type:
class Int(BaseObject):
"""Integer class."""
description = 'An integer.'
default_value = 0
@classmethod
def get_schema(cls):
"""Returns the object schema.
Returns:
dict. The object schema.
"""
return {
'type': 'int'
}
Object classes all meet the following criteria:
-
They inherit from either
BaseObject
or another object class. -
Their
description
class variables are set to a string describing the object. -
Their
default_value
class variables are set to the value that should be the default whenever this type is used in a rule. Note that this variable is technically not required if the object will never be used in a [[rule|Creating-rules]]. -
They provide a
normalize()
class method that accepts a raw Python object. The method should first validate the raw object to check whether it is malformed. If the validation checks pass, then the method should return a normalized form of the object. Note that in this context, "normalized" means a primitive Python type or multiple primitive types combined with lists and/or dicts.- The
BaseObject
class provides anormalize
class method that can normalize any object so long as the object's class provides aget_schema
class method that returns a schema describing the object.BaseObject.normalize
passes the schema toschema_utils.normalize_against_schema
, so make sure thatnormalize_against_schema
won't try to normalize the object using the object class. Otherwise, there will be an endless loop betweennormalize_against_schema
andBaseObject.normalize
.
- The
Every object is accompanied by a component defined in extensions/objects/templates
and imported in extensions/objects/object-components.module.ts
. Each component provides the HTML and frontend code needed to create a form for the user to provide input in the form of the object. These components rely heavily on schema-based forms. For example, the int-editor
component's HTML is just this:
<schema-based-editor [schema]="getSchema.bind(this)"
[localValue]="value"
(localValueChange)="updateValue($event)">
</schema-based-editor>
The getSchema
function is defined in int-editor.component.ts
and returns this schema:
{
type: "int",
validators: [{
id: "is_integer"
}]
}
The schema-based-editor
component already knows how to construct a form for this schema.
Let's suppose you're creating a new object called MyObject
(in practice you should use a more descriptive name). You'll need to follow these steps:
- Create a new object class named
MyObject
. It must satisfy the criteria documented above. - Add tests for your class in
extensions/objects/models/objects_test.py
. You should add normalization tests to theObjectNormalizationUnitTests
class. You can also create a new test class to hold additional test cases. - Add a new Angular component to accept input in the form of your object. Your component files should be named
my-object-editor.component.*
, and your component class should be namedMyObjectEditorComponent
. - Import your component in
extensions/objects/object-components.module.ts
and add it to the module there.
You will usually use objects by referencing them as you [[create interactions|Creating-Interactions]], [[define rules|Creating-Rules]], or otherwise use [[schemas|Schemas]].