Skip to content

Normalizer

The Normalizer class is the discrete heavy-lifter providing the logic for converting the data from the service item into a NormalModel, and back again. For reference within this framework, serializing the service's data to the NormalModel is called "normalizing" and from the NormalModel to the service's data structure is called "denormalizing". A subclass of Normalizer requires two class attributes to be declared: normal_model which is the NormalModel class that the data will be delivered as, and transform which is a dictionary that maps the NormalModel's fields to keys or lists of keys through the original data's structure which end at the value that belongs in that field. This allows the normalizer to traverse and find values that may be nested several layers deep in the data.

ID Sync

In order to keep item records synced and prevent duplicates, each normalizer class must have the attribute id_path defined as the path to the id in the item's data structure. This is just like any other field defined in the transform, however it is required separately, since every record must have an id. When an item is read, its id will be stored, and when it is created in another destination the returned id will be stored in the same row. In this way Zync will keep track of where data exists, only create records when necessary, and always update the correct record.

Translator

The Normalizer class also has an optional inner class called Translator where methods for modifying the values of any fields may be declared. For example, the units of measurements may need to be changed, names capitalized, or datetimes set to UTC. When normalizing, these methods are called before the NormalModel object is created, so they do interfere with any validation the NormalModel may include, rather they may be necessary for it. Keep in mind that after translation the values will be automatically cast to the type defined in the NormalModel field, and an ValidationError will be raised if this fails. To create a translator method, methods for translating data on normalization must be named normalize_<field> where field is the name of the field in the NormalModel, and methods for translating data on denormalization must be named denormalize_{field}.

Since these methods may require information in the item's data beyond the single field, data is provided as the second argument to these methods which is the entire data structure

Example

Let's say you have a service called Bingo, and you need to sync events with them using their API. Their API returns event data that looks like this:

bingoevent = {
    "id": 9,
    "name": 'Thursday Night Bingo',
    "properties": {
        "start_datetime": 1648767600,
        "end_datetime": 1648778400
    }
}
Notice that start_datetime and end_datetime are one level deeper than the other fields, within properties. Also, their values provided as Unix timestamps.
from datetime import datetime
from pydantic import BaseModel
from  zync import Normalizer



# This Event NormalModel is for all events defining the names and types of the fields
class Event(BaseModel):
    _id: str
    name: str
    start: datetime
    end: datetime


class BingoEventNormalizer(Normalizer):
    # The normal_model attribute tells the normalizer which class to cast the data to when normalizing, and what type to expect when denormalizing
    normal_model = Event

    # The elements of transform dictionary have the following structure:
    # "normalmodel_field_name": "service_data_field_name" | ["service_data_field_names",...]
    # If the field's value is at the root level of the data, simply providing the value's key is enough.
    # If it is nested, provide a list of keys from highest to lowest, starting with the root level and ending with the key which points directly to the value.
    transform = {    
        "name": "name",
        "start": ["properties", "start_datetime"],
        "end": ["properties", "end_datetime"]
    }

class BingoItem(Item):
    normalizer = BingoEventNormalizer

    def read(self):
        bingoevent = {
            "id": 9,
            "name": 'Thursday Night Bingo',
            "properties": {
                "start_datetime": 1648767600,
                "end_datetime": 1648778400
            }
        }
        return bingoevent


    def write(self):
        eventob = BingoEvent(
            name="Big Saturday Bingo Festival",
            start=datetime.datetime(2022,4,2,12),
            end=datetime.datetime(2022,4,2,20)
        )
        return eventob

    def read_many(self):
        pass
    def update(self):
        pass
    def delete(self):
        pass