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

94
ev.py
View file

@ -2,21 +2,18 @@
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
explore it interactively from a python shell.
This is basically a set of shortcuts for accessing things in src/ with less boiler plate.
Import this from your code or explore it interactively from a python shell.
Notes:
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
following:
dot-notation to import deeper. Hence, to access a default command, you can do
import ev
ev.default_cmds.CmdLook
or
from ev import default_cmds
default_cmds.CmdLook
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.
@ -38,34 +35,48 @@ Notes:
some more error checking.
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
game/settings.py and restart the server.
settings. Note that this is for viewing only - you cannot *change* settings from here in a meaningful
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
the modules in src/ directly. You can always do this anyway, if you do not want to go through
this API.
"""
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
# not yet initialized)
######################################################################
if __name__ == "__main__":
info = __doc__ + \
print \
"""
| This module gives access to Evennia's programming API. It should
| not be run on its own, but be imported and accessed as described
| above.
|
| To start the Evennia server, see game/manage.py and game/evennia.py.
| More help can be found at http://www.evennia.com.
"""
print info
Evennia MU* creation system (%s)
This module gives access to Evennia's API (Application Programming
Interface). It should *not* be run on its own, but be imported and
accessed from your code or explored interactively from a Python
shell.
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()
######################################################################
@ -79,18 +90,6 @@ from game import settings
setup_environ(settings)
del setup_environ
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
######################################################################
@ -110,7 +109,7 @@ from src.players.models import PlayerDB, PlayerAttribute, PlayerNick
# commands
from src.commands.command import Command
from src.commands.cmdset import CmdSet
from src.commands import default as default_cmds
# (default_cmds is created below)
# locks
from src.locks import lockfuncs
@ -175,6 +174,37 @@ class DBmanagers(object):
managers = 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):
"""
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.
# 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
# remember the actual string constants. If not defined, Evennia

View file

@ -1,27 +1,4 @@
"""
Groups all default commands for access from the API.
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.
This package contains all default commands of Evennia, grouped after category.
"""
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())
# System commands
self.add(system.CmdReload())
self.add(system.CmdReset())
self.add(system.CmdShutdown())
self.add(system.CmdPy())
self.add(system.CmdScripts())
self.add(system.CmdObjects())
@ -51,9 +48,7 @@ class DefaultCmdSet(CmdSet):
self.add(admin.CmdBoot())
self.add(admin.CmdBan())
self.add(admin.CmdUnban())
self.add(admin.CmdDelPlayer())
self.add(admin.CmdEmit())
self.add(admin.CmdNewPassword())
self.add(admin.CmdPerm())
self.add(admin.CmdWall())

View file

@ -6,7 +6,7 @@ a Player object as caller rather than a Character.
"""
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):
"""
@ -19,7 +19,7 @@ class OOCCmdSet(CmdSet):
def at_cmdset_creation(self):
"Populates the cmdset"
# general commands
# General commands
self.add(general.CmdOOCLook())
self.add(general.CmdIC())
self.add(general.CmdOOC())
@ -27,11 +27,15 @@ class OOCCmdSet(CmdSet):
self.add(general.CmdQuit())
self.add(general.CmdPassword())
# help command
# Help command
self.add(help.CmdHelp())
# admin commands
self.add(admin.CmdBoot())
# system commands
self.add(system.CmdReload())
self.add(system.CmdReset())
self.add(system.CmdShutdown())
# Admin commands
self.add(admin.CmdDelPlayer())
self.add(admin.CmdNewPassword())

View file

@ -15,7 +15,7 @@ __all__ = ("CmdHome", "CmdLook", "CmdPassword", "CmdNick",
"CmdSay", "CmdPose", "CmdEncoding", "CmdAccess",
"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
class CmdHome(MuxCommand):

View file

@ -14,7 +14,7 @@ __all__ = ("ObjectManager",)
# 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):
"""

View file

@ -28,12 +28,12 @@ from src.commands.cmdsethandler import CmdSetHandler
from src.commands import cmdhandler
from src.scripts.scripthandler import ScriptHandler
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")
_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__
_SA = object.__setattr__

View file

@ -55,7 +55,7 @@ from src.commands import cmdhandler
__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

@ -268,7 +268,7 @@ if __name__ == '__main__':
except AttributeError:
# use default
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)
t0 = time.time()

View file

@ -7,7 +7,7 @@ be of use when designing your own game.
"""
from inspect import ismodule
import os, sys, imp
import os, sys, imp, types
import textwrap
import datetime
import random
@ -538,11 +538,19 @@ def has_parent(basepath, obj):
# instance. Not sure if one should defend against this.
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)
and imports it. If property is given, return the named
property from this module instead of the module itself.
A generic Python module loader.
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):
@ -567,18 +575,23 @@ def mod_import(mod_path, propname=None):
for line in errmsg.splitlines():
log.msg('[EE] %s' % line)
if not mod_path:
if not module:
return None
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:
mod = __import__(mod_path, fromlist=["None"])
mod = __import__(module, 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)
if not os.path.isabs(module):
module = os.path.abspath(module)
path, filename = module.rsplit(os.path.sep, 1)
modname = filename.rstrip('.py')
try:
@ -593,48 +606,52 @@ def mod_import(mod_path, propname=None):
mod = None
# we have to close the file handle manually
result[0].close()
if mod and propname:
# we have a module, extract the sought property from it.
try:
mod_prop = mod.__dict__[to_str(propname)]
except KeyError:
log_trace("Could not import property '%s' from module %s." % (propname, mod_path))
return None
return mod_prop
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
globally in the module. If no variable is given, a random variable
is returned from the module.
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 (or a list entry is None), a random variable
is extracted from the module.
If module cannot be imported or given variable not found, default
is returned.
"""
if not modpath:
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
try:
mod = __import__(modpath, fromlist=["None"])
except ImportError:
return default
if variable:
mod = mod_import(module)
result = []
for var in make_iter(variable):
if var:
# try to pick a named variable
return mod.__dict__.get(variable, default)
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))]
return mvars and random.choice(mvars)
result.append((mvars and random.choice(mvars)) or default)
if len(result) == 1:
return result[0]
return result
def string_from_module(modpath, variable=None, default=None):
def string_from_module(module, variable=None, default=None):
"""
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.
"""
val = variable_from_module(modpath, variable=variable, default=default)
val = variable_from_module(module, variable=variable, default=default)
if isinstance(val, basestring):
return val
elif is_iter(val):
return [(isinstance(v, basestring) and v or default) for v in val]
return default
def init_new_player(player):