Skip to main content
Code Review

Return to Question

Notice removed Draw attention by Community Bot
Bounty Ended with no winning answer by Community Bot
added 177 characters in body
Source Link
Dan Oberlam
  • 8k
  • 2
  • 33
  • 74

This made sense to me; a lot of the general work about making sure a command looks correct can probably be abstracted out such that I can define what valid parameters to a command would look like. I started by defining a set of parameters (note, I'm using the numpydoc package style for Sphinx documentation generation throughout):

This made sense to me; a lot of the general work about making sure a command looks correct can probably be abstracted out such that I can define what valid parameters to a command would look like. I started by defining a set of parameters:

This made sense to me; a lot of the general work about making sure a command looks correct can probably be abstracted out such that I can define what valid parameters to a command would look like. I started by defining a set of parameters (note, I'm using the numpydoc package style for Sphinx documentation generation throughout):

added 12 characters in body
Source Link
Dan Oberlam
  • 8k
  • 2
  • 33
  • 74

I asked this question a while ago, and this was one of the comments was this:

There (mostly) seems to be a one-to-one mapping of a lot of your functions to IRC command verbs. For example, leave_serverleave_server -> QUITQUIT, join_channeljoin_channel -> JOINJOIN, send_private_message send_private_message-> PRIVMSGPRIVMSG, etc. Part of me just feels that (as you say) there is just a tad too much repetition in that architecture. (Perhaps some more abstract encapsulation of a server command and arguments?

I asked this question a while ago, and one of the comments was this:

There (mostly) seems to be a one-to-one mapping of a lot of your functions to IRC command verbs. For example, leave_server -> QUIT, join_channel -> JOIN, send_private_message -> PRIVMSG, etc. Part of me just feels that (as you say) there is just a tad too much repetition in that architecture. (Perhaps some more abstract encapsulation of a server command and arguments?

I asked this question a while ago, and this was one of the comments:

There (mostly) seems to be a one-to-one mapping of a lot of your functions to IRC command verbs. For example, leave_server -> QUIT, join_channel -> JOIN, send_private_message-> PRIVMSG, etc. Part of me just feels that (as you say) there is just a tad too much repetition in that architecture. (Perhaps some more abstract encapsulation of a server command and arguments?

Notice added Draw attention by Dan Oberlam
Bounty Started worth 50 reputation by Dan Oberlam
added 857 characters in body
Source Link
Dan Oberlam
  • 8k
  • 2
  • 33
  • 74
class InvalidCommandParametersException(Exception):
 """Raised when the parameters to a command are invalid."""
 def __init__(self, command, param_problems):
 """Raise an error that a command's parameters are invalid.
 Parameters
 ----------
 command: Command
 The command type that failed validation.
 param_problems: iterable[string]
 Collection of problems with the parameters
 """
 message = '\n'.join(["Command: {}".format(command.name)] + param_problems)
 super(InvalidCommandParametersException, self).__init__(message)
class ExecutableCommandMixin(object):
 """Mixin to make an enum of commands executable.
 Enables parameterizing a given command and specifying how to 
 validate each parameter, as well as specifying what "executing" the
 command means. Does so by providing decorator functions that will
 register functions as parameter validation or command execution.
 Notes
 -----
 Does not subclass from `enum.Enum` because working around the
 non-extensibility of enums with defined members is pretty inelegant.
 Properties
 ----------
 parameters: CommandParameterSet
 Set of parameters for this command.
 execution: function(*values) -> Object
 Function that takes in the parameters and returns something.
 """
 @property
 def parameters(self):
 return self.__class__._parameters[self]
 _executions = {}
 @property
 def execution(self):
 return self.__class__._executions[self]
 def execute_command(self, *values):
 problems = self._validate_arguments(values)
 if problems:
 raise InvalidCommandParametersException(self, problems)
 return self.execution(*values)
 def register_execution(self, func):
 """Register a function as this command's action.
 Parameters
 ----------
 func: function
 Function to execute for this parameter.
 Returns
 -------
 func: function
 The original function, unchanged.
 """
 self.__class__._executions[self] = func
 return func
 _parameters = defaultdict(CommandParameterSet)
 def register_parameter(self, name, n_th, optional=False, count=1, count_type=None):
 """Decorator to register a function to validate a given parameter.
 Parameters
 ----------
 name: string
 Name of the parameter; used to describe the parameter.
 n_th: indexer
 Which parameter this should be in the parameter list.
 optional: boolean, default=False
 Whether or not the parameter is optional. If it is optional,
 then `None` should be passed for validation.
 count: integer, default=1
 Describes how many instances of this value are allowed. If 
 greater than 1, then `count_type` is required. If 0 or more
 are allowed, then pass `count=0` and
 `count_type=CountTypes.MIN`
 count_type: CountType, default=None
 Required if a `count` is given; describes how to interpret 
 the count (as a max, min, or exact requirement).
 Returns
 -------
 decorator: function -> function
 Wrapper function that wraps its argument function and adds
 it as validation for this parameter
 """
 def decorator(validator):
 """Add the actual validator function for this parameter.
 Parameters
 ----------
 validator: function(value) -> string | None
 Function that takes in a value and returns an error 
 message, or None if it was okay. If there are multiple 
 values allowed (i.e. `count_type != None`) then this is 
 called for each item in the collection, _instead_ of on 
 the entire collection.
 Returns
 -------
 validator: function(value) -> string | None
 The original function, unchanged.
 """
 self.__class__._parameters[self].insert_parameter(
 CommandParameter(name, validator, optional=optional, 
 count=count, count_type=count_type),
 n_th)
 return validator
 return decorator
 def _validate_arguments(self, arguments):
 """Validate the command's arguments.
 Parameters
 ----------
 arguments: collection[Object]
 List of the arguments being passed.
 Returns
 -------
 list[string]
 List of all of the problems with the arguments. Will be an
 empty list if no problems are present.
 """
 errors = list(self.__class__._parameters[self].validate(arguments))
 if not errors or all(err is None for err in errors):
 return []
 return errors

If you need it, my folder structure looks like this (also available on GitHub)GitHub PyIRC Repository URL:

executable_command.py is all of the code besides the sample (IrcCommand) and the tests, plus a handful of imports.

class ExecutableCommandMixin(object):
 """Mixin to make an enum of commands executable.
 Enables parameterizing a given command and specifying how to 
 validate each parameter, as well as specifying what "executing" the
 command means. Does so by providing decorator functions that will
 register functions as parameter validation or command execution.
 Notes
 -----
 Does not subclass from `enum.Enum` because working around the
 non-extensibility of enums with defined members is pretty inelegant.
 Properties
 ----------
 parameters: CommandParameterSet
 Set of parameters for this command.
 execution: function(*values) -> Object
 Function that takes in the parameters and returns something.
 """
 @property
 def parameters(self):
 return self.__class__._parameters[self]
 _executions = {}
 @property
 def execution(self):
 return self.__class__._executions[self]
 def execute_command(self, *values):
 problems = self._validate_arguments(values)
 if problems:
 raise InvalidCommandParametersException(self, problems)
 return self.execution(*values)
 def register_execution(self, func):
 """Register a function as this command's action.
 Parameters
 ----------
 func: function
 Function to execute for this parameter.
 Returns
 -------
 func: function
 The original function, unchanged.
 """
 self.__class__._executions[self] = func
 return func
 _parameters = defaultdict(CommandParameterSet)
 def register_parameter(self, name, n_th, optional=False, count=1, count_type=None):
 """Decorator to register a function to validate a given parameter.
 Parameters
 ----------
 name: string
 Name of the parameter; used to describe the parameter.
 n_th: indexer
 Which parameter this should be in the parameter list.
 optional: boolean, default=False
 Whether or not the parameter is optional. If it is optional,
 then `None` should be passed for validation.
 count: integer, default=1
 Describes how many instances of this value are allowed. If 
 greater than 1, then `count_type` is required. If 0 or more
 are allowed, then pass `count=0` and
 `count_type=CountTypes.MIN`
 count_type: CountType, default=None
 Required if a `count` is given; describes how to interpret 
 the count (as a max, min, or exact requirement).
 Returns
 -------
 decorator: function -> function
 Wrapper function that wraps its argument function and adds
 it as validation for this parameter
 """
 def decorator(validator):
 """Add the actual validator function for this parameter.
 Parameters
 ----------
 validator: function(value) -> string | None
 Function that takes in a value and returns an error 
 message, or None if it was okay. If there are multiple 
 values allowed (i.e. `count_type != None`) then this is 
 called for each item in the collection, _instead_ of on 
 the entire collection.
 Returns
 -------
 validator: function(value) -> string | None
 The original function, unchanged.
 """
 self.__class__._parameters[self].insert_parameter(
 CommandParameter(name, validator, optional=optional, 
 count=count, count_type=count_type),
 n_th)
 return validator
 return decorator
 def _validate_arguments(self, arguments):
 """Validate the command's arguments.
 Parameters
 ----------
 arguments: collection[Object]
 List of the arguments being passed.
 Returns
 -------
 list[string]
 List of all of the problems with the arguments. Will be an
 empty list if no problems are present.
 """
 errors = list(self.__class__._parameters[self].validate(arguments))
 if not errors or all(err is None for err in errors):
 return []
 return errors

If you need it, my folder structure looks like this:

class InvalidCommandParametersException(Exception):
 """Raised when the parameters to a command are invalid."""
 def __init__(self, command, param_problems):
 """Raise an error that a command's parameters are invalid.
 Parameters
 ----------
 command: Command
 The command type that failed validation.
 param_problems: iterable[string]
 Collection of problems with the parameters
 """
 message = '\n'.join(["Command: {}".format(command.name)] + param_problems)
 super(InvalidCommandParametersException, self).__init__(message)
class ExecutableCommandMixin(object):
 """Mixin to make an enum of commands executable.
 Enables parameterizing a given command and specifying how to 
 validate each parameter, as well as specifying what "executing" the
 command means. Does so by providing decorator functions that will
 register functions as parameter validation or command execution.
 Notes
 -----
 Does not subclass from `enum.Enum` because working around the
 non-extensibility of enums with defined members is pretty inelegant.
 Properties
 ----------
 parameters: CommandParameterSet
 Set of parameters for this command.
 execution: function(*values) -> Object
 Function that takes in the parameters and returns something.
 """
 @property
 def parameters(self):
 return self.__class__._parameters[self]
 _executions = {}
 @property
 def execution(self):
 return self.__class__._executions[self]
 def execute_command(self, *values):
 problems = self._validate_arguments(values)
 if problems:
 raise InvalidCommandParametersException(self, problems)
 return self.execution(*values)
 def register_execution(self, func):
 """Register a function as this command's action.
 Parameters
 ----------
 func: function
 Function to execute for this parameter.
 Returns
 -------
 func: function
 The original function, unchanged.
 """
 self.__class__._executions[self] = func
 return func
 _parameters = defaultdict(CommandParameterSet)
 def register_parameter(self, name, n_th, optional=False, count=1, count_type=None):
 """Decorator to register a function to validate a given parameter.
 Parameters
 ----------
 name: string
 Name of the parameter; used to describe the parameter.
 n_th: indexer
 Which parameter this should be in the parameter list.
 optional: boolean, default=False
 Whether or not the parameter is optional. If it is optional,
 then `None` should be passed for validation.
 count: integer, default=1
 Describes how many instances of this value are allowed. If 
 greater than 1, then `count_type` is required. If 0 or more
 are allowed, then pass `count=0` and
 `count_type=CountTypes.MIN`
 count_type: CountType, default=None
 Required if a `count` is given; describes how to interpret 
 the count (as a max, min, or exact requirement).
 Returns
 -------
 decorator: function -> function
 Wrapper function that wraps its argument function and adds
 it as validation for this parameter
 """
 def decorator(validator):
 """Add the actual validator function for this parameter.
 Parameters
 ----------
 validator: function(value) -> string | None
 Function that takes in a value and returns an error 
 message, or None if it was okay. If there are multiple 
 values allowed (i.e. `count_type != None`) then this is 
 called for each item in the collection, _instead_ of on 
 the entire collection.
 Returns
 -------
 validator: function(value) -> string | None
 The original function, unchanged.
 """
 self.__class__._parameters[self].insert_parameter(
 CommandParameter(name, validator, optional=optional, 
 count=count, count_type=count_type),
 n_th)
 return validator
 return decorator
 def _validate_arguments(self, arguments):
 """Validate the command's arguments.
 Parameters
 ----------
 arguments: collection[Object]
 List of the arguments being passed.
 Returns
 -------
 list[string]
 List of all of the problems with the arguments. Will be an
 empty list if no problems are present.
 """
 errors = list(self.__class__._parameters[self].validate(arguments))
 if not errors or all(err is None for err in errors):
 return []
 return errors

If you need it, my folder structure looks like this (also available on GitHub)GitHub PyIRC Repository URL:

executable_command.py is all of the code besides the sample (IrcCommand) and the tests, plus a handful of imports.

Tweeted twitter.com/StackCodeReview/status/966249451181494273
Source Link
Dan Oberlam
  • 8k
  • 2
  • 33
  • 74
Loading
lang-py

AltStyle によって変換されたページ (->オリジナル) /