commands¶
This page contains tutorials about thecommands package.
Typed commands¶
The typed commands API allows you to create robust server, say and clientcommands that are protected against users entering invalid data. If a user hasentered invalid data, he will be notified with an understandable errormessage and your callback won’t get called. Invalid data can be an invalidvalue for an argument or simply too many/less arguments. A quick example willmake this more clear.
fromcommands.typedimportTypedServerCommand@TypedServerCommand('multiply')defon_test(command_info,x:int,y:int):"""Multiply x and y and print the result."""print('Result:',x*y)
Example input/output:
multiplyNot enough arguments: multiply <x:int> <y:int>multiply 10 5Result: 50multiply 10 5 3Too many arguments: multiply <x:int> <y:int>multiply 10 a'a' is an invalid value for 'y:int'.sp help multiplymultiply <x:int> <y:int> Multiply x and y and print the result.
As you can see the API does all the error checking for you.
There are 3 typed command decorator classes in total.
commands.typed.TypedServerCommandto create server commands.commands.typed.TypedSayCommandto create say commands.commands.typed.TypedClientCommandto create client commands.
These 3 classes share the same constructor(commands.typed._TypedCommand.__init__()). The only difference is thatcommands.typed.TypedServerCommand ignores thepermission andfail_callback arguments. Another thing to remember is which attributes ofthecommands.typed.CommandInfo instance are set in each of thesedecorator types.
commands.typed.TypedServerCommandonly sets thecommandattribute.commands.typed.TypedSayCommandsets the attributescommandandindex.commands.typed.TypedClientCommandsets all of the 3 attributes (command,indexandteam_only).
Creating optional arguments¶
You can create optional arguments by assigning a default value to an argument.
fromcommands.typedimportTypedServerCommand@TypedServerCommand('add')defon_add(command_info,x:int,y:int=10):"""Add x and y and print the result."""print('Result:',x+y)
Example input/output:
addNot enough arguments: add <x:int> [y:int=10]add 5Result: 15add 5 3Result: 8
Creating variadic commands¶
You can create variadic commands by making the decorated function variadic using the asterisk (*).
@TypedServerCommand('add')defon_add(command_info,*args:int):"""Add all passed arguments and print the result."""print('Result:',sum(args))
Example input/output:
addResult: 0add 3Result: 3add 3 5Result: 8add 3 5 2Result: 10add 3 a 4'a' is an invalid value for 'args:int'.sp help addadd [*args:int] Add all passed arguments and print the result.
If you create variadic commands, you are not restricted to only use variadicarguments. You can still use optional and required arguments.
fromcommands.typedimportTypedServerCommand@TypedServerCommand('add')defon_add(command_info,x,y=None,*args:int):"""Add all passed arguments and print the result."""print('Result:',(x,y),sum(args))
Example input/output:
addNot enough arguments: add <x> [y=None] [*args:int]add aResult: ('a', None) 0add a bResult: ('a', 'b') 0add a b 5Result: ('a', 'b') 5add a b 5 10Result: ('a', 'b') 15Creating sub-commands¶
You can easily create sub-commands. All you need to do is passing an iterable as the first argument.
fromcommands.typedimportTypedServerCommand@TypedServerCommand(['test','a'])defon_test(command_info,x):print('a',x)@TypedServerCommand(['test','b'])defon_test(command_info,x,y):print('b',(x,y))
Example input/output:
testA sub-command is required: test a <x> test b <x> <y>test aNot enough arguments: test a <x>test b "Hello, world!"Not enough arguments: test b <x> <y>test b "Hello, world!" blab ('Hello, world!', 'bla')There is no limit on the number or depth of the sub-commands. That means youcan create an abitrary number of sub-commands and these sub-commands can havesub-commands as well.
See also
Thespauth sub-command has a high sub-command depth.
Adding value validation¶
As you might have noticed in the very first example, it’s quite easy to addvalue validation to your command. All you need to do is adding a colon (:) anda callable object behind your argument. When the command gets triggered, theAPI will call the given object with the passed value. If any exception wasraised during this call, the validation is considered as a failure. Thus, anerror message is printed to the user. If the call has been finishedsuccessfully, its return value will be passed to your callback.
fromcommands.typedimportTypedServerCommand@TypedServerCommand('test')defon_test(command_info,x:float):print('Got:',x)
Example input/output:
testNot enough arguments: test <x:float>test a'a' is an invalid value for 'x:float'.test 3Got: 3.0test 3.3Got: 3.3
Using custom error messages¶
If you are not happy with the automatic error message, you can simply raiseyour owncommands.typed.ValidationError exception in your validator.
fromcommands.typedimportTypedServerCommandfromcommands.typedimportValidationErrordefmy_float(value):try:returnfloat(value)except:raiseValidationError('"{}" is not a floating value.'.format(value))@TypedServerCommand('test')defon_test(command_info,x:my_float):print('Got:',x)
Example input/output:
testNot enough arguments: test <x:my_float>test a'a' is not a floating value.
Creating custom validators¶
Just like we created custom error messages, you can also use the sameprinciple to create a validator that doesn’t use the built-in types likeint orfloat.
fromcommands.typedimportTypedServerCommanddefpositive_int(value):value=int(value)ifvalue<0:raiseExceptionreturnvalue@TypedServerCommand('test')defon_test(command_info,x:positive_int):print('Got:',x)
Example input/output:
testNot enough arguments: test <x:positive_int>test -1'-1' is an invalid value for 'x:positive_int'.test a'a' is an invalid value for 'x:positive_int'.test 0Got: 0
Source.Python has one custom validator built-in to allow iterating overplayers easily (commands.typed.filter_str()).
fromcommands.typedimportTypedServerCommandfromcommands.typedimportfilter_str@TypedServerCommand('test')defon_test(command_info,players:filter_str):forplayerinplayers:print('{player.name} in team{player.team}. Dead?:{player.dead}'.format(player=player))
Example input/output:
status# userid name uniqueid# 2 "Xavier" BOT# 3 "Shawn" BOT# 4 "Rick" BOT# 5 "Ian" BOT# 6 "Ayuto" [U:1:39094154]test allXavier in team 3. Dead?: FalseShawn in team 2. Dead?: TrueRick in team 3. Dead?: FalseIan in team 2. Dead?: TrueAyuto in team 2. Dead?: Falsetest humanAyuto in team 2. Dead?: Falsetest botXavier in team 3. Dead?: FalseShawn in team 2. Dead?: FalseRick in team 3. Dead?: FalseIan in team 2. Dead?: Falsetest tShawn in team 2. Dead?: FalseIan in team 2. Dead?: FalseAyuto in team 2. Dead?: Falsetest ctXavier in team 3. Dead?: FalseRick in team 3. Dead?: Falsetest deadAyuto in team 2. Dead?: Truetest aliveXavier in team 3. Dead?: FalseShawn in team 2. Dead?: FalseRick in team 3. Dead?: FalseIan in team 2. Dead?: Falsetest 2+6Xavier in team 3. Dead?: FalseAyuto in team 2. Dead?: Falsetest all-ctShawn in team 2. Dead?: FalseIan in team 2. Dead?: FalseAyuto in team 2. Dead?: Falsetest bot-2-4Shawn in team 2. Dead?: TrueIan in team 2. Dead?: Truetest ct+tXavier in team 3. Dead?: FalseShawn in team 2. Dead?: TrueRick in team 3. Dead?: FalseIan in team 2. Dead?: TrueAyuto in team 2. Dead?: Falsetest asd'asd' is an invalid value for 'players:filter_str'.
This custom validator is also cappable to parse complex expressions that useparentheses and multiple plus and minus operators. The plus sign stands forthe set operationUnion and the minus sign forComplement.
Using permissions¶
It’s quite common that some commands should only be executable by specificplayers. Thus, there is thepermission parameter in the constructor of thetyped command decorators.
fromcommands.typedimportTypedClientCommand@TypedClientCommand('test','my_plugin.test')defon_test(command_info):print('Executed!')
Example input/output:
testUnknown command: testYou are not authorized to use this command.Required permission: my_plugin.test
In this case the player requires the permissionmy_plugin.test to executethe command.
Note
The permission is checked before arguments are validated.
You can also auto-generate the permission string by setting the second parameter toTrue.
fromcommands.typedimportTypedClientCommand@TypedClientCommand('test',True)defon_test(command_info):print('Executed!')
Example input/output:
testUnknown command: testYou are not authorized to use this command.Required permission: test
Note
You should only use this feature if it generates a permission string thatdoesn’t conflict with our guideline on permission names. That means youshould only use it if you are using sub-commands. E.g. for the sub-commandxykick it would generate the permission stringxy.kick.
You can also add a callback that gets called when an unauthorized player triesto execute the command.
fromcommands.typedimportTypedClientCommanddefon_test_failed(command_info,args):print('Not authorized.',args)@TypedClientCommand('test','my_plugin.test',on_test_failed)defon_test(command_info):print('Executed!')
Example input/output:
testUnknown command: testNot authorized. []test 1 2Unknown command: testNot authorized. ['1', '2']
