Creating objects
Table of Contents
Introduction
When collecting user input, we often define the kind of input we expect using 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
.
How objects are defined
Python classes
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.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
.
Angular components
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.
Create a new object
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.
Using objects
You will usually use objects by referencing them as you create interactions, define rules, or otherwise use schemas.