Python plugins can be installed and invoked via HTTP requests. A plugin is a regular Python function registered via pyvgx.system.AddPlugin(). A unique service URI is automatically created for the registered function. It is executed by sending a HTTP request to that URI.
1. Hello World
It is easy to create a new service as illustrated by this minimal implementation example.
from pyvgx import *
system.Initialize( "hello", http=9000 )
def hello( request:PluginRequest, message:str ):
return "Hello, you said '{}'".format( message )
system.AddPlugin( hello )
curl "http://127.0.0.1:9000/vgx/plugin/hello?message=hi"
{
"status": "OK",
"response": "Hello, you said 'hi'",
"level": 0,
"partitions": null,
"exec_ms": 0.061
}
2. Plugin Functions
Plugins are regular Python functions. A plugin function can serve one of three roles (Figure 1, “Plugin Functions”):
- pre
-
Request pre-processor executed in a VGX Dispatch instance before the request is forwarded to the dispatch back-end matrix
- post
-
Response post-processor executed in a VGX Dispatch instance after the aggregated response has been created from dispatch back-end matrix responses
- engine
-
Main request processor executed in a VGX Engine
2.1. Request Flow
A request is generally processed as shown below. When a dispatcher is used both pre and post-processors are optional. Any number of dispatcher layers is allowed.
2.2. Plugin Function Arguments
HTTP request parameters with matching plugin function argument names are mapped directly to those arguments. Function arguments without default values require matching HTTP parameters to be present in the request. Function arguments with type annotation param: type require compatible values to be passed in the HTTP request.
See Parameter Annotation for supported annotation types.
Any number of plugin function arguments can be declared for pre and engine plugins. Some argument names are reserved as described in Section 2.3.1, “Engine and Pre-processor Signature”. One such argument is request which exposes all HTTP request parameters as dict attribute request.params.
Arguments for post plugins are described in Section 2.3.2, “Post-processor Signature”.
http://127.0.0.1:9000/vgx/plugin/test?num=123&txt=456&extra=[7,8,9]
def test( request, num:int, txt):
return {
"num": "type:{} value:{}".format(type(num),num),
"txt": "type:{} value:{}".format(type(txt),txt),
"all": request.params
}
{
"status": "OK",
"response": {
"num": "type:<class 'int'> value:123",
"txt": "type:<class 'str'> value:456",
"all": {
"num": 123,
"txt": "456",
"extra": "[7,8,9]"
}
},
"level": 0,
"partitions": null,
"exec_ms": 0.096
}
2.3. Plugin Function Signatures
Plugin functions for engine and pre-processors may include any number of user defined arguments, optionally annotated, and with default values where appropriate. Some argument names are reserved for special use. Plugin functions for post-processors take one or two reserved arguments.
2.3.1. Engine and Pre-processor Signature
Function signatures for pre and engine plugins are the same and include five reserved keyword arguments which automatically receive their values as described in Section 2.3.3, “Engine and Pre-processor Reserved Keyword Arguments”.
def func( request[, graph[, method[, headers[, content, ...]]]] ):
...
2.3.2. Post-processor Signature
Function signatures for post plugins have two reserved keyword arguments which automatically receive their values as described in Section 2.3.4, “Post Reserved Keyword Arguments”.
The function takes no user defined arguments.
def func( response[, graph] ):
...
2.3.3. Engine and Pre-processor Reserved Keyword Arguments
| Argument | Type | Required? | Description |
|---|---|---|---|
Always required |
Contains all request parameters, general request information, and meta data |
||
Required when system.AddPlugin() is called with graph argument |
Graph instance passed to system.AddPlugin() when registering the plugin function. If registered without a graph this function argument is not required and can be freely used for other purposes. |
||
str |
Optional |
"GET" or "POST" |
|
bytes (default) |
Optional |
HTTP request headers are received according to declared type: bytes: Raw bytes, as received str: Raw UTF-8 header bytes, decoded to string dict: Key-value pairs as {field:value} list: Key-value pairs as [(field,value), …] |
|
bytes (default) |
Optional |
Receives HTTP content in POST requests according to declared type: bytes: Raw bytes, as received str: Raw UTF-8 content bytes, decoded to string json: Python object decoded from raw JSON data |
2.3.4. Post Reserved Keyword Arguments
| Argument | Type | Required? | Description |
|---|---|---|---|
Always required |
Contains response payload, general response information and meta data |
||
Required when system.AddPlugin() is called with graph argument |
Graph instance passed to system.AddPlugin() when registering the plugin function. If registered without a graph this function argument is not used. |
2.4. Plugin Function Return Values
The types of objects returned from plugin function depend on plugin type and context. Two general rules apply:
- Dispatcher Forward
-
If a pre-processor request is to be forwarded to a back-end matrix the returned object type must be PluginRequest
- Dispatcher Merge
-
If a response is to be merged with other responses in a higher dispatcher layer the returned object type must be PluginResponse
2.4.1. Return value from pre-processor plugin
Pre-processors can return a PluginRequest object, a PluginResponse object, or a regular Python object. The plugin controls dispatcher behavior by the type of object returned.
- pre: return PluginRequest
-
This can be the original object passed to the plugin function’s request argument or a new instance created by the function. When this object type is returned VGX Server will forward the request to the VGX Dispatch back-end matrix.
- pre: return PluginResponse
-
The plugin pre-processor may create and return a response object. This prevents the request from being dispatched to the back-end matrix, resulting in early termination of the request.
- pre: return any other Python object
-
A plugin running in a topmost VGX Dispatch instance may return any serializable Python object. This prevents the request from being dispatched to the back-end matrix, resulting in early termination of the request. Only top level dispatchers are allowed to return a regular Python object. Doing so in a lower level dispatcher will generate a partial merge error in the dispatcher above.
def pre( request:PluginRequest, ... ):
# PluginRequest: Forward to back-end
if continue_dispatch():
return request
# PluginResponse: Early termination at lower level dispatch
elif not request.toplevel:
r = PluginResponse( sortby=S_VAL|S_ASC )
r.Append( 1, "First" )
r.Append( 2, "Second" )
return r
# Any object: Early termination at top level dispatch
else:
return [(1, "First"), (2, "Second")]
2.4.2. Return value from engine processor plugin
Main request processors must return a PluginResponse object if part of a dispatcher back-end matrix. A stand-alone engine can return any Python object.
- engine: return PluginResponse
-
The main request processor must create and return a new object of this type if a higher level dispatcher will merge this response with responses from other engine instances
- engine: return any other Python object
-
The main request processor may return any Python object if no higher level merge will take place
def engine( request:PluginRequest, ... ):
# PluginResponse: when result will be merged at higher level
if is_part_of_backend():
r = PluginResponse( sortby=S_VAL|S_ASC )
r.Append( 1, "First" )
r.Append( 2, "Second" )
return r
# Any object: when this engine is standalone instance
else:
return [(1, "First"), (2, "Second")]
2.4.3. Return value from post-processor plugin
Post-processors can return a PluginResponse object or a regular Python object.
- post: return PluginResponse
-
The plugin post-processor may modify the original response object or create and return a new response object.
- post: return any other Python object
-
A plugin running in a topmost VGX Dispatch instance may return any serializable Python object. Only top level dispatchers are allowed to return a regular Python object. Doing so in a lower level dispatcher will generate a partial merge error in the dispatcher above.
def post( response:PluginResponse ):
# PluginResponse: when result will be merged at higher level
if not response.toplevel:
return response
# Any object: this is the top dispatcher
else:
return [(1, "First"), (2, "Second")]
3. pyvgx.system Plugin Management
3.1. AddPlugin
pyvgx.system.AddPlugin( [plugin[, name[, graph[, engine[, pre[, post]]]]]] )-
plugin: Register Python function plugin as a new HTTP endpoint. The plugin is published at its unique service URL:
http://host:_port_/vgx/plugin/servicename?parametersHTTP parameters are passed to the plugin function as described in [request_parameters].
name: Specify servicename with this optional argument. By default servicename is the function name (i.e. plugin.__name__).
graph: A plugin may be bound to a specific graph instance by supplying it through the graph argument. Such plugins are required to have a graph argument in their function signature.
engine: Alias for plugin, useful for making plugin function role more explicit in multi-instance deployments.
pre and post: In dispatcher mode create a new HTTP endpoint with Python functions pre and/or post acting as request pre-processor and response post-processor. Endpoint must be specified with name in this configuration, and pre/post cannot be combined with plugin or engine.
3.3. GetPlugins
pyvgx.system.GetPlugins()-
Return a list of registered plugins. Each entry in the list is a dictionary containing the following information:
-
path: http service path for invoking plugin function. -
description: plugin function’s docstring. -
parameters: dict mapping plugin function’s parameter names to their type annotations. -
bound_graph: graph instance, if plugin function takesgraphparameter.[ { "path": <URI_path>, "description": <plugin_docstring>, "parameters": { <param1>: <annotation>, <param2>: <annotation>, ... }, "bound_graph": <graph_instance_repr_or_None> }, ... ]
-
3.4. GetBuiltins
pyvgx.system.GetBuiltins()-
Return a list of built-in plugins. The return format is the same as for GetPlugins().
4. pyvgx.PluginRequest
PluginRequest objects are passed in the request argument of pre and engine plugin functions. When a pre plugin returns an object of this type the request is forwarded to the VGX Dispatch instance back-end matrix.
4.1. PluginRequest Class
class pyvgx.PluginRequest( plugin[, params[, headers[, content[, partial]]]] )-
Create a new PluginRequest instance.
The request target path will be
/vgx/plugin/pluginAssign request parameters and headers via dicts params and headers
Assign request payload data via content (becomes POST request)
Target specific partition in back-end matrix with non-negative partial
4.2. PluginRequest Methods
4.3. PluginRequest Attributes
| Attribute | Type | Access | Description | ||
|---|---|---|---|---|---|
int |
r/w |
Non-negative index of preferred replica (row) for this request when forwarded to VGX Dispatch back-end matrix. This attribute is writable and should be set in a pre-processor to specify replica affinity. The selected replica will be used regardless of its current running execution cost, but if unavailable (due to channel exhaustion or other reasons) another available replica will be used instead. |
|||
int |
readonly |
Server main port |
|||
int |
readonly |
Channel id (0 an up) for current socket connection from parent dispatcher which issued this request |
|||
bytes |
r/w |
Raw content data as received in POST request. Type is bytes regardless of annotation used for content argument in plugin function if declared. |
|||
int |
readonly |
Maximum number of channels to this VGX Server instance from parent dispatcher which issued this request |
|||
int |
readonly |
Identifier number 0-31 of executor assigned to handle this request |
|||
str or None |
r/w |
Assignable string of up to eight characters (bytes) which is carried forward to response.flag in post-processor. This allows up to 64 bits of user-defined meta data for a request to be communicated from a pre-processor to a post-processor. |
|||
bool |
readonly |
Return True if server request may be dispatched to a back-end matrix, False otherwise. |
|||
bytes |
readonly |
HTTP request headers object. Always a dict for POST requests. Defaults to bytes for GET requests. If plugin function declares a headers argument then for GET requests headers argument and request.headers attribute always share the same underlying object. For POST requests the underlying object is shared if annotated as dict. |
|||
int |
height |
Number of replicas in matrix of parent dispatcher which issued this request |
|||
tuple |
readonly |
Matrix identity parameters for this VGX Server instance as viewed from dispatcher which issued the request: (defined, width, height, depth, partition, replica, channel, primary) The first parameter defined is 1 if request came from a dispatcher instance, 0 otherwise. |
|||
object or None |
r/w |
Assignable object to be carried forward to response.local in post-processor. This allows any Python object created in a pre-processor to be picked up later in a post-processor for the same request. |
|||
str |
readonly |
"GET" or "POST" |
|||
dict |
readonly |
All request parameters, i.e. all key-value pairs after the first "?" in URL. The dictionary can be modified, but a new dictionary cannot be assigned. |
|||
int or None |
r/w |
Non-negative index of target partition for this request when forwarded to VGX Dispatch back-end matrix. This attribute is writable and should be set in a pre-processor if the request is to target a specific partition in the back-end matrix. Value of HTTP header Effective target partition index is computed as request.partial MOD P, where P is the number of partitions managed by this dispatcher.
|
|||
int |
readonly |
This VGX Server instance’s partition id (0 and up) in matrix of parent dispatcher which issued this request |
|||
str |
readonly |
URL path for this request "/vgx/plugin/<name>?<params>" |
|||
int |
readonly |
Port used for this request |
|||
int |
r/w |
1 if this VGX Server instance is the primary replica in matrix of parent dispatcher which issued this request, or if request did not come from a dispatcher. Otherwise 0. |
|||
int |
readonly |
This VGX Server instance’s replica id (0 and up) in matrix of parent dispatcher which issued this request |
|||
str |
r/w |
Digest of request parameters |
|||
int |
readonly |
Unique serial number assigned to this request instance |
|||
int |
readonly |
Timestamp of first request byte handled by server loop, in nanoseconds since 1970, as measured by server loop thread. (Do not compare with timestamps generated by other threads.) |
|||
bool |
readonly |
True if VGX Server instance is topmost in a dispatcher matrix hierarchy, i.e. the first instance receiving a request by an external client. False if VGX Server is part of another dispatcher’s back-end matrix.
|
|||
int |
readonly |
Number of partitions in matrix of parent dispatcher which issued this request |
5. pyvgx.PluginResponse
All plugin processors in VGX Server instances part of a back-end matrix must return a PluginResponse object. If any other object is returned the parent dispatcher will raise an exception. Dispatchers only know how to merge and post-process objects of type PluginResponse.
Processors running in a toplevel VGX Server may return any Python object, as long as it is serializable according to the request’s accept header. PluginResponse implements a default JSON serializer.
PluginResponse is a container whose primary purposes are:
-
Encapsulate a list of (optionally sorted) items
-
Specify sort order and key type
-
Manage numeric aggregation fields
-
Expose meta data
Plugin pre processor functions may create and return a PluginResponse instance to terminate the request early.
Plugin engine processor functions create and return a new PluginResponse instance.
Plugin post processor functions receive a PluginResponse object in the response parameter. The object may be inspected and modified before it is returned. A new PluginResponse instance may also be created and returned instead, replacing the original response with a new one.
5.1. PluginResponse Class
class pyvgx.PluginResponse( [maxhits[, sortby[, keytype]]] )-
Create a new PluginResponse instance.
Set maxhits to a positive integer to limit the number of result entries that can be added. By default maxhits is -1 (unlimited). It is also possible to change this value later via the maxhits attribute.
Define sort order and key type using sortby parameter. Valid sortby constants are the same as those used in graph queries. By default sortby is
S_NONE.Explicitly define the keytype as int, float, bytes or str. Must be comparable with sortby if provided.
A dispatcher automatically creates its own internal PluginResponse instance for each request. Partial responses arriving from the back-end matrix carry result items along with maxhits and sortby parameters. The dispatcher response object inherits these parameters by picking the greatest maxhits among all partial results and sortby from the lowest partition’s response with non-zero hits. As a best practice, configure all PluginResponse instances with identical sortby, keytype and maxhits to avoid confusion.
5.2. PluginResponse Methods
Append( [sortkey,] item )-
Add a result item to the response object.
Optional first argument sortkey must be int, float, str or bytes. Any other object type will be ignored. Note that arguments are positional (not keywords). If only a single argument is given it is interpreted as item.
Calls to
Append()must provide a sortkey compatible with the response object’s sortby and keytype configuration, otherwisepyvgx.ResponseErroris raised. A compatible sortkey is one with the expected type and a value consistent with sort direction.As a shorthand for adding items with a sequence number as their sortkey
Append()may be called with item only, provided sortby direction isS_ASC. In this case every nth call toAppend()without sortkey will pass the integer n as sortkey, where n=1 for the first call.If the response object’s sortby is undefined (
S_NONE) it is automatically determined from sortkey whenAppend()is first called. sortby is set toS_VAL|S_ASCwhen sortkey type is int or float, andS_ID|S_ASCwhen sortkey type is str or bytes.Attempting to call
Append()more times than the response object’s maxhits limit will raisepyvgx.ResponseError. If maxhits is -1 (unlimited)Append()can be called any number of times. Deserialize( data )-
Deserialize data bytes to a new PluginResponse object. This method is provided for internal testing and debugging.
Resubmittable()-
Return a PluginRequest object which, when returned from a plugin post processor function, will resubmit the request to the back-end matrix.
Serialize()-
Serialize response object to bytes. This method is provided for internal testing and debugging.
ToJSON()-
Serialize response object to JSON string. This method is provided for internal testing and debugging. When a toplevel dispatcher returns a PluginResponse object it is rendered using this method.
5.3. PluginResponse Attributes
| Attribute | Type | Access | Description | ||
|---|---|---|---|---|---|
int |
readonly |
Server main port |
|||
int |
readonly |
||||
int |
readonly |
||||
list |
readonly |
List of tuples [(sortkey, item), …] stored in the response object resulting from calls to Append().
|
|||
int |
readonly |
Identifier number 0-31 of executor assigned to process this response |
|||
float |
r/w |
General purpose float aggregator. Partial response f2 values are summed into dispatcher’s merged response f2 value. |
|||
float |
r/w |
General purpose float aggregator. Partial response f3 values are summed into dispatcher’s merged response f3 value. |
|||
str or None |
readonly |
Up to 64 bits of information communicated from pre-processor’s request.flag, in the form of a string containing up to eight characters (bytes). |
|||
bool |
readonly |
||||
int |
readonly |
||||
int |
r/w |
Freely assignable non-negative integer indicating the maximum number of result entries that could have been returned. By default it is set to the number of times Append() has been called. |
|||
int |
r/w |
General purpose integer aggregator. Partial response i0 values are summed into dispatcher’s merged response i0 value. |
|||
int |
r/w |
General purpose integer aggregator. Partial response i1 values are summed into dispatcher’s merged response i1 value. |
|||
tuple |
readonly |
||||
type |
readonly |
Type of sortkey (int, float, str, bytes), or None if undefined |
|||
int |
readonly |
Number of dispatcher levels this VGX Server instance is removed from the bottommost instance in the back-end matrix that produced a response. |
|||
int |
readonly |
Number of VGX Server instances at the immediate lower level in dispatcher’s back-end matrix. |
|||
object or None |
r/w |
Object carried forward from request.local in pre-processor. This allows any Python object created in a pre-processor to be picked up later in a post-processor for the same request. |
|||
int |
r/w |
Maximum number of entries that can be added to a response. Default is -1 (unlimited). |
|||
str or None |
r/w |
For internal use and debugging purposes. Can be used in a top level dispatcher to render a message string in the final response.
|
|||
int |
r/w |
Number of VGX Server instances in the dispatcher back-end matrix that contributed to this response. For multi-level dispatch hierarchies this is the deep aggregation of all leaf instances reached. |
|||
int |
readonly |
||||
int |
readonly |
Port used for this request |
|||
int |
readonly |
||||
str |
readonly |
"GET" or "POST" |
|||
str |
readonly |
||||
int |
readonly |
Number of times request was resubmitted to backend matrix |
|||
str or None |
readonly |
See request.signature. The value of response.signature equals request.signature as it was at completion of pre-processor. This value is None if there is no pre-processor, or request.signature was not accessed (or deleted after access) in the pre-processor. |
|||
int |
readonly |
||||
int |
readonly |
Sort order and key type in effect for this response |
|||
int |
readonly |
||||
float |
readonly |
Execution time in seconds elapsed since start of request until all response(s) received from back-end matrix. (Does not include time spent merging results or any time spent in a post-processor.) |
|||
bool |
readonly |
|
|||
int |
readonly |
5.4. PluginResponse Serialized as JSON
A topmost dispatcher or engine returning a PluginResponse object will automatically render the object as JSON by default.
{
"status": "OK",
"response": {
"level": 0,
"levelparts": 1,
"partials": 1,
"hitcount": 3,
"aggregator": [
0,
0,
0,
0
],
"entries": [
[
1,
"First"
],
[
2,
"Second"
],
[
3,
"Third"
]
]
},
"level": 0,
"partitions": null,
"exec_ms": 0.241
}
