Field reference
This page lists all the available field types to use in models.
Hint
Some examples on this page return filter objects.
These should be used when filtering results,
for example when calling find()
.
Scalar fields
Scalar fields are used where values from the Sugar backend can be directly mapped to some Python equivalent without any side effects. That means that these values can also be serialized and deserialized without using a model at all. Further, scalar fields mostly don’t depend on any state or other records.
This field type is mainly used for primitive data types. All of them inherit from this common base class and share a few options for filtering:
- class zucker.model.fields.base.ScalarField(api_name: Optional[str] = None, *, validators: Optional[Sequence[Union[Pattern[str], Callable[[ApiType], None]]]] = None)
Scalar fields are fields that also support some basic filtering operations.
- Parameters:
validators – Validators can be provided to make sure that data meets specific requirements. These will be checked both for incoming values from the server and for any value that is set on the field, before writing changes. A validator may either be a function or a regular expression object. The former will be called with the value to be checked as the single argument and should raise a
ValueError
when validation fails. The latter will pass the check if the entire value matches the provided regular expression.
Note
A few notes to keep in mind when using validators:
The default strategy for validating regular expressions will coerce the incoming type to a string. That means that – for example – the number
0xff
will match the expression2..
, because the string representation is255
.Validators are always evaluated on the api data type. That means that they are run after serializing any user input.
- __eq__(other: Optional[Union[NativeType, ApiType]]) NegatableFilter[Any]
Filter for exact values of this field.
Depending on type of the given value, this is is equivalent to one of the other filtering methods:
>>> Person.name == "Ben" # Is the same as Person.name.values("Ben") >>> Person.age == 3 # Is the same as Person.age.values(3) >>> Person.supervisor == None # Is the same as Person.supervisor.null()
- __ne__(other: Optional[Union[NativeType, ApiType]]) NegatableFilter[Any]
Inverse of the
==
filter operator.Use the
!=
operator to exclude specific values:>>> Person.name != "Ben" # Is the same as ~(Person.name.values("Ben")) >>> Person.supervisor != None # Is the same as ~(Person.supervisor.null())
- null() NullishFilter
Filter for whether the field is null.
Use the filter like this:
>>> Person.employer.null()
This will return objects where the ‘employer’ field is not set.
To find only objects where a field is explicitly not null, invert the filter:
>>> ~Person.employer.null()
As a shorthand, you can also use the equals operator for the above examples:
>>> Person.employer == None >>> Person.employer != None
- values(*values: Union[NativeType, ApiType]) ValuesFilter[ApiType]
Filter for exact values of this field.
Most basic use for this filter is finding objects by value. The filter
>>> Person.name.values("Ben")
will return objects who’s name is ‘Ben’.
This filter takes one or more arguments. It matches entries where this set directly contains the field’s value, for example:
>>> Person.name.values("Paul", "Spencer")
will match objects who’s name is either ‘Paul’ or ‘Spencer’.
Inverting this filter yields a ‘not-equal’ filter, for example:
>>> ~Person.name.values("Mike")
This query will match all objects where the name is not equal to ‘Mike’.
The above examples are also available as a shorthand through the equals operator (although you can only check for a single value here):
>>> Person.name == "Ben" >>> Person.name != "Ben"
Strings
For text data, use a string field:
- class zucker.model.StringField(api_name: Optional[str] = None, *, validators: Optional[Sequence[Union[Pattern[str], Callable[[ApiType], None]]]] = None)
Mutable field that handles the various string columns.
This will handle any backend field that has the database type
varchar
,text
,text
,encrypt
,longtext
ortextarea
.- contains(infix: str) StringFilter
Filter for values that contain a given string.
- ends_with(suffix: str) StringFilter
Filter for values that end with a given string.
- not_empty() NotEmptyFilter
Filter for non-empty values.
- starts_with(prefix: str) StringFilter
Filter for values that start with a given string.
Booleans
Boolean fields store either True
or False
:
Numbers
Depending on the type of number stored, use one of these two fields:
- class zucker.model.IntegerField(api_name: Optional[str] = None, *, validators: Optional[Sequence[Union[Pattern[str], Callable[[ApiType], None]]]] = None)
Mutable field for integer columns.
Use this for backend fields of type
int
,integer
,long
,smallint
,tinyint
orulong
.
- class zucker.model.FloatField(api_name: Optional[str] = None, *, validators: Optional[Sequence[Union[Pattern[str], Callable[[ApiType], None]]]] = None)
Mutable field for floating-point number columns.
This is appropriate for backend fields that have the type
float
ordecimal
.
Both of these fields share an API for filtering:
- class zucker.model.fields.base.NumericField(api_name: Optional[str] = None, *, validators: Optional[Sequence[Union[Pattern[str], Callable[[ApiType], None]]]] = None)
Scalar field with filtering operators that produce a total ordering.
- __gt__(other: Any) NumericFilter
Filter for values less than the specified scalar:
>>> Person.age > 60
- __gte__(other: Any) NumericFilter
Filter for values greater than or equal to the specified scalar:
>>> Person.age >= 21
- __lt__(other: Any) NumericFilter
Filter for values less than the specified scalar:
>>> Person.age < 10
- __lte__(other: Any) NumericFilter
Filter for values less than or equal to the specified scalar:
>>> Person.age <= 18
URLs
URLs can be accessed with this field:
- class zucker.model.URLField(api_name: Optional[str] = None, *, validators: Optional[Sequence[Union[Pattern[str], Callable[[ApiType], None]]]] = None)
Mutable string field for URLs.
This should be used for database columns of type
url
. URLs will be decoded and returned as an instance ofurllib.parse.ParseResult
, which contains the parsed components of the URL. To get a string, callstr()
with the result object.
Emails
Sugar exposes emails with two APIs: a legacy version and a new version.
The new version supports connecting any number of emails to a record and treats them like a link.
This also allows setting metadata on emails, for example to opt out of communication.
The legacy version exposes two fields email1
and email2
on the record which map to the first and second email provided through the first method.
See the documentation for more details.
Zucker currently only supports the legacy API through this field:
- class zucker.model.LegacyEmailField(api_name: Optional[str] = None, *, validators: Optional[Sequence[Union[Pattern[str], Callable[[ApiType], None]]]] = None)
Field for legacy email addresses (
email1
andemail2
).This is a string field that also validates [1] any input to be email-like. It should only be used on string columns with names
email1
andemail2
.Normally, this field is used like this (with the second instance being optional):
class Lead(model.UnboundModule): email1 = model.LegacyEmailField() email2 = model.LegacyEmailField()
Enumerations
The Sugar Studio allows to define fields of a type called Dropdown.
This field type allows users to select exactly one out of a predefined set of values.
Zucker maps these fields to Python’s enum
types:
- class zucker.model.EnumField(enum: Type[EnumType], /, api_name: Optional[str] = None, **kwargs: Any)
Mutable field that represents Dropdown Sugar fields.
- Parameters:
enum – Pass an
enum.Enum
type that represents the options for this field. This enum must have a non-null member namedDEFAULT
. Further, elements should be strings (unless otherwise specified on the server side).
Define an enum and use the field like this:
import enum class LeadSource(enum.Enum): DEFAULT = "" OTHER = "Other" EXISTING_CUSTOMER = "Existing Customer" DIRECT_MAIL = "Direct Mail" COLD_CALL = "Cold Call" WORD_OF_MOUTH = "Word of mouth" # ... class Lead(model.UnboundModule): lead_source = model.EnumField(LeadSource)
Again, note the
DEFAULT
field.
Note
In the metadata API, Sugar represents these fields with the additional
parameter options
. Other than that, enumerations are regular text fields.
IDs
Record IDs can be accessed using fields of this type:
- class zucker.model.IdField(api_name: Optional[str] = None, *, validators: Optional[Sequence[Union[Pattern[str], Callable[[ApiType], None]]]] = None)
Immutable field for accessing record IDs.
An instance of this field is automatically created for every module. Manually creating fields of this type should only be necessary for referencing links. In Python, IDs are represented by
UUID
objects.
Note that modules automatically receive a field of this type with the name id
.
Relationships
Sugar offers a number of ways to model bidirectional data relationships. The main way is by using relationship links. Here, a link is defined between two module types and both models get a corresponding field. In Zucker, use this field to access a link:
- class zucker.model.RelatedField(related_module: Union[Type[SyncModule], Type[AsyncModule]], link_name: str)
Field that returns a view on a relationship link.
- Parameters:
related_module – The module type on the other side of the relationship.
link_name – Name of the link.
Note
The related module used to initialize this field must be a bound module with the same client as the module the field is attached to. You can’t mix synchronous and asynchronous models here.
Implementing fields
Next to the already mentioned ScalarField
and NumericField
, there are also other base classes available to use when implementing custom field types.
In fact, most of the above scalar fields above actually implement the mutable variants:
- class zucker.model.fields.base.MutableScalarField(api_name: Optional[str] = None, *, validators: Optional[Sequence[Union[Pattern[str], Callable[[ApiType], None]]]] = None)
Mutable version of
ScalarField
.
- class zucker.model.fields.base.MutableNumericField(api_name: Optional[str] = None, *, validators: Optional[Sequence[Union[Pattern[str], Callable[[ApiType], None]]]] = None)
Mutable version of
NumericField
.
A field being mutable means that when a field name
is used on a Record
record type, both of the following will work:
the_name = record.name # Get the name of a record object
record.name = "New Name" # Set a new value
To implement a scalar field, choose and applicable superclass and override it.
Scalar fields require two generic arguments - native type and an API type.
The latter is the type of data the Sugar API returns for the field and should be a JSON type.
Normally, this will be one of the JSON-native scalars str
, int
, float
or bool
.
The native type may be the same, but can also be something different.
This is the rich Python type that the API response gets converted into.
A typical example here is for dates, which are typically encoded as strings and returned as native datetime
objects.
To handle this conversion, you will need to implement these two methods on the new field class:
- abstract ScalarField.load_value(raw_value: JsonType) NativeType
Load a value from the API into a native data type.
- Parameters:
raw_value – Response from the API for this field. This will be a JSON primitive, which should either be returned as-is (where appropriate) or converted into a native Python data type.
- Returns:
A Python data type for this field.
- abstract ScalarField.serialize(value: Union[NativeType, ApiType]) ApiType
Serialize a native data type into something the API can take back for saving.
This method also supports “serializing” api types. In this case implementors are advised to verify the input’s validity and return it as-is.
- Parameters:
value – Native or API data type for this field.
- Returns:
An API-compatible data type.
Note
When setting the value of a mutable field, you can provide both the native as well as the API type.
For a hypothetical BirdField
field on a Zoo
model, this would both be possible:
>>> the_bird = zoo.bird
>>> the_bird
<Bird object at ...>
>>> zoo.bird = the_bird # Set the field value using the native data type
>>> zoo.bird = "zazu" # Set the field value using the API type (assuming birds serialize to strings)
This is why the serialize()
takes both input types.
Numeric fields only have one generic argument, as the native and API types are assumed to be the same.