PyCOM 0.6.0 documentation

Public API Reference

This is public Python API for both server and client side of PyCOM. Private API bits are not stated here. Still, you can find their documentation in the docstrings.

PyCOM uses standard logging module to do it’s logging, logger name being pycom (or use pycom.utils.logger()).

If you are interested in C++ API, have a look at this page: https://bitbucket.org/divius/libpycom

Writing services

pycom.interface(name, **kwargs)

Decorator for interface name.

Example:

import pycom

@pycom.interface("com.example.interface.foo")
class ClassProvidingInterface(pycom.Service): pass

Deriving from pycom.Service is completely optional, but this base class provides support for introspecting your methods.

Interfaces are stateless by default, you can make them steteful by using stateful argument:

import pycom

@pycom.interface("com.example.interface.foo", stateful=True)
class ClassProvidingInterface(pycom.Service): pass

Argument authentication overrides global authentication policy and can take two string values: "required" and "never".

Additional attribute is defined on class with interface:

__interface__

Interface metainformation for this class - instance of pycom.interfaces.Interface.

class pycom.Service

Helpful base class (or mix-in) for services.

Adds introspection support via introspect remote call.

Imagine a service:

import pycom

@pycom.interface("com.example.interface.foo")
class ClassProvidingInterface(pycom.Service):
    "My Demo Service"

    introspection_data = {
        "com.example.interface.foo.x": None
    }

    @pycom.method(results=("res1", "res2"))
    def method_name(self, request, arg1, arg2, arg3=None):
        "My Demo Method"
        return arg2, arg3

This call will return the following JSON:

{
  "name": "com.example.interface.foo",
  "methods": {
    "introspect": {
      "required_arguments": [],
      "optional_arguments": [],
      "results": ["name", "methods", "docstring", "data"],
      "docstring": "Provides introspection information for a service.",
      "attachments": {
        "incoming": None,
        "outgoing": None
      }
    },
    "method_name": {
      "required_arguments": ["arg1", "arg2"],
      "optional_arguments": ["arg3"],
      "results": ["res1", "res2"],
      "docstring": "My Demo Method",
      "attachments": {
        "incoming": None,
        "outgoing": None
      }
    }
  },
  "docstring": "My Demo Service",
  "data": {
    "com.example.interface.foo.x": None
  }
}

Note that introspect method is also included.

introspection_data (if present) should be a dictionary.

introspect(request)

Provides introspection information for a service.

pycom.method(name=None, body=None, method_factory=None, results=(), attachments=(None, None))

Decorator for method name.

Method should take at least one positional argument: request, which is an object of type zerojson.Request.

Method should return either JSON-serializable entity, or result of request.response() call (see zerojson.Request.response()), i.e. zerojson.Response object.

Example:

import pycom

@pycom.interface("com.example.interface.foo")
class ClassProvidingInterface(pycom.Service):

    @pycom.method
    def method_name(self, request): print request.args

If body is present, this function does not act as a decorator, but directly return appropriate object for pycom.interfaces.Interface.register_method(). Here is a useful snippet for adding method in runtime:

import pycom

@pycom.interface("com.example.interface.foo")
class ClassProvidingInterface(pycom.Service): pass

def method_body(self, request): pass

ClassProvidingInterface.__interface__.register_method(
    pycom.method("method_name", body=method_body))

method_factory is used internaly for overriding method class (default is pycom.interfaces.Method).

PyCOM will do it’s best to detect any additional arguments that your method can take, e.g. you can write:

import pycom

@pycom.interface("com.example.interface.foo")
class ClassProvidingInterface(pycom.Service):

    @pycom.method
    def method_name(self, request, arg1, arg2, arg3=None):
        print arg1, arg2, arg3

PyCOM will use Python’s introspection features to guess that arg1 and arg2 are required arguments, arg3 is an optional one. pycom.BadRequest will be raised, if required argument is missing, or input is not a JSON object.

However, PyCOM cannot guess whether you have one result or several, and what are their names. You can give it a hint by filling results tuple with names of the resulting arguments, e.g.:

import pycom

@pycom.interface("com.example.interface.foo")
class ClassProvidingInterface(pycom.Service):

    @pycom.method(results=("res1", "res2"))
    def method_name(self, request, arg1, arg2, arg3=None):
        return arg2, arg3

If you are going to use introspection (and e.g. pycom.ProxyComponent) with the method, you may also fill attachments argument with a tuple of 2 elements: names for incoming and outgoing attachment parameters or None‘s:

import pycom

@pycom.interface("com.example.interface.foo")
class ClassProvidingInterface(pycom.Service):

    @pycom.method(attachments=("blob", None))
    def method_name(self, request, blob):
        return blob.decode("utf-8")

Note that incoming attachment turned into an argument to the method. For outgoing attachment you should add outgoing attachment name to results list and return the attachment in a result tuple:

import pycom

@pycom.interface("com.example.interface.foo")
class ClassProvidingInterface(pycom.Service):

    @pycom.method(attachments=(None, "blob"), results=("orig", "blob"))
    def method_name(self, request, string):
        return string, string.encode("utf-8")

Finally, you can explicitly state remote method name:

import pycom

@pycom.interface("com.example.interface.foo")
class ClassProvidingInterface(pycom.Service):

    @pycom.method("method_name")
    def method_impl(self, request): print request.args

If interface is stateful (see pycom.interface()), request will have session attribute with dictionary. You can store arbitrary data there and it will be persisted across requests.

For strict type checks you can use an extensions (available in module pycom.ext.checks):

pycom.ext.checks.check_argument(name, valid_types=None, validator=None, error_message=None)[source]

A decorator for checking types of incoming arguments.

name is a name of argument to check.

valid_types is a tuple with allowed types or just a single type.

validator is a callable that takes 2 arguments
(argument name and argument value), positive result designate valid value, negative result or raising TypeError or ValueError designate invalid value (error message is taken from the exception).

error_message overrides default error message.

Example:

import pycom
from pycom.ext import checks

@pycom.interface("com.example.interface.foo")
class SimpleInterface(pycom.Service):

    @checks.check_argument("arg1", valid_types=str,
        validator=lambda name, value: len(value) > 0)
    @checks.check_argument("arg2", valid_types=(int, type(None)),
        validator=(lambda name, value: value is None or value > 0),
        error_mesage="Required positive integer or None for arg2")
    @pycom.method
    def my_method(self, request, arg1, arg2): pass

This decorator can be used any number of times, but should be placed before pycom.method().

pycom.BadRequest is raised on failed checks.

pycom.main(argv=None, client_context=None)

Starts main loop for a service.

Raises pycom.ConfigurationError if not configured properly - see Running services for details.

if argv is not None, options parser is used to get options from it. Example (from pycom-launcher utility):

import sys
import pycom
pycom.main(sys.argv[1:])

Returns 0MQ IOLoop exit code.

client_context can be used to override context, which is used to register services.

Here we use classes from ZeroJSON implementation package:

class zerojson.Request(interface, method, session_id=None, args=None, extensions=None, attachment=None)

Request object.

interface

Current interface name

method

Current method name

session

Session for current component - dict-like object of type zerojson.Session or None when session is not initialized.

args

Arguments passed by client side as Python object

extensions

Dictionary with extensions data

attachment

Binary attachment with this request

context()

Generates preconfigured pycom.ProxyContext. If authentication token is provided by client, it is also set on a context.

Note

This method is set by PyCOM and is absent in pure ZeroJSON.

error(code, message=None)

Build response object with error.

Returns zerojson.Response object.

response(result, attachment=None)

Build response object to return from method.

result is a JSON-serializable entity to return to client. attachment is a binary attachment to return.

Returns zerojson.Response object.

session_id

Session unique identifier.

None when session is not initialized.

class zerojson.Response(request, result=None, code=0, message=None, session_id=None, extensions=None, attachment=None)

Response object.

request

zerojson.Request object that issued this response

result

Call result as Python object

extensions

Dictionary with extensions data

attachment

Binary attachment with this response

class zerojson.Session(session_id, *args, **kw)

Class representing session.

Inherits from dict, provides one additional attribute:

discard()

Discard this sessions and all it’s data.

There is a way to write asynchronous services with PyCOM/ZeroJSON:

class zerojson.Future(request)

Object pointing that request will be fulfilled in the future.

error(code, message=None)

Send an error.

This method is thread-safe.

exception()

Send an exception.

This method should only be called from an exception handler.

This method is thread-safe.

response(result, attachment=None)

Send a result.

This method is thread-safe.

Return Future object from your method instead of a real response and do not forget to call one of it’s methods later.

Invoking services

class pycom.Context(nameserver=None)

Context is a main high-level class for accessing components.

If nameserver is not present, uses global configuration.

prehooks
posthooks

Lists of hooks that should be executed for pre- and post- processing incoming and outgoing data. Hook is a callable with the following signature:

hook(component, data)
component

Remote or local component

data

Data to be processed (zerojson.Request for prehooks, zerojson.Response for posthooks).

Context are thread-safe, unlike components they provide.

authenticate(user=None, credentials=None, token=None)

Authenticate a user on this context with given credentials.

Alternatively, you can pass only token.

connect(address, iface)

Connect to a service providing iface on a given address.

locate(iface, service_name=None)

Locate a service providing given interface iface.

If service_name is specified, only service with this name will be returned.

nameserver()

Get nameserver component.

user_info()

Fetches info for user currently logged in.

Result is anything returned by authentication service, but for standart-conforming service it will be a dictionary with (at least) keys name and roles.

Returns None if no user is logged in. Can possibly raise pycom.AccessDenied if user token is expired.

Both of these functions return special objects called components, each of them implementing interface:

class pycom.BaseComponent(interface, context=None)

Abstract base class for components.

Any component can be used as a context manager.

name

Interface name.

Components are NOT thread-safe! Do not share them across threads.

close()

Closes component, if possible.

introspect()

Get structure with introspection information.

See pycom.Service for details.

invoke(method_name, args=None, timeout=None, attachment=None)

Calls method_name on a service, passing args as argument.

args should be anything JSON-serializable. timeout can be used to prevent invoke from hanging forever. timeout is in milliseconds and defaults to 5000.

attachment is any binary of type bytes.

Raises pycom.MethodNotFound if service doesn’t provide method method_name. Raises pycom.ServiceNotAvailable if service cannot be reached during request.

Returns call result as a JSON-serializable entity if there is no attachments. If an attachment is present, result is a tuple with the first item being JSON result, the second being an attachment.

invoke_with_request(request, timeout=None)

Invoke method using given request object.

Must be overriden in a real component class.

Returns zerojson.Response object.

Both of these functions will raise pycom.ServiceNotFound if service is unknown or unreachable.

Example client code:

ctx = pycom.Context(nameserver="tcp://...")
ns = ctx.locate("org.pycom.nameserver")
for service in ns.invoke("list_services"):
    print(service["service"], service["interface"])

or (the same, but faster and cleaner)

ctx = pycom.Context(nameserver="tcp://...")
ns = ctx.nameserver()
for service in ns.invoke("list_services"):
    print(service["service"], service["interface"])

or (the same, but with correct closing)

ctx = pycom.Context(nameserver="tcp://...")
with ctx.nameserver() as ns:
    for service in ns.invoke("list_services"):
        print(service["service"], service["interface"])

A more convenient way is to use proxying context and components:

class pycom.ProxyContext(*args, **kwargs)

Context object with handy access to methods.

Can also be used to wrap a real pycom.Context object, e.g.:

ctx = pycom.Context()
# ...
proxy_ctx = pycom.ProxyContext(ctx)

All members are the same as in pycom.Context, except:

connect(*args, **kwargs)

Connect to a service, return proxying component.

See pycom.Context.connect().

locate(*args, **kwargs)

Locate a service, return proxying component.

See pycom.Context.locate().

nameserver(*args, **kwargs)

Locate a service, return proxying component.

See pycom.Context.nameserver().

class pycom.ProxyComponent(wrapped)

Component that passes Python method calls as remote method calls.

Examples:

import pycom

context = pycom.ProxyContext(nameserver="tcp://127.0.0.1:2012")
with context.locate("org.pycom.nameserver") as proxy:
    services = proxy.list_services()
    services = proxy.list_services(interface="org.pycom.nameserver")

    service = proxy.locate("org.pycom.nameserver")
    service = proxy.locate(interface="org.pycom.nameserver")
    service = proxy.locate("org.pycom.nameserver",
        service="/org/pycom/nameserver")
    service = proxy.locate(interface="org.pycom.nameserver",
        service="/org/pycom/nameserver")

Methods with multiple results return namedtuple.

Be careful with attachments: they will NOT work unless properly declared using attachments and results arguments of pycom.method(). Both attachments are dialed with just like any other argument.

Note

Never store components for a long time! Any component is bound to the specific address, but your services can change their location. Furthermore, a session bound to your component can expire and you’ll get pycom.SessionExpired.

Altering configuration

pycom.configure(*args, **kwargs)

Safe way to alter configuration.

The only position argument should be a JSON file. Keyword arguments are added to global configuration.

Example:

import pycom
pycom.configure(nameserver="tcp://127.0.0.1:2012")
pycom.configuration

Dictionary with current configuration. Should not be altered directly!

Exceptions

Following exceptions can be raised on client side or on remote side:

exception pycom.RemoteError(msg, code=None)

User exception on a remote side.

The following attribute is set from the constructor:

code

Exception code.

Base class for any exceptions that can be raised by remote side.

exception pycom.ServiceNotFound(msg, code=None)

Remote service not found.

exception pycom.MethodNotFound(msg, code=None)

Remote method not found.

exception pycom.BadRequest(msg, code=None)

Client sent bad request.

exception pycom.AccessDenied(msg, code=None)

Access denied to this function.

exception pycom.SessionExpired(msg, code=None)

Client session has expired.

Following exceptions can be raised on client side only:

exception pycom.ServiceNotAvailable

Remote service not available.

exception pycom.ConfigurationError

Error found in configuration.

Note that all but the last exceptions are in fact defined in zerojson package.