<commit_before>
import re
import random
from datetime import datetime, timedelta

from django.db.models import (Model, Manager, Q,
    BooleanField,
    CharField,
    DateTimeField,
    PositiveIntegerField,
    ForeignKey)
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.generic import GenericForeignKey

from norc.core.constants import SCHEDULER_PERIOD
from norc.norc_utils import search
from norc.norc_utils.parallel import MultiTimer
from norc.norc_utils.log import make_log

class ScheduleManager(Manager):
    
    @property
    def unfinished(self):
        return self.filter(Q(remaining__gt=0) | Q(repetitions=0))
    
    def unclaimed(self):
        return self.unfinished.filter(scheduler__isnull=True)
    
    def orphaned(self):
        wait = SCHEDULER_PERIOD * 1.5
        cutoff = datetime.utcnow() - timedelta(seconds=wait)
        active = self.unfinished.filter(scheduler__active=True)
        return active.exclude(scheduler__heartbeat__gt=cutoff)
    

class BaseSchedule(Model):
    """A schedule of executions for a specific task."""
    
    objects = ScheduleManager()
    
    class Meta:
        app_label = 'core'
        abstract = True
    
    # The Task this is a schedule for.
    task_type = ForeignKey(ContentType, related_name='%(class)ss')
    task_id = PositiveIntegerField()
    task = GenericForeignKey('task_type', 'task_id')
    
    # The Queue to execute the Task through.
    queue_type = ForeignKey(ContentType, related_name='%(class)s_set')
    queue_id = PositiveIntegerField()
    queue = GenericForeignKey('queue_type', 'queue_id')
    
    # The total number of repetitions of the Task.  0 for infinite.
    repetitions = PositiveIntegerField()
    
    # The number of repetitions remaining.
    remaining = PositiveIntegerField()
    
    # The Scheduler that has scheduled the next execution.
    scheduler = ForeignKey('Scheduler', null=True, related_name='%(class)ss')
    
    # Whether or not to make up missed executions.
    make_up = BooleanField(default=False)
    
    @property
    def instances(self):
        """Custom implemented to avoid cascade-deleting instances."""
        schedule_type = ContentType.objects.get_for_model(self)
        return Instance.objects.filter(
            schedule_type__pk=schedule_type.pk, schedule_id=self.id)
    
    def enqueued(self):
        """Called when the next instance has been enqueued."""
        raise NotImplementedError
    
    def finished(self):
        """Checks whether all runs of the Schedule have been completed."""
        return self.remaining == 0 and self.repetitions > 0
    

class Schedule(BaseSchedule):
    
    # Next execution.
    next = DateTimeField(null=True)
    
    # The delay in between executions.
    period = PositiveIntegerField()
    
    @staticmethod
    def create(task, queue, period, start=0, reps=1):
        if type(start) == int:
            start = timedelta(seconds=start)
        if type(start) == timedelta:
            start = datetime.utcnow() + start
        return Schedule.objects.create(task=task, queue=queue, next=start,
            repetitions=reps, remaining=reps, period=period)
    
    def enqueued(self):
        """Called when the next instance has been enqueued."""
        now = datetime.utcnow()
        # Sanity check: this method should never be called before self.next.
        assert self.next < now, "Enqueued too early!"
        if self.repetitions > 0:
            self.remaining -= 1
        if not self.finished() and self.period > 0:
            period = timedelta(seconds=self.period)
            self.next += period
            while not self.make_up and self.next < now:
                self.next += period
        elif self.finished():
            self.next = None
        return 
    

ri = random.randint

def _make_halfhourly():
    m = ri(0, 29)
    return 'o*d*w*h*m%s,%ss%s' % (m, m + 30, ri(0, 59))

def _make_hourly():
    return 'o*d*w*h*m%ss%s' % (ri(0, 59), ri(0, 59))

def _make_daily():
    return 'o*d*w*h%sm%ss%s' % (ri(0, 23), ri(0, 59), ri(0, 59))

def _make_weekly():
    return 'o*d*w%sh%sm%ss%s' % (ri(0, 6), ri(0, 23), ri(0, 59), ri(0, 59))

def _make_monthly():
    return 'o*d%sw*h%sm%ss%s' % (ri(1, 28), ri(0, 23), ri(0, 59), ri(0, 59))

class CronSchedule(BaseSchedule):
    
    # The datetime that the next execution time is based off of.
    base = DateTimeField(null=True)
    
    # The string encoding of the schedule.
    encoding = CharField(max_length=864)
    
    MONTHS = range(1,13)
    DAYS = range(1,32)
    DAYSOFWEEK = range(7)
    HOURS = range(24)
    MINUTES = range(60)
    SECONDS = range(60)
    <commit_msg>allow scheduler and base fields to be empty in the admin screen<commit_after>
import re
import random
from datetime import datetime, timedelta

from django.db.models import (Model, Manager, Q,
    BooleanField,
    CharField,
    DateTimeField,
    PositiveIntegerField,
    ForeignKey)
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.generic import GenericForeignKey

from norc.core.constants import SCHEDULER_PERIOD
from norc.norc_utils import search
from norc.norc_utils.parallel import MultiTimer
from norc.norc_utils.log import make_log

class ScheduleManager(Manager):
    
    @property
    def unfinished(self):
        return self.filter(Q(remaining__gt=0) | Q(repetitions=0))
    
    def unclaimed(self):
        return self.unfinished.filter(scheduler__isnull=True)
    
    def orphaned(self):
        wait = SCHEDULER_PERIOD * 1.5
        cutoff = datetime.utcnow() - timedelta(seconds=wait)
        active = self.unfinished.filter(scheduler__active=True)
        return active.exclude(scheduler__heartbeat__gt=cutoff)
    

class BaseSchedule(Model):
    """A schedule of executions for a specific task."""
    
    objects = ScheduleManager()
    
    class Meta:
        app_label = 'core'
        abstract = True
    
    # The Task this is a schedule for.
    task_type = ForeignKey(ContentType, related_name='%(class)ss')
    task_id = PositiveIntegerField()
    task = GenericForeignKey('task_type', 'task_id')
    
    # The Queue to execute the Task through.
    queue_type = ForeignKey(ContentType, related_name='%(class)s_set')
    queue_id = PositiveIntegerField()
    queue = GenericForeignKey('queue_type', 'queue_id')
    
    # The total number of repetitions of the Task.  0 for infinite.
    repetitions = PositiveIntegerField()
    
    # The number of repetitions remaining.
    remaining = PositiveIntegerField()
    
    # The Scheduler that has scheduled the next execution.
    scheduler = ForeignKey('Scheduler', null=True, blank=True, 
        related_name='%(class)ss')
    
    # Whether or not to make up missed executions.
    make_up = BooleanField(default=False)
    
    @property
    def instances(self):
        """Custom implemented to avoid cascade-deleting instances."""
        schedule_type = ContentType.objects.get_for_model(self)
        return Instance.objects.filter(
            schedule_type__pk=schedule_type.pk, schedule_id=self.id)
    
    def enqueued(self):
        """Called when the next instance has been enqueued."""
        raise NotImplementedError
    
    def finished(self):
        """Checks whether all runs of the Schedule have been completed."""
        return self.remaining == 0 and self.repetitions > 0
    

class Schedule(BaseSchedule):
    
    # Next execution.
    next = DateTimeField(null=True)
    
    # The delay in between executions.
    period = PositiveIntegerField()
    
    @staticmethod
    def create(task, queue, period, start=0, reps=1):
        if type(start) == int:
            start = timedelta(seconds=start)
        if type(start) == timedelta:
            start = datetime.utcnow() + start
        return Schedule.objects.create(task=task, queue=queue, next=start,
            repetitions=reps, remaining=reps, period=period)
    
    def enqueued(self):
        """Called when the next instance has been enqueued."""
        now = datetime.utcnow()
        # Sanity check: this method should never be called before self.next.
        assert self.next < now, "Enqueued too early!"
        if self.repetitions > 0:
            self.remaining -= 1
        if not self.finished() and self.period > 0:
            period = timedelta(seconds=self.period)
            self.next += period
            while not self.make_up and self.next < now:
                self.next += period
        elif self.finished():
            self.next = None
        return 
    

ri = random.randint

def _make_halfhourly():
    m = ri(0, 29)
    return 'o*d*w*h*m%s,%ss%s' % (m, m + 30, ri(0, 59))

def _make_hourly():
    return 'o*d*w*h*m%ss%s' % (ri(0, 59), ri(0, 59))

def _make_daily():
    return 'o*d*w*h%sm%ss%s' % (ri(0, 23), ri(0, 59), ri(0, 59))

def _make_weekly():
    return 'o*d*w%sh%sm%ss%s' % (ri(0, 6), ri(0, 23), ri(0, 59), ri(0, 59))

def _make_monthly():
    return 'o*d%sw*h%sm%ss%s' % (ri(1, 28), ri(0, 23), ri(0, 59), ri(0, 59))

class CronSchedule(BaseSchedule):
    
    # The datetime that the next execution time is based off of.
    base = DateTimeField(null=True, blank=True)
    
    # The string encoding of the schedule.
    encoding = CharField(max_length=864)
    
    MONTHS = range(1,13)
    DAYS = range(1,32)
    DAYSOFWEEK = range(7)
    HOURS = range(24)
    MINUTES = range(60)
    SECONDS = range(60)
    
{'fraction_non_alphanumeric': 0.10744063324538258, 'fraction_numerical': 0.01562005277044855, 'mean_word_length': 2.8661770705834355, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6853161', 'n_tokens_mistral': 3074, 'n_tokens_neox': 2942, 'n_words': 791}