Merge pull request #191 from lykoss/proxy
Introduce proxies as a means of breaking circular import chains
This commit is contained in:
commit
837cf49c44
@ -5,6 +5,32 @@ import time
|
|||||||
import botconfig
|
import botconfig
|
||||||
import src.settings as var
|
import src.settings as var
|
||||||
|
|
||||||
|
# Segue to logger, since src.gamemodes requires it
|
||||||
|
# TODO: throw this into a logger.py perhaps so we aren't breaking up imports with non-import stuff
|
||||||
|
def logger(file, write=True, display=True):
|
||||||
|
if file is not None:
|
||||||
|
open(file, "a").close() # create the file if it doesn't exist
|
||||||
|
def log(*output, write=write, display=display):
|
||||||
|
output = " ".join([str(x) for x in output]).replace("\u0002", "").replace("\\x02", "") # remove bold
|
||||||
|
if botconfig.DEBUG_MODE:
|
||||||
|
write = True
|
||||||
|
if botconfig.DEBUG_MODE or botconfig.VERBOSE_MODE:
|
||||||
|
display = True
|
||||||
|
timestamp = get_timestamp()
|
||||||
|
if display:
|
||||||
|
print(timestamp + output, file=utf8stdout)
|
||||||
|
if write and file is not None:
|
||||||
|
with open(file, "a", errors="replace") as f:
|
||||||
|
f.seek(0, 2)
|
||||||
|
f.write(timestamp + output + "\n")
|
||||||
|
|
||||||
|
return log
|
||||||
|
|
||||||
|
stream_handler = logger(None)
|
||||||
|
debuglog = logger("debug.log", write=False, display=False)
|
||||||
|
errlog = logger("errors.log")
|
||||||
|
plog = logger(None) # use this instead of print so that logs have timestamps
|
||||||
|
|
||||||
# Import the user-defined game modes
|
# Import the user-defined game modes
|
||||||
# These are not required, so failing to import it doesn't matter
|
# These are not required, so failing to import it doesn't matter
|
||||||
# The file then imports our game modes
|
# The file then imports our game modes
|
||||||
@ -86,28 +112,6 @@ def get_timestamp(use_utc=None, ts_format=None):
|
|||||||
offset += str(time.timezone // 36).zfill(4)
|
offset += str(time.timezone // 36).zfill(4)
|
||||||
return tmf.format(tzname=tz, tzoffset=offset).strip().upper() + " "
|
return tmf.format(tzname=tz, tzoffset=offset).strip().upper() + " "
|
||||||
|
|
||||||
def logger(file, write=True, display=True):
|
|
||||||
if file is not None:
|
|
||||||
open(file, "a").close() # create the file if it doesn't exist
|
|
||||||
def log(*output, write=write, display=display):
|
|
||||||
output = " ".join([str(x) for x in output]).replace("\u0002", "").replace("\\x02", "") # remove bold
|
|
||||||
if botconfig.DEBUG_MODE:
|
|
||||||
write = True
|
|
||||||
if botconfig.DEBUG_MODE or botconfig.VERBOSE_MODE:
|
|
||||||
display = True
|
|
||||||
timestamp = get_timestamp()
|
|
||||||
if display:
|
|
||||||
print(timestamp + output, file=utf8stdout)
|
|
||||||
if write and file is not None:
|
|
||||||
with open(file, "a", errors="replace") as f:
|
|
||||||
f.seek(0, 2)
|
|
||||||
f.write(timestamp + output + "\n")
|
|
||||||
|
|
||||||
return log
|
|
||||||
|
|
||||||
stream_handler = logger(None)
|
|
||||||
debuglog = logger("debug.log", write=False, display=False)
|
|
||||||
|
|
||||||
def stream(output, level="normal"):
|
def stream(output, level="normal"):
|
||||||
if botconfig.VERBOSE_MODE or botconfig.DEBUG_MODE:
|
if botconfig.VERBOSE_MODE or botconfig.DEBUG_MODE:
|
||||||
stream_handler(output)
|
stream_handler(output)
|
||||||
|
@ -7,8 +7,7 @@ import botconfig
|
|||||||
import src.settings as var
|
import src.settings as var
|
||||||
from src.utilities import *
|
from src.utilities import *
|
||||||
from src.messages import messages
|
from src.messages import messages
|
||||||
# utilities imported per Vgr's temporary fix to chk_nightdone(cli) in Sleepy
|
from src import events
|
||||||
from src import events, utilities
|
|
||||||
|
|
||||||
def game_mode(name, minp, maxp, likelihood = 0):
|
def game_mode(name, minp, maxp, likelihood = 0):
|
||||||
def decor(c):
|
def decor(c):
|
||||||
@ -852,7 +851,7 @@ class SleepyMode(GameMode):
|
|||||||
if "correct" in self.on_path:
|
if "correct" in self.on_path:
|
||||||
pm(cli, self.having_nightmare, messages["sleepy_nightmare_wake"])
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_wake"])
|
||||||
self.having_nightmare = None
|
self.having_nightmare = None
|
||||||
utilities.chk_nightdone(cli)
|
chk_nightdone(cli)
|
||||||
elif "fake1" in self.on_path:
|
elif "fake1" in self.on_path:
|
||||||
pm(cli, self.having_nightmare, messages["sleepy_nightmare_fake_1"])
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_fake_1"])
|
||||||
self.step = 0
|
self.step = 0
|
||||||
|
101
src/proxy.py
Normal file
101
src/proxy.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import inspect
|
||||||
|
|
||||||
|
from src.decorators import handle_error
|
||||||
|
|
||||||
|
""" This module introduces two decorators - @proxy.stub and @proxy.impl
|
||||||
|
|
||||||
|
@proxy.stub is used to decorate a stub method that should be filled in
|
||||||
|
with an implementation in some other module. Calling the stub method
|
||||||
|
calls the implementation method in the other module instead of the stub
|
||||||
|
method itself (or raises a NotImplementedError if no such
|
||||||
|
implementation exists)
|
||||||
|
|
||||||
|
@proxy.impl is used to define a previously-declared stub with an actual
|
||||||
|
implementation to call -- the signature for the implementation must
|
||||||
|
exactly match the signature for the stub (enforced for Python 3.3+).
|
||||||
|
|
||||||
|
Attempting to implement a non-existent stub is an error, as is trying
|
||||||
|
to re-implement a stub that is already implemented.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
(foo.py)
|
||||||
|
@proxy.stub
|
||||||
|
def my_method(foo, bar=10):
|
||||||
|
pass
|
||||||
|
|
||||||
|
(bar.py)
|
||||||
|
@proxy.impl
|
||||||
|
def my_method(foo, bar=10):
|
||||||
|
return foo * bar
|
||||||
|
"""
|
||||||
|
IMPLS = {}
|
||||||
|
SIGS = {}
|
||||||
|
|
||||||
|
def stub(f):
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
if f.__name__ not in IMPLS:
|
||||||
|
raise NotImplementedError(("This proxy stub has not yet been "
|
||||||
|
"implemented in another module"))
|
||||||
|
return IMPLS[f.__name__](*args, **kwargs)
|
||||||
|
|
||||||
|
if hasattr(inspect, "signature"):
|
||||||
|
# Python 3.3+
|
||||||
|
if f.__name__ in SIGS:
|
||||||
|
_sigmatch(f)
|
||||||
|
SIGS[f.__name__] = inspect.signature(f)
|
||||||
|
else:
|
||||||
|
# Python 3.2; not allowed to have nice things
|
||||||
|
SIGS[f.__name__] = None
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
def impl(f):
|
||||||
|
if f.__name__ not in SIGS:
|
||||||
|
raise NameError(("Attempting to implement a proxy stub {0} that does "
|
||||||
|
"not exist").format(f.__name__))
|
||||||
|
if f.__name__ in IMPLS:
|
||||||
|
raise SyntaxError(("Attempting to implement a proxy stub {0} that "
|
||||||
|
"already has an implementation").format(f.__name__))
|
||||||
|
if hasattr(inspect, "signature"):
|
||||||
|
# Python 3.3+
|
||||||
|
_sigmatch(f)
|
||||||
|
|
||||||
|
# Always wrap proxy implementations in an error handler
|
||||||
|
IMPLS[f.__name__] = handle_error(f)
|
||||||
|
# allows this method to be called directly in our module rather
|
||||||
|
# than forcing use of the stub's module
|
||||||
|
return handle_error(f)
|
||||||
|
|
||||||
|
def _sigmatch(f):
|
||||||
|
rs = inspect.signature(f)
|
||||||
|
ts = SIGS[f.__name__]
|
||||||
|
if len(rs.parameters) != len(ts.parameters):
|
||||||
|
raise TypeError(
|
||||||
|
("Arity does not match existing stub, "
|
||||||
|
"expected {0} parameters but got {1}.").format(
|
||||||
|
len(ts.parameters), len(rs.parameters)))
|
||||||
|
opl = list(rs.parameters.values())
|
||||||
|
tpl = list(ts.parameters.values())
|
||||||
|
for i in range(len(rs.parameters)):
|
||||||
|
op = opl[i]
|
||||||
|
tp = tpl[i]
|
||||||
|
if op.name != tp.name:
|
||||||
|
raise TypeError(
|
||||||
|
("Parameter name does not match existing stub, "
|
||||||
|
"expected {0} but got {1}.").format(tp.name, op.name))
|
||||||
|
if op.default != tp.default:
|
||||||
|
raise TypeError(
|
||||||
|
("Default value of parameter does not match existing stub "
|
||||||
|
"for parameter {0}, expected {1} but got {2}.").format(
|
||||||
|
op.name,
|
||||||
|
("no default" if tp.default is inspect.Parameter.empty
|
||||||
|
else repr(tp.default)),
|
||||||
|
("no default" if op.default is inspect.Parameter.empty
|
||||||
|
else repr(op.default))))
|
||||||
|
if op.kind != tp.kind:
|
||||||
|
raise TypeError(
|
||||||
|
("Parameter type does not match existing stub for "
|
||||||
|
"parameter {0}, expected {1} but got {2}.").format(
|
||||||
|
op.name, str(tp.kind), str(op.kind)))
|
||||||
|
|
||||||
|
# vim: set expandtab:sw=4:ts=4:
|
@ -1,7 +1,9 @@
|
|||||||
import src.settings as var
|
|
||||||
import botconfig
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import botconfig
|
||||||
|
import src.settings as var
|
||||||
|
from src import proxy, debuglog
|
||||||
|
|
||||||
# Some miscellaneous helper functions
|
# Some miscellaneous helper functions
|
||||||
|
|
||||||
def mass_mode(cli, md_param, md_plain):
|
def mass_mode(cli, md_param, md_plain):
|
||||||
@ -156,4 +158,16 @@ def relay_wolfchat_command(cli, nick, message, roles, is_wolf_command=False, is_
|
|||||||
mass_privmsg(cli, wcwolves, message)
|
mass_privmsg(cli, wcwolves, message)
|
||||||
mass_privmsg(cli, var.SPECTATING_WOLFCHAT, "[wolfchat] " + message)
|
mass_privmsg(cli, var.SPECTATING_WOLFCHAT, "[wolfchat] " + message)
|
||||||
|
|
||||||
|
@proxy.stub
|
||||||
|
def chk_nightdone(cli):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@proxy.stub
|
||||||
|
def chk_decision(cli, force=""):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@proxy.stub
|
||||||
|
def chk_win(cli, end_game=True, winner=None):
|
||||||
|
pass
|
||||||
|
|
||||||
# vim: set expandtab:sw=4:ts=4:
|
# vim: set expandtab:sw=4:ts=4:
|
||||||
|
@ -44,19 +44,12 @@ from oyoyo.parse import parse_nick
|
|||||||
import botconfig
|
import botconfig
|
||||||
import src.settings as var
|
import src.settings as var
|
||||||
from src.utilities import *
|
from src.utilities import *
|
||||||
from src import decorators, events, logger, utilities, debuglog
|
from src import decorators, events, logger, proxy, debuglog, errlog, plog
|
||||||
|
|
||||||
# make debuglog accessible anywhere
|
|
||||||
utilities.debuglog = debuglog
|
|
||||||
|
|
||||||
from src.messages import messages
|
from src.messages import messages
|
||||||
|
|
||||||
# done this way so that events is accessible in !eval (useful for debugging)
|
# done this way so that events is accessible in !eval (useful for debugging)
|
||||||
Event = events.Event
|
Event = events.Event
|
||||||
|
|
||||||
errlog = logger("errors.log")
|
|
||||||
plog = logger(None) #use this instead of print so that logs have timestamps
|
|
||||||
|
|
||||||
is_admin = var.is_admin
|
is_admin = var.is_admin
|
||||||
is_owner = var.is_owner
|
is_owner = var.is_owner
|
||||||
|
|
||||||
@ -2073,7 +2066,7 @@ def fday(cli, nick, chan, rest):
|
|||||||
transition_day(cli)
|
transition_day(cli)
|
||||||
|
|
||||||
# Specify force = "nick" to force nick to be lynched
|
# Specify force = "nick" to force nick to be lynched
|
||||||
@handle_error
|
@proxy.impl
|
||||||
def chk_decision(cli, force = ""):
|
def chk_decision(cli, force = ""):
|
||||||
with var.GRAVEYARD_LOCK:
|
with var.GRAVEYARD_LOCK:
|
||||||
if var.PHASE != "day":
|
if var.PHASE != "day":
|
||||||
@ -2595,6 +2588,7 @@ def stop_game(cli, winner = "", abort = False, additional_winners = None):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@proxy.impl
|
||||||
def chk_win(cli, end_game=True, winner=None):
|
def chk_win(cli, end_game=True, winner=None):
|
||||||
""" Returns True if someone won """
|
""" Returns True if someone won """
|
||||||
chan = botconfig.CHANNEL
|
chan = botconfig.CHANNEL
|
||||||
@ -4603,6 +4597,7 @@ def transition_day(cli, gameid=0):
|
|||||||
|
|
||||||
begin_day(cli)
|
begin_day(cli)
|
||||||
|
|
||||||
|
@proxy.impl
|
||||||
def chk_nightdone(cli):
|
def chk_nightdone(cli):
|
||||||
if var.PHASE != "night":
|
if var.PHASE != "night":
|
||||||
return
|
return
|
||||||
@ -4702,8 +4697,6 @@ def chk_nightdone(cli):
|
|||||||
if var.PHASE == "night": # Double check
|
if var.PHASE == "night": # Double check
|
||||||
transition_day(cli)
|
transition_day(cli)
|
||||||
|
|
||||||
utilities.chk_nightdone = chk_nightdone # for some events to access
|
|
||||||
|
|
||||||
@cmd("nolynch", "nl", "novote", "nv", "abstain", "abs", playing=True, phases=("day",))
|
@cmd("nolynch", "nl", "novote", "nv", "abstain", "abs", playing=True, phases=("day",))
|
||||||
def no_lynch(cli, nick, chan, rest):
|
def no_lynch(cli, nick, chan, rest):
|
||||||
"""Allows you to abstain from voting for the day."""
|
"""Allows you to abstain from voting for the day."""
|
||||||
|
Loading…
Reference in New Issue
Block a user