Multiple fixes to ev and utils:

Made utils.variable_from_module more generic (it can now load pretty much any form of module it's given and also supports searching and returning multiple variables).

Removed the variable-load functionality from utils.load_module; this is now purely a loader - use variable_from_module instead.

I found out that one couldn't import from src.commands.default due to the __init__ file being restrictive for the sake of the ev API. Removed that and instead imported the default commands into ev.py with the help of utils.variable_from_module instead. Some more fixes in ev followed on this.
This commit is contained in:
Griatch 2012-04-22 12:23:42 +02:00
parent 3306e36d82
commit 4678234e9a
11 changed files with 211 additions and 188 deletions

96
ev.py
View file

@ -2,21 +2,18 @@
Central API for the Evennia MUD/MUX/MU* creation system. Central API for the Evennia MUD/MUX/MU* creation system.
This basically a set of shortcuts to the main modules in src/. Import this from your code or This is basically a set of shortcuts for accessing things in src/ with less boiler plate.
explore it interactively from a python shell. Import this from your code or explore it interactively from a python shell.
Notes: Notes:
1) You should import things explicitly from the root of this module - you can not use 1) You should import things explicitly from the root of this module - you can not use
dot-notation to import deeper. Hence, to access a default command, you can do the dot-notation to import deeper. Hence, to access a default command, you can do
following:
import ev import ev
ev.default_cmds.CmdLook ev.default_cmds.CmdLook
or or
from ev import default_cmds from ev import default_cmds
default_cmds.CmdLook default_cmds.CmdLook
But trying to import CmdLook directly with "from ev.default_cmds import CmdLook" will But trying to import CmdLook directly with "from ev.default_cmds import CmdLook" will
not work since default_cmds is a property on the "ev" module, not a module of its own. not work since default_cmds is a property on the "ev" module, not a module of its own.
@ -38,34 +35,48 @@ Notes:
some more error checking. some more error checking.
5) "settings" links to Evennia's game/settings file. "settings_full" shows all of django's available 5) "settings" links to Evennia's game/settings file. "settings_full" shows all of django's available
settings. Note that you cannot change settings from here in a meaningful way, you need to update settings. Note that this is for viewing only - you cannot *change* settings from here in a meaningful
game/settings.py and restart the server. way but have to update game/settings.py and restart the server.
6) The API accesses all relevant and most-neeeded functions/classes from src/, but might not 6) The API accesses all relevant and most-neeeded functions/classes from src/ but might not
always include all helper-functions referenced from each such entity. To get to those, access always include all helper-functions referenced from each such entity. To get to those, access
the modules in src/ directly. You can always do this anyway, if you do not want to go through the modules in src/ directly. You can always do this anyway, if you do not want to go through
this API. this API.
""" """
import sys, os import sys, os
######################################################################
# set Evennia version in __version__ property
######################################################################
try:
f = open(os.path.dirname(os.path.abspath(__file__)) + os.sep + "VERSION", 'r')
__version__ = "Evennia %s-r%s" % (f.read().strip(), os.popen("hg id -i").read().strip())
f.close()
del f
except IOError:
__version__ = "Evennia (unknown version)"
###################################################################### ######################################################################
# Stop erroneous direct run (would give a traceback since django is # Stop erroneous direct run (would give a traceback since django is
# not yet initialized) # not yet initialized)
###################################################################### ######################################################################
if __name__ == "__main__": if __name__ == "__main__":
info = __doc__ + \ print \
""" """
| This module gives access to Evennia's programming API. It should Evennia MU* creation system (%s)
| not be run on its own, but be imported and accessed as described
| above. This module gives access to Evennia's API (Application Programming
| Interface). It should *not* be run on its own, but be imported and
| To start the Evennia server, see game/manage.py and game/evennia.py. accessed from your code or explored interactively from a Python
| More help can be found at http://www.evennia.com. shell.
"""
print info For help configuring and starting the Evennia server, see the
INSTALL file. More help can be found at http://www.evennia.com.
""" % __version__
sys.exit() sys.exit()
###################################################################### ######################################################################
@ -79,18 +90,6 @@ from game import settings
setup_environ(settings) setup_environ(settings)
del setup_environ del setup_environ
from django.conf import settings as settings_full from django.conf import settings as settings_full
######################################################################
# set Evennia version in __version__ property
######################################################################
try:
f = open(os.path.dirname(os.path.abspath(__file__)) + os.sep + "VERSION", 'r')
__version__ = "Evennia %s-r%s" % (f.read().strip(), os.popen("hg id -i").read().strip())
f.close()
del f
except IOError:
__version__ = "Evennia (unknown version)"
del sys, os del sys, os
###################################################################### ######################################################################
@ -110,7 +109,7 @@ from src.players.models import PlayerDB, PlayerAttribute, PlayerNick
# commands # commands
from src.commands.command import Command from src.commands.command import Command
from src.commands.cmdset import CmdSet from src.commands.cmdset import CmdSet
from src.commands import default as default_cmds # (default_cmds is created below)
# locks # locks
from src.locks import lockfuncs from src.locks import lockfuncs
@ -175,6 +174,37 @@ class DBmanagers(object):
managers = DBmanagers() managers = DBmanagers()
del DBmanagers del DBmanagers
class DefaultCmds(object):
"""
This container holds direct shortcuts to all default commands in
Evennia.
"""
from src.commands.default.cmdset_default import DefaultCmdSet
from src.commands.default.cmdset_ooc import OOCCmdSet
from src.commands.default.cmdset_unloggedin import UnloggedinCmdSet
from src.commands.default.muxcommand import MuxCommand
def __init__(self):
"populate the object with commands"
def add_cmds(module):
"helper method for populating this object with cmds"
cmdlist = utils.variable_from_module(module, module.__all__)
self.__dict__.update(dict([(c.__name__, c) for c in cmdlist]))
from src.commands.default import admin, batchprocess, building, comms, general, help, system, unloggedin
add_cmds(building)
add_cmds(batchprocess)
add_cmds(building)
add_cmds(comms)
add_cmds(general)
add_cmds(help)
add_cmds(system)
add_cmds(unloggedin)
default_cmds = DefaultCmds()
del DefaultCmds
class SystemCmds(object): class SystemCmds(object):
""" """
Creating commands with keys set to these constants will make Creating commands with keys set to these constants will make

View file

@ -47,7 +47,7 @@ __all__ = ("cmdhandler",)
# This decides which command parser is to be used. # This decides which command parser is to be used.
# You have to restart the server for changes to take effect. # You have to restart the server for changes to take effect.
_COMMAND_PARSER = utils.mod_import(*settings.COMMAND_PARSER.rsplit('.', 1)) _COMMAND_PARSER = utils.variable_from_module(*settings.COMMAND_PARSER.rsplit('.', 1))
# System command names - import these variables rather than trying to # System command names - import these variables rather than trying to
# remember the actual string constants. If not defined, Evennia # remember the actual string constants. If not defined, Evennia

View file

@ -1,27 +1,4 @@
""" """
Groups all default commands for access from the API. This package contains all default commands of Evennia, grouped after category.
To use via ev API, import this module with
from ev import default_cmds,
you can then access the command classes as members of default_cmds.
""" """
from src.commands.default.cmdset_default import DefaultCmdSet
from src.commands.default.cmdset_ooc import OOCCmdSet
from src.commands.default.cmdset_unloggedin import UnloggedinCmdSet
from src.commands.default.muxcommand import MuxCommand
from src.commands.default.admin import *
from src.commands.default.batchprocess import *
from src.commands.default.building import *
from src.commands.default.comms import *
from src.commands.default.general import *
from src.commands.default.help import *
from src.commands.default import syscommands
from src.commands.default.system import *
from src.commands.default.unloggedin import *
del cmdset_default, cmdset_ooc, cmdset_unloggedin, muxcommand
del admin, batchprocess, building, comms, general,
del help, system, unloggedin

View file

@ -35,9 +35,6 @@ class DefaultCmdSet(CmdSet):
self.add(help.CmdSetHelp()) self.add(help.CmdSetHelp())
# System commands # System commands
self.add(system.CmdReload())
self.add(system.CmdReset())
self.add(system.CmdShutdown())
self.add(system.CmdPy()) self.add(system.CmdPy())
self.add(system.CmdScripts()) self.add(system.CmdScripts())
self.add(system.CmdObjects()) self.add(system.CmdObjects())
@ -51,9 +48,7 @@ class DefaultCmdSet(CmdSet):
self.add(admin.CmdBoot()) self.add(admin.CmdBoot())
self.add(admin.CmdBan()) self.add(admin.CmdBan())
self.add(admin.CmdUnban()) self.add(admin.CmdUnban())
self.add(admin.CmdDelPlayer())
self.add(admin.CmdEmit()) self.add(admin.CmdEmit())
self.add(admin.CmdNewPassword())
self.add(admin.CmdPerm()) self.add(admin.CmdPerm())
self.add(admin.CmdWall()) self.add(admin.CmdWall())

View file

@ -6,20 +6,20 @@ a Player object as caller rather than a Character.
""" """
from src.commands.cmdset import CmdSet from src.commands.cmdset import CmdSet
from src.commands.default import help, comms, general, admin from src.commands.default import help, comms, general, admin, system
class OOCCmdSet(CmdSet): class OOCCmdSet(CmdSet):
""" """
Implements the player command set. Implements the player command set.
""" """
key = "DefaultOOC" key = "DefaultOOC"
priority = -5 priority = -5
def at_cmdset_creation(self): def at_cmdset_creation(self):
"Populates the cmdset" "Populates the cmdset"
# general commands # General commands
self.add(general.CmdOOCLook()) self.add(general.CmdOOCLook())
self.add(general.CmdIC()) self.add(general.CmdIC())
self.add(general.CmdOOC()) self.add(general.CmdOOC())
@ -27,11 +27,15 @@ class OOCCmdSet(CmdSet):
self.add(general.CmdQuit()) self.add(general.CmdQuit())
self.add(general.CmdPassword()) self.add(general.CmdPassword())
# help command # Help command
self.add(help.CmdHelp()) self.add(help.CmdHelp())
# admin commands # system commands
self.add(admin.CmdBoot()) self.add(system.CmdReload())
self.add(system.CmdReset())
self.add(system.CmdShutdown())
# Admin commands
self.add(admin.CmdDelPlayer()) self.add(admin.CmdDelPlayer())
self.add(admin.CmdNewPassword()) self.add(admin.CmdNewPassword())

View file

@ -15,7 +15,7 @@ __all__ = ("CmdHome", "CmdLook", "CmdPassword", "CmdNick",
"CmdSay", "CmdPose", "CmdEncoding", "CmdAccess", "CmdSay", "CmdPose", "CmdEncoding", "CmdAccess",
"CmdOOCLook", "CmdIC", "CmdOOC") "CmdOOCLook", "CmdIC", "CmdOOC")
AT_SEARCH_RESULT = utils.mod_import(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
BASE_PLAYER_TYPECLASS = settings.BASE_PLAYER_TYPECLASS BASE_PLAYER_TYPECLASS = settings.BASE_PLAYER_TYPECLASS
class CmdHome(MuxCommand): class CmdHome(MuxCommand):

View file

@ -14,7 +14,7 @@ __all__ = ("ObjectManager",)
# Try to use a custom way to parse id-tagged multimatches. # Try to use a custom way to parse id-tagged multimatches.
_AT_MULTIMATCH_INPUT = utils.mod_import(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1)) _AT_MULTIMATCH_INPUT = utils.variable_from_module(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1))
class ObjectManager(TypedObjectManager): class ObjectManager(TypedObjectManager):
""" """

View file

@ -28,12 +28,12 @@ from src.commands.cmdsethandler import CmdSetHandler
from src.commands import cmdhandler from src.commands import cmdhandler
from src.scripts.scripthandler import ScriptHandler from src.scripts.scripthandler import ScriptHandler
from src.utils import logger from src.utils import logger
from src.utils.utils import make_iter, to_unicode, mod_import from src.utils.utils import make_iter, to_unicode, variable_from_module
#__all__ = ("ObjAttribute", "Alias", "ObjectNick", "ObjectDB") #__all__ = ("ObjAttribute", "Alias", "ObjectNick", "ObjectDB")
_AT_SEARCH_RESULT = mod_import(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
_GA = object.__getattribute__ _GA = object.__getattribute__
_SA = object.__setattr__ _SA = object.__setattr__

View file

@ -55,7 +55,7 @@ from src.commands import cmdhandler
__all__ = ("PlayerAttribute", "PlayerNick", "PlayerDB") __all__ = ("PlayerAttribute", "PlayerNick", "PlayerDB")
_AT_SEARCH_RESULT = utils.mod_import(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) _AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
#------------------------------------------------------------ #------------------------------------------------------------
# #

View file

@ -6,13 +6,13 @@ an Evennia game. It will launch any number of fake clients. These
clients will log into the server and start doing random operations. clients will log into the server and start doing random operations.
Customizing and weighing these operations differently depends on Customizing and weighing these operations differently depends on
which type of game is tested. The module contains a testing module which type of game is tested. The module contains a testing module
for plain Evennia. for plain Evennia.
Please note that you shouldn't run this on a production server! Please note that you shouldn't run this on a production server!
Launch the program without any arguments or options to see a Launch the program without any arguments or options to see a
full step-by-step setup help. full step-by-step setup help.
Basically (for testing default Evennia): Basically (for testing default Evennia):
- Use an empty/testing database. - Use an empty/testing database.
- set PERMISSION_PLAYER_DEFAULT = "Builders" - set PERMISSION_PLAYER_DEFAULT = "Builders"
@ -22,7 +22,7 @@ Basically (for testing default Evennia):
If you want to customize the runner's client actions If you want to customize the runner's client actions
(because you changed the cmdset or needs to better (because you changed the cmdset or needs to better
match your use cases or add more actions), you can match your use cases or add more actions), you can
change which actions by adding a path to change which actions by adding a path to
DUMMYRUNNER_ACTIONS_MODULE = <path.to.your.module> DUMMYRUNNER_ACTIONS_MODULE = <path.to.your.module>
@ -31,13 +31,13 @@ for instructions on how to define this module.
""" """
import os, sys, time, random import os, sys, time, random
from optparse import OptionParser from optparse import OptionParser
from twisted.conch import telnet from twisted.conch import telnet
from twisted.internet import reactor, protocol from twisted.internet import reactor, protocol
# from twisted.application import internet, service # from twisted.application import internet, service
# from twisted.web import client # from twisted.web import client
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
# Tack on the root evennia directory to the python path and initialize django settings # Tack on the root evennia directory to the python path and initialize django settings
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
@ -49,7 +49,7 @@ from django.conf import settings
from src.utils import utils from src.utils import utils
HELPTEXT = """ HELPTEXT = """
DO NOT RUN THIS ON A PRODUCTION SERVER! USE A CLEAN/TESTING DATABASE! DO NOT RUN THIS ON A PRODUCTION SERVER! USE A CLEAN/TESTING DATABASE!
This stand-alone program launches dummy telnet clients against a This stand-alone program launches dummy telnet clients against a
running Evennia server. The idea is to mimic real players logging in running Evennia server. The idea is to mimic real players logging in
@ -61,28 +61,28 @@ running clients will create new objects and rooms all over the place
as part of their running, so using a clean/testing database is as part of their running, so using a clean/testing database is
strongly recommended. strongly recommended.
Setup: Setup:
1) setup a fresh/clean database (if using sqlite, just safe-copy 1) setup a fresh/clean database (if using sqlite, just safe-copy
away your real evennia.db3 file and create a new one with away your real evennia.db3 file and create a new one with
manage.py) manage.py)
2) in game/settings.py, add 2) in game/settings.py, add
PERMISSION_PLAYER_DEFAULT="Builders" PERMISSION_PLAYER_DEFAULT="Builders"
3a) Start Evennia like normal. 3a) Start Evennia like normal.
3b) If you want profiling, start Evennia like this instead: 3b) If you want profiling, start Evennia like this instead:
python runner.py -S start python runner.py -S start
this will start Evennia under cProfiler with output server.prof. this will start Evennia under cProfiler with output server.prof.
4) run this dummy runner: 4) run this dummy runner:
python dummyclients.py <nr_of_clients> [timestep] [port] python dummyclients.py <nr_of_clients> [timestep] [port]
Default is to connect one client to port 4000, using a 5 second Default is to connect one client to port 4000, using a 5 second
timestep. Increase the number of clients and shorten the timestep. Increase the number of clients and shorten the
timestep (minimum is 1s) to further stress the game. timestep (minimum is 1s) to further stress the game.
You can stop the dummy runner with Ctrl-C. You can stop the dummy runner with Ctrl-C.
5) Log on and determine if game remains responsive despite the 5) Log on and determine if game remains responsive despite the
@ -98,12 +98,12 @@ DEFAULT_NCLIENTS = 1
# time between each 'tick', in seconds, if not set on command # time between each 'tick', in seconds, if not set on command
# line. All launched clients will be called upon to possibly do an # line. All launched clients will be called upon to possibly do an
# action with this frequency. # action with this frequency.
DEFAULT_TIMESTEP = 5 DEFAULT_TIMESTEP = 5
# Port to use, if not specified on command line # Port to use, if not specified on command line
DEFAULT_PORT = settings.TELNET_PORTS[0] DEFAULT_PORT = settings.TELNET_PORTS[0]
# chance of an action happening, per timestep. This helps to # chance of an action happening, per timestep. This helps to
# spread out usage randomly, like it would be in reality. # spread out usage randomly, like it would be in reality.
CHANCE_OF_ACTION = 0.1 CHANCE_OF_ACTION = 0.1
#------------------------------------------------------------ #------------------------------------------------------------
@ -111,37 +111,37 @@ CHANCE_OF_ACTION = 0.1
#------------------------------------------------------------ #------------------------------------------------------------
def idcounter(): def idcounter():
"generates subsequent id numbers" "generates subsequent id numbers"
idcount = 0 idcount = 0
while True: while True:
idcount += 1 idcount += 1
yield idcount yield idcount
OID = idcounter() OID = idcounter()
CID = idcounter() CID = idcounter()
def makeiter(obj): def makeiter(obj):
"makes everything iterable" "makes everything iterable"
if not hasattr(obj, '__iter__'): if not hasattr(obj, '__iter__'):
return [obj] return [obj]
return obj return obj
#------------------------------------------------------------ #------------------------------------------------------------
# Client classes # Client classes
#------------------------------------------------------------ #------------------------------------------------------------
class DummyClient(telnet.StatefulTelnetProtocol): class DummyClient(telnet.StatefulTelnetProtocol):
""" """
Handles connection to a running Evennia server, Handles connection to a running Evennia server,
mimicking a real player by sending commands on mimicking a real player by sending commands on
a timer. a timer.
""" """
def connectionMade(self): def connectionMade(self):
# public properties # public properties
self.cid = CID.next() self.cid = CID.next()
self.istep = 0 self.istep = 0
self.exits = [] # exit names created self.exits = [] # exit names created
self.objs = [] # obj names created self.objs = [] # obj names created
self._actions = self.factory.actions self._actions = self.factory.actions
@ -150,16 +150,16 @@ class DummyClient(telnet.StatefulTelnetProtocol):
#print " ** client %i connected." % self.cid #print " ** client %i connected." % self.cid
reactor.addSystemEventTrigger('before', 'shutdown', self.logout) reactor.addSystemEventTrigger('before', 'shutdown', self.logout)
# start client tick # start client tick
d = LoopingCall(self.step) d = LoopingCall(self.step)
d.start(self.factory.timestep, now=True).addErrback(self.error) d.start(self.factory.timestep, now=True).addErrback(self.error)
def dataReceived(self, data): def dataReceived(self, data):
"Echo incoming data to stdout" "Echo incoming data to stdout"
if self._echo_all: if self._echo_all:
print data print data
def connectionLost(self, reason): def connectionLost(self, reason):
"loosing the connection" "loosing the connection"
#print " ** client %i lost connection." % self.cid #print " ** client %i lost connection." % self.cid
@ -175,25 +175,25 @@ class DummyClient(telnet.StatefulTelnetProtocol):
def logout(self): def logout(self):
"Causes the client to log out of the server. Triggered by ctrl-c signal." "Causes the client to log out of the server. Triggered by ctrl-c signal."
cmd, report = self._actions[1](self) cmd, report = self._actions[1](self)
print "client %i %s (%s actions)" % (self.cid, report, self.istep) print "client %i %s (%s actions)" % (self.cid, report, self.istep)
self.sendLine(cmd) self.sendLine(cmd)
def step(self): def step(self):
""" """
Perform a step. This is called repeatedly by the runner Perform a step. This is called repeatedly by the runner
and causes the client to issue commands to the server. and causes the client to issue commands to the server.
This holds all "intelligence" of the dummy client. This holds all "intelligence" of the dummy client.
""" """
if random.random() > CHANCE_OF_ACTION: if random.random() > CHANCE_OF_ACTION:
return return
if self.istep == 0: if self.istep == 0:
cfunc = self._actions[0] cfunc = self._actions[0]
else: # random selection using cumulative probabilities else: # random selection using cumulative probabilities
rand = random.random() rand = random.random()
cfunc = [func for cprob, func in self._actions[2] if cprob >= rand][0] cfunc = [func for cprob, func in self._actions[2] if cprob >= rand][0]
# launch the action (don't hide tracebacks) # launch the action (don't hide tracebacks)
cmd, report = cfunc(self) cmd, report = cfunc(self)
# handle the result # handle the result
cmd = "\n".join(makeiter(cmd)) cmd = "\n".join(makeiter(cmd))
if self.istep == 0 or self._echo_brief or self._echo_all: if self.istep == 0 or self._echo_brief or self._echo_all:
print "client %i %s" % (self.cid, report) print "client %i %s" % (self.cid, report)
@ -205,74 +205,74 @@ class DummyFactory(protocol.ClientFactory):
def __init__(self, actions, timestep, verbose): def __init__(self, actions, timestep, verbose):
"Setup the factory base (shared by all clients)" "Setup the factory base (shared by all clients)"
self.actions = actions self.actions = actions
self.timestep = timestep self.timestep = timestep
self.verbose = verbose self.verbose = verbose
#------------------------------------------------------------ #------------------------------------------------------------
# Access method: # Access method:
# Starts clients and connects them to a running server. # Starts clients and connects them to a running server.
#------------------------------------------------------------ #------------------------------------------------------------
def start_all_dummy_clients(actions, nclients=1, timestep=5, telnet_port=4000, verbose=0): def start_all_dummy_clients(actions, nclients=1, timestep=5, telnet_port=4000, verbose=0):
# validating and preparing the action tuple # validating and preparing the action tuple
# make sure the probabilities add up to 1 # make sure the probabilities add up to 1
pratio = 1.0 / sum(tup[0] for tup in actions[2:]) pratio = 1.0 / sum(tup[0] for tup in actions[2:])
flogin, flogout, probs, cfuncs = actions[0], actions[1], [tup[0] * pratio for tup in actions[2:]], [tup[1] for tup in actions[2:]] flogin, flogout, probs, cfuncs = actions[0], actions[1], [tup[0] * pratio for tup in actions[2:]], [tup[1] for tup in actions[2:]]
# create cumulative probabilies for the random actions # create cumulative probabilies for the random actions
cprobs = [sum(v for i,v in enumerate(probs) if i<=k) for k in range(len(probs))] cprobs = [sum(v for i,v in enumerate(probs) if i<=k) for k in range(len(probs))]
# rebuild a new, optimized action structure # rebuild a new, optimized action structure
actions = (flogin, flogout, zip(cprobs, cfuncs)) actions = (flogin, flogout, zip(cprobs, cfuncs))
# setting up all clients (they are automatically started) # setting up all clients (they are automatically started)
factory = DummyFactory(actions, timestep, verbose) factory = DummyFactory(actions, timestep, verbose)
for i in range(nclients): for i in range(nclients):
reactor.connectTCP("localhost", telnet_port, factory) reactor.connectTCP("localhost", telnet_port, factory)
# start reactor # start reactor
reactor.run() reactor.run()
#------------------------------------------------------------ #------------------------------------------------------------
# Command line interface # Command line interface
#------------------------------------------------------------ #------------------------------------------------------------
if __name__ == '__main__': if __name__ == '__main__':
# parsing command line with default vals # parsing command line with default vals
parser = OptionParser(usage="%prog [options] <nclients> [timestep, [port]]", parser = OptionParser(usage="%prog [options] <nclients> [timestep, [port]]",
description="This program requires some preparations to run properly. Start it without any arguments or options for full help.") description="This program requires some preparations to run properly. Start it without any arguments or options for full help.")
parser.add_option('-v', '--verbose', action='store_const', const=1, dest='verbose', parser.add_option('-v', '--verbose', action='store_const', const=1, dest='verbose',
default=0,help="echo brief description of what clients do every timestep.") default=0,help="echo brief description of what clients do every timestep.")
parser.add_option('-V', '--very-verbose', action='store_const',const=2, dest='verbose', parser.add_option('-V', '--very-verbose', action='store_const',const=2, dest='verbose',
default=0,help="echo all client returns to stdout (hint: use only with nclients=1!)") default=0,help="echo all client returns to stdout (hint: use only with nclients=1!)")
options, args = parser.parse_args() options, args = parser.parse_args()
nargs = len(args) nargs = len(args)
nclients = DEFAULT_NCLIENTS nclients = DEFAULT_NCLIENTS
timestep = DEFAULT_TIMESTEP timestep = DEFAULT_TIMESTEP
port = DEFAULT_PORT port = DEFAULT_PORT
try: try:
if not args : raise Exception if not args : raise Exception
if nargs > 0: nclients = max(1, int(args[0])) if nargs > 0: nclients = max(1, int(args[0]))
if nargs > 1: timestep = max(1, int(args[1])) if nargs > 1: timestep = max(1, int(args[1]))
if nargs > 2: port = int(args[2]) if nargs > 2: port = int(args[2])
except Exception: except Exception:
print HELPTEXT print HELPTEXT
sys.exit() sys.exit()
# import the ACTION tuple from a given module # import the ACTION tuple from a given module
try: try:
action_modpath = settings.DUMMYRUNNER_ACTIONS_MODULE action_modpath = settings.DUMMYRUNNER_ACTIONS_MODULE
except AttributeError: except AttributeError:
# use default # use default
action_modpath = "src.utils.dummyrunner_actions" action_modpath = "src.utils.dummyrunner_actions"
actions = utils.mod_import(action_modpath, "ACTIONS") actions = utils.variable_from_module(action_modpath, "ACTIONS")
print "Connecting %i dummy client(s) to port %i using a %i second timestep ... " % (nclients, port, timestep) print "Connecting %i dummy client(s) to port %i using a %i second timestep ... " % (nclients, port, timestep)
t0 = time.time() t0 = time.time()
start_all_dummy_clients(actions, nclients, timestep, port, start_all_dummy_clients(actions, nclients, timestep, port,
verbose=options.verbose) verbose=options.verbose)
ttot = time.time() - t0 ttot = time.time() - t0
print "... dummy client runner finished after %i seconds." % ttot print "... dummy client runner finished after %i seconds." % ttot

View file

@ -7,7 +7,7 @@ be of use when designing your own game.
""" """
from inspect import ismodule from inspect import ismodule
import os, sys, imp import os, sys, imp, types
import textwrap import textwrap
import datetime import datetime
import random import random
@ -538,11 +538,19 @@ def has_parent(basepath, obj):
# instance. Not sure if one should defend against this. # instance. Not sure if one should defend against this.
return False return False
def mod_import(mod_path, propname=None): def mod_import(module):
""" """
Takes filename of a module (a python path or a full pathname) A generic Python module loader.
and imports it. If property is given, return the named
property from this module instead of the module itself. Args:
module - this can be either a Python path (dot-notation like src.objects.models),
an absolute path (e.g. /home/eve/evennia/src/objects.models.py)
or an already import module object (e.g. models)
Returns:
an imported module. If the input argument was already a model, this is returned as-is,
otherwise the path is parsed and imported.
Error:
returns None. The error is also logged.
""" """
def log_trace(errmsg=None): def log_trace(errmsg=None):
@ -567,74 +575,83 @@ def mod_import(mod_path, propname=None):
for line in errmsg.splitlines(): for line in errmsg.splitlines():
log.msg('[EE] %s' % line) log.msg('[EE] %s' % line)
if not mod_path: if not module:
return None return None
# first try to import as a python path
try:
mod = __import__(mod_path, fromlist=["None"])
except ImportError:
# try absolute path import instead
if not os.path.isabs(mod_path):
mod_path = os.path.abspath(mod_path)
path, filename = mod_path.rsplit(os.path.sep, 1)
modname = filename.rstrip('.py')
if type(module) == types.ModuleType:
# if this is already a module, we are done
mod = module
else:
# first try to import as a python path
try: try:
result = imp.find_module(modname, [path]) mod = __import__(module, fromlist=["None"])
except ImportError: except ImportError:
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
return
try:
mod = imp.load_module(modname, *result)
except ImportError:
log_trace("Could not find or import module %s at path '%s'" % (modname, path))
mod = None
# we have to close the file handle manually
result[0].close()
if mod and propname: # try absolute path import instead
# we have a module, extract the sought property from it.
try: if not os.path.isabs(module):
mod_prop = mod.__dict__[to_str(propname)] module = os.path.abspath(module)
except KeyError: path, filename = module.rsplit(os.path.sep, 1)
log_trace("Could not import property '%s' from module %s." % (propname, mod_path)) modname = filename.rstrip('.py')
return None
return mod_prop try:
result = imp.find_module(modname, [path])
except ImportError:
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
return
try:
mod = imp.load_module(modname, *result)
except ImportError:
log_trace("Could not find or import module %s at path '%s'" % (modname, path))
mod = None
# we have to close the file handle manually
result[0].close()
return mod return mod
def variable_from_module(modpath, variable=None, default=None): def variable_from_module(module, variable=None, default=None):
""" """
Retrieve a variable from a module. The variable must be defined Retrieve a variable or list of variables from a module. The variable(s) must be defined
globally in the module. If no variable is given, a random variable globally in the module. If no variable is given (or a list entry is None), a random variable
is returned from the module. is extracted from the module.
If module cannot be imported or given variable not found, default If module cannot be imported or given variable not found, default
is returned. is returned.
"""
if not modpath:
return default
try:
mod = __import__(modpath, fromlist=["None"])
except ImportError:
return default
if variable:
# try to pick a named variable
return mod.__dict__.get(variable, default)
else:
# random selection
mvars = [val for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val))]
return mvars and random.choice(mvars)
def string_from_module(modpath, variable=None, default=None): Args:
module (string or module)- python path, absolute path or a module
variable (string or iterable) - single variable name or iterable of variable names to extract
default (string) - default value to use if a variable fails to be extracted.
Returns:
a single value or a list of values depending on the type of 'variable' argument. Errors in lists
are replaced by the 'default' argument."""
if not module:
return default
mod = mod_import(module)
result = []
for var in make_iter(variable):
if var:
# try to pick a named variable
result.append(mod.__dict__.get(var, default))
else:
# random selection
mvars = [val for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val))]
result.append((mvars and random.choice(mvars)) or default)
if len(result) == 1:
return result[0]
return result
def string_from_module(module, variable=None, default=None):
""" """
This is a wrapper for variable_from_module that requires return This is a wrapper for variable_from_module that requires return
value to be a string to pass. It's primarily used by login screen. value to be a string to pass. It's primarily used by login screen.
""" """
val = variable_from_module(modpath, variable=variable, default=default) val = variable_from_module(module, variable=variable, default=default)
if isinstance(val, basestring): if isinstance(val, basestring):
return val return val
elif is_iter(val):
return [(isinstance(v, basestring) and v or default) for v in val]
return default return default
def init_new_player(player): def init_new_player(player):