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.

Step 1: Complete code for Hello World Service
from pyvgx import *

system.Initialize( "hello", http=9000 )

def hello( request:PluginRequest, message:str ):
  return "Hello, you said '{}'".format( message )

system.AddPlugin( hello )
Step 2: Test the service
curl "http://127.0.0.1:9000/vgx/plugin/hello?message=hi"
Step 3: Inspect the response
{
    "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.

pre_post_engine
Figure 1. Plugin Functions

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 parameters are mapped to plugin function arguments
http://127.0.0.1:9000/vgx/plugin/test?num=123&txt=456&extra=[7,8,9]
Annotated arguments are converted, all arguments in request.params
def test( request, num:int, txt):
  return {
    "num": "type:{} value:{}".format(type(num),num),
    "txt": "type:{} value:{}".format(type(txt),txt),
    "all": request.params
  }
Non-annotated arguments are strings
{
    "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”.

Five reserved arguments for engine and pre plugin functions, request is mandatory
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.

Two reserved arguments for post plugin functions, response is mandatory
def func( response[, graph] ):
  ...

2.3.3. Engine and Pre-processor Reserved Keyword Arguments

Argument Type Required? Description

request

pyvgx.PluginRequest

Always required

Contains all request parameters, general request information, and meta data

graph

pyvgx.Graph

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.

method

str

Optional

"GET" or "POST"

headers

bytes (default)
str
dict
list

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), …​]

content

bytes (default)
str
json

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

response

pyvgx.PluginResponse

Always required

Contains response payload, general response information and meta data

graph

pyvgx.Graph

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.

Return object types for pre plugin functions
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

Return object types for engine plugin functions
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.

Return object types for post plugin functions
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?parameters

HTTP 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.2. RemovePlugin

pyvgx.system.RemovePlugin( name )

Remove a registered plugin from the system.


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 takes graph parameter.

    [
        {
            "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/plugin

Assign 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

Serialize()

Return the raw HTTP request bytes representing this request object

get( param[, default] )

Like PluginRequest.params.get()

items()

Like PluginRequest.params.items()

keys()

Like PluginRequest.params.keys()

values()

Like PluginRequest.params.values()

4.3. PluginRequest Attributes

Table 1. PluginRequest Attributes
Attribute Type Access Description

affinity

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.

baseport

int

readonly

Server main port

channel

int

readonly

Channel id (0 an up) for current socket connection from parent dispatcher which issued this request

content

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.

depth

int

readonly

Maximum number of channels to this VGX Server instance from parent dispatcher which issued this request

executor

int

readonly

Identifier number 0-31 of executor assigned to handle this request

flag (DEPRECATED, use local instead)

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.

hasmatrix

bool

readonly

Return True if server request may be dispatched to a back-end matrix, False otherwise.

headers

bytes
str
dict
list

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.

height

int

height

Number of replicas in matrix of parent dispatcher which issued this request

ident

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.
All other parameters <p> are the same as request.<p>.

local

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.

method

str

readonly

"GET" or "POST"

params

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.

partial

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 x-vgx-partial-target, if present, is automatically assigned to this attribute. Attribute is cleared (and will have value None) if deleted using del, or assigned None or a negative integer.

Effective target partition index is computed as request.partial MOD P, where P is the number of partitions managed by this dispatcher.

Target partition index is propagated to lower level dispatcher(s) via the x-vgx-partial-target header. Each dispatcher level is responsible for overriding or clearing this attribute as necessary.

partition

int

readonly

This VGX Server instance’s partition id (0 and up) in matrix of parent dispatcher which issued this request

path

str

readonly

URL path for this request "/vgx/plugin/<name>?<params>"

port

int

readonly

Port used for this request

primary

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.

replica

int

readonly

This VGX Server instance’s replica id (0 and up) in matrix of parent dispatcher which issued this request

signature

str

r/w

Digest of request parameters

sn

int

readonly

Unique serial number assigned to this request instance

t0

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.)

toplevel

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.

Use this to determine the object type to return in a pre-processor if the request is terminated early.

width

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:

  1. Encapsulate a list of (optionally sorted) items

  2. Specify sort order and key type

  3. Manage numeric aggregation fields

  4. 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, otherwise pyvgx.ResponseError is 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 is S_ASC. In this case every nth call to Append() 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 when Append() is first called. sortby is set to S_VAL|S_ASC when sortkey type is int or float, and S_ID|S_ASC when sortkey type is str or bytes.

Attempting to call Append() more times than the response object’s maxhits limit will raise pyvgx.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

Table 2. PluginResponse Attributes
Attribute Type Access Description

baseport

int

readonly

Server main port

channel

int

readonly

See request.channel

depth

int

readonly

See request.depth

entries

list

readonly

List of tuples [(sortkey, item), …​] stored in the response object resulting from calls to Append().

Be careful when modifying this list. Every entry in response.entries must be a tuple (sortkey, item). It is best to use Append() to add new items. Since an existing entry is a tuple it cannot be modified, but any contained item is safe to modify if it is a mutable object such as dict or list.

executor

int

readonly

Identifier number 0-31 of executor assigned to process this response

f2

float

r/w

General purpose float aggregator. Partial response f2 values are summed into dispatcher’s merged response f2 value.

f3

float

r/w

General purpose float aggregator. Partial response f3 values are summed into dispatcher’s merged response f3 value.

flag (DEPRECATED, use local instead)

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).

hasmatrix

bool

readonly

See request.hasmatrix

height

int

readonly

See request.height

hitcount

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.

i0

int

r/w

General purpose integer aggregator. Partial response i0 values are summed into dispatcher’s merged response i0 value.

i1

int

r/w

General purpose integer aggregator. Partial response i1 values are summed into dispatcher’s merged response i1 value.

ident

tuple

readonly

See request.ident

keytype

type

readonly

Type of sortkey (int, float, str, bytes), or None if undefined

level

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.

levelparts

int

readonly

Number of VGX Server instances at the immediate lower level in dispatcher’s back-end matrix.

local

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.

maxhits

int

r/w

Maximum number of entries that can be added to a response. Default is -1 (unlimited).

message

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.

Message strings set in matrix back-end responses are discarded when those responses are merged in a dispatcher.

partials

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.

partition

int

readonly

See request.partition

port

int

readonly

Port used for this request

replica

int

readonly

See request.replica

requestmethod

str

readonly

"GET" or "POST"

requestpath

str

readonly

See request.path

resubmits

int

readonly

Number of times request was resubmitted to backend matrix

signature

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.

sn

int

readonly

See request.sn

sortby

int

readonly

Sort order and key type in effect for this response

t0

int

readonly

See request.t0

texec

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.)

toplevel

bool

readonly

Use this to determine the object type to return in a post-processor.

width

int

readonly

See request.width

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
}

PYVGX