Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
hepheir committed Sep 4, 2024
2 parents daaa159 + 6ee5264 commit 2d7dbca
Show file tree
Hide file tree
Showing 142 changed files with 25,318 additions and 1 deletion.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ cover/
local_settings.py
db.sqlite3
db.sqlite3-journal
.static/
.media/

# Flask stuff:
instance/
Expand Down Expand Up @@ -159,4 +161,8 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/

# VS Code
.vscode/
*.code-workspace
Empty file added app/apps/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions app/apps/background_task/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# django-background-tasks

Source: https://github.com/django-background-tasks/django-background-tasks
8 changes: 8 additions & 0 deletions app/apps/background_task/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
__version__ = '1.2.8'

default_app_config = 'background_task.apps.BackgroundTasksAppConfig'

def background(*arg, **kw):
from apps.background_task.tasks import tasks
return tasks.background(*arg, **kw)
32 changes: 32 additions & 0 deletions app/apps/background_task/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from django.contrib import admin
from apps.background_task.models import Task
from apps.background_task.models import CompletedTask


def inc_priority(modeladmin, request, queryset):
for obj in queryset:
obj.priority += 1
obj.save()
inc_priority.short_description = "priority += 1"

def dec_priority(modeladmin, request, queryset):
for obj in queryset:
obj.priority -= 1
obj.save()
dec_priority.short_description = "priority -= 1"

class TaskAdmin(admin.ModelAdmin):
display_filter = ['task_name']
search_fields = ['task_name', 'task_params', ]
list_display = ['task_name', 'task_params', 'run_at', 'priority', 'attempts', 'has_error', 'locked_by', 'locked_by_pid_running', ]
actions = [inc_priority, dec_priority]

class CompletedTaskAdmin(admin.ModelAdmin):
display_filter = ['task_name']
search_fields = ['task_name', 'task_params', ]
list_display = ['task_name', 'task_params', 'run_at', 'priority', 'attempts', 'has_error', 'locked_by', 'locked_by_pid_running', ]


admin.site.register(Task, TaskAdmin)
admin.site.register(CompletedTask, CompletedTaskAdmin)
31 changes: 31 additions & 0 deletions app/apps/background_task/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from logging import getLogger
from threading import Thread

from django.apps import AppConfig


logger = getLogger(__name__)


class BackgroundTasksAppConfig(AppConfig):
name = 'apps.background_task'
from apps.background_task import __version__ as version_info
verbose_name = 'Background Tasks ({})'.format(version_info)

def ready(self):
from apps.background_task import signals # noqa
from apps.background_task.management.commands.process_tasks import Command as ProcessTasksCommand

def task_runner(*args, **kwargs):
logger.info('background tasks thread started')
try:
command = ProcessTasksCommand()
command.handle()
except Exception as exception:
logger.error(exception)
finally:
logger.info('shutting down background task thread.')

thread = Thread(target=task_runner)
thread.setDaemon(True)
thread.start()
15 changes: 15 additions & 0 deletions app/apps/background_task/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-


class BackgroundTaskError(Exception):

def __init__(self, message, errors=None):
super(BackgroundTaskError, self).__init__(message)
self.errors = errors


class InvalidTaskError(BackgroundTaskError):
"""
The task will not be rescheduled if it fails with this error
"""
pass
Empty file.
Empty file.
118 changes: 118 additions & 0 deletions app/apps/background_task/management/commands/process_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
import logging
import random
import sys
import time

from django import VERSION
from django.core.management.base import BaseCommand
from django.utils import autoreload

from apps.background_task.tasks import tasks, autodiscover
from apps.background_task.utils import SignalManager
from django.db import close_old_connections as close_connection


logger = logging.getLogger(__name__)


def _configure_log_std():
class StdOutWrapper(object):
def write(self, s):
logger.info(s)

class StdErrWrapper(object):
def write(self, s):
logger.error(s)
sys.stdout = StdOutWrapper()
sys.stderr = StdErrWrapper()


class Command(BaseCommand):
help = 'Run tasks that are scheduled to run on the queue'

# Command options are specified in an abstract way to enable Django < 1.8 compatibility
OPTIONS = (
(('--duration', ), {
'action': 'store',
'dest': 'duration',
'type': int,
'default': 0,
'help': 'Run task for this many seconds (0 or less to run forever) - default is 0',
}),
(('--sleep', ), {
'action': 'store',
'dest': 'sleep',
'type': float,
'default': 5.0,
'help': 'Sleep for this many seconds before checking for new tasks (if none were found) - default is 5',
}),
(('--queue', ), {
'action': 'store',
'dest': 'queue',
'help': 'Only process tasks on this named queue',
}),
(('--log-std', ), {
'action': 'store_true',
'dest': 'log_std',
'help': 'Redirect stdout and stderr to the logging system',
}),
)

if VERSION < (1, 8):
from optparse import make_option
option_list = BaseCommand.option_list + tuple([make_option(*args, **kwargs) for args, kwargs in OPTIONS])

# Used in Django >= 1.8
def add_arguments(self, parser):
for (args, kwargs) in self.OPTIONS:
parser.add_argument(*args, **kwargs)

def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
self.sig_manager = None
self._tasks = tasks

def run(self, *args, **options):
duration = options.get('duration', 0)
sleep = options.get('sleep', 5.0)
queue = options.get('queue', None)
log_std = options.get('log_std', False)
is_dev = options.get('dev', False)
sig_manager = self.sig_manager

if is_dev:
# raise last Exception is exist
autoreload.raise_last_exception()

if log_std:
_configure_log_std()

autodiscover()

start_time = time.time()

while (duration <= 0) or (time.time() - start_time) <= duration:
if sig_manager.kill_now:
# shutting down gracefully
break

if not self._tasks.run_next_task(queue):
# there were no tasks in the queue, let's recover.
close_connection()
logger.debug('waiting for tasks')
time.sleep(sleep)
else:
# there were some tasks to process, let's check if there is more work to do after a little break.
time.sleep(random.uniform(sig_manager.time_to_wait[0], sig_manager.time_to_wait[1]))

def handle(self, *args, **options):
# 메인 스레드에서 수행할 것이 아니므로 시그널을 처리하지 못한다.
# 임의로 Placeholder Class만 만들어서 사용한다.
self.sig_manager = FakeSignalManager()
self.run(*args, **options)


class FakeSignalManager(SignalManager):
def __init__(self):
self.slow_down(None, None)
167 changes: 167 additions & 0 deletions app/apps/background_task/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Generated by Django 4.2.13 on 2024-09-01 07:35

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
("contenttypes", "0002_remove_content_type_name"),
]

operations = [
migrations.CreateModel(
name="Task",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("task_name", models.CharField(db_index=True, max_length=190)),
("task_params", models.TextField()),
("task_hash", models.CharField(db_index=True, max_length=40)),
(
"verbose_name",
models.CharField(blank=True, max_length=255, null=True),
),
("priority", models.IntegerField(db_index=True, default=0)),
("run_at", models.DateTimeField(db_index=True)),
(
"repeat",
models.BigIntegerField(
choices=[
(3600, "hourly"),
(86400, "daily"),
(604800, "weekly"),
(1209600, "every 2 weeks"),
(2419200, "every 4 weeks"),
(0, "never"),
],
default=0,
),
),
("repeat_until", models.DateTimeField(blank=True, null=True)),
(
"queue",
models.CharField(
blank=True, db_index=True, max_length=190, null=True
),
),
("attempts", models.IntegerField(db_index=True, default=0)),
(
"failed_at",
models.DateTimeField(blank=True, db_index=True, null=True),
),
("last_error", models.TextField(blank=True)),
(
"locked_by",
models.CharField(
blank=True, db_index=True, max_length=64, null=True
),
),
(
"locked_at",
models.DateTimeField(blank=True, db_index=True, null=True),
),
(
"creator_object_id",
models.PositiveIntegerField(blank=True, null=True),
),
(
"creator_content_type",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="background_task",
to="contenttypes.contenttype",
),
),
],
options={
"db_table": "background_task",
},
),
migrations.CreateModel(
name="CompletedTask",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("task_name", models.CharField(db_index=True, max_length=190)),
("task_params", models.TextField()),
("task_hash", models.CharField(db_index=True, max_length=40)),
(
"verbose_name",
models.CharField(blank=True, max_length=255, null=True),
),
("priority", models.IntegerField(db_index=True, default=0)),
("run_at", models.DateTimeField(db_index=True)),
(
"repeat",
models.BigIntegerField(
choices=[
(3600, "hourly"),
(86400, "daily"),
(604800, "weekly"),
(1209600, "every 2 weeks"),
(2419200, "every 4 weeks"),
(0, "never"),
],
default=0,
),
),
("repeat_until", models.DateTimeField(blank=True, null=True)),
(
"queue",
models.CharField(
blank=True, db_index=True, max_length=190, null=True
),
),
("attempts", models.IntegerField(db_index=True, default=0)),
(
"failed_at",
models.DateTimeField(blank=True, db_index=True, null=True),
),
("last_error", models.TextField(blank=True)),
(
"locked_by",
models.CharField(
blank=True, db_index=True, max_length=64, null=True
),
),
(
"locked_at",
models.DateTimeField(blank=True, db_index=True, null=True),
),
(
"creator_object_id",
models.PositiveIntegerField(blank=True, null=True),
),
(
"creator_content_type",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="completed_background_task",
to="contenttypes.contenttype",
),
),
],
),
]
Empty file.
Loading

0 comments on commit 2d7dbca

Please sign in to comment.