Implementing a custom type

To implement a custom python-level type, one can use the py.type() builtin. At the JS-level, it is a function with the same signature as the type builtin [1]. It returns a child type of its one base (or py.object if no base is provided).

The dict parameter to py.type() can contain any attribute, javascript-level or python-level: the default __getattribute__ implementation will ensure they are converted to Python-level attributes if needed. Most methods are also wrapped and converted to Python-level callable, although there are a number of special cases:

Python-level callable

Wrapped javascript function or the __call__() method itself follow the Python calling conventions. As a result, they can’t (easily) be called directly from javascript code. Because __new__() and __init__() follow from __call__(), they also follow the Python calling conventions.

py.PY_call() should be used when interacting with them from javascript is necessary.

Because __call__ follows the Python calling conventions, instantiating a py.js type from javascript requires using py.PY_call().

Python calling conventions

The python-level arguments should be considered completely opaque, they should be interacted with through py.PY_parseArgs() (to extract python-level arguments to javascript implementation code) and py.PY_call() (to call Python-level callable from javascript code).

A callable following the Python calling conventions must return a py.js object, an error will be generated when failing to do so.

Magic methods

py.js doesn’t support calling magic (“dunder”) methods of the datamodel from Python code, and these methods remain javascript-level (they don’t follow the Python calling conventions).

Here is a list of the understood datamodel methods, refer to the relevant Python documentation for their roles.

Basic customization

__hash__()
Returns:String
__eq__(other)

The default implementation tests for identity

Parameters:otherpy.object to compare this object with
Returns:py.bool
__ne__(other)

The default implementation calls __eq__() and reverses its result.

Parameters:otherpy.object to compare this object with
Returns:py.bool
__lt__(other)

The default implementation simply returns py.NotImplemented.

Parameters:otherpy.object to compare this object with
Returns:py.bool
__le__(other)

The default implementation simply returns py.NotImplemented.

Parameters:otherpy.object to compare this object with
Returns:py.bool
__ge__(other)

The default implementation simply returns py.NotImplemented.

Parameters:otherpy.object to compare this object with
Returns:py.bool
__gt__(other)

The default implementation simply returns py.NotImplemented.

Parameters:otherpy.object to compare this object with
Returns:py.bool
__str__()

Simply calls __unicode__(). This method should not be overridden, __unicode__() should be overridden instead.

Returns:py.str
__unicode__()
Returns:py.unicode
__nonzero__()

The default implementation always returns py.True

Returns:py.bool

Customizing attribute access

__getattribute__(name)
Parameters:name (String) – name of the attribute, as a javascript string
Returns:py.object
__getattr__(name)
Parameters:name (String) – name of the attribute, as a javascript string
Returns:py.object
__setattr__(name, value)
Parameters:
  • name (String) – name of the attribute, as a javascript string
  • valuepy.object

Implementing descriptors

__get__(instance)

Note

readable descriptors don’t currently handle “owner classes”

Parameters:instancepy.object
Returns:py.object
__set__(instance, value)
Parameters:

Emulating Numeric Types

  • Non-in-place binary numeric methods (e.g. __add__, __mul__, ...) should all be supported including reversed calls (in case the primary call is not available or returns py.NotImplemented). They take a single py.object parameter and return a single py.object parameter.

  • Unary operator numeric methods are all supported:

    __pos__()
    Returns:py.object
    __neg__()
    Returns:py.object
    __invert__()
    Returns:py.object
  • For non-operator numeric methods, support is contingent on the corresponding builtins being implemented

Emulating container types

__len__()
Returns:py.int
__getitem__(name)
Parameters:namepy.object
Returns:py.object
__setitem__(name, value)
Parameters:
__iter__()
Returns:py.object
__reversed__()
Returns:py.object
__contains__(other)
Parameters:otherpy.object
Returns:py.bool
[1]with the limitation that, because py.js builds its object model on top of javascript’s, only one base is allowed.