Package: command-handler
Command HandlerCommand Handler is a library forFlask frameworkwhich provides:API method for posting new commands,tools for easy command handlers management.Installationpipinstallcommand_handlerUsageTo usecommand_handlerimportCommandHandlerobject and call it with yourflaskapplication passed.fromcommand_handlerimportCommandHandlerfromflaskimportFlaskapp=Flask(__name__)ch=CommandHandler(app)This will add a new endpoint to your API:/commandwhich is specified inCommand inputsection.Handlers can be added byaddHandlermethod of the returned object as described inDefining handlerssection.ConfigurationThe wayCommandHandleris designed allows to pass config parameters to the initializer.Command input endpoint URLIt is possible to prefix default/commandroute with any string accepted by Flask's routing
by definingrulePrefixparameter.Following code sets URL to/foo/bar/command:ch=CommandHandler(app,rulePrefix="/foo/bar")It is also possible to change default/commandroute suffix by definingruleSuffix.Following code sets URL to/baz:ch=CommandHandler(app,ruleSuffix="baz")Request validatorsIt is possible to define command request validators.CommandHandlerby default setscommandandjsonvalidators and it is not possible to remove them.Validators can be defined by settingvalidatorsparameter which accepts list of strings.ch=CommandHandler(app,validators=["command","json"])List of possible validators can be found inValidatorssectionCustom validatorsTo register custom validator pass it to thecommand_handler.request.validator.ValidatorFactory.addAssertmethod.
It has to accept one positional parameter which contains request passed to the view defined byCommandHandler.If request is invalid it is needed to raisecommand_handler.request.validator.exceptions.AssertionFailedException.
First parameter of its constructor will be send to the response's body and second will be send as response code.fromcommand_handler.request.validator.ValidatorFactoryfromcommand_handler.request.validator.exceptionsimportAssertionFailedExceptiondeffooValidator(request):raiseAssertionFailedException("Something went wrong",418)ValidatorFactory.addAssert("foo",fooValidator)Command inputBy default route to the command input endpoint is/command.
It can be configured as described inConfigurationsection.Command input endpoint accepts onlyPOSTrequests
with the content matching followingJSON schema:{"type":"object","properties":{"command":{"type":"string","description":"Name of the sent command","examples":["foo","foo.bar","foo.bar.baz"]},"payload":{"type":"object","description":"Payload of the sent command",}},"required":["command","payload"]}It is also required to sendContent-Typeheader which value matches^application/.*json$regular expression.Additional validators can be defined as specified inValidatorssection.Defining handlersCommand handlers can be defined by callingaddHandlermethod ofCommandHandlerinstance.ch=CommandHandler(app)ch.addHandler(lambdapayload,command:None,"foo.*",{})The following parameters are accepted byaddHandlermethod:handlerInvokable object which is called when matching command has been posted.handlerfunction has to accept two parameters:command's payload,command's name.commandString which command's name is matched against.Command matching is described inCommand matchingsection.schemaJSON schemaobject used to validate command's payload.transformer = Nonetransformerparameter can be used to transform command's payload before passing it to the handler.It gets command's payload as only parameter and returns object passed to the handler as payload.postProcessor = NoneInvokable object which is called when handler processing is done.It has to accept same parameters ashandler.
Iftransformerwas passed to theaddHandlercall it has additional named parameterorigPayloadwhich contains original payload passed to the invoker.If inner handler returns any value, it is passed asinnerResultnamed parameter.Raising exceptionsThere is no possibility of changing response of acommand input endpointexcept raising an exception.Whencommand_handler.request.exceptions.InvalidRequestExceptionis raisedcommand input endpointreturns response with HTTP code
specified duringInvalidRequestEndpointcreation (by default it is400: Bad Request)
and message which matches followingJSON schema:{"type":"object","properties":{"type":"string","description":"Message passed to the `InvalidRequestException` during creation","default":""},"required":["error"]}It is preferred to use4xxcodes withInvalidRequestException.Following snippet will respond with418: I'm a teapotHTTP code and{"error": "I'm a teapot handler"}whenfoo.barcommand is sent:deffooHandler(payload,command):raiseInvalidRequestException("I'm a teapot handler",code=418)ch=CommandHandler(app)ch.addHandler(fooHandler,"foo.bar",{})When any other exception is raisedcommand input endpointreturns response with HTTP code500: Internal server error.Command matchingEach command name sent tocommand input endpointhas to be a list of words delimited by dots.Command name passed toaddHandlermethod must also be
in the same form. There are two special words for command name assigned to the handler:*can substitute exactly one word,#can substitute zero or more words.Command handler invoker will call handler which matches command name of handler
passed toaddHandlermethod. If there is no matching
handlercommand input endpointwill respond with500: Internal server error.If there is more than one match the handler which was added first will be called.
However handler's registry is not allowing to add handler which is assigned to the already covered name.
It means that order of adding handler matters.For example following snippet will work properly but adding those handlers in reverse order will cause
in raising an exception.ch.addHandler(lambdapayload,command:None,"foo.bar",{})ch.addHandler(lambdapayload,command:None,"foo.#",{})ValidatorsCommand Handler contains following predefined request validators:commandValidator which verifies if:request containspayloadandcommandfields,commandfield value is a string,payloadfield value is a dictionary,value ofcommandfield is matchable to defined handlers,value ofpayloadfield matches schema which had been assigned to the handler,jsonValidator which verifies if:request containsContent-Typeheader with value matching to^application/.*json$regex,request content is json-parseable.privateIpValidator which verifies if remote IP address of the request
isprivate.