Example #1
0
def datetime_range(
        lower=datetime.now(tz=timezone.utc), upper=None, nullable=False):
    """Postgres DateTime Range column mapping.
    """
    return sa.Column(postgresql.TSTZRANGE(),
                     default=pg_extras.DateTimeTZRange(lower, upper),
                     nullable=nullable)
Example #2
0
    def make_clock(effective_lower: dt.datetime, vclock_lower: int,
                   **kwargs) -> _ClockSet:
        """construct a clock set tuple"""
        effective_upper = kwargs.get('effective_upper', None)
        vclock_upper = kwargs.get('vclock_upper', None)

        effective = psql_extras.DateTimeTZRange(effective_lower,
                                                effective_upper)
        vclock = psql_extras.NumericRange(vclock_lower, vclock_upper)

        return _ClockSet(effective, vclock)
Example #3
0
def effective_now():
    """UTC DateTime Range starting from now.
    """
    utc_now = datetime.now(tz=timezone.utc)
    return pg_extras.DateTimeTZRange(utc_now.date(), None)
Example #4
0
def effective_now() -> psql_extras.DateTimeTZRange:
    utc_now = dt.datetime.now(tz=dt.timezone.utc)
    return psql_extras.DateTimeTZRange(utc_now, None)
Example #5
0
    def _record_history(self, clocked: Clocked):
        """
        Record all history for a given clocked object

        Args:
            clocked (Clocked): instance of clocked object
        """

        #
        # Check for activity misuse
        #
        if clocked.temporal_options.activity_model is not None and clocked.activity is None:
            raise ValueError('An activity is required when saving a %s' %
                             type(clocked).__name__)
        if clocked.temporal_options.activity_model is None and clocked.activity is not None:
            raise ValueError(
                'There is no activity model for %s; you cannot supply an activity'
                % type(clocked).__name__)

        #
        # Determine which fields have changed
        #
        changed_fields = {}
        for field, history_model in self.history_models.items():
            new_val = clocked._meta.get_field(field).value_from_object(clocked)
            prev_val = clocked._state._django_temporal_previous[field]
            if new_val != prev_val or clocked._state._django_temporal_add:
                changed_fields[(field, history_model)] = new_val

        if not changed_fields:
            return

        #
        # Increment the clock and build the effective and vclock ranges for the next tick
        #
        timestamp = timezone.now()
        clocked.vclock += 1
        new_tick = clocked.vclock

        #
        # Create the EntityClock for this tick
        #
        clock_model = type(clocked).temporal_options.clock_model
        if clocked.temporal_options.activity_model is not None:
            clock = clock_model(entity=clocked,
                                activity=clocked.activity,
                                tick=new_tick)
        else:
            clock = clock_model(entity=clocked, tick=new_tick)
        clock.save()

        #
        # Create the field history for this tick
        #
        for (field, history_model), new_val in changed_fields.items():
            if new_tick > 1:
                # This is an update, not a create, so update the upper bounds of the previous tick.
                #
                # This cannot be done with F expressions because of the range updates, unless we write our
                # own implementations of int4range/tstzrange.
                #
                # Instead use .raw and force execution by wrapping with list(). This approach ensures that
                # the update happens in the correct connection/transaction.
                list(
                    history_model.objects.raw(
                        """ UPDATE {table_name}
                        SET vclock = int4range(lower(vclock), %s),
                            effective = tstzrange(lower(effective), %s)
                        WHERE entity_id = %s AND upper(vclock) IS NULL
                        RETURNING id;
                    """.format(table_name=connection.ops.quote_name(
                            history_model._meta.db_table)),
                        [new_tick, timestamp, clocked.pk]))

            hist = history_model(**{field: new_val},
                                 entity=clocked,
                                 vclock=psql_extras.NumericRange(
                                     new_tick, None),
                                 effective=psql_extras.DateTimeTZRange(
                                     timestamp, None))
            hist.save()

            # Update the stored state for this field to detect future changes
            clocked._state._django_temporal_previous[field] = new_val

        # Reset the activity so it can't be accidentally reused easily
        clocked.activity = None

        # Update the vclock value without triggering a recursive `record_history`
        type(clocked).objects \
            .filter(**{clocked._meta.pk.name: getattr(clocked, clocked._meta.pk.name)}) \
            .update(vclock=new_tick)