Add Babeltrace 2 Python bindings
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Sat, 4 Feb 2017 03:38:48 +0000 (22:38 -0500)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Sun, 28 May 2017 16:57:37 +0000 (12:57 -0400)
commit81447b5ba52670c15ec17edfb8fa40d17aeb061a
treea37f257531d35b6b9aebff8f6b3fff80b5cd6ddc
parent3f3c46b84dc67e4839dce4ca6a74719f9f35bd63
Add Babeltrace 2 Python bindings

This patch adds new Babeltrace 2 Python bindings to the Babeltrace
project.

Those bindings are compatible with Python 3 (only).

The new bindings still make use of SWIG to simplify the Python-to-native
and native-to-Python calls.

The new bindings, from Python's point of view, are available in the new
`bt2` package. This package imports (__init__.py does) everything that
is public from its modules, so that every public name is available as
bt2.something. This is considered more Pythonic than asking the user to
import specific modules from a given package.

The goal with this, to keep the "old" `babeltrace` package working, is
to make the `babeltrace` package a simple Python-only wrapper of the bt2
package (which offers much more, as you will discover in this commit
message).

Summary of features:

* All the current Babeltrace 2 APIs are wrapped:
  * Clock class
  * Component, component class, notification iterator, and notification
  * CTF writer, CTF writer clock, CTF writer stream
  * Event and event classe
  * Packet, stream, and stream class
  * Fields and field types
  * Plugin
  * Trace
  * Values
* Automatic BT reference count handling for the user (just like the
  `babeltrace` package does).
* Type checking of the arguments of each method/function (before it gets
  to SWIG, where the exception is not as obvious).
* Package exceptions:
  * Error
  * CreationError
  * FrozenError
  * UnsupportedFeature
  * TryAgain
  * Stop
  * IncompleteUserClassError
* Full support of user component classes.
* Package is as Pythonic as possible, with extensive use of collection
  ABCs, number and other protocols, iterators, properties, inheritance
  for the user, and exceptions.
* Easy to extend if we ever add new BT objects or APIs.
* The bindings only use the public Babeltrace 2 C API; they could be
  built outside the Babeltrace repository without altering the code.

Build system
============
Makefile.am does pretty much the same job as previously, although it is
organized so that it's easy to add Python modules and partial SWIG
interfaces. All the rules and commands are built from two simple lists.

SWIG
====
I created one SWIG interface file (.i) for each Babeltrace API. All the
native_bt*.i files are included at the end of native_bt.i. This is the
only input for SWIG.

native_bt.i does more than including partial interface files. It adds
rules to remove the bt_ and BT_ prefixes of all the wrapped functions
and enumeration items.

native_bt.i also adds a few custom typemaps to convert special arguments
back and forth between Python and the C API. For example,
`const char **BTOUTSTR` is a typemap to append a Python string (Unicode
object) to the current SWIG result tuple when the argument is named
as such, as in:

int bt_ctf_field_type_enumeration_get_mapping_signed(
        struct bt_ctf_field_type *enum_field_type, int index,
        const char **BTOUTSTR, int64_t *OUTPUT, int64_t *OUTPUT);

Note that, in example above, OUTPUT is a typemap provided by SWIG for
very simple types.

Another typemap is BTUUID to accept and return Babeltrace UUIDs as
Python `bytes` objects:

    BTUUID bt_ctf_clock_class_get_uuid(struct bt_ctf_clock_class *clock_class);

    int bt_ctf_clock_class_set_uuid(struct bt_ctf_clock_class *clock_class,
            BTUUID uuid);

Modules
=======
I'll now go into the details of each module of the bt2 package, in a
relevant order.

Most of the objects described below are comparable. Their compare
function usually start with a simple address comparison, and then falls
back to a rich comparison (sometimes implemented in Python if the
equivalent C function is missing).

utils
-----
Small utility functions: type checking, automatic exception raising,
power of two, etc.

object
------
bt_object API and reference counting.

Base class for a wrapped BT object (with reference counting), not
meant to be instantiated by the user.

Provides:

* self._ptr: SWIG pointer (native BT object), for the functions of
  the bt2 package (private).

* __init__(): called by subclass to wrap a SWIG pointer.

* addr(): public property which returns the address (integer, not the
  SWIG pointer object) of the object, mostly for debug purposes (for the
  user) and for a user to know if two bt2 objects actually wrap the
  same BT native object.

* _create_from_ptr(): class method to wrap a given SWIG pointer as an
  objet of a given class:

      event_class = EventClass._create_from_ptr(ptr)

* __repr__(): Shows the type of the object and its native address.

* __del__(): Puts its BT object reference (calls bt_put()).

object.py also contains _Freezable, a mixin for publicly freezable
objects.

values
------
bt_value API.

The classes in bt2.values are full Python wrappers of the native
bt_value types.

Features:

* BoolValue acts just like Python's bool.
* IntegerValue acts just like Python's int.
* FloatValue acts just like Python's float.
* StringValue acts just like Python's str.
* ArrayValue acts just like Python's list.
* MapValue acts just like Python's dict.
* bt_value_null is the equivalent of None.

In other words:

* All types are comparable and copyable (copy/deep copy).
* All the needed operators are implemented to make the value objects
  act like Python native objects.
* Number classes inherit ABCs in the numbers module.

There's also a bt2.create_value() function which returns a bt2.values
object from any value (bt2.values or native Python object).

I decided to wrap actual bt_value objects instead of always converting
from native Python objects to them and vice versa, because they can
still be shared in this case. Otherwise the conversion would remove this
sharing feature which is implicit with BT reference counting.

A few examples:

    my_int = bt2.IntegerValue(23)
    my_int += 283
    print(my_int)
    some_flt = bt2.FloatValue(45.3)
    print(my_int * some_flt)

    s = bt2.create_value('hello there')
    print(s[3:9])

    my_map = bt2.create_value({'ho': 23, 'meow': (4, 5, False, None)})
    print(my_map)
    print(my_map['meow'][1] >= 5)
    print(my_map.addr)

    for k, v in my_map.items():
        print('{}: {}'.format(k, v))

field_types
-----------
bt_ctf_field_type API.

This looks pretty much like the original field type objects of
the `babeltrace.writer` module, except for the following features:

* `Declaration` suffix is replaced with `FieldType` suffix to match
  the C API convention.

* Copy, deep copy, and comparison support.

* You can pass all the properties of an object at construction time:

      int_ft = bt2.IntegerFieldType(size=23, align=16, is_signed=True,
                                    base=8, mapped_clock_class=cc)

* Enumeration field type honors the sequence protocol:

      for mapping in enum_ft:
          print(mapping.name, mapping.lower, mapping.upper)

* Enumeration field type mapping iterator support, e.g.:

      for mapping in enum_ft.mappings_by_name('APPLE'):
          print(mapping.name, mapping.lower, mapping.upper)

  It's easy to add the mappings of another enumeration field type:

      enum_ft += other_enum_ft

* EnumerationFieldType inherits IntegerFieldType so that you can do:

      enum_ft = bt2.EnumerationFieldType(size=23, align=16,
                                         is_signed=True,
                                         base=bt2.Base.HEXADECIMAL,
                                         byte_order=bt2.ByteOrder.BIG_ENDIAN)
      print(enum_ft.size)
      enum_ft.is_signed = False

  instead of getting the underlying integer field type object manually.

* Structure and variant field types honor the mapping protocol:

      for name, ft in struct_ft:
          print(name, ft)

* You can set the `min_alignment` property of a structure field type
  (but you cannot get it), and you can get its `alignment` property
  (but you cannot set it). Those names represent exactly what they
  mean (less ambiguous than the C API equivalent IMO).

* You can instantiate a field object from a field type object by
  "calling" the field type object. This is closer to the concept of
  a class in the Python world:

      my_field = int_ft()

fields
------
bt_ctf_field API.

A bt2._Field is the result of instantiating a field type object. The
type (and all its subclasses) starts with an underscore because you
cannot instatiate them directly (possibly with an initial value):

    int_field = bt2.IntegerFieldType(32)(17)
    str_field = bt2.StringFieldType()('hello there')

Features:

* Copy, deep copy, and comparison support.
* IntegerField and EnumerationField act just like Python's int.
* FloatingPointNumberField acts just like Python's float.
* StringField acts just like Python's str.
* ArrayField and SequenceField honor the mutable sequence protocol.
* StructureField honors the mutable mapping protocol.

Field objects are just like value objects: they act like native Python
objects:

    int_field = bt2.IntegerFieldType(32)(152)
    int_field += 194
    print(int_field % 51)

    str_field = bt2.StringFieldType()('hello there')
    print(len(str_field))
    str_field += ' World!'
    print(str_field)

    print(struct_field['oh']['noes'][23])

    print(variant_field.selected_field)

    for mapping in enum_field.mappings:
        print(mapping.name, mapping.lower, mapping.upper)

clock_class
-----------
bt_ctf_clock_class API.

A straightforward clock class wrapper, pretty much equivalent to the
previous one (CTFWriter.Clock), except that:

* Copy, deep copy, and comparison support.

* You can pass all the properties of an object at construction time:

      cc = bt2.ClockClass('my_clock', frequency=18000000,
                          is_absolute=True, precision=500,
                          offset=bt2.ClockClassOffset(seconds=22,
                                                      cycles=187232))

* A clock offset is represented with a ClockClassOffset object.

* You can create a clock value from a clock class object with a given
  number of cycles:

      clock_val = cc.create_clock_value(234)

  This clock value object is copyable, deep-copyable, and comparable.
  You can get its number of cycles (raw value), its clock class,
  and the number of nanoseconds since Epoch:

      print(clock_val.ns_from_epoch())

event_class
-----------
bt_ctf_event_class API.

Features:

* Copy, deep copy, and comparison support.

* You can pass all the properties of an object at construction time:

      ec = bt2.EventClass('my_event', id=23, payload_field_type=ft)

* Parent stream class access (returns None if not set):

      print(ec.stream_class.id)

* Attributes property which honor the mutable mapping protocol:

      event_class.attributes['model.emf.uri'] = 'http://diamon.org/'

* Payload and context field type R/W properties.

* Call the class to instantiate an event:

      my_event = my_event_class()

stream_class
------------
bt_ctf_stream_class API.

Features:

* Copy, deep copy, and comparison support.

* You can pass all the properties of an object at construction time:

      sc = bt2.StreamClass(name='my_stream_class',
                           event_header_field_type=ev_header_ft,
                           event_classes=(ec1, ec2, ec3))

* Parent trace access (returns None if not set):

      print(sc.trace)

* A stream class object honors the mapping protocol to access its
  event class children by name:

      ec = sc['my_event']

      for ec_name, ec in sc.items():
          print(ec_name, ec.id)

* Packet context, event header, and stream event context field type R/W
  properties.

* Call the class to instantiate a stream:

      my_stream = my_stream_class('optional_name')

trace
-----
bt_ctf_trace API.

Features:

* Copy, deep copy, and comparison support.

* You can pass all the properties of an object at construction time:

      trace = bt2.Trace(name='my_trace',
                        native_byte_order=bt2.ByteOrder.LITTLE_ENDIAN,
                        env={'tracer_name': 'BestTracer', 'custom': 23},
                        packet_header_field_type=pkt_head_ft,
                        clock_classes=(cc1, cc2),
                        stream_classes=(sc1, sc2))

* A trace object honors the mapping protocol to access its stream
  class children by ID:

      sc = trace[23]

      for sc_id, sc in trace.items():
          print(sc_id, len(sc))

* Trace environment honors the mutable mapping protocol:

      trace.env['tracer_major'] = 1
      trace.env['tracer_minor'] = 2
      trace.env['uname_r'] = '4.7.2-1-ARCH'

      for k, v in trace.env.items():
          print(k, v)

* Trace clock classes honor the mapping protocol:

      cc = trace.clock_classes['my_clock']

      for cc_name, cc in trace.clock_classes.items():
          print(cc_name, cc.frequency)

* Packet header field type R/W property.

event
-----
bt_ctf_event API.

Features:

* Copy, deep copy, and comparison support.

* Event class, name, ID, and stream read-only properties.

* Event object implements __getitem__() to retrieve a field in different
  scopes:

      # can be found in context, if not in payload, for example
      my_event['cpu_id']

  This is the same behaviour as in the `babeltrace` package. Use the
  specific field properties instead of a field_with_scope() method:

      print(my_event.context_field['specific'])

* Packet property to set and get the event's packet:

      event.packet = my_packet

* Header, stream event context, context, and payload field R/W
  properties.

* You can assign a clock value mapped to a specific clock class and
  get it back:

      event.set_clock_value(some_clock_value)
      print(event.get_clock_value(some_cc).ns_from_epoch)

stream
------
bt_ctf_stream API.

This module defines a base class (_StreamBase) for _Stream (non-writer
stream) and _CtfWriterStream (writer stream).

Features:

* Copy, deep copy, and comparison support.

* You can create a packet from a non-writer stream:

      packet = stream.create_packet()

packet
------
bt_ctf_packet API.

Features:

* Copy, deep copy, and comparison support.

* Stream read-only property (gives back the stream object which
  created it).

* Packet context and packet header field R/W properties.

notification
------------
bt_notification API.

The classes in this module wrap their equivalent in the C API in a
pretty straightfoward way.

notification_iterator
---------------------
bt_notification_iterator API.

A notification iterator object is always created from a source/filter
component object:

    source_component.create_notification_iterator()

A notification iterator object has a next() method to go to the next
notification, and a `notification` property to get the current
notification:

    notif_iter.next()
    print(notif_iter.notification)

The next() method can raise:

* bt2.Stop: End of the iteration (inherits StopIteration).
* bt2.UnsupportedFeature: Unsupported feature.
* bt2.Error: Any other error.

A notification iterator also honors the iterator protocol, that is, you
can use it like any Python iterator:

    for notif in notif_iter:
        print(notif)

Note that the iteration can still raise bt2.UnsupportedFeature or
bt2.Error in this scenario (bt2.Stop stops the iteration: it's not
raised outside the iteration).

You can use the seek_to_time() method to make a notification iterator
seek to a specific time.

The `component` property of a notification iterator returns the original
source/filter component which was used to create it.

You can create your own notification iterator class (to be used by your
own source/filter component class) by inheriting
bt2.UserNotificationIterator. This asks you to write your own _next()
and _get() methods which are eventually called by
bt_notification_iterator_next() and
bt_notification_iterator_get_notification(). You can also define an
__init__() method, a _destroy() method, and a _seek_to_time() method.

Minimal user notification iterator class:

    class MyIterator(bt2.UserNotificationIterator):
        def _get(self):
            # ...

        def _next(self):
            # ...

Your _next() method can raise bt2.Stop to signal the end of the
iteration. If it raises anything else, bt_notification_iterator_next()
returns BT_NOTIFICATION_ITERATOR_STATUS_ERROR to its caller. Since
the C API user only gets a status from this function, any exception
value is lost during this translation. However the C next method could
still log this value and the traceback in verbose mode.

Your _get() method can raise anything so that the caller of
bt_notification_iterator_get_notification() gets an error status.

Complete user notification iterator class:

    class MyIterator(bt2.UserNotificationIterator):
        def __init__(self):
            # Called when the user calls
            # bt_component_source_create_notification_iterator() or
            # bt_component_filter_create_notification_iterator().
            # Anything you raise here makes this function return
            # NULL (creation error).

        def _get(self):
            # ...

        def _next(self):
            # ...

        def _seek_to_time(self, origin, time):
            # You can raise anything or bt2.UnsupportedFeature.
            # `origin` is one of the values of
            # bt2.NotificationIteratorSeekOrigin.

        def _destroy(self):
            # This is called when the actual native BT notification
            # iterator object is destroyed. Anything you raise here
            # is ignored. You cannot use __del__() for this for
            # implementation reasons.

You CANNOT manually instantiate a user notification iterator, e.g.:

    my_iter = MyIterator()

This makes no sense because a notification iterator is always created by
a source/filter component. It probably won't work anyway because
bt2.UserNotificationIterator.__new__() expects a SWIG pointer and your
__init__() probably does not.

component
---------
bt_component_class and bt_component APIs.

This is where the fun begins.

All component objects have the following properties:

* name: Component's name or None.
* component_class: Component class object.

Source components have:

* create_notification_iterator(): Returns a new notification
  iterator object.

Filter components have:

* create_notification_iterator(): Returns a new notification
  iterator object.
* add_notification_iterator(): Adds a notification iterator to the
  filter component.

Sink components have:

* consume(): Consumes notifications from its input notification
  iterators.
* add_notification_iterator(): Adds a notification iterator to the
  filter component.

A component class object has the following properties:

* name: Component class's name or None.
* description: Component class's description or None.

You can also call a component class object to create a component
object, with optional parameters and an optional component name:

    comp = comp_class(name='my_comp', params={'path': '/tmp/lel'})

The `params` argument is passed to bt2.create_value() so you can use
a direct *Value object or anything accepted by this utility function.

What is described above is the _generic_ part of components and
component classes. There's another part for user-defined component
classes. For a user of those classes, both generic and user-defined
classes expose the same interface. The relative complexity of how this
is achieved is justified by the great simplicity from the component
developer's perspective:

    class MySink(bt2.UserSinkComponent):
        def _consume(self):
            notif_iter = self._input_notification_iterators[0]
            notif = next(notif_iter)

            if isinstance(notif, bt2.TraceEventNotification):
                print(notif.event.name)

That's it: some kind of minimal sink component class. Note that
next(notif_iter) can raise bt2.Stop here which is passed to the eventual
caller of bt_component_sink_consume() as the BT_COMPONENT_STATUS_END
status.

Behind the scenes, bt2.UserSinkComponent uses _UserComponentType as its
metaclass. When the class itself is initialized, its metaclass checks if
the subclass has the required interface (depending on its base class,
bt2.UserSinkComponent in this case) and creates a bt_component_class
owned by the Python user class. This bt_component_class is associated to
the Python class thanks to a global GHashTable in the shared object
module (_native_bt.so). Both the key and the value are weak references.

The name of the created bt_component_class is the user class's name by
default (MySink above), but it can also be passed as a class argument.
The description of the created bt_component_class is the docstring of
the user class:

    class MySink(bt2.UserSinkComponent, name='another-name'):
        'this is a custom sink'

        def _consume(self):
            # ...

Source and filter user component classes need to specify a notification
iterator class to use when the user calls
bt_component_*_create_notification_iterator(). This is specified as
a class argument.

    class MyIterator(bt2.UserNotificationIterator):
        def __init__(self):
            # ...

        def _get(self):
            # ...

        def _next(self):
            # ...

        def _seek_to_time(self, origin, time):
            # ...

        def _destroy(self):
            # ...

    class MySource(bt2.UserSinkComponent,
                   notification_iterator_class=MyIterator):
        # no mandatory methods here for a source/filter component class

Note that, within the notification iterator methods, self.component
refers to the actual user Python object which was used to create the
iterator object. This is the way to access custom, component-wide data
when the notification iterator is created (self.component._whatever).

Optional methods for all user-defined component classes are:

    class AnyComponent(...):
        def __init__(self, params, name):
            # `params` is a *Value Python object (bt2.values module),
            # `name` is the optional (can be None) component name,
            # which you can also access as self.name at this point.

        def _destroy(self):
            # This is called when the actual native BT component
            # object is destroyed. Anything you raise here
            # is ignored. You cannot use __del__() for this for
            # implementation reasons.

Optional methods for filter and sink user-defined component classes
are:

    class FilterOrSinkComponent(...):
        def _add_notification_iterator(self, notif_iter):
            # This is called when a notification iterator is added to
            # the component (using bt_component_*_add_iterator()).

Additionally, the __init__() method of filter and sink component classes
can use the self._minimum_input_notification_iterator_count and
self._maximum_input_notification_iterator_count properties to set their
minimum and maximum number of allowed input notification iterators:

    class FilterOrSinkComponent(...):
        def __init__(self, params, name):
            self._maximum_input_notification_iterator_count = 10
            self._minimum_input_notification_iterator_count = 4

They can also use the self._input_notification_iterators property at the
appropriate time to get their connected input notification iterators.
This property honors the sequence protocol. For filter components, this
is most probably going to be used by the iterator class, as such:

    class MyIterator(bt2.UserNotificationIterator):
        def _get(self):
            # ...

        def _next(self):
            notif_iter = self.component._input_notification_iterators[0]
            # ...

The beauty of all this is that both a Python user and the C API side can
instantiate user components:

    Python:

        my_sink = MySink(params={'janine': 'sutto'})

        for _ in my_sink.consume():
            pass

    C API (provided you have access to the bt_component_class object
           created for the Python user class):

        my_sink = bt_component_create(my_sink_comp_class, NULL, params);

        while (true) {
            status = bt_component_sink_consume(my_sink);
            if (status == BT_COMPONENT_STATUS_END) {
                break;
            }
        }

This is possible thanks to the overridden metaclass's __call__() method:

* When a Python user instantiates a user-defined component class, the
  metaclass's __call__() method creates an uninitialized user component
  and calls bt_component_create_with_init_method_data(), giving to this
  function `self` (the uninitialized component).

  When the component initialization method is called with some init
  method data, it sets the bt_component pointer in the received Python
  object and calls its __init__() method so that its intialization
  happens within the bt_component_create_with_init_method_data() call
  (important because some functions cannot be called outside this
  function).

  If the user's __init__() method raises, the error is not cleared on
  the C side, so that the Python user who instantiates the component
  can catch the actual, original Python exception instead of getting
  a generic one.

  In this scenario, the created user component Python object OWNS its
  bt_component. The component is marked as NOT being owned by its
  bt_component:

      self._belongs_to_native_component = False

  The bt_component has the user Python object as its private data
  (borrowed reference).

* When a C user instantiates a user-defined Python component class, he
  calls bt_component_create(). Then the component initialization
  function for this class receives a NULL init method data and knows
  it is called from the C/generic side.

  The initialization method finds the corresponding Python component
  class thanks to the aforementioned global GHashTable. It calls it with
  the `__comp_ptr` keyword argument set to the native bt_component SWIG
  pointer and the `params` keyword argument set to the *Value object
  converted from the `params` parameter. This call (metaclass's
  __call__()), in this scenario, calls the user's __init__() method
  itself. This call returns a new component instance, which is set as
  the private data of the native bt_component.

  In this scenario, the created user component Python object as a
  borrowed reference to the native bt_component. The native bt_component
  OWNS the Python user's component:

      self._belongs_to_native_component = True

The self._belongs_to_native_component property is used for the following
situation:

    my_source = MySource()

    # At this point, my_source is a Python object which owns its
    # bt_component.

    notif_iter = my_source.create_notification_iterator()

    # notif_iter is a generic notification iterator (a dumb
    # bt_notification_iterator wrapper) here, not the actual user
    # Python notification iterator. This method only calls
    # bt_component_source_create_notification_iterator() and wraps the
    # returned pointer.

    del my_source

    # At this point, the Python reference count of the source component
    # object falls to zero. Its __del__() method is called. However
    # we don't want this object to be destroyed here, because it is
    # still needed by the user notification iterator. This __del__()
    # method, if self._belongs_to_native_component is false, inverts
    # the ownership, literally:
    #
    #     if not self._belongs_to_native_component:
    #         self._belongs_to_native_component = True
    #         native_bt.py3_component_on_del(self)
    #         native_bt.put(self._ptr)
    #
    # bt_py3_component_on_del() simply increments the given
    # Python object's reference count. With its reference count back
    # to 1, Python does not actually destroy the object. It is now
    # owned by the bt_component.

    del notif_iter

    # Now, the wrapper puts its bt_notification_iterator object. Its
    # reference count falls to zero. Its bt_component is put: its
    # reference count falls to zero. The user's (C) destroy method for
    # this component class decrements the reference count of its
    # private Python object (the same object referenced by my_source
    # above). __del__() is (possibly) called again, but is a no-op
    # now. Then the user's _destroy() method is called.

plugin
------
bt_plugin API.

You can create plugin objects with bt2.create_plugins_from_file().
This is the equivalent of bt_plugin_create_all_from_file(). You can
also use bt2.create_plugins_from_dir() which is the equivalent of
bt_plugin_create_all_from_dir().

The return value of those functions is a list of _Plugin objects.

Here's an example of printing all the event names of a CTF trace:

    import bt2
    import sys

    def print_all():
        plugins = bt2.create_plugins_from_file(sys.argv[1])
        fs_cc = plugins[0].source_component_class('fs')
        fs_comp = fs_cc(params={'path': sys.argv[2]})
        notif_iter = fs_comp.create_notification_iterator()

        for notif in notif_iter:
            if isinstance(notif, bt2.TraceEventNotification):
                print(notif.event.name)

    print_all()

You would run this script like this:

    python3 script.py /path/to/ctf-plugin.so /path/to/trace

You can access the properties of a plugin:

    print(plugin.path)
    print(plugin.name)
    print(plugin.description)
    print(plugin.author)
    print(plugin.license)
    print(plugin.version)

A plugin object honors the sequence protocol:

    for comp_class in plugin:
        print(comp_class.name, comp_class.description)

ctf_writer
----------
Everything in this module is exclusive to the CTF writer API:

* CtfWriterClock (bt_ctf_clock)
* _CtfWriterStream (bt_ctf_stream, CTF writer interface only)
* CtfWriter (bt_ctf_writer)

I removed the CTFWriter.Writer.create_stream() method because it's the
equivalent of this:

    writer.trace.add_stream_class(sc)
    stream = sc()

This returns a _CtfWriterStream object.

Also removed is CTFWriter.Writer.add_environment_field() which you can
do like this now:

    writer.trace.env['name'] = value

The CTFWriter.Writer.byte_order property is now the `native_byte_order`
property of the CTF writer's trace:

    writer.trace.native_byte_order = bt2.ByteOrder.BIG_ENDIAN

CtfWriter.add_clock() expects a CtfWriterClock object.

__init__
--------
This imports * from each module, thus exposing only the public names of
each one.

It also defines the package's exceptions. bt2.CreationError is raised
when an object cannot be created. bt2.FrozenError is raised when an
operation fails because the object is frozen. This is only raised for
bt2.values objects since this API has a status to indicate the exact
error. bt2.Error is a general error.

The package also does this:

    import bt2.native_bt as _native_bt
    import atexit

    atexit.register(_native_bt.py3_cc_exit_handler)
    _native_bt.py3_cc_init_from_bt2()

bt_py3_cc_init_from_bt2() is used to import some bt2 modules and objects
on the C side and the exit handler, bt_py3_cc_exit_handler(), puts those
objects.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
41 files changed:
.gitignore
bindings/python/Makefile.am
bindings/python/bt2/.gitignore [new file with mode: 0644]
bindings/python/bt2/Makefile.am [new file with mode: 0644]
bindings/python/bt2/__init__.py.in [new file with mode: 0644]
bindings/python/bt2/clock_class.py [new file with mode: 0644]
bindings/python/bt2/component.py [new file with mode: 0644]
bindings/python/bt2/ctf_writer.py [new file with mode: 0644]
bindings/python/bt2/event.py [new file with mode: 0644]
bindings/python/bt2/event_class.py [new file with mode: 0644]
bindings/python/bt2/field_types.py [new file with mode: 0644]
bindings/python/bt2/fields.py [new file with mode: 0644]
bindings/python/bt2/native_bt.i [new file with mode: 0644]
bindings/python/bt2/native_btclockclass.i [new file with mode: 0644]
bindings/python/bt2/native_btcomponent.i [new file with mode: 0644]
bindings/python/bt2/native_btcomponentclass.i [new file with mode: 0644]
bindings/python/bt2/native_btctfwriter.i [new file with mode: 0644]
bindings/python/bt2/native_btevent.i [new file with mode: 0644]
bindings/python/bt2/native_bteventclass.i [new file with mode: 0644]
bindings/python/bt2/native_btfields.i [new file with mode: 0644]
bindings/python/bt2/native_btft.i [new file with mode: 0644]
bindings/python/bt2/native_btnotification.i [new file with mode: 0644]
bindings/python/bt2/native_btnotifiter.i [new file with mode: 0644]
bindings/python/bt2/native_btpacket.i [new file with mode: 0644]
bindings/python/bt2/native_btplugin.i [new file with mode: 0644]
bindings/python/bt2/native_btref.i [new file with mode: 0644]
bindings/python/bt2/native_btstream.i [new file with mode: 0644]
bindings/python/bt2/native_btstreamclass.i [new file with mode: 0644]
bindings/python/bt2/native_bttrace.i [new file with mode: 0644]
bindings/python/bt2/native_btvalues.i [new file with mode: 0644]
bindings/python/bt2/notification.py [new file with mode: 0644]
bindings/python/bt2/notification_iterator.py [new file with mode: 0644]
bindings/python/bt2/object.py [new file with mode: 0644]
bindings/python/bt2/packet.py [new file with mode: 0644]
bindings/python/bt2/plugin.py [new file with mode: 0644]
bindings/python/bt2/stream.py [new file with mode: 0644]
bindings/python/bt2/stream_class.py [new file with mode: 0644]
bindings/python/bt2/trace.py [new file with mode: 0644]
bindings/python/bt2/utils.py [new file with mode: 0644]
bindings/python/bt2/values.py [new file with mode: 0644]
configure.ac
This page took 0.03651 seconds and 4 git commands to generate.