@@ -48,4 +48,5 @@ if not __release__: | |||||
finally: | finally: | ||||
del _add_git_commit_id_to_version | del _add_git_commit_id_to_version | ||||
from earwigbot import blowfish, bot, commands, config, irc, tasks, util, wiki | |||||
from earwigbot import (blowfish, bot, commands, config, irc, managers, tasks, | |||||
util, wiki) |
@@ -24,11 +24,10 @@ import logging | |||||
from threading import Lock, Thread | from threading import Lock, Thread | ||||
from time import sleep, time | from time import sleep, time | ||||
from earwigbot.commands import CommandManager | |||||
from earwigbot.config import BotConfig | from earwigbot.config import BotConfig | ||||
from earwigbot.irc import Frontend, Watcher | from earwigbot.irc import Frontend, Watcher | ||||
from earwigbot.tasks import TaskManager | |||||
from earwigbot.wiki import SitesDBManager | |||||
from earwigbot.managers import CommandManager, TaskManager | |||||
from earwigbot.wiki import SitesDB | |||||
__all__ = ["Bot"] | __all__ = ["Bot"] | ||||
@@ -58,7 +57,7 @@ class Bot(object): | |||||
self.logger = logging.getLogger("earwigbot") | self.logger = logging.getLogger("earwigbot") | ||||
self.commands = CommandManager(self) | self.commands = CommandManager(self) | ||||
self.tasks = TaskManager(self) | self.tasks = TaskManager(self) | ||||
self.wiki = SitesDBManager(self.config) | |||||
self.wiki = SitesDB(self.config) | |||||
self.frontend = None | self.frontend = None | ||||
self.watcher = None | self.watcher = None | ||||
@@ -73,12 +72,12 @@ class Bot(object): | |||||
if self.config.components.get("irc_frontend"): | if self.config.components.get("irc_frontend"): | ||||
self.logger.info("Starting IRC frontend") | self.logger.info("Starting IRC frontend") | ||||
self.frontend = Frontend(self) | self.frontend = Frontend(self) | ||||
Thread(name=name, target=self.frontend.loop).start() | |||||
Thread(name="irc_frontend", target=self.frontend.loop).start() | |||||
if self.config.components.get("irc_watcher"): | if self.config.components.get("irc_watcher"): | ||||
self.logger.info("Starting IRC watcher") | self.logger.info("Starting IRC watcher") | ||||
self.watcher = Watcher(self) | self.watcher = Watcher(self) | ||||
Thread(name=name, target=self.watcher.loop).start() | |||||
Thread(name="irc_watcher", target=self.watcher.loop).start() | |||||
def _start_wiki_scheduler(self): | def _start_wiki_scheduler(self): | ||||
def wiki_scheduler(): | def wiki_scheduler(): | ||||
@@ -92,7 +91,7 @@ class Bot(object): | |||||
if self.config.components.get("wiki_scheduler"): | if self.config.components.get("wiki_scheduler"): | ||||
self.logger.info("Starting wiki scheduler") | self.logger.info("Starting wiki scheduler") | ||||
Thread(name=name, target=wiki_scheduler).start() | |||||
Thread(name="wiki_scheduler", target=wiki_scheduler).start() | |||||
def _stop_irc_components(self): | def _stop_irc_components(self): | ||||
if self.frontend: | if self.frontend: | ||||
@@ -21,20 +21,16 @@ | |||||
# SOFTWARE. | # SOFTWARE. | ||||
""" | """ | ||||
EarwigBot's IRC Command Manager | |||||
EarwigBot's IRC Commands | |||||
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 | |||||
class. This can be accessed through `bot.commands`. | |||||
`from earwigbot.commands import BaseCommand`), whereas the package contains | |||||
various built-in commands. Additional commands can be installed as plugins in | |||||
the bot's working directory. | |||||
""" | """ | ||||
import imp | |||||
from os import listdir, path | |||||
from re import sub | |||||
from threading import Lock | |||||
__all__ = ["BaseCommand", "CommandManager"] | |||||
__all__ = ["BaseCommand"] | |||||
class BaseCommand(object): | class BaseCommand(object): | ||||
"""A base class for commands on IRC. | """A base class for commands on IRC. | ||||
@@ -90,95 +86,3 @@ class BaseCommand(object): | |||||
Note that | Note that | ||||
""" | """ | ||||
pass | pass | ||||
class CommandManager(object): | |||||
def __init__(self, bot): | |||||
self.bot = bot | |||||
self.logger = bot.logger.getChild("commands") | |||||
self._commands = {} | |||||
self._command_access_lock = Lock() | |||||
def __iter__(self): | |||||
for name in self._commands: | |||||
yield name | |||||
def _load_command(self, name, path): | |||||
"""Load a specific command from a module, identified by name and path. | |||||
We'll first try to import it using imp magic, and if that works, make | |||||
an instance of the 'Command' class inside (assuming it is an instance | |||||
of BaseCommand), add it to self._commands, and log the addition. Any | |||||
problems along the way will either be ignored or logged. | |||||
""" | |||||
f, path, desc = imp.find_module(name, [path]) | |||||
try: | |||||
module = imp.load_module(name, f, path, desc) | |||||
except Exception: | |||||
e = "Couldn't load module {0} from {1}" | |||||
self.logger.exception(e.format(name, path)) | |||||
return | |||||
finally: | |||||
f.close() | |||||
try: | |||||
command_class = module.Command | |||||
except AttributeError: | |||||
return # No command in this module | |||||
try: | |||||
command = command_class(self.bot) | |||||
except Exception: | |||||
e = "Error initializing Command() class in {0} (from {1})" | |||||
self.logger.exception(e.format(name, path)) | |||||
return | |||||
if not isinstance(command, BaseCommand): | |||||
return | |||||
self._commands[command.name] = command | |||||
self.logger.debug("Loaded command {0}".format(command.name)) | |||||
def _load_directory(self, dir): | |||||
"""Load all valid commands in a given directory.""" | |||||
processed = [] | |||||
for name in listdir(dir): | |||||
if not name.endswith(".py") and not name.endswith(".pyc"): | |||||
continue | |||||
if name.startswith("_") or name.startswith("."): | |||||
continue | |||||
modname = sub("\.pyc?$", "", name) # Remove extension | |||||
if modname not in processed: | |||||
self._load_command(modname, dir) | |||||
processed.append(modname) | |||||
def load(self): | |||||
"""Load (or reload) all valid commands into self._commands.""" | |||||
with self._command_access_lock: | |||||
self._commands.clear() | |||||
builtin_dir = path.dirname(__file__) | |||||
plugins_dir = path.join(self.bot.config.root_dir, "commands") | |||||
self._load_directory(builtin_dir) # Built-in commands | |||||
self._load_directory(plugins_dir) # Custom commands, aka plugins | |||||
msg = "Loaded {0} commands: {1}" | |||||
commands = ", ".join(self._commands.keys()) | |||||
self.logger.info(msg.format(len(self._commands), commands)) | |||||
def check(self, hook, data): | |||||
"""Given an IRC event, check if there's anything we can respond to.""" | |||||
with self._command_access_lock: | |||||
for command in self._commands.values(): | |||||
if hook in command.hooks: | |||||
if command.check(data): | |||||
try: | |||||
command._wrap_process(data) | |||||
except Exception: | |||||
e = "Error executing command '{0}':" | |||||
self.logger.exception(e.format(data.command)) | |||||
break | |||||
def get(self, command_name): | |||||
"""Return the class instance associated with a certain command name. | |||||
Will raise KeyError if the command is not found. | |||||
""" | |||||
return self._command[command_name] |
@@ -0,0 +1,250 @@ | |||||
#! /usr/bin/env python | |||||
# -*- 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 imp | |||||
from os import listdir, path | |||||
from re import sub | |||||
from threading import Lock, Thread | |||||
from time import gmtime, strftime | |||||
from earwigbot.commands import BaseCommand | |||||
from earwigbot.tasks import BaseTask | |||||
__all__ = ["CommandManager", "TaskManager"] | |||||
class _BaseManager(object): | |||||
pass | |||||
class CommandManager(_BaseManager): | |||||
def __init__(self, bot): | |||||
self.bot = bot | |||||
self.logger = bot.logger.getChild("commands") | |||||
self._commands = {} | |||||
self._command_access_lock = Lock() | |||||
def __iter__(self): | |||||
for name in self._commands: | |||||
yield name | |||||
def _load_command(self, name, path): | |||||
"""Load a specific command from a module, identified by name and path. | |||||
We'll first try to import it using imp magic, and if that works, make | |||||
an instance of the 'Command' class inside (assuming it is an instance | |||||
of BaseCommand), add it to self._commands, and log the addition. Any | |||||
problems along the way will either be ignored or logged. | |||||
""" | |||||
f, path, desc = imp.find_module(name, [path]) | |||||
try: | |||||
module = imp.load_module(name, f, path, desc) | |||||
except Exception: | |||||
e = "Couldn't load module {0} from {1}" | |||||
self.logger.exception(e.format(name, path)) | |||||
return | |||||
finally: | |||||
f.close() | |||||
try: | |||||
command_class = module.Command | |||||
except AttributeError: | |||||
return # No command in this module | |||||
try: | |||||
command = command_class(self.bot) | |||||
except Exception: | |||||
e = "Error initializing Command() class in {0} (from {1})" | |||||
self.logger.exception(e.format(name, path)) | |||||
return | |||||
if not isinstance(command, BaseCommand): | |||||
return | |||||
self._commands[command.name] = command | |||||
self.logger.debug("Loaded command {0}".format(command.name)) | |||||
def _load_directory(self, dir): | |||||
"""Load all valid commands in a given directory.""" | |||||
processed = [] | |||||
for name in listdir(dir): | |||||
if not name.endswith(".py") and not name.endswith(".pyc"): | |||||
continue | |||||
if name.startswith("_") or name.startswith("."): | |||||
continue | |||||
modname = sub("\.pyc?$", "", name) # Remove extension | |||||
if modname not in processed: | |||||
self._load_command(modname, dir) | |||||
processed.append(modname) | |||||
def load(self): | |||||
"""Load (or reload) all valid commands into self._commands.""" | |||||
with self._command_access_lock: | |||||
self._commands.clear() | |||||
builtin_dir = path.join(path.dirname(__file__), "commands") | |||||
plugins_dir = path.join(self.bot.config.root_dir, "commands") | |||||
self._load_directory(builtin_dir) # Built-in commands | |||||
self._load_directory(plugins_dir) # Custom commands, aka plugins | |||||
msg = "Loaded {0} commands: {1}" | |||||
commands = ", ".join(self._commands.keys()) | |||||
self.logger.info(msg.format(len(self._commands), commands)) | |||||
def check(self, hook, data): | |||||
"""Given an IRC event, check if there's anything we can respond to.""" | |||||
with self._command_access_lock: | |||||
for command in self._commands.values(): | |||||
if hook in command.hooks: | |||||
if command.check(data): | |||||
try: | |||||
command._wrap_process(data) | |||||
except Exception: | |||||
e = "Error executing command '{0}':" | |||||
self.logger.exception(e.format(data.command)) | |||||
break | |||||
def get(self, command_name): | |||||
"""Return the class instance associated with a certain command name. | |||||
Will raise KeyError if the command is not found. | |||||
""" | |||||
return self._command[command_name] | |||||
class TaskManager(_BaseManager): | |||||
def __init__(self, bot): | |||||
self.bot = bot | |||||
self.logger = bot.logger.getChild("tasks") | |||||
self._tasks = {} | |||||
self._task_access_lock = Lock() | |||||
def __iter__(self): | |||||
for name in self._tasks: | |||||
yield name | |||||
def _wrapper(self, task, **kwargs): | |||||
"""Wrapper for task classes: run the task and catch any errors.""" | |||||
try: | |||||
task.run(**kwargs) | |||||
except Exception: | |||||
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_task(self, name, path): | |||||
"""Load a specific task from a module, identified by name and path. | |||||
We'll first try to import it using imp magic, and if that works, make | |||||
an instance of the 'Task' class inside (assuming it is an instance of | |||||
BaseTask), add it to self._tasks, and log the addition. Any problems | |||||
along the way will either be ignored or logged. | |||||
""" | |||||
f, path, desc = imp.find_module(name, [path]) | |||||
try: | |||||
module = imp.load_module(name, f, path, desc) | |||||
except Exception: | |||||
e = "Couldn't load module {0} from {1}" | |||||
self.logger.exception(e.format(name, path)) | |||||
return | |||||
finally: | |||||
f.close() | |||||
try: | |||||
task_class = module.Task | |||||
except AttributeError: | |||||
return # No task in this module | |||||
try: | |||||
task = task_class(self.bot) | |||||
except Exception: | |||||
e = "Error initializing Task() class in {0} (from {1})" | |||||
self.logger.exception(e.format(name, path)) | |||||
return | |||||
if not isinstance(task, BaseTask): | |||||
return | |||||
self._tasks[task.name] = task | |||||
self.logger.debug("Loaded task {0}".format(task.name)) | |||||
def _load_directory(self, dir): | |||||
"""Load all valid tasks in a given directory.""" | |||||
processed = [] | |||||
for name in listdir(dir): | |||||
if not name.endswith(".py") and not name.endswith(".pyc"): | |||||
continue | |||||
if name.startswith("_") or name.startswith("."): | |||||
continue | |||||
modname = sub("\.pyc?$", "", name) # Remove extension | |||||
if modname not in processed: | |||||
self._load_task(modname, dir) | |||||
processed.append(modname) | |||||
def load(self): | |||||
"""Load (or reload) all valid tasks into self._tasks.""" | |||||
with self._task_access_lock: | |||||
self._tasks.clear() | |||||
builtin_dir = path.join(path.dirname(__file__), "tasks") | |||||
plugins_dir = path.join(self.bot.config.root_dir, "tasks") | |||||
self._load_directory(builtin_dir) # Built-in tasks | |||||
self._load_directory(plugins_dir) # Custom tasks, aka plugins | |||||
msg = "Loaded {0} tasks: {1}" | |||||
tasks = ', '.join(self._tasks.keys()) | |||||
self.logger.info(msg.format(len(self._tasks), tasks)) | |||||
def start(self, task_name, **kwargs): | |||||
"""Start a given task in a new thread. kwargs are passed to task.run""" | |||||
msg = "Starting task '{0}' in a new thread" | |||||
self.logger.info(msg.format(task_name)) | |||||
with self._task_access_lock: | |||||
try: | |||||
task = self._tasks[task_name] | |||||
except KeyError: | |||||
e = "Couldn't find task '{0}':" | |||||
self.logger.error(e.format(task_name)) | |||||
return | |||||
task_thread = Thread(target=self._wrapper, args=(task,), kwargs=kwargs) | |||||
start_time = strftime("%b %d %H:%M:%S") | |||||
task_thread.name = "{0} ({1})".format(task_name, start_time) | |||||
task_thread.start() | |||||
def schedule(self, now=None): | |||||
"""Start all tasks that are supposed to be run at a given time.""" | |||||
if not now: | |||||
now = gmtime() | |||||
# Get list of tasks to run this turn: | |||||
tasks = self.bot.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 | |||||
else: # Otherwise, just pass task_name | |||||
self.start(task) | |||||
def get(self, 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] |
@@ -21,22 +21,20 @@ | |||||
# SOFTWARE. | # SOFTWARE. | ||||
""" | """ | ||||
EarwigBot's Wiki Task Manager | |||||
EarwigBot's Bot Tasks | |||||
This package provides the wiki bot "tasks" EarwigBot runs. This module contains | 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 `bot.tasks`. | |||||
""" | |||||
the BaseTask class (import with `from earwigbot.tasks import BaseTask`), | |||||
whereas the package contains various built-in tasks. Additional tasks can be | |||||
installed as plugins in the bot's working directory. | |||||
import imp | |||||
from os import listdir, path | |||||
from re import sub | |||||
from threading import Lock, Thread | |||||
from time import gmtime, strftime | |||||
To run a task, use bot.tasks.start(name, **kwargs). **kwargs get passed to the | |||||
Task's run() function. | |||||
""" | |||||
from earwigbot import wiki | from earwigbot import wiki | ||||
__all__ = ["BaseTask", "TaskManager"] | |||||
__all__ = ["BaseTask"] | |||||
class BaseTask(object): | class BaseTask(object): | ||||
"""A base class for bot tasks that edit Wikipedia.""" | """A base class for bot tasks that edit Wikipedia.""" | ||||
@@ -131,125 +129,3 @@ class BaseTask(object): | |||||
self.logger.warn("Emergency task shutoff has been enabled!") | self.logger.warn("Emergency task shutoff has been enabled!") | ||||
return True | return True | ||||
class TaskManager(object): | |||||
def __init__(self, bot): | |||||
self.bot = bot | |||||
self.logger = bot.logger.getChild("tasks") | |||||
self._tasks = {} | |||||
self._task_access_lock = Lock() | |||||
def __iter__(self): | |||||
for name in self._tasks: | |||||
yield name | |||||
def _wrapper(self, task, **kwargs): | |||||
"""Wrapper for task classes: run the task and catch any errors.""" | |||||
try: | |||||
task.run(**kwargs) | |||||
except Exception: | |||||
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_task(self, name, path): | |||||
"""Load a specific task from a module, identified by name and path. | |||||
We'll first try to import it using imp magic, and if that works, make | |||||
an instance of the 'Task' class inside (assuming it is an instance of | |||||
BaseTask), add it to self._tasks, and log the addition. Any problems | |||||
along the way will either be ignored or logged. | |||||
""" | |||||
f, path, desc = imp.find_module(name, [path]) | |||||
try: | |||||
module = imp.load_module(name, f, path, desc) | |||||
except Exception: | |||||
e = "Couldn't load module {0} from {1}" | |||||
self.logger.exception(e.format(name, path)) | |||||
return | |||||
finally: | |||||
f.close() | |||||
try: | |||||
task_class = module.Task | |||||
except AttributeError: | |||||
return # No task in this module | |||||
try: | |||||
task = task_class(self.bot) | |||||
except Exception: | |||||
e = "Error initializing Task() class in {0} (from {1})" | |||||
self.logger.exception(e.format(name, path)) | |||||
return | |||||
if not isinstance(task, BaseTask): | |||||
return | |||||
self._tasks[task.name] = task | |||||
self.logger.debug("Loaded task {0}".format(task.name)) | |||||
def _load_directory(self, dir): | |||||
"""Load all valid tasks in a given directory.""" | |||||
processed = [] | |||||
for name in listdir(dir): | |||||
if not name.endswith(".py") and not name.endswith(".pyc"): | |||||
continue | |||||
if name.startswith("_") or name.startswith("."): | |||||
continue | |||||
modname = sub("\.pyc?$", "", name) # Remove extension | |||||
if modname not in processed: | |||||
self._load_task(modname, dir) | |||||
processed.append(modname) | |||||
def load(self): | |||||
"""Load (or reload) all valid tasks into self._tasks.""" | |||||
with self._task_access_lock: | |||||
self._tasks.clear() | |||||
builtin_dir = path.dirname(__file__) | |||||
plugins_dir = path.join(self.bot.config.root_dir, "tasks") | |||||
self._load_directory(builtin_dir) # Built-in tasks | |||||
self._load_directory(plugins_dir) # Custom tasks, aka plugins | |||||
msg = "Loaded {0} tasks: {1}" | |||||
tasks = ', '.join(self._tasks.keys()) | |||||
self.logger.info(msg.format(len(self._tasks), tasks)) | |||||
def start(self, task_name, **kwargs): | |||||
"""Start a given task in a new thread. kwargs are passed to task.run""" | |||||
msg = "Starting task '{0}' in a new thread" | |||||
self.logger.info(msg.format(task_name)) | |||||
with self._task_access_lock: | |||||
try: | |||||
task = self._tasks[task_name] | |||||
except KeyError: | |||||
e = "Couldn't find task '{0}':" | |||||
self.logger.error(e.format(task_name)) | |||||
return | |||||
task_thread = Thread(target=self._wrapper, args=(task,), kwargs=kwargs) | |||||
start_time = strftime("%b %d %H:%M:%S") | |||||
task_thread.name = "{0} ({1})".format(task_name, start_time) | |||||
task_thread.start() | |||||
def schedule(self, now=None): | |||||
"""Start all tasks that are supposed to be run at a given time.""" | |||||
if not now: | |||||
now = gmtime() | |||||
# Get list of tasks to run this turn: | |||||
tasks = self.bot.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 | |||||
else: # Otherwise, just pass task_name | |||||
self.start(task) | |||||
def get(self, 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] |
@@ -29,10 +29,10 @@ written by Mr.Z-man, other than a similar purpose. We share no code. | |||||
Import the toolset directly with `from earwigbot import wiki`. If using the | Import the toolset directly with `from earwigbot import wiki`. If using the | ||||
built-in integration with the rest of the bot, Bot() objects contain a `wiki` | built-in integration with the rest of the bot, Bot() objects contain a `wiki` | ||||
attribute, which is a SitesDBManager object tied to the sites.db file located | |||||
in the same directory as config.yml. That object has the principal methods | |||||
get_site, add_site, and remove_site that should handle all of your Site (and | |||||
thus, Page, Category, and User) needs. | |||||
attribute, which is a SitesDB object tied to the sites.db file located in the | |||||
same directory as config.yml. That object has the principal methods get_site, | |||||
add_site, and remove_site that should handle all of your Site (and thus, Page, | |||||
Category, and User) needs. | |||||
""" | """ | ||||
import logging as _log | import logging as _log | ||||
@@ -32,9 +32,9 @@ from earwigbot import __version__ | |||||
from earwigbot.wiki.exceptions import SiteNotFoundError | from earwigbot.wiki.exceptions import SiteNotFoundError | ||||
from earwigbot.wiki.site import Site | from earwigbot.wiki.site import Site | ||||
__all__ = ["SitesDBManager"] | |||||
__all__ = ["SitesDB"] | |||||
class SitesDBManager(object): | |||||
class SitesDB(object): | |||||
""" | """ | ||||
EarwigBot's Wiki Toolset: Sites Database Manager | EarwigBot's Wiki Toolset: Sites Database Manager | ||||
@@ -49,8 +49,7 @@ class SitesDBManager(object): | |||||
here are available as bot.wiki.get_site(), bot.wiki.add_site(), and | here are available as bot.wiki.get_site(), bot.wiki.add_site(), and | ||||
bot.wiki.remove_site(), which use a sites.db file located in the same | bot.wiki.remove_site(), which use a sites.db file located in the same | ||||
directory as our config.yml file. Lower-level access can be achieved | directory as our config.yml file. Lower-level access can be achieved | ||||
by importing the manager class | |||||
(`from earwigbot.wiki import SitesDBManager`). | |||||
by importing the manager class (`from earwigbot.wiki import SitesDB`). | |||||
""" | """ | ||||
def __init__(self, config): | def __init__(self, config): | ||||