@@ -32,6 +32,5 @@ __version__ = "0.1.dev" | |||||
__email__ = "ben.kurtovic@verizon.net" | __email__ = "ben.kurtovic@verizon.net" | ||||
from earwigbot import ( | from earwigbot import ( | ||||
blowfish, config, classes, commands, config, irc, main, rules, runner, | |||||
tasks, tests, wiki | |||||
blowfish, commands, config, irc, main, rules, runner, tasks, tests, wiki | |||||
) | ) |
@@ -1,23 +0,0 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2009-2012 by Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
# SOFTWARE. | |||||
from earwigbot.classes.base_task import * |
@@ -1,117 +0,0 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2009-2012 by Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
# | |||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
# of this software and associated documentation files (the "Software"), to deal | |||||
# in the Software without restriction, including without limitation the rights | |||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
# copies of the Software, and to permit persons to whom the Software is | |||||
# furnished to do so, subject to the following conditions: | |||||
# | |||||
# The above copyright notice and this permission notice shall be included in | |||||
# all copies or substantial portions of the Software. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
# SOFTWARE. | |||||
import logging | |||||
from earwigbot.config import config | |||||
from earwigbot import wiki | |||||
__all__ = ["BaseTask"] | |||||
class BaseTask(object): | |||||
"""A base class for bot tasks that edit Wikipedia.""" | |||||
name = None | |||||
number = 0 | |||||
def __init__(self): | |||||
"""Constructor for new tasks. | |||||
This is called once immediately after the task class is loaded by | |||||
the task manager (in tasks._load_task()). | |||||
""" | |||||
pass | |||||
def _setup_logger(self): | |||||
"""Set up a basic module-level logger.""" | |||||
logger_name = ".".join(("earwigbot", "tasks", self.name)) | |||||
self.logger = logging.getLogger(logger_name) | |||||
self.logger.setLevel(logging.DEBUG) | |||||
def run(self, **kwargs): | |||||
"""Main entry point to run a given task. | |||||
This is called directly by tasks.start() and is the main way to make a | |||||
task do stuff. kwargs will be any keyword arguments passed to start() | |||||
which are entirely optional. | |||||
The same task instance is preserved between runs, so you can | |||||
theoretically store data in self (e.g. | |||||
start('mytask', action='store', data='foo')) and then use it later | |||||
(e.g. start('mytask', action='save')). | |||||
""" | |||||
pass | |||||
def make_summary(self, comment): | |||||
"""Makes an edit summary by filling in variables in a config value. | |||||
config.wiki["summary"] is used, where $2 is replaced by the main | |||||
summary body, given as a method arg, and $1 is replaced by the task | |||||
number. | |||||
If the config value is not found, we just return the arg as-is. | |||||
""" | |||||
try: | |||||
summary = config.wiki["summary"] | |||||
except KeyError: | |||||
return comment | |||||
return summary.replace("$1", str(self.number)).replace("$2", comment) | |||||
def shutoff_enabled(self, site=None): | |||||
"""Returns whether on-wiki shutoff is enabled for this task. | |||||
We check a certain page for certain content. This is determined by | |||||
our config file: config.wiki["shutoff"]["page"] is used as the title, | |||||
with $1 replaced by our username and $2 replaced by the task number, | |||||
and config.wiki["shutoff"]["disabled"] is used as the content. | |||||
If the page has that content or the page does not exist, then shutoff | |||||
is "disabled", meaning the bot is supposed to run normally, and we | |||||
return False. If the page's content is something other than what we | |||||
expect, shutoff is enabled, and we return True. | |||||
If a site is not provided, we'll try to use self.site if it's set. | |||||
Otherwise, we'll use our default site. | |||||
""" | |||||
if not site: | |||||
try: | |||||
site = self.site | |||||
except AttributeError: | |||||
site = wiki.get_site() | |||||
try: | |||||
cfg = config.wiki["shutoff"] | |||||
except KeyError: | |||||
return False | |||||
title = cfg.get("page", "User:$1/Shutoff/Task $2") | |||||
username = site.get_user().name() | |||||
title = title.replace("$1", username).replace("$2", str(self.number)) | |||||
page = site.get_page(title) | |||||
try: | |||||
content = page.get() | |||||
except wiki.PageNotFoundError: | |||||
return False | |||||
if content == cfg.get("disabled", "run"): | |||||
return False | |||||
self.logger.warn("Emergency task shutoff has been enabled!") | |||||
return True |
@@ -26,7 +26,7 @@ EarwigBot's IRC Command Manager | |||||
This package provides the IRC "commands" used by the bot's front-end component. | This package provides the IRC "commands" used by the bot's front-end component. | ||||
This module contains the BaseCommand class (import with | This module contains the BaseCommand class (import with | ||||
`from earwigbot.commands import BaseCommand`) and an internal _CommandManager | `from earwigbot.commands import BaseCommand`) and an internal _CommandManager | ||||
class. This can be accessed through the singleton `command_manager`. | |||||
class. This can be accessed through the `command_manager` singleton. | |||||
""" | """ | ||||
import logging | import logging | ||||
@@ -22,9 +22,9 @@ | |||||
import re | import re | ||||
from earwigbot import tasks | |||||
from earwigbot import wiki | from earwigbot import wiki | ||||
from earwigbot.commands import BaseCommand | from earwigbot.commands import BaseCommand | ||||
from earwigbot.tasks import task_manager | |||||
class Command(BaseCommand): | class Command(BaseCommand): | ||||
"""Get information about an AFC submission by name.""" | """Get information about an AFC submission by name.""" | ||||
@@ -36,7 +36,7 @@ class Command(BaseCommand): | |||||
self.data = data | self.data = data | ||||
try: | try: | ||||
self.statistics = tasks.get("afc_statistics") | |||||
self.statistics = task_manager.get("afc_statistics") | |||||
except KeyError: | except KeyError: | ||||
e = "Cannot run command: requires afc_statistics task." | e = "Cannot run command: requires afc_statistics task." | ||||
self.logger.error(e) | self.logger.error(e) | ||||
@@ -23,10 +23,10 @@ | |||||
import threading | import threading | ||||
import re | import re | ||||
from earwigbot import tasks | |||||
from earwigbot.commands import BaseCommand | from earwigbot.commands import BaseCommand | ||||
from earwigbot.config import config | from earwigbot.config import config | ||||
from earwigbot.irc import KwargParseException | from earwigbot.irc import KwargParseException | ||||
from earwigbot.tasks import task_manager | |||||
class Command(BaseCommand): | class Command(BaseCommand): | ||||
"""Manage wiki tasks from IRC, and check on thread status.""" | """Manage wiki tasks from IRC, and check on thread status.""" | ||||
@@ -106,7 +106,7 @@ class Command(BaseCommand): | |||||
def do_listall(self): | def do_listall(self): | ||||
"""With !tasks listall or !tasks all, list all loaded tasks, and report | """With !tasks listall or !tasks all, list all loaded tasks, and report | ||||
whether they are currently running or idle.""" | whether they are currently running or idle.""" | ||||
all_tasks = tasks.get_all().keys() | |||||
all_tasks = task_manager.get_all().keys() | |||||
threads = threading.enumerate() | threads = threading.enumerate() | ||||
tasklist = [] | tasklist = [] | ||||
@@ -147,14 +147,14 @@ class Command(BaseCommand): | |||||
self.connection.reply(data, msg) | self.connection.reply(data, msg) | ||||
return | return | ||||
if task_name not in tasks.get_all().keys(): | |||||
if task_name not in task_manager.get_all().keys(): | |||||
# This task does not exist or hasn't been loaded: | # This task does not exist or hasn't been loaded: | ||||
msg = "task could not be found; either bot/tasks/{0}.py doesn't exist, or it wasn't loaded correctly." | |||||
msg = "task could not be found; either tasks/{0}.py doesn't exist, or it wasn't loaded correctly." | |||||
self.connection.reply(data, msg.format(task_name)) | self.connection.reply(data, msg.format(task_name)) | ||||
return | return | ||||
data.kwargs["fromIRC"] = True | data.kwargs["fromIRC"] = True | ||||
tasks.start(task_name, **data.kwargs) | |||||
task_manager.start(task_name, **data.kwargs) | |||||
msg = "task \x0302{0}\x0301 started.".format(task_name) | msg = "task \x0302{0}\x0301 started.".format(task_name) | ||||
self.connection.reply(data, msg) | self.connection.reply(data, msg) | ||||
@@ -49,9 +49,9 @@ import logging | |||||
import threading | import threading | ||||
import time | import time | ||||
from earwigbot import tasks | |||||
from earwigbot.irc import Frontend, Watcher | |||||
from earwigbot.config import config | from earwigbot.config import config | ||||
from earwigbot.irc import Frontend, Watcher | |||||
from earwigbot.tasks import task_manager | |||||
logger = logging.getLogger("earwigbot") | logger = logging.getLogger("earwigbot") | ||||
@@ -72,10 +72,7 @@ def wiki_scheduler(): | |||||
primary thread if the IRC frontend is not enabled.""" | primary thread if the IRC frontend is not enabled.""" | ||||
while 1: | while 1: | ||||
time_start = time.time() | time_start = time.time() | ||||
now = time.gmtime(time_start) | |||||
tasks.schedule(now) | |||||
task_manager.schedule() | |||||
time_end = time.time() | time_end = time.time() | ||||
time_diff = time_start - time_end | time_diff = time_start - time_end | ||||
if time_diff < 60: # Sleep until the next minute | if time_diff < 60: # Sleep until the next minute | ||||
@@ -90,7 +87,7 @@ def irc_frontend(): | |||||
if config.components.get("wiki_schedule"): | if config.components.get("wiki_schedule"): | ||||
logger.info("Starting wiki scheduler") | logger.info("Starting wiki scheduler") | ||||
tasks.load() | |||||
task_manager.load() | |||||
t_scheduler = threading.Thread(target=wiki_scheduler) | t_scheduler = threading.Thread(target=wiki_scheduler) | ||||
t_scheduler.name = "wiki-scheduler" | t_scheduler.name = "wiki-scheduler" | ||||
t_scheduler.daemon = True | t_scheduler.daemon = True | ||||
@@ -119,7 +116,7 @@ def main(): | |||||
# Run the scheduler on the main thread, but also run the IRC watcher on | # Run the scheduler on the main thread, but also run the IRC watcher on | ||||
# another thread iff it is enabled: | # another thread iff it is enabled: | ||||
logger.info("Starting wiki scheduler") | logger.info("Starting wiki scheduler") | ||||
tasks.load() | |||||
task_manager.load() | |||||
if "irc_watcher" in enabled: | if "irc_watcher" in enabled: | ||||
logger.info("Starting IRC watcher") | logger.info("Starting IRC watcher") | ||||
t_watcher = threading.Thread(target=irc_watcher) | t_watcher = threading.Thread(target=irc_watcher) | ||||
@@ -29,7 +29,7 @@ recieves an event from IRC. | |||||
import re | import re | ||||
from earwigbot import tasks | |||||
from earwigbot.tasks import task_manager | |||||
afc_prefix = "wikipedia( talk)?:(wikiproject )?articles for creation" | afc_prefix = "wikipedia( talk)?:(wikiproject )?articles for creation" | ||||
@@ -56,7 +56,7 @@ def process(rc): | |||||
chans.update(("##earwigbot", "#wikipedia-en-afc-feed")) | chans.update(("##earwigbot", "#wikipedia-en-afc-feed")) | ||||
if r_page.search(page_name): | if r_page.search(page_name): | ||||
#tasks.start("afc_copyvios", page=rc.page) | |||||
#task_manager.start("afc_copyvios", page=rc.page) | |||||
chans.add("#wikipedia-en-afc-feed") | chans.add("#wikipedia-en-afc-feed") | ||||
elif r_ffu.match(page_name): | elif r_ffu.match(page_name): | ||||
@@ -76,7 +76,7 @@ def process(rc): | |||||
elif rc.flags == "restore" and r_restore.match(comment): | elif rc.flags == "restore" and r_restore.match(comment): | ||||
p = r_restored_page.findall(rc.comment)[0] | p = r_restored_page.findall(rc.comment)[0] | ||||
#tasks.start("afc_copyvios", page=p) | |||||
#task_manager.start("afc_copyvios", page=p) | |||||
chans.add("#wikipedia-en-afc-feed") | chans.add("#wikipedia-en-afc-feed") | ||||
elif rc.flags == "protect" and r_protect.match(comment): | elif rc.flags == "protect" and r_protect.match(comment): | ||||
@@ -23,8 +23,10 @@ | |||||
""" | """ | ||||
EarwigBot's Wiki Task Manager | EarwigBot's Wiki Task Manager | ||||
This package provides the wiki bot "tasks" EarwigBot runs. Here in __init__, | |||||
you can find some functions used to load and run these tasks. | |||||
This package provides the wiki bot "tasks" EarwigBot runs. This module contains | |||||
the BaseTask class (import with `from earwigbot.tasks import BaseTask`) and an | |||||
internal _TaskManager class. This can be accessed through the `task_manager` | |||||
singleton. | |||||
""" | """ | ||||
import logging | import logging | ||||
@@ -33,106 +35,199 @@ import sys | |||||
import threading | import threading | ||||
import time | import time | ||||
from earwigbot.classes import BaseTask | |||||
from earwigbot import wiki | |||||
from earwigbot.config import config | from earwigbot.config import config | ||||
__all__ = ["load", "schedule", "start", "get", "get_all"] | |||||
# Base directory when searching for tasks: | |||||
base_dir = os.path.dirname(os.path.abspath(__file__)) | |||||
# Store loaded tasks as a dict where the key is the task name and the value is | |||||
# an instance of the task class: | |||||
_tasks = {} | |||||
# Logger for this module: | |||||
logger = logging.getLogger("earwigbot.commands") | |||||
def _load_task(filename): | |||||
"""Try to load a specific task from a module, identified by file name.""" | |||||
global _tasks | |||||
# Strip .py from the end of the filename and join with our package name: | |||||
name = ".".join(("tasks", filename[:-3])) | |||||
try: | |||||
__import__(name) | |||||
except: | |||||
logger.exception("Couldn't load file {0}:".format(filename)) | |||||
return | |||||
task = sys.modules[name].Task() | |||||
task._setup_logger() | |||||
if not isinstance(task, BaseTask): | |||||
return | |||||
_tasks[task.name] = task | |||||
logger.debug("Added task {0}".format(task.name)) | |||||
def _wrapper(task, **kwargs): | |||||
"""Wrapper for task classes: run the task and catch any errors.""" | |||||
try: | |||||
task.run(**kwargs) | |||||
except: | |||||
error = "Task '{0}' raised an exception and had to stop" | |||||
logger.exception(error.format(task.name)) | |||||
else: | |||||
logger.info("Task '{0}' finished without error".format(task.name)) | |||||
def load(): | |||||
"""Load all valid tasks from bot/tasks/, into the _tasks variable.""" | |||||
files = os.listdir(base_dir) | |||||
files.sort() | |||||
for filename in files: | |||||
if filename.startswith("_") or not filename.endswith(".py"): | |||||
continue | |||||
__all__ = ["BaseTask", "task_manager"] | |||||
class BaseTask(object): | |||||
"""A base class for bot tasks that edit Wikipedia.""" | |||||
name = None | |||||
number = 0 | |||||
def __init__(self): | |||||
"""Constructor for new tasks. | |||||
This is called once immediately after the task class is loaded by | |||||
the task manager (in tasks._load_task()). | |||||
""" | |||||
pass | |||||
def _setup_logger(self): | |||||
"""Set up a basic module-level logger.""" | |||||
logger_name = ".".join(("earwigbot", "tasks", self.name)) | |||||
self.logger = logging.getLogger(logger_name) | |||||
self.logger.setLevel(logging.DEBUG) | |||||
def run(self, **kwargs): | |||||
"""Main entry point to run a given task. | |||||
This is called directly by tasks.start() and is the main way to make a | |||||
task do stuff. kwargs will be any keyword arguments passed to start() | |||||
which are entirely optional. | |||||
The same task instance is preserved between runs, so you can | |||||
theoretically store data in self (e.g. | |||||
start('mytask', action='store', data='foo')) and then use it later | |||||
(e.g. start('mytask', action='save')). | |||||
""" | |||||
pass | |||||
def make_summary(self, comment): | |||||
"""Makes an edit summary by filling in variables in a config value. | |||||
config.wiki["summary"] is used, where $2 is replaced by the main | |||||
summary body, given as a method arg, and $1 is replaced by the task | |||||
number. | |||||
If the config value is not found, we just return the arg as-is. | |||||
""" | |||||
try: | try: | ||||
_load_task(filename) | |||||
except AttributeError: | |||||
pass # The file is doesn't contain a task, so just move on | |||||
summary = config.wiki["summary"] | |||||
except KeyError: | |||||
return comment | |||||
return summary.replace("$1", str(self.number)).replace("$2", comment) | |||||
def shutoff_enabled(self, site=None): | |||||
"""Returns whether on-wiki shutoff is enabled for this task. | |||||
We check a certain page for certain content. This is determined by | |||||
our config file: config.wiki["shutoff"]["page"] is used as the title, | |||||
with $1 replaced by our username and $2 replaced by the task number, | |||||
and config.wiki["shutoff"]["disabled"] is used as the content. | |||||
If the page has that content or the page does not exist, then shutoff | |||||
is "disabled", meaning the bot is supposed to run normally, and we | |||||
return False. If the page's content is something other than what we | |||||
expect, shutoff is enabled, and we return True. | |||||
If a site is not provided, we'll try to use self.site if it's set. | |||||
Otherwise, we'll use our default site. | |||||
""" | |||||
if not site: | |||||
try: | |||||
site = self.site | |||||
except AttributeError: | |||||
site = wiki.get_site() | |||||
try: | |||||
cfg = config.wiki["shutoff"] | |||||
except KeyError: | |||||
return False | |||||
title = cfg.get("page", "User:$1/Shutoff/Task $2") | |||||
username = site.get_user().name() | |||||
title = title.replace("$1", username).replace("$2", str(self.number)) | |||||
page = site.get_page(title) | |||||
try: | |||||
content = page.get() | |||||
except wiki.PageNotFoundError: | |||||
return False | |||||
if content == cfg.get("disabled", "run"): | |||||
return False | |||||
self.logger.warn("Emergency task shutoff has been enabled!") | |||||
return True | |||||
class _TaskManager(object): | |||||
def __init__(self): | |||||
self.logger = logging.getLogger("earwigbot.commands") | |||||
self._base_dir = os.path.dirname(os.path.abspath(__file__)) | |||||
self._tasks = {} | |||||
def _load_task(self, filename): | |||||
"""Load a specific task from a module, identified by file name.""" | |||||
# Strip .py from the filename's end and join with our package name: | |||||
name = ".".join(("tasks", filename[:-3])) | |||||
try: | |||||
__import__(name) | |||||
except: | |||||
self.logger.exception("Couldn't load file {0}:".format(filename)) | |||||
return | |||||
logger.info("Found {0} tasks: {1}".format(len(_tasks), ', '.join(_tasks.keys()))) | |||||
try: | |||||
task = sys.modules[name].Task() | |||||
except AttributeError: | |||||
return # No task in this module | |||||
if not isinstance(task, BaseTask): | |||||
return | |||||
task._setup_logger() | |||||
def schedule(now=time.gmtime()): | |||||
"""Start all tasks that are supposed to be run at a given time.""" | |||||
# Get list of tasks to run this turn: | |||||
tasks = config.schedule(now.tm_min, now.tm_hour, now.tm_mday, now.tm_mon, | |||||
now.tm_wday) | |||||
self._tasks[task.name] = task | |||||
self.logger.debug("Added task {0}".format(task.name)) | |||||
for task in tasks: | |||||
if isinstance(task, list): # they've specified kwargs | |||||
start(task[0], **task[1]) # so pass those to start_task | |||||
else: # otherwise, just pass task_name | |||||
start(task) | |||||
def _wrapper(self, task, **kwargs): | |||||
"""Wrapper for task classes: run the task and catch any errors.""" | |||||
try: | |||||
task.run(**kwargs) | |||||
except: | |||||
msg = "Task '{0}' raised an exception and had to stop" | |||||
self.logger.exception(msg.format(task.name)) | |||||
else: | |||||
msg = "Task '{0}' finished without error" | |||||
self.logger.info(msg.format(task.name)) | |||||
def load(self): | |||||
"""Load all valid tasks from tasks/ into self._tasks.""" | |||||
files = os.listdir(self._base_dir) | |||||
files.sort() | |||||
for filename in files: | |||||
if filename.startswith("_") or not filename.endswith(".py"): | |||||
continue | |||||
self._load_task(filename) | |||||
msg = "Found {0} tasks: {1}" | |||||
tasks = ', '.join(self._tasks.keys()) | |||||
self.logger.info(msg.format(len(self._tasks), tasks)) | |||||
def schedule(self, now=None): | |||||
"""Start all tasks that are supposed to be run at a given time.""" | |||||
if not now: | |||||
now = time.gmtime() | |||||
# Get list of tasks to run this turn: | |||||
tasks = config.schedule(now.tm_min, now.tm_hour, now.tm_mday, | |||||
now.tm_mon, now.tm_wday) | |||||
for task in tasks: | |||||
if isinstance(task, list): # They've specified kwargs, | |||||
self.start(task[0], **task[1]) # so pass those to start_task | |||||
else: # Otherwise, just pass task_name | |||||
self.start(task) | |||||
def start(self, task_name, **kwargs): | |||||
"""Start a given task in a new thread. Pass args to the task's run() | |||||
function.""" | |||||
msg = "Starting task '{0}' in a new thread" | |||||
self.logger.info(msg.format(task_name)) | |||||
def start(task_name, **kwargs): | |||||
"""Start a given task in a new thread. Pass args to the task's run() | |||||
function.""" | |||||
logger.info("Starting task '{0}' in a new thread".format(task_name)) | |||||
try: | |||||
task = self._tasks[task_name] | |||||
except KeyError: | |||||
e = "Couldn't find task '{0}': bot/tasks/{0}.py does not exist" | |||||
self.logger.error(e.format(task_name)) | |||||
return | |||||
try: | |||||
task = _tasks[task_name] | |||||
except KeyError: | |||||
error = "Couldn't find task '{0}': bot/tasks/{0}.py does not exist" | |||||
logger.error(error.format(task_name)) | |||||
return | |||||
func = lambda: self._wrapper(task, **kwargs) | |||||
task_thread = threading.Thread(target=func) | |||||
start_time = time.strftime("%b %d %H:%M:%S") | |||||
task_thread.name = "{0} ({1})".format(task_name, start_time) | |||||
task_thread = threading.Thread(target=lambda: _wrapper(task, **kwargs)) | |||||
start_time = time.strftime("%b %d %H:%M:%S") | |||||
task_thread.name = "{0} ({1})".format(task_name, start_time) | |||||
# Stop bot task threads automagically if the main bot stops: | |||||
task_thread.daemon = True | |||||
# Stop bot task threads automagically if the main bot stops: | |||||
task_thread.daemon = True | |||||
task_thread.start() | |||||
task_thread.start() | |||||
def get(self, task_name): | |||||
"""Return the class instance associated with a certain task name. | |||||
def get(task_name): | |||||
"""Return the class instance associated with a certain task name. | |||||
Will raise KeyError if the task is not found. | |||||
""" | |||||
return self._tasks[task_name] | |||||
Will raise KeyError if the task is not found. | |||||
""" | |||||
return _tasks[task_name] | |||||
def get_all(self): | |||||
"""Return our dict of all loaded tasks.""" | |||||
return self._tasks | |||||
def get_all(): | |||||
"""Return our dict of all loaded tasks.""" | |||||
return _tasks | |||||
task_manager = _TaskManager() |
@@ -20,7 +20,7 @@ | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
# SOFTWARE. | # SOFTWARE. | ||||
from earwigbot.classes import BaseTask | |||||
from earwigbot.tasks import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to delink mainspace categories in declined [[WP:AFC]] | """A task to delink mainspace categories in declined [[WP:AFC]] | ||||
@@ -27,8 +27,8 @@ from threading import Lock | |||||
import oursql | import oursql | ||||
from earwigbot import wiki | from earwigbot import wiki | ||||
from earwigbot.classes import BaseTask | |||||
from earwigbot.config import config | from earwigbot.config import config | ||||
from earwigbot.tasks import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to check newly-edited [[WP:AFC]] submissions for copyright | """A task to check newly-edited [[WP:AFC]] submissions for copyright | ||||
@@ -20,7 +20,7 @@ | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
# SOFTWARE. | # SOFTWARE. | ||||
from earwigbot.classes import BaseTask | |||||
from earwigbot.tasks import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
""" A task to create daily categories for [[WP:AFC]].""" | """ A task to create daily categories for [[WP:AFC]].""" | ||||
@@ -32,8 +32,8 @@ from numpy import arange | |||||
import oursql | import oursql | ||||
from earwigbot import wiki | from earwigbot import wiki | ||||
from earwigbot.classes import BaseTask | |||||
from earwigbot.config import config | from earwigbot.config import config | ||||
from earwigbot.tasks import BaseTask | |||||
# Valid submission statuses: | # Valid submission statuses: | ||||
STATUS_NONE = 0 | STATUS_NONE = 0 | ||||
@@ -30,8 +30,8 @@ from time import sleep | |||||
import oursql | import oursql | ||||
from earwigbot import wiki | from earwigbot import wiki | ||||
from earwigbot.classes import BaseTask | |||||
from earwigbot.config import config | from earwigbot.config import config | ||||
from earwigbot.tasks import BaseTask | |||||
# Chart status number constants: | # Chart status number constants: | ||||
CHART_NONE = 0 | CHART_NONE = 0 | ||||
@@ -20,7 +20,7 @@ | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
# SOFTWARE. | # SOFTWARE. | ||||
from earwigbot.classes import BaseTask | |||||
from earwigbot.tasks import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to clear [[Category:Undated AfC submissions]].""" | """A task to clear [[Category:Undated AfC submissions]].""" | ||||
@@ -20,7 +20,7 @@ | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
# SOFTWARE. | # SOFTWARE. | ||||
from earwigbot.classes import BaseTask | |||||
from earwigbot.tasks import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to add |blp=yes to {{WPB}} or {{WPBS}} when it is used along with | """A task to add |blp=yes to {{WPB}} or {{WPBS}} when it is used along with | ||||
@@ -20,7 +20,7 @@ | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
# SOFTWARE. | # SOFTWARE. | ||||
from earwigbot.classes import BaseTask | |||||
from earwigbot.tasks import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to create daily categories for [[WP:FEED]].""" | """A task to create daily categories for [[WP:FEED]].""" | ||||
@@ -20,7 +20,7 @@ | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
# SOFTWARE. | # SOFTWARE. | ||||
from earwigbot.classes import BaseTask | |||||
from earwigbot.tasks import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to tag files whose extensions do not agree with their MIME | """A task to tag files whose extensions do not agree with their MIME | ||||