Persistently adds an appointment

        Args:
            query (str):
                storm query to run
            reqs (Union[None, Dict[TimeUnit, Union[int, Tuple[int]], List[...]):
                one or more dicts of the fixed aspects of the appointment.  dict value may be a single or multiple.
                May be an empty dict or None.
            incunit (Union[None, TimeUnit]):
                the unit that changes for recurring, or None for non-recurring.  It is an error for this value to match
                a key in reqdict.
            incvals (Union[None, int, Iterable[int]): count of units of incunit or explicit day of week or day of month.
                Not allowed for incunit == None, required for others (1 would be a typical
                value)

        Notes:
            For values in reqs that are lists and incvals if a list, all combinations of all values (the product) are
            used

        Returns:
            iden of new appointment

async def add(self, useriden, query: str, reqs, incunit=None, incvals=None):
        '''
        Persistently adds an appointment

        Args:
            query (str):
                storm query to run
            reqs (Union[None, Dict[TimeUnit, Union[int, Tuple[int]], List[...]):
                one or more dicts of the fixed aspects of the appointment.  dict value may be a single or multiple.
                May be an empty dict or None.
            incunit (Union[None, TimeUnit]):
                the unit that changes for recurring, or None for non-recurring.  It is an error for this value to match
                a key in reqdict.
            incvals (Union[None, int, Iterable[int]): count of units of incunit or explicit day of week or day of month.
                Not allowed for incunit == None, required for others (1 would be a typical
                value)

        Notes:
            For values in reqs that are lists and incvals if a list, all combinations of all values (the product) are
            used

        Returns:
            iden of new appointment
        '''
        iden = s_common.guid()
        recur = incunit is not None
        indx = self._next_indx
        self._next_indx += 1

        if reqs is None:
            reqs = {}

        if not query:
            raise ValueError('empty query')

        if not reqs and incunit is None:
            raise ValueError('at least one of reqs and incunit must be non-empty')

        if incunit is not None and incvals is None:
            raise ValueError('incvals must be non-None if incunit is non-None')

        if isinstance(reqs, Mapping):
            reqs = [reqs]

        # Find all combinations of values in reqdict values and incvals values
        recs = []
        for req in reqs:

            reqdicts = self._dictproduct(req)
            if not isinstance(incvals, Iterable):
                incvals = (incvals, )
            recs.extend(ApptRec(rd, incunit, v) for (rd, v) in itertools.product(reqdicts, incvals))

        appt = _Appt(iden, recur, indx, query, useriden, recs)
        self._addappt(iden, appt)

        await self._storeAppt(appt)

        return iden