if obj.rank == <NUM_LIT:1>:<EOL><INDENT>return '<STR_LIT>'<EOL><DEDENT>content_type = ContentType.objects.get_for_model(obj)<EOL>link = reverse('<STR_LIT>', args=(content_type.id, obj.id, <EOL>obj.rank - <NUM_LIT:1>))<EOL>return '<STR_LIT>' % (link, link_text)<EOL>

Returns a link to a view that moves the passed in object up in rank.

    :param obj:
        Object to move
    :param link_text:
        Text to display in the link.  Defaults to "up"
    :returns:
        HTML link code to view for moving the object


if obj.rank == obj.grouped_filter().count():<EOL><INDENT>return '<STR_LIT>'<EOL><DEDENT>content_type = ContentType.objects.get_for_model(obj)<EOL>link = reverse('<STR_LIT>', args=(content_type.id, obj.id, <EOL>obj.rank + <NUM_LIT:1>))<EOL>return '<STR_LIT>' % (link, link_text)<EOL>

Returns a link to a view that moves the passed in object down in rank.

    :param obj:
        Object to move
    :param link_text:
        Text to display in the link.  Defaults to "down"
    :returns:
        HTML link code to view for moving the object


<EOL>url = reverse('<STR_LIT>' % (obj._meta.app_label,<EOL>obj._meta.model_name))<EOL>url += '<STR_LIT>' % obj.id<EOL>text = str(obj)<EOL>if display:<EOL><INDENT>text = display<EOL><DEDENT>return format_html('<STR_LIT>', url, text)<EOL>

Returns a link to the django admin change list with a filter set to
    only the object given.

    :param obj:
        Object to create the admin change list display link for
    :param display:
        Text to display in the link.  Defaults to string call of the object
    :returns:
        Text containing HTML for a link


try:<EOL><INDENT>field_obj = get_obj_attr(obj, attr)<EOL>if not field_obj:<EOL><INDENT>return '<STR_LIT>'<EOL><DEDENT><DEDENT>except AttributeError:<EOL><INDENT>return '<STR_LIT>'<EOL><DEDENT>return field_obj<EOL>

A safe version of :func:``utils.get_obj_attr`` that returns and empty
    string in the case of an exception or an empty object


result = '<STR_LIT>'<EOL>if not display:<EOL><INDENT>result = str(obj)<EOL><DEDENT>else:<EOL><INDENT>template = Template(display)<EOL>context = Context({'<STR_LIT>':obj})<EOL>result = template.render(context)<EOL><DEDENT>return result<EOL>

Returns string representation of an object, either the default or based
    on the display template passed in.


@classmethod<EOL>def add_obj_link(cls, funcname, attr, title='<STR_LIT>', display='<STR_LIT>'):<EOL><INDENT>if not title:<EOL><INDENT>title = attr.capitalize()<EOL><DEDENT>_display = display<EOL>def _link(self, obj):<EOL><INDENT>field_obj = admin_obj_attr(obj, attr)<EOL>if not field_obj:<EOL><INDENT>return '<STR_LIT>'<EOL><DEDENT>text = _obj_display(field_obj, _display)<EOL>return admin_obj_link(field_obj, text)<EOL><DEDENT>_link.short_description = title<EOL>_link.allow_tags = True<EOL>_link.admin_order_field = attr<EOL>setattr(cls, funcname, _link)<EOL><DEDENT>@classmethod<EOL>def add_obj_ref(cls, funcname, attr, title='<STR_LIT>', display='<STR_LIT>'):<EOL><INDENT>if not title:<EOL><INDENT>title = attr.capitalize()<EOL><DEDENT>_display = display<EOL>def _ref(self, obj):<EOL><INDENT>field_obj = admin_obj_attr(obj, attr)<EOL>if not field_obj:<EOL><INDENT>return '<STR_LIT>'<EOL><DEDENT>return _obj_display(field_obj, _display)<EOL><DEDENT>_ref.short_description = title<EOL>_ref.allow_tags = True<EOL>_ref.admin_order_field = attr<EOL>setattr(cls, funcname, _ref)<EOL><DEDENT>klass = type(name, (), {})<EOL>klass.add_obj_link = add_obj_link<EOL>klass.add_obj_ref = add_obj_ref<EOL>return klass<EOL>

This method dynamically creates a mixin to be used with your
    :class:`ModelAdmin` classes.  The mixin provides utility methods that can
    be referenced in side of the admin object's ``list_display`` and other
    similar attributes.

    :param name:
        Each usage of the mixin must be given a unique name for the mixin class
        being created
    :returns:
        Dynamically created mixin class

    The created class supports the following methods:

    .. code-block:: python

        add_obj_ref(funcname, attr, [title, display])

    Django admin ``list_display`` does not support the double underscore
    semantics of object references.  This method adds a function to the mixin
    that returns the ``str(obj)`` value from object relations.

    :param funcname:
        Name of the function to be added to the mixin.  In the admin class
        object that includes the mixin, this name is used in the
        ``list_display`` tuple.
    :param attr:
        Name of the attribute to dereference from the corresponding object,
        i.e. what will be dereferenced.  This name supports double underscore
        object link referencing for ``models.ForeignKey`` members.
    :param title:
        Title for the column of the django admin table.  If not given it
        defaults to a capitalized version of ``attr``
    :param display:
        What to display as the text in the column.  If not given it defaults
        to the string representation of the object for the row: ``str(obj)`` .
        This parameter supports django templating, the context for which
        contains a dictionary key named "obj" with the value being the object
        for the row.

    .. code-block:: python

        add_obj_link(funcname, attr, [title, display])

    This method adds a function to the mixin that returns a link to a django
    admin change list page for the member attribute of the object being
    displayed.

    :param funcname:
        Name of the function to be added to the mixin.  In the admin class
        object that includes the mixin, this name is used in the
        ``list_display`` tuple.
    :param attr:
        Name of the attribute to dereference from the corresponding object,
        i.e. what will be lined to.  This name supports double underscore
        object link referencing for ``models.ForeignKey`` members.
    :param title:
        Title for the column of the django admin table.  If not given it
        defaults to a capitalized version of ``attr``
    :param display:
        What to display as the text for the link being shown.  If not given it
        defaults to the string representation of the object for the row:
        ``str(obj)`` .  This parameter supports django templating, the context
        for which contains a dictionary key named "obj" with the value being
        the object for the row.

    Example usage:

    .. code-block:: python

        # ---- models.py file ----
        class Author(models.Model):
            name = models.CharField(max_length=100)

        class Book(models.Model):
            title = models.CharField(max_length=100)
            author = models.ForeignKey(Author, on_delete=models.CASCADE)

    .. code-block:: python

        # ---- admin.py file ----
        @admin.register(Author)
        class Author(admin.ModelAdmin):
            list_display = ('name', )

        mixin = make_admin_obj_mixin('BookMixin')
        mixin.add_obj_link('show_author', 'Author', 'Our Authors',
            '{{obj.name}} (id={{obj.id}})')

        @admin.register(Book)
        class BookAdmin(admin.ModelAdmin, mixin):
            list_display = ('name', 'show_author')

    A sample django admin page for "Book" would have the table:

    +---------------------------------+------------------------+
    | Name                            | Our Authors            |
    +=================================+========================+
    | Hitchhikers Guide To The Galaxy | *Douglas Adams (id=1)* |
    +---------------------------------+------------------------+
    | War and Peace                   | *Tolstoy (id=2)*       |
    +---------------------------------+------------------------+
    | Dirk Gently                     | *Douglas Adams (id=1)* |
    +---------------------------------+------------------------+

    Each of the *items* in the "Our Authors" column would be a link to the
    django admin change list for the "Author" object with a filter set to show
    just the object that was clicked.  For example, if you clicked "Douglas
    Adams (id=1)" you would be taken to the Author change list page filtered
    just for Douglas Adams books.

    The ``add_obj_ref`` method is similar to the above, but instead of showing
    links, it just shows text and so can be used for view-only attributes of
    dereferenced objects.


global klass_count<EOL>klass_count += <NUM_LIT:1><EOL>name = '<STR_LIT>' % klass_count<EOL>klass = type(name, (FancyModelAdmin,), {})<EOL>klass.list_display = []<EOL>if len(args) > <NUM_LIT:0>:<EOL><INDENT>klass.add_displays(*args)<EOL><DEDENT>return klass<EOL>

Returns a new copy of a :class:`FancyModelAdmin` class (a class, not
    an instance!). This can then be inherited from when declaring a model
    admin class. The :class:`FancyModelAdmin` class has additional methods
    for managing the ``list_display`` attribute.

    :param ``*args``: [optional] any arguments given will be added to the
        ``list_display`` property using regular django ``list_display``
        functionality.

    This function is meant as a replacement for :func:`make_admin_obj_mixin`,
    it does everything the old one does with fewer bookkeeping needs for the
    user as well as adding functionality.

    Example usage:

    .. code-block:: python

        # ---- models.py file ----
        class Author(models.Model):
            name = models.CharField(max_length=100)

        class Book(models.Model):
            title = models.CharField(max_length=100)
            author = models.ForeignKey(Author, on_delete=models.CASCADE)

    .. code-block:: python

        # ---- admin.py file ----
        @admin.register(Author)
        class Author(admin.ModelAdmin):
            list_display = ('name', )

        base = fany_list_display_modeladmin()
        base.add_displays('id', 'name')
        base.add_obj_link('author', 'Our Authors',
            '{{obj.name}} (id={{obj.id}})')

        @admin.register(Book)
        class BookAdmin(base):
            list_display = ('name', 'show_author')

    A sample django admin page for "Book" would have the table:

    +----+---------------------------------+------------------------+
    | ID | Name                            | Our Authors            |
    +====+=================================+========================+
    |  1 | Hitchhikers Guide To The Galaxy | *Douglas Adams (id=1)* |
    +----+---------------------------------+------------------------+
    |  2 | War and Peace                   | *Tolstoy (id=2)*       |
    +----+---------------------------------+------------------------+
    |  3 | Dirk Gently                     | *Douglas Adams (id=1)* |
    +----+---------------------------------+------------------------+

    See :class:`FancyModelAdmin` for a full list of functionality
    provided by the returned base class.


for arg in args:<EOL><INDENT>cls.list_display.append(arg)<EOL><DEDENT>

Each arg is added to the ``list_display`` property without any
        extra wrappers, using only the regular django functionality


@classmethod<EOL><INDENT>def add_display(cls, attr, title='<STR_LIT>'):<DEDENT>

global klass_count<EOL>klass_count += <NUM_LIT:1><EOL>fn_name = '<STR_LIT>' % klass_count<EOL>cls.list_display.append(fn_name)<EOL>if not title:<EOL><INDENT>title = attr.capitalize()<EOL><DEDENT>def _ref(self, obj):<EOL><INDENT>_, _, value = lookup_field(attr, obj, cls)<EOL>return value<EOL><DEDENT>_ref.short_description = title<EOL>_ref.allow_tags = True<EOL>_ref.admin_order_field = attr<EOL>setattr(cls, fn_name, _ref)<EOL>

Adds a ``list_display`` property without any extra wrappers,
        similar to :func:`add_displays`, but can also change the title.

        :param attr:
            Name of the attribute to add to the display

        :param title:
            Title for the column of the django admin table.  If not given it
            defaults to a capitalized version of ``attr``


@classmethod<EOL><INDENT>def add_link(cls, attr, title='<STR_LIT>', display='<STR_LIT>'):<DEDENT>

global klass_count<EOL>klass_count += <NUM_LIT:1><EOL>fn_name = '<STR_LIT>' % klass_count<EOL>cls.list_display.append(fn_name)<EOL>if not title:<EOL><INDENT>title = attr.capitalize()<EOL><DEDENT>_display = display<EOL>def _link(self, obj):<EOL><INDENT>field_obj = admin_obj_attr(obj, attr)<EOL>if not field_obj:<EOL><INDENT>return '<STR_LIT>'<EOL><DEDENT>text = _obj_display(field_obj, _display)<EOL>return admin_obj_link(field_obj, text)<EOL><DEDENT>_link.short_description = title<EOL>_link.allow_tags = True<EOL>_link.admin_order_field = attr<EOL>setattr(cls, fn_name, _link)<EOL>

Adds a ``list_display`` attribute that appears as a link to the
        django admin change page for the type of object being shown. Supports
        double underscore attribute name dereferencing.

        :param attr:
            Name of the attribute to dereference from the corresponding
            object, i.e. what will be lined to.  This name supports double
            underscore object link referencing for ``models.ForeignKey``
            members.

        :param title:
            Title for the column of the django admin table.  If not given it
            defaults to a capitalized version of ``attr``

        :param display:
            What to display as the text for the link being shown.  If not
            given it defaults to the string representation of the object for
            the row: ``str(obj)`` .  This parameter supports django
            templating, the context for which contains a dictionary key named
            "obj" with the value being the object for the row.

        Example usage:

        .. code-block:: python

            # ---- admin.py file ----

            base = fancy_modeladmin('id')
            base.add_link('author', 'Our Authors',
                '{{obj.name}} (id={{obj.id}})')

            @admin.register(Book)
            class BookAdmin(base):
                pass

        The django admin change page for the Book class would have a column
        for "id" and another titled "Our Authors". The "Our Authors" column
        would have a link for each Author object referenced by "book.author".
        The link would go to the Author django admin change listing. The
        display of the link would be the name of the author with the id in
        brakcets, e.g. "Douglas Adams (id=42)"


@classmethod<EOL><INDENT>def add_object(cls, attr, title='<STR_LIT>', display='<STR_LIT>'):<DEDENT>

global klass_count<EOL>klass_count += <NUM_LIT:1><EOL>fn_name = '<STR_LIT>' % klass_count<EOL>cls.list_display.append(fn_name)<EOL>if not title:<EOL><INDENT>title = attr.capitalize()<EOL><DEDENT>_display = display<EOL>def _ref(self, obj):<EOL><INDENT>field_obj = admin_obj_attr(obj, attr)<EOL>if not field_obj:<EOL><INDENT>return '<STR_LIT>'<EOL><DEDENT>return _obj_display(field_obj, _display)<EOL><DEDENT>_ref.short_description = title<EOL>_ref.allow_tags = True<EOL>_ref.admin_order_field = attr<EOL>setattr(cls, fn_name, _ref)<EOL>

Adds a ``list_display`` attribute showing an object.  Supports
        double underscore attribute name dereferencing.

        :param attr:
            Name of the attribute to dereference from the corresponding
            object, i.e. what will be lined to.  This name supports double
            underscore object link referencing for ``models.ForeignKey``
            members.

        :param title:
            Title for the column of the django admin table.  If not given it
            defaults to a capitalized version of ``attr``

        :param display:
            What to display as the text for the link being shown.  If not
            given it defaults to the string representation of the object for
            the row: ``str(obj)``.  This parameter supports django templating,
            the context for which contains a dictionary key named "obj" with
            the value being the object for the row.


@classmethod<EOL><INDENT>def add_formatted_field(cls, field, format_string, title='<STR_LIT>'):<DEDENT>

global klass_count<EOL>klass_count += <NUM_LIT:1><EOL>fn_name = '<STR_LIT>' % klass_count<EOL>cls.list_display.append(fn_name)<EOL>if not title:<EOL><INDENT>title = field.capitalize()<EOL><DEDENT>_format_string = format_string<EOL>def _ref(self, obj):<EOL><INDENT>return _format_string % getattr(obj, field)<EOL><DEDENT>_ref.short_description = title<EOL>_ref.allow_tags = True<EOL>_ref.admin_order_field = field<EOL>setattr(cls, fn_name, _ref)<EOL>

Adds a ``list_display`` attribute showing a field in the object
        using a python %formatted string.

        :param field:
            Name of the field in the object.

        :param format_string:
            A old-style (to remain python 2.x compatible) % string formatter
            with a single variable reference. The named ``field`` attribute
            will be passed to the formatter using the "%" operator.

        :param title:
            Title for the column of the django admin table.  If not given it
            defaults to a capitalized version of ``field``


field_names = (<EOL>'<STR_LIT>', <EOL>'<STR_LIT>', <EOL>'<STR_LIT>', <EOL>'<STR_LIT>', <EOL>'<STR_LIT>',<EOL>'<STR_LIT>'<EOL>)<EOL>validator = CSVValidator(field_names)<EOL>validator.add_header_check('<STR_LIT>', '<STR_LIT>')<EOL>validator.add_record_length_check('<STR_LIT>', '<STR_LIT>')<EOL>validator.add_value_check('<STR_LIT>', int, <EOL>'<STR_LIT>', '<STR_LIT>')<EOL>validator.add_value_check('<STR_LIT>', int, <EOL>'<STR_LIT>', '<STR_LIT>')<EOL>validator.add_value_check('<STR_LIT>', enumeration('<STR_LIT:M>', '<STR_LIT:F>'), <EOL>'<STR_LIT>', '<STR_LIT>')<EOL>validator.add_value_check('<STR_LIT>', number_range_inclusive(<NUM_LIT:0>, <NUM_LIT>, int), <EOL>'<STR_LIT>', '<STR_LIT>')<EOL>validator.add_value_check('<STR_LIT>', datetime_string('<STR_LIT>'),<EOL>'<STR_LIT>', '<STR_LIT>')<EOL>def check_age_variables(r):<EOL><INDENT>age_years = int(r['<STR_LIT>'])<EOL>age_months = int(r['<STR_LIT>'])<EOL>valid = (age_months >= age_years * <NUM_LIT:12> and <EOL>age_months % age_years < <NUM_LIT:12>)<EOL>if not valid:<EOL><INDENT>raise RecordError('<STR_LIT>', '<STR_LIT>')<EOL><DEDENT><DEDENT>validator.add_record_check(check_age_variables)<EOL>return validator<EOL>

Create an example CSV validator for patient demographic data.


<EOL>description = '<STR_LIT>'<EOL>parser = argparse.ArgumentParser(description=description)<EOL>parser.add_argument('<STR_LIT:file>', <EOL>metavar='<STR_LIT>', <EOL>help='<STR_LIT>')<EOL>parser.add_argument('<STR_LIT>', '<STR_LIT>',<EOL>dest='<STR_LIT>',<EOL>type=int,<EOL>action='<STR_LIT:store>',<EOL>default=<NUM_LIT:0>,<EOL>help='<STR_LIT>'<EOL>)<EOL>parser.add_argument('<STR_LIT>', '<STR_LIT>',<EOL>dest='<STR_LIT>',<EOL>action='<STR_LIT:store_true>',<EOL>default=False,<EOL>help='<STR_LIT>'<EOL>)<EOL>parser.add_argument('<STR_LIT>', '<STR_LIT>',<EOL>dest='<STR_LIT>',<EOL>action='<STR_LIT:store_true>',<EOL>default=False,<EOL>help='<STR_LIT>'<EOL>)<EOL>args = parser.parse_args()<EOL>if not os.path.isfile(args.file):<EOL><INDENT>print('<STR_LIT>' % args.file)<EOL>sys.exit(<NUM_LIT:1>)<EOL><DEDENT>with open(args.file, '<STR_LIT:r>') as f:<EOL><INDENT>data = csv.reader(f, delimiter='<STR_LIT:\t>')<EOL>validator = create_validator()<EOL>problems = validator.validate(data, <EOL>summarize=args.summarize,<EOL>report_unexpected_exceptions=args.report_unexpected_exceptions,<EOL>context={'<STR_LIT:file>': args.file})<EOL>write_problems(problems, sys.stdout, <EOL>summarize=args.summarize, <EOL>limit=args.limit)<EOL>if problems: <EOL><INDENT>sys.exit(<NUM_LIT:1>)<EOL><DEDENT>else:<EOL><INDENT>sys.exit(<NUM_LIT:0>)<EOL><DEDENT><DEDENT>


assert len(args) > <NUM_LIT:0>, '<STR_LIT>'<EOL>if len(args) == <NUM_LIT:1>:<EOL><INDENT>members = args[<NUM_LIT:0>]<EOL><DEDENT>else:<EOL><INDENT>members = args<EOL><DEDENT>def checker(value):<EOL><INDENT>if value not in members:<EOL><INDENT>raise ValueError(value)<EOL><DEDENT><DEDENT>return checker<EOL>

Return a value check function which raises a value error if the value is not
in a pre-defined enumeration of values.

If you pass in a list, tuple or set as the single argument, it is assumed
that the list/tuple/set defines the membership of the enumeration.

If you pass in more than on argument, it is assumed the arguments themselves
define the enumeration.


prog = re.compile(regex)<EOL>def checker(v):<EOL><INDENT>result = prog.match(v)<EOL>if result is None:<EOL><INDENT>raise ValueError(v)<EOL><DEDENT><DEDENT>return checker<EOL>

Return a value check function which raises a ValueError if the value does
not match the supplied regular expression, see also `re.match`.


prog = re.compile(regex)<EOL>def checker(v):<EOL><INDENT>result = prog.search(v)<EOL>if result is None:<EOL><INDENT>raise ValueError(v)<EOL><DEDENT><DEDENT>return checker<EOL>

Return a value check function which raises a ValueError if the supplied
regular expression does not match anywhere in the value, see also
`re.search`.


def number_range_inclusive(min, max, type=float):

def checker(v):<EOL><INDENT>if type(v) < min or type(v) > max:<EOL><INDENT>raise ValueError(v)<EOL><DEDENT><DEDENT>return checker<EOL>

Return a value check function which raises a ValueError if the supplied
value when cast as `type` is less than `min` or greater than `max`.


def number_range_exclusive(min, max, type=float):

def checker(v):<EOL><INDENT>if type(v) <= min or type(v) >= max:<EOL><INDENT>raise ValueError(v)<EOL><DEDENT><DEDENT>return checker<EOL>

Return a value check function which raises a ValueError if the supplied
value when cast as `type` is less than or equal to `min` or greater than
or equal to `max`.


def checker(v):<EOL><INDENT>datetime.strptime(v, format)<EOL><DEDENT>return checker<EOL>

Return a value check function which raises a ValueError if the supplied
value cannot be converted to a datetime using the supplied format string.

See also `datetime.strptime`.


def datetime_range_inclusive(min, max, format):

dmin = datetime.strptime(min, format)<EOL>dmax = datetime.strptime(max, format)<EOL>def checker(v):<EOL><INDENT>dv = datetime.strptime(v, format)<EOL>if dv < dmin or dv > dmax:<EOL><INDENT>raise ValueError(v)<EOL><DEDENT><DEDENT>return checker<EOL>

Return a value check function which raises a ValueError if the supplied
value when converted to a datetime using the supplied `format` string is
less than `min` or greater than `max`.


def datetime_range_exclusive(min, max, format):

dmin = datetime.strptime(min, format)<EOL>dmax = datetime.strptime(max, format)<EOL>def checker(v):<EOL><INDENT>dv = datetime.strptime(v, format)<EOL>if dv <= dmin or dv >= dmax:<EOL><INDENT>raise ValueError(v)<EOL><DEDENT><DEDENT>return checker<EOL>

Return a value check function which raises a ValueError if the supplied
value when converted to a datetime using the supplied `format` string is
less than or equal to `min` or greater than or equal to `max`.


def write_problems(problems, file, summarize=False, limit=<NUM_LIT:0>):

w = file.write <EOL>w("""<STR_LIT>"""<EOL>lems<EOL>====<EOL>total += <NUM_LIT:1><EOL>code = p['<STR_LIT:code>']<EOL>if code in counts:<EOL>counts[code] += <NUM_LIT:1><EOL>else:<EOL>counts[code] = <NUM_LIT:1><EOL>if not summarize:<EOL>ptitle = '<STR_LIT>' % (p['<STR_LIT:code>'], p['<STR_LIT:message>'])<EOL>w(ptitle)<EOL>underline = '<STR_LIT>'<EOL>for i in range(len(ptitle.strip())):<EOL>underline += '<STR_LIT:->'<EOL>underline += '<STR_LIT:\n>'<EOL>w(underline)<EOL>for k in sorted(p.viewkeys() - set(['<STR_LIT:code>', '<STR_LIT:message>', '<STR_LIT>'])):<EOL>w('<STR_LIT>' % (k, p[k]))<EOL>if '<STR_LIT>' in p:<EOL>c = p['<STR_LIT>']<EOL>for k in sorted(c.viewkeys()):<EOL>w('<STR_LIT>' % (k, c[k]))<EOL>w(

Write problems as restructured text to a file (or stdout/stderr).


self._field_names = tuple(field_names)<EOL>self._value_checks = []<EOL>self._header_checks = []<EOL>self._record_length_checks = []<EOL>self._value_predicates = []<EOL>self._record_checks = []<EOL>self._record_predicates = []<EOL>self._unique_checks = []<EOL>self._skips = []<EOL>

Instantiate a `CSVValidator`, supplying expected `field_names` as a
sequence of strings.


t = code, message<EOL>self._header_checks.append(t)<EOL>

Add a header check, i.e., check whether the header record is consistent
with the expected field names.

Arguments
---------

`code` - problem code to report if the header record is not valid,
defaults to `HEADER_CHECK_FAILED`

`message` - problem message to report if a value is not valid


t = code, message, modulus<EOL>self._record_length_checks.append(t)<EOL>

Add a record length check, i.e., check whether the length of a record is
consistent with the number of expected fields.

Arguments
---------

`code` - problem code to report if a record is not valid, defaults to
`RECORD_LENGTH_CHECK_FAILED`

`message` - problem message to report if a record is not valid

`modulus` - apply the check to every nth record, defaults to 1 (check
every record)


<EOL>assert field_name in self._field_names, '<STR_LIT>' % field_name<EOL>assert callable(value_check), '<STR_LIT>'<EOL>t = field_name, value_check, code, message, modulus<EOL>self._value_checks.append(t)<EOL>

Add a value check function for the specified field.

Arguments
---------

`field_name` - the name of the field to attach the value check function
to

`value_check` - a function that accepts a single argument (a value) and
raises a `ValueError` if the value is not valid

`code` - problem code to report if a value is not valid, defaults to
`VALUE_CHECK_FAILED`

`message` - problem message to report if a value is not valid

`modulus` - apply the check to every nth record, defaults to 1 (check
every record)


assert field_name in self._field_names, '<STR_LIT>' % field_name<EOL>assert callable(value_predicate), '<STR_LIT>'<EOL>t = field_name, value_predicate, code, message, modulus<EOL>self._value_predicates.append(t)<EOL>

Add a value predicate function for the specified field.

N.B., everything you can do with value predicates can also be done with
value check functions, whether you use one or the other is a matter of
style.

Arguments
---------

`field_name` - the name of the field to attach the value predicate
function to

`value_predicate` - a function that accepts a single argument (a value)
and returns False if the value is not valid

`code` - problem code to report if a value is not valid, defaults to
`VALUE_PREDICATE_FALSE`

`message` - problem message to report if a value is not valid

`modulus` - apply the check to every nth record, defaults to 1 (check
every record)


def add_record_check(self, record_check, modulus=<NUM_LIT:1>):

assert callable(record_check), '<STR_LIT>'<EOL>t = record_check, modulus<EOL>self._record_checks.append(t)<EOL>

Add a record check function.

Arguments
---------

`record_check` - a function that accepts a single argument (a record as
a dictionary of values indexed by field name) and raises a
`RecordError` if the record is not valid

`modulus` - apply the check to every nth record, defaults to 1 (check
every record)


assert callable(record_predicate), '<STR_LIT>'<EOL>t = record_predicate, code, message, modulus<EOL>self._record_predicates.append(t)<EOL>

Add a record predicate function.

N.B., everything you can do with record predicates can also be done with
record check functions, whether you use one or the other is a matter of
style.

Arguments
---------

`record_predicate` - a function that accepts a single argument (a record
as a dictionary of values indexed by field name) and returns False if
the value is not valid

`code` - problem code to report if a record is not valid, defaults to
`RECORD_PREDICATE_FALSE`

`message` - problem message to report if a record is not valid

`modulus` - apply the check to every nth record, defaults to 1 (check
every record)


if isinstance(key, basestring):<EOL><INDENT>assert key in self._field_names, '<STR_LIT>' % key<EOL><DEDENT>else:<EOL><INDENT>for f in key:<EOL><INDENT>assert f in self._field_names, '<STR_LIT>' % key<EOL><DEDENT><DEDENT>t = key, code, message<EOL>self._unique_checks.append(t)<EOL>

Add a unique check on a single column or combination of columns.

Arguments
---------

`key` - a single field name (string) specifying a field in which all
values are expected to be unique, or a sequence of field names (tuple
or list of strings) specifying a compound key

`code` - problem code to report if a record is not valid, defaults to
`UNIQUE_CHECK_FAILED`

`message` - problem message to report if a record is not valid


assert callable(skip), '<STR_LIT>'<EOL>self._skips.append(skip)<EOL>

Add a `skip` function which accepts a single argument (a record as a
sequence of values) and returns True if all checks on the record should
be skipped.


problems = list()<EOL>problem_generator = self.ivalidate(data, expect_header_row,<EOL>ignore_lines, summarize, context,<EOL>report_unexpected_exceptions)<EOL>for i, p in enumerate(problem_generator):<EOL><INDENT>if not limit or i < limit:<EOL><INDENT>problems.append(p)<EOL><DEDENT><DEDENT>return problems<EOL>

Validate `data` and return a list of validation problems found.

Arguments
---------

`data` - any source of row-oriented data, e.g., as provided by a
`csv.reader`, or a list of lists of strings, or ...

`expect_header_row` - does the data contain a header row (i.e., the
first record is a list of field names)? Defaults to True.

`ignore_lines` - ignore n lines (rows) at the beginning of the data

`summarize` - only report problem codes, no other details

`limit` - report at most n problems

`context` - a dictionary of any additional information to be added to
any problems found - useful if problems are being aggregated from
multiple validators

`report_unexpected_exceptions` - value check function, value predicates,
record check functions, record predicates, and other user-supplied
validation functions may raise unexpected exceptions. If this argument
is true, any unexpected exceptions will be reported as validation
problems; if False, unexpected exceptions will be handled silently.


unique_sets = self._init_unique_sets() <EOL>for i, r in enumerate(data):<EOL><INDENT>if expect_header_row and i == ignore_lines:<EOL><INDENT>for p in self._apply_header_checks(i, r, summarize, context):<EOL><INDENT>yield p<EOL><DEDENT><DEDENT>elif i >= ignore_lines:<EOL><INDENT>skip = False<EOL>for p in self._apply_skips(i, r, summarize,<EOL>report_unexpected_exceptions,<EOL>context):<EOL><INDENT>if p is True:<EOL><INDENT>skip = True<EOL><DEDENT>else:<EOL><INDENT>yield p<EOL><DEDENT><DEDENT>if not skip:<EOL><INDENT>for p in self._apply_each_methods(i, r, summarize,<EOL>report_unexpected_exceptions,<EOL>context):<EOL><INDENT>yield p <EOL><DEDENT>for p in self._apply_value_checks(i, r, summarize,<EOL>report_unexpected_exceptions,<EOL>context):<EOL><INDENT>yield p<EOL><DEDENT>for p in self._apply_record_length_checks(i, r, summarize,<EOL>context):<EOL><INDENT>yield p<EOL><DEDENT>for p in self._apply_value_predicates(i, r, summarize,<EOL>report_unexpected_exceptions,<EOL>context):<EOL><INDENT>yield p<EOL><DEDENT>for p in self._apply_record_checks(i, r, summarize,<EOL>report_unexpected_exceptions,<EOL>context):<EOL><INDENT>yield p<EOL><DEDENT>for p in self._apply_record_predicates(i, r, summarize,<EOL>report_unexpected_exceptions,<EOL>context):<EOL><INDENT>yield p<EOL><DEDENT>for p in self._apply_unique_checks(i, r, unique_sets, summarize):<EOL><INDENT>yield p<EOL><DEDENT>for p in self._apply_check_methods(i, r, summarize,<EOL>report_unexpected_exceptions,<EOL>context):<EOL><INDENT>yield p<EOL><DEDENT>for p in self._apply_assert_methods(i, r, summarize,<EOL>report_unexpected_exceptions,<EOL>context):<EOL><INDENT>yield p<EOL><DEDENT><DEDENT><DEDENT><DEDENT>for p in self._apply_finally_assert_methods(summarize,<EOL>report_unexpected_exceptions,<EOL>context):<EOL><INDENT>yield p<EOL><DEDENT>

Validate `data` and return a iterator over problems found.

Use this function rather than validate() if you expect a large number
of problems.

Arguments
---------

`data` - any source of row-oriented data, e.g., as provided by a
`csv.reader`, or a list of lists of strings, or ...

`expect_header_row` - does the data contain a header row (i.e., the
first record is a list of field names)? Defaults to True.

`ignore_lines` - ignore n lines (rows) at the beginning of the data

`summarize` - only report problem codes, no other details

`context` - a dictionary of any additional information to be added to
any problems found - useful if problems are being aggregated from
multiple validators

`report_unexpected_exceptions` - value check function, value predicates,
record check functions, record predicates, and other user-supplied
validation functions may raise unexpected exceptions. If this argument
is true, any unexpected exceptions will be reported as validation
problems; if False, unexpected exceptions will be handled silently.


ks = dict()<EOL>for t in self._unique_checks:<EOL><INDENT>key = t[<NUM_LIT:0>]<EOL>ks[key] = set() <EOL><DEDENT>return ks<EOL>

Initialise sets used for uniqueness checking.


def _apply_value_checks(self, i, r,<EOL>summarize=False,<EOL>report_unexpected_exceptions=True,<EOL>context=None):

for field_name, check, code, message, modulus in self._value_checks:<EOL><INDENT>if i % modulus == <NUM_LIT:0>: <EOL><INDENT>fi = self._field_names.index(field_name)<EOL>if fi < len(r): <EOL><INDENT>value = r[fi]<EOL>try:<EOL><INDENT>check(value)<EOL><DEDENT>except ValueError:<EOL><INDENT>p = {'<STR_LIT:code>': code}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = message<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = fi + <NUM_LIT:1><EOL>p['<STR_LIT>'] = field_name<EOL>p['<STR_LIT:value>'] = value<EOL>p['<STR_LIT>'] = r<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT>except Exception as e:<EOL><INDENT>if report_unexpected_exceptions:<EOL><INDENT>p = {'<STR_LIT:code>': UNEXPECTED_EXCEPTION}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = MESSAGES[UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = fi + <NUM_LIT:1><EOL>p['<STR_LIT>'] = field_name<EOL>p['<STR_LIT:value>'] = value<EOL>p['<STR_LIT>'] = r<EOL>p['<STR_LIT>'] = e<EOL>p['<STR_LIT>'] = '<STR_LIT>' % (check.__name__,<EOL>check.__doc__)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT><DEDENT><DEDENT><DEDENT>

Apply value check functions on the given record `r`.


def _apply_header_checks(self, i, r, summarize=False, context=None):

for code, message in self._header_checks:<EOL><INDENT>if tuple(r) != self._field_names:<EOL><INDENT>p = {'<STR_LIT:code>': code}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = message<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = tuple(r)<EOL>p['<STR_LIT>'] = set(self._field_names) - set(r)<EOL>p['<STR_LIT>'] = set(r) - set(self._field_names)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT>

Apply header checks on the given record `r`.


def _apply_record_length_checks(self, i, r, summarize=False, context=None):

for code, message, modulus in self._record_length_checks:<EOL><INDENT>if i % modulus == <NUM_LIT:0>: <EOL><INDENT>if len(r) != len(self._field_names):<EOL><INDENT>p = {'<STR_LIT:code>': code}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = message<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>p['<STR_LIT>'] = len(r)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT><DEDENT>

Apply record length checks on the given record `r`.


def _apply_value_predicates(self, i, r,<EOL>summarize=False,<EOL>report_unexpected_exceptions=True,<EOL>context=None):

for field_name, predicate, code, message, modulus in self._value_predicates:<EOL><INDENT>if i % modulus == <NUM_LIT:0>: <EOL><INDENT>fi = self._field_names.index(field_name)<EOL>if fi < len(r): <EOL><INDENT>value = r[fi]<EOL>try:<EOL><INDENT>valid = predicate(value)<EOL>if not valid:<EOL><INDENT>p = {'<STR_LIT:code>': code}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = message<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = fi + <NUM_LIT:1><EOL>p['<STR_LIT>'] = field_name<EOL>p['<STR_LIT:value>'] = value<EOL>p['<STR_LIT>'] = r<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT>except Exception as e:<EOL><INDENT>if report_unexpected_exceptions:<EOL><INDENT>p = {'<STR_LIT:code>': UNEXPECTED_EXCEPTION}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = MESSAGES[UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = fi + <NUM_LIT:1><EOL>p['<STR_LIT>'] = field_name<EOL>p['<STR_LIT:value>'] = value<EOL>p['<STR_LIT>'] = r<EOL>p['<STR_LIT>'] = e<EOL>p['<STR_LIT>'] = '<STR_LIT>' % (predicate.__name__,<EOL>predicate.__doc__)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT><DEDENT><DEDENT><DEDENT>

Apply value predicates on the given record `r`.


def _apply_record_checks(self, i, r,<EOL>summarize=False,<EOL>report_unexpected_exceptions=True,<EOL>context=None):

for check, modulus in self._record_checks:<EOL><INDENT>if i % modulus == <NUM_LIT:0>: <EOL><INDENT>rdict = self._as_dict(r)<EOL>try:<EOL><INDENT>check(rdict)<EOL><DEDENT>except RecordError as e:<EOL><INDENT>code = e.code if e.code is not None else RECORD_CHECK_FAILED<EOL>p = {'<STR_LIT:code>': code}<EOL>if not summarize:<EOL><INDENT>message = e.message if e.message is not None else MESSAGES[RECORD_CHECK_FAILED]<EOL>p['<STR_LIT:message>'] = message<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>if context is not None: p['<STR_LIT>'] = context<EOL>if e.details is not None: p['<STR_LIT>'] = e.details<EOL><DEDENT>yield p<EOL><DEDENT>except Exception as e:<EOL><INDENT>if report_unexpected_exceptions:<EOL><INDENT>p = {'<STR_LIT:code>': UNEXPECTED_EXCEPTION}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = MESSAGES[UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>p['<STR_LIT>'] = e<EOL>p['<STR_LIT>'] = '<STR_LIT>' % (check.__name__,<EOL>check.__doc__)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT><DEDENT><DEDENT>


def _apply_record_predicates(self, i, r,<EOL>summarize=False,<EOL>report_unexpected_exceptions=True,<EOL>context=None):

for predicate, code, message, modulus in self._record_predicates:<EOL><INDENT>if i % modulus == <NUM_LIT:0>: <EOL><INDENT>rdict = self._as_dict(r)<EOL>try:<EOL><INDENT>valid = predicate(rdict)<EOL>if not valid:<EOL><INDENT>p = {'<STR_LIT:code>': code}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = message<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT>except Exception as e:<EOL><INDENT>if report_unexpected_exceptions:<EOL><INDENT>p = {'<STR_LIT:code>': UNEXPECTED_EXCEPTION}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = MESSAGES[UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>p['<STR_LIT>'] = e<EOL>p['<STR_LIT>'] = '<STR_LIT>' % (predicate.__name__,<EOL>predicate.__doc__)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT><DEDENT><DEDENT>


def _apply_unique_checks(self, i, r, unique_sets,<EOL>summarize=False,<EOL>context=None):

for key, code, message in self._unique_checks:<EOL><INDENT>value = None<EOL>values = unique_sets[key]<EOL>if isinstance(key, basestring): <EOL><INDENT>fi = self._field_names.index(key)<EOL>if fi >= len(r):<EOL><INDENT>continue<EOL><DEDENT>value = r[fi]<EOL><DEDENT>else: <EOL><INDENT>value = []<EOL>for f in key:<EOL><INDENT>fi = self._field_names.index(f)<EOL>if fi >= len(r):<EOL><INDENT>break<EOL><DEDENT>value.append(r[fi])<EOL><DEDENT>value = tuple(value) <EOL><DEDENT>if value in values:<EOL><INDENT>p = {'<STR_LIT:code>': code}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = message<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>p['<STR_LIT:key>'] = key<EOL>p['<STR_LIT:value>'] = value<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT>values.add(value)<EOL><DEDENT>


def _apply_each_methods(self, i, r,<EOL>summarize=False,<EOL>report_unexpected_exceptions=True,<EOL>context=None):

for a in dir(self):<EOL><INDENT>if a.startswith('<STR_LIT>'):<EOL><INDENT>rdict = self._as_dict(r)<EOL>f = getattr(self, a)<EOL>try:<EOL><INDENT>f(rdict)<EOL><DEDENT>except Exception as e:<EOL><INDENT>if report_unexpected_exceptions:<EOL><INDENT>p = {'<STR_LIT:code>': UNEXPECTED_EXCEPTION}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = MESSAGES[UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>p['<STR_LIT>'] = e<EOL>p['<STR_LIT>'] = '<STR_LIT>' % (f.__name__,<EOL>f.__doc__)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT><DEDENT><DEDENT>


def _apply_assert_methods(self, i, r,<EOL>summarize=False,<EOL>report_unexpected_exceptions=True,<EOL>context=None):

for a in dir(self):<EOL><INDENT>if a.startswith('<STR_LIT>'):<EOL><INDENT>rdict = self._as_dict(r)<EOL>f = getattr(self, a)<EOL>try:<EOL><INDENT>f(rdict)<EOL><DEDENT>except AssertionError as e:<EOL><INDENT>code = ASSERT_CHECK_FAILED<EOL>message = MESSAGES[ASSERT_CHECK_FAILED]<EOL>if len(e.args) > <NUM_LIT:0>:<EOL><INDENT>custom = e.args[<NUM_LIT:0>]<EOL>if isinstance(custom, (list, tuple)):<EOL><INDENT>if len(custom) > <NUM_LIT:0>:<EOL><INDENT>code = custom[<NUM_LIT:0>]<EOL><DEDENT>if len(custom) > <NUM_LIT:1>:<EOL><INDENT>message = custom[<NUM_LIT:1>]<EOL><DEDENT><DEDENT>else:<EOL><INDENT>code = custom<EOL><DEDENT><DEDENT>p = {'<STR_LIT:code>': code}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = message<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT>except Exception as e:<EOL><INDENT>if report_unexpected_exceptions:<EOL><INDENT>p = {'<STR_LIT:code>': UNEXPECTED_EXCEPTION}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = MESSAGES[UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>p['<STR_LIT>'] = e<EOL>p['<STR_LIT>'] = '<STR_LIT>' % (f.__name__,<EOL>f.__doc__)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT><DEDENT><DEDENT>


def _apply_check_methods(self, i, r,<EOL>summarize=False,<EOL>report_unexpected_exceptions=True,<EOL>context=None):

for a in dir(self):<EOL><INDENT>if a.startswith('<STR_LIT>'):<EOL><INDENT>rdict = self._as_dict(r)<EOL>f = getattr(self, a)<EOL>try:<EOL><INDENT>f(rdict)<EOL><DEDENT>except RecordError as e:<EOL><INDENT>code = e.code if e.code is not None else RECORD_CHECK_FAILED<EOL>p = {'<STR_LIT:code>': code}<EOL>if not summarize:<EOL><INDENT>message = e.message if e.message is not None else MESSAGES[RECORD_CHECK_FAILED]<EOL>p['<STR_LIT:message>'] = message<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>if context is not None: p['<STR_LIT>'] = context<EOL>if e.details is not None: p['<STR_LIT>'] = e.details<EOL><DEDENT>yield p<EOL><DEDENT>except Exception as e:<EOL><INDENT>if report_unexpected_exceptions:<EOL><INDENT>p = {'<STR_LIT:code>': UNEXPECTED_EXCEPTION}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = MESSAGES[UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>p['<STR_LIT>'] = e<EOL>p['<STR_LIT>'] = '<STR_LIT>' % (f.__name__,<EOL>f.__doc__)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT><DEDENT><DEDENT>


for a in dir(self):<EOL><INDENT>if a.startswith('<STR_LIT>'):<EOL><INDENT>f = getattr(self, a)<EOL>try:<EOL><INDENT>f()<EOL><DEDENT>except AssertionError as e:<EOL><INDENT>code = ASSERT_CHECK_FAILED<EOL>message = MESSAGES[ASSERT_CHECK_FAILED]<EOL>if len(e.args) > <NUM_LIT:0>:<EOL><INDENT>custom = e.args[<NUM_LIT:0>]<EOL>if isinstance(custom, (list, tuple)):<EOL><INDENT>if len(custom) > <NUM_LIT:0>:<EOL><INDENT>code = custom[<NUM_LIT:0>]<EOL><DEDENT>if len(custom) > <NUM_LIT:1>:<EOL><INDENT>message = custom[<NUM_LIT:1>]<EOL><DEDENT><DEDENT>else:<EOL><INDENT>code = custom<EOL><DEDENT><DEDENT>p = {'<STR_LIT:code>': code}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = message<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT>except Exception as e:<EOL><INDENT>if report_unexpected_exceptions:<EOL><INDENT>p = {'<STR_LIT:code>': UNEXPECTED_EXCEPTION}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = MESSAGES[UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)<EOL>p['<STR_LIT>'] = e<EOL>p['<STR_LIT>'] = '<STR_LIT>' % (f.__name__,<EOL>f.__doc__)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT><DEDENT><DEDENT>


def _apply_skips(self, i, r,<EOL>summarize=False,<EOL>report_unexpected_exceptions=True,<EOL>context=None):

for skip in self._skips:<EOL><INDENT>try:<EOL><INDENT>result = skip(r)<EOL>if result is True:<EOL><INDENT>yield True<EOL><DEDENT><DEDENT>except Exception as e:<EOL><INDENT>if report_unexpected_exceptions:<EOL><INDENT>p = {'<STR_LIT:code>': UNEXPECTED_EXCEPTION}<EOL>if not summarize:<EOL><INDENT>p['<STR_LIT:message>'] = MESSAGES[UNEXPECTED_EXCEPTION] % (e.__class__.__name__, e)<EOL>p['<STR_LIT>'] = i + <NUM_LIT:1><EOL>p['<STR_LIT>'] = r<EOL>p['<STR_LIT>'] = e<EOL>p['<STR_LIT>'] = '<STR_LIT>' % (skip.__name__,<EOL>skip.__doc__)<EOL>if context is not None: p['<STR_LIT>'] = context<EOL><DEDENT>yield p<EOL><DEDENT><DEDENT><DEDENT>


d = dict()<EOL>for i, f in enumerate(self._field_names):<EOL><INDENT>d[f] = r[i] if i < len(r) else None<EOL><DEDENT>return d<EOL>

Convert the record to a dictionary using field names as keys.


def _decode_field(message, field, value):

if field.type == FieldDescriptor.TYPE_MESSAGE:<EOL><INDENT>decode(getattr(message, field.name), value)<EOL><DEDENT>else:<EOL><INDENT>try:<EOL><INDENT>if field.type == FieldDescriptor.TYPE_BYTES:<EOL><INDENT>value = base64.b64decode(value)<EOL><DEDENT>setattr(message, field.name, value)<EOL><DEDENT>except (ValueError, TypeError) as e:<EOL><INDENT>logger.warning('<STR_LIT>',<EOL>message.__class__.__name__, field.name, e)<EOL><DEDENT><DEDENT>


def _decode_repeated_field(message, field, value_list):

if field.type == FieldDescriptor.TYPE_MESSAGE:<EOL><INDENT>for value in value_list:<EOL><INDENT>decode(getattr(message, field.name).add(), value)<EOL><DEDENT><DEDENT>else:<EOL><INDENT>try:<EOL><INDENT>for value in value_list:<EOL><INDENT>if field.type == FieldDescriptor.TYPE_BYTES:<EOL><INDENT>value = base64.b64decode(value)<EOL><DEDENT>getattr(message, field.name).append(value)<EOL><DEDENT><DEDENT>except (ValueError, TypeError) as e:<EOL><INDENT>logger.warning('<STR_LIT>',<EOL>message.__class__.__name__, field.name, e)<EOL>message.ClearField(field.name)<EOL><DEDENT><DEDENT>


def decode(message, pblite, ignore_first_item=False):

if not isinstance(pblite, list):<EOL><INDENT>logger.warning('<STR_LIT>',<EOL>type(pblite))<EOL>return<EOL><DEDENT>if ignore_first_item:<EOL><INDENT>pblite = pblite[<NUM_LIT:1>:]<EOL><DEDENT>if pblite and isinstance(pblite[-<NUM_LIT:1>], dict):<EOL><INDENT>extra_fields = {int(field_number): value for field_number, value<EOL>in pblite[-<NUM_LIT:1>].items()}<EOL>pblite = pblite[:-<NUM_LIT:1>]<EOL><DEDENT>else:<EOL><INDENT>extra_fields = {}<EOL><DEDENT>fields_values = itertools.chain(enumerate(pblite, start=<NUM_LIT:1>),<EOL>extra_fields.items())<EOL>for field_number, value in fields_values:<EOL><INDENT>if value is None:<EOL><INDENT>continue<EOL><DEDENT>try:<EOL><INDENT>field = message.DESCRIPTOR.fields_by_number[field_number]<EOL><DEDENT>except KeyError:<EOL><INDENT>if value not in [[], '<STR_LIT>', <NUM_LIT:0>]:<EOL><INDENT>logger.debug('<STR_LIT>'<EOL>'<STR_LIT>', message.__class__.__name__, field_number,<EOL>value)<EOL><DEDENT>continue<EOL><DEDENT>if field.label == FieldDescriptor.LABEL_REPEATED:<EOL><INDENT>_decode_repeated_field(message, field, value)<EOL><DEDENT>else:<EOL><INDENT>_decode_field(message, field, value)<EOL><DEDENT><DEDENT>

Decode pblite to Protocol Buffer message.

    This method is permissive of decoding errors and will log them as warnings
    and continue decoding where possible.

    The first element of the outer pblite list must often be ignored using the
    ignore_first_item parameter because it contains an abbreviation of the name
    of the protobuf message (eg.  cscmrp for ClientSendChatMessageResponseP)
    that's not part of the protobuf.

    Args:
        message: protocol buffer message instance to decode into.
        pblite: list representing a pblite-serialized message.
        ignore_first_item: If True, ignore the item at index 0 in the pblite
            list, making the item at index 1 correspond to field 1 in the
            message.


return (MARKDOWN_START.format(tag=tag), MARKDOWN_END.format(tag=tag))<EOL>

Return start and end regex pattern sequences for simple Markdown tag.


return (HTML_START.format(tag=tag), HTML_END.format(tag=tag))<EOL>

Return sequence of start and end regex patterns for simple HTML tag


return url if url_proto_regex.search(url) else '<STR_LIT>' + url<EOL>

If URL doesn't start with protocol, prepend it with http://


<EOL>return text.replace('<STR_LIT:U+0020>', '<STR_LIT>')<EOL>


<EOL>return markdown_unescape_regex.sub(r'<STR_LIT>', text)<EOL>


decoder = Utf8IncrementalDecoder()<EOL>return decoder.decode(data_bytes)<EOL>

Decode as much of data_bytes as possible as UTF-8.


res = json.loads(list(ChunkParser().get_chunks(res))[<NUM_LIT:0>])<EOL>sid = res[<NUM_LIT:0>][<NUM_LIT:1>][<NUM_LIT:1>]<EOL>gsessionid = res[<NUM_LIT:1>][<NUM_LIT:1>][<NUM_LIT:0>]['<STR_LIT>']<EOL>return (sid, gsessionid)<EOL>

Parse response format for request for new channel SID.

    Example format (after parsing JS):
    [   [0,["c","SID_HERE","",8]],
        [1,[{"gsid":"GSESSIONID_HERE"}]]]

    Returns (SID, gsessionid) tuple.


self._buf += new_data_bytes<EOL>while True:<EOL><INDENT>buf_decoded = _best_effort_decode(self._buf)<EOL>buf_utf16 = buf_decoded.encode('<STR_LIT>')[<NUM_LIT:2>:]<EOL>length_str_match = LEN_REGEX.match(buf_decoded)<EOL>if length_str_match is None:<EOL><INDENT>break<EOL><DEDENT>else:<EOL><INDENT>length_str = length_str_match.group(<NUM_LIT:1>)<EOL>length = int(length_str) * <NUM_LIT:2><EOL>length_length = len((length_str + '<STR_LIT:\n>').encode('<STR_LIT>')[<NUM_LIT:2>:])<EOL>if len(buf_utf16) - length_length < length:<EOL><INDENT>break<EOL><DEDENT>submission = buf_utf16[length_length:length_length + length]<EOL>yield submission.decode('<STR_LIT>')<EOL>drop_length = (len((length_str + '<STR_LIT:\n>').encode()) +<EOL>len(submission.decode('<STR_LIT>').encode()))<EOL>self._buf = self._buf[drop_length:]<EOL><DEDENT><DEDENT>

Yield chunks generated from received data.

        The buffer may not be decodable as UTF-8 if there's a split multi-byte
        character at the end. To handle this, do a "best effort" decode of the
        buffer to decode as much of it as possible.

        The length is actually the length of the string as reported by
        JavaScript. JavaScript's string length function returns the number of
        code units in the string, represented in UTF-16. We can emulate this by
        encoding everything in UTF-16 and multiplying the reported length by 2.

        Note that when encoding a string in UTF-16, Python will prepend a
        byte-order character, so we need to remove the first two bytes.


def __init__(self, session, max_retries, retry_backoff_base):

<EOL>self.on_connect = event.Event('<STR_LIT>')<EOL>self.on_reconnect = event.Event('<STR_LIT>')<EOL>self.on_disconnect = event.Event('<STR_LIT>')<EOL>self.on_receive_array = event.Event('<STR_LIT>')<EOL>self._max_retries = max_retries<EOL>self._retry_backoff_base = retry_backoff_base<EOL>self._is_connected = False<EOL>self._on_connect_called = False<EOL>self._chunk_parser = None<EOL>self._session = session<EOL>self._sid_param = None<EOL>self._gsessionid_param = None<EOL>

Create a new channel.

        Args:
            session (http_utils.Session): Request session.
            max_retries (int): Number of retries for long-polling request.
            retry_backoff_base (int): The base term for the long-polling
                exponential backoff.


return self._is_connected<EOL>

Whether the channel is currently connected.


retries = <NUM_LIT:0>  <EOL>need_new_sid = True  <EOL>while retries <= self._max_retries:<EOL><INDENT>if retries > <NUM_LIT:0>:<EOL><INDENT>backoff_seconds = self._retry_backoff_base ** retries<EOL>logger.info('<STR_LIT>', backoff_seconds)<EOL>await asyncio.sleep(backoff_seconds)<EOL><DEDENT>if need_new_sid:<EOL><INDENT>await self._fetch_channel_sid()<EOL>need_new_sid = False<EOL><DEDENT>self._chunk_parser = ChunkParser()<EOL>try:<EOL><INDENT>await self._longpoll_request()<EOL><DEDENT>except ChannelSessionError as err:<EOL><INDENT>logger.warning('<STR_LIT>', err)<EOL>need_new_sid = True<EOL><DEDENT>except exceptions.NetworkError as err:<EOL><INDENT>logger.warning('<STR_LIT>', err)<EOL><DEDENT>else:<EOL><INDENT>retries = <NUM_LIT:0><EOL>continue<EOL><DEDENT>retries += <NUM_LIT:1><EOL>logger.info('<STR_LIT>', retries)<EOL>if self._is_connected:<EOL><INDENT>self._is_connected = False<EOL>await self.on_disconnect.fire()<EOL><DEDENT><DEDENT>logger.error('<STR_LIT>')<EOL>

Listen for messages on the backwards channel.

        This method only returns when the connection has been closed due to an
        error.


params = {<EOL>'<STR_LIT>': <NUM_LIT:8>,  <EOL>'<STR_LIT>': <NUM_LIT>,  <EOL>'<STR_LIT>': '<STR_LIT>',  <EOL>}<EOL>if self._gsessionid_param is not None:<EOL><INDENT>params['<STR_LIT>'] = self._gsessionid_param<EOL><DEDENT>if self._sid_param is not None:<EOL><INDENT>params['<STR_LIT>'] = self._sid_param<EOL><DEDENT>data_dict = dict(count=len(map_list), ofs=<NUM_LIT:0>)<EOL>for map_num, map_ in enumerate(map_list):<EOL><INDENT>for map_key, map_val in map_.items():<EOL><INDENT>data_dict['<STR_LIT>'.format(map_num, map_key)] = map_val<EOL><DEDENT><DEDENT>res = await self._session.fetch(<EOL>'<STR_LIT>', CHANNEL_URL, params=params, data=data_dict<EOL>)<EOL>return res<EOL>

Sends a request to the server containing maps (dicts).


logger.info('<STR_LIT>')<EOL>self._sid_param = None<EOL>self._gsessionid_param = None<EOL>res = await self.send_maps([])<EOL>self._sid_param, self._gsessionid_param = _parse_sid_response(res.body)<EOL>logger.info('<STR_LIT>'.format(self._sid_param))<EOL>logger.info('<STR_LIT>'.format(self._gsessionid_param))<EOL>

Creates a new channel for receiving push data.

        Sending an empty forward channel request will create a new channel on
        the server.

        There's a separate API to get the gsessionid alone that Hangouts for
        Chrome uses, but if we don't send a gsessionid with this request, it
        will return a gsessionid as well as the SID.

        Raises hangups.NetworkError if the channel can not be created.


params = {<EOL>'<STR_LIT>': <NUM_LIT:8>,  <EOL>'<STR_LIT>': self._gsessionid_param,<EOL>'<STR_LIT>': '<STR_LIT>',  <EOL>'<STR_LIT:t>': <NUM_LIT:1>,  <EOL>'<STR_LIT>': self._sid_param,  <EOL>'<STR_LIT>': <NUM_LIT:0>,  <EOL>'<STR_LIT>': '<STR_LIT>',  <EOL>'<STR_LIT>': '<STR_LIT>',  <EOL>}<EOL>logger.info('<STR_LIT>')<EOL>try:<EOL><INDENT>async with self._session.fetch_raw('<STR_LIT:GET>', CHANNEL_URL,<EOL>params=params) as res:<EOL><INDENT>if res.status != <NUM_LIT:200>:<EOL><INDENT>if res.status == <NUM_LIT> and res.reason == '<STR_LIT>':<EOL><INDENT>raise ChannelSessionError('<STR_LIT>')<EOL><DEDENT>raise exceptions.NetworkError(<EOL>'<STR_LIT>'.format(<EOL>res.status, res.reason))<EOL><DEDENT>while True:<EOL><INDENT>async with async_timeout.timeout(PUSH_TIMEOUT):<EOL><INDENT>chunk = await res.content.read(MAX_READ_BYTES)<EOL><DEDENT>if not chunk:<EOL><INDENT>break<EOL><DEDENT>await self._on_push_data(chunk)<EOL><DEDENT><DEDENT><DEDENT>except asyncio.TimeoutError:<EOL><INDENT>raise exceptions.NetworkError('<STR_LIT>')<EOL><DEDENT>except aiohttp.ServerDisconnectedError as err:<EOL><INDENT>raise exceptions.NetworkError(<EOL>'<STR_LIT>' % err)<EOL><DEDENT>except aiohttp.ClientPayloadError:<EOL><INDENT>raise ChannelSessionError('<STR_LIT>')<EOL><DEDENT>except aiohttp.ClientError as err:<EOL><INDENT>raise exceptions.NetworkError('<STR_LIT>' % err)<EOL><DEDENT>

Open a long-polling request and receive arrays.

        This method uses keep-alive to make re-opening the request faster, but
        the remote server will set the "Connection: close" header once an hour.

        Raises hangups.NetworkError or ChannelSessionError.


async def _on_push_data(self, data_bytes):

logger.debug('<STR_LIT>'.format(data_bytes))<EOL>for chunk in self._chunk_parser.get_chunks(data_bytes):<EOL><INDENT>if not self._is_connected:<EOL><INDENT>if self._on_connect_called:<EOL><INDENT>self._is_connected = True<EOL>await self.on_reconnect.fire()<EOL><DEDENT>else:<EOL><INDENT>self._on_connect_called = True<EOL>self._is_connected = True<EOL>await self.on_connect.fire()<EOL><DEDENT><DEDENT>container_array = json.loads(chunk)<EOL>for inner_array in container_array:<EOL><INDENT>array_id, data_array = inner_array<EOL>logger.debug('<STR_LIT>',<EOL>array_id, data_array)<EOL>await self.on_receive_array.fire(data_array)<EOL><DEDENT><DEDENT>


proxy = os.environ.get('<STR_LIT>')<EOL>self._session = http_utils.Session(self._cookies, proxy=proxy)<EOL>try:<EOL><INDENT>self._channel = channel.Channel(<EOL>self._session, self._max_retries, self._retry_backoff_base<EOL>)<EOL>self._channel.on_connect.add_observer(self.on_connect.fire)<EOL>self._channel.on_reconnect.add_observer(self.on_reconnect.fire)<EOL>self._channel.on_disconnect.add_observer(self.on_disconnect.fire)<EOL>self._channel.on_receive_array.add_observer(self._on_receive_array)<EOL>self._listen_future = asyncio.ensure_future(self._channel.listen())<EOL>try:<EOL><INDENT>await self._listen_future<EOL><DEDENT>except asyncio.CancelledError:<EOL><INDENT>self._listen_future.cancel()<EOL><DEDENT>logger.info(<EOL>'<STR_LIT>'<EOL>)<EOL><DEDENT>finally:<EOL><INDENT>await self._session.close()<EOL><DEDENT>

Establish a connection to the chat server.

        Returns when an error has occurred, or :func:`disconnect` has been
        called.


logger.info('<STR_LIT>')<EOL>self._listen_future.cancel()<EOL>

Gracefully disconnect from the server.

        When disconnection is complete, :func:`connect` will return.


<EOL>if self._client_id is not None:<EOL><INDENT>self._request_header.client_identifier.resource = self._client_id<EOL><DEDENT>return self._request_header<EOL>

Return ``request_header`` for use when constructing requests.

        Returns:
            Populated request header.


return random.randint(<NUM_LIT:0>, <NUM_LIT:2>**<NUM_LIT:32>)<EOL>

Return ``client_generated_id`` for use when constructing requests.

        Returns:
            Client generated ID.


is_active = (self._active_client_state ==<EOL>hangouts_pb2.ACTIVE_CLIENT_STATE_IS_ACTIVE)<EOL>timed_out = (time.time() - self._last_active_secs ><EOL>SETACTIVECLIENT_LIMIT_SECS)<EOL>if not is_active or timed_out:<EOL><INDENT>self._active_client_state = (<EOL>hangouts_pb2.ACTIVE_CLIENT_STATE_IS_ACTIVE<EOL>)<EOL>self._last_active_secs = time.time()<EOL>if self._email is None:<EOL><INDENT>try:<EOL><INDENT>get_self_info_request = hangouts_pb2.GetSelfInfoRequest(<EOL>request_header=self.get_request_header(),<EOL>)<EOL>get_self_info_response = await self.get_self_info(<EOL>get_self_info_request<EOL>)<EOL><DEDENT>except exceptions.NetworkError as e:<EOL><INDENT>logger.warning('<STR_LIT>'<EOL>.format(e))<EOL>return<EOL><DEDENT>self._email = (<EOL>get_self_info_response.self_entity.properties.email[<NUM_LIT:0>]<EOL>)<EOL><DEDENT>if self._client_id is None:<EOL><INDENT>logger.info(<EOL>'<STR_LIT>'<EOL>)<EOL>return<EOL><DEDENT>try:<EOL><INDENT>set_active_request = hangouts_pb2.SetActiveClientRequest(<EOL>request_header=self.get_request_header(),<EOL>is_active=True,<EOL>full_jid="<STR_LIT>".format(self._email, self._client_id),<EOL>timeout_secs=ACTIVE_TIMEOUT_SECS,<EOL>)<EOL>await self.set_active_client(set_active_request)<EOL><DEDENT>except exceptions.NetworkError as e:<EOL><INDENT>logger.warning('<STR_LIT>'.format(e))<EOL><DEDENT>else:<EOL><INDENT>logger.info('<STR_LIT>'<EOL>.format(ACTIVE_TIMEOUT_SECS))<EOL><DEDENT><DEDENT>

Set this client as active.

        While a client is active, no other clients will raise notifications.
        Call this method whenever there is an indication the user is
        interacting with this client. This method may be called very
        frequently, and it will only make a request when necessary.


async def upload_image(self, image_file, filename=None, *,<EOL>return_uploaded_image=False):

image_filename = filename or os.path.basename(image_file.name)<EOL>image_data = image_file.read()<EOL>res = await self._base_request(<EOL>IMAGE_UPLOAD_URL,<EOL>'<STR_LIT>', '<STR_LIT>',<EOL>json.dumps({<EOL>"<STR_LIT>": "<STR_LIT>",<EOL>"<STR_LIT>": {<EOL>"<STR_LIT>": [{<EOL>"<STR_LIT>": {<EOL>"<STR_LIT:name>": "<STR_LIT:file>",<EOL>"<STR_LIT:filename>": image_filename,<EOL>"<STR_LIT>": {},<EOL>"<STR_LIT:size>": len(image_data)<EOL>}<EOL>}]<EOL>}<EOL>})<EOL>)<EOL>try:<EOL><INDENT>upload_url = self._get_upload_session_status(res)[<EOL>'<STR_LIT>'<EOL>][<NUM_LIT:0>]['<STR_LIT>']['<STR_LIT:url>']<EOL><DEDENT>except KeyError:<EOL><INDENT>raise exceptions.NetworkError(<EOL>'<STR_LIT>'<EOL>)<EOL><DEDENT>res = await self._base_request(<EOL>upload_url, '<STR_LIT>', '<STR_LIT>', image_data<EOL>)<EOL>try:<EOL><INDENT>raw_info = (<EOL>self._get_upload_session_status(res)['<STR_LIT>']<EOL>['<STR_LIT>']<EOL>['<STR_LIT>']['<STR_LIT>']<EOL>)<EOL>image_id = raw_info['<STR_LIT>']<EOL>url = raw_info['<STR_LIT:url>']<EOL><DEDENT>except KeyError:<EOL><INDENT>raise exceptions.NetworkError(<EOL>'<STR_LIT>'<EOL>)<EOL><DEDENT>result = UploadedImage(image_id=image_id, url=url)<EOL>return result if return_uploaded_image else result.image_id<EOL>

Upload an image that can be later attached to a chat message.

        Args:
            image_file: A file-like object containing an image.
            filename (str): (optional) Custom name for the uploaded file.
            return_uploaded_image (bool): (optional) If True, return
                :class:`.UploadedImage` instead of image ID. Defaults to False.

        Raises:
            hangups.NetworkError: If the upload request failed.

        Returns:
            :class:`.UploadedImage` instance, or ID of the uploaded image.


response = json.loads(res.body.decode())<EOL>if '<STR_LIT>' not in response:<EOL><INDENT>try:<EOL><INDENT>info = (<EOL>response['<STR_LIT>']['<STR_LIT>']<EOL>['<STR_LIT>']<EOL>['<STR_LIT>']['<STR_LIT>']<EOL>)<EOL>reason = '<STR_LIT>'.format(info['<STR_LIT:status>'], info['<STR_LIT:message>'])<EOL><DEDENT>except KeyError:<EOL><INDENT>reason = '<STR_LIT>'<EOL><DEDENT>raise exceptions.NetworkError('<STR_LIT>'.format(<EOL>reason<EOL>))<EOL><DEDENT>return response['<STR_LIT>']<EOL>

Parse the image upload response to obtain status.

        Args:
            res: http_utils.FetchResponse instance, the upload response

        Returns:
            dict, sessionStatus of the response

        Raises:
            hangups.NetworkError: If the upload request failed.


async def _on_receive_array(self, array):

if array[<NUM_LIT:0>] == '<STR_LIT>':<EOL><INDENT>pass  <EOL><DEDENT>else:<EOL><INDENT>wrapper = json.loads(array[<NUM_LIT:0>]['<STR_LIT:p>'])<EOL>if '<STR_LIT:3>' in wrapper:<EOL><INDENT>self._client_id = wrapper['<STR_LIT:3>']['<STR_LIT:2>']<EOL>logger.info('<STR_LIT>', self._client_id)<EOL>await self._add_channel_services()<EOL><DEDENT>if '<STR_LIT:2>' in wrapper:<EOL><INDENT>pblite_message = json.loads(wrapper['<STR_LIT:2>']['<STR_LIT:2>'])<EOL>if pblite_message[<NUM_LIT:0>] == '<STR_LIT>':<EOL><INDENT>batch_update = hangouts_pb2.BatchUpdate()<EOL>pblite.decode(batch_update, pblite_message,<EOL>ignore_first_item=True)<EOL>for state_update in batch_update.state_update:<EOL><INDENT>logger.debug('<STR_LIT>', state_update)<EOL>header = state_update.state_update_header<EOL>self._active_client_state = header.active_client_state<EOL>await self.on_state_update.fire(state_update)<EOL><DEDENT><DEDENT>else:<EOL><INDENT>logger.info('<STR_LIT>', pblite_message[<NUM_LIT:0>])<EOL><DEDENT><DEDENT><DEDENT>

Parse channel array and call the appropriate events.


logger.info('<STR_LIT>')<EOL>services = ["<STR_LIT>", "<STR_LIT>"]<EOL>map_list = [<EOL>dict(p=json.dumps({"<STR_LIT:3>": {"<STR_LIT:1>": {"<STR_LIT:1>": service}}}))<EOL>for service in services<EOL>]<EOL>await self._channel.send_maps(map_list)<EOL>logger.info('<STR_LIT>')<EOL>

Add services to the channel.

        The services we add to the channel determine what kind of data we will
        receive on it.

        The "babel" service includes what we need for Hangouts. If this fails
        for some reason, hangups will never receive any events. The
        "babel_presence_last_seen" service is also required to receive presence
        notifications.

        This needs to be re-called whenever we open a new channel (when there's
        a new SID and client_id.


async def _pb_request(self, endpoint, request_pb, response_pb):

logger.debug('<STR_LIT>', endpoint,<EOL>request_pb)<EOL>res = await self._base_request(<EOL>'<STR_LIT>'.format(endpoint),<EOL>'<STR_LIT>',  <EOL>'<STR_LIT>',  <EOL>request_pb.SerializeToString()<EOL>)<EOL>try:<EOL><INDENT>response_pb.ParseFromString(base64.b64decode(res.body))<EOL><DEDENT>except binascii.Error as e:<EOL><INDENT>raise exceptions.NetworkError(<EOL>'<STR_LIT>'.format(e)<EOL>)<EOL><DEDENT>except google.protobuf.message.DecodeError as e:<EOL><INDENT>raise exceptions.NetworkError(<EOL>'<STR_LIT>'.format(e)<EOL>)<EOL><DEDENT>logger.debug('<STR_LIT>', response_pb)<EOL>status = response_pb.response_header.status<EOL>if status != hangouts_pb2.RESPONSE_STATUS_OK:<EOL><INDENT>description = response_pb.response_header.error_description<EOL>raise exceptions.NetworkError(<EOL>'<STR_LIT>'<EOL>.format(status, description)<EOL>)<EOL><DEDENT>

Send a Protocol Buffer formatted chat API request.

        Args:
            endpoint (str): The chat API endpoint to use.
            request_pb: The request body as a Protocol Buffer message.
            response_pb: The response body as a Protocol Buffer message.

        Raises:
            NetworkError: If the request fails.


async def _base_request(self, url, content_type, response_type, data):

headers = {<EOL>'<STR_LIT>': content_type,<EOL>'<STR_LIT>': '<STR_LIT>',<EOL>}<EOL>params = {<EOL>'<STR_LIT>': response_type,<EOL>'<STR_LIT:key>': API_KEY,<EOL>}<EOL>res = await self._session.fetch(<EOL>'<STR_LIT>', url, headers=headers, params=params, data=data,<EOL>)<EOL>return res<EOL>

Send a generic authenticated POST request.

        Args:
            url (str): URL of request.
            content_type (str): Request content type.
            response_type (str): The desired response format. Valid options
                are: 'json' (JSON), 'protojson' (pblite), and 'proto' (binary
                Protocol Buffer). 'proto' requires manually setting an extra
                header 'X-Goog-Encode-Response-If-Executable: base64'.
            data (str): Request body data.

        Returns:
            FetchResponse: Response containing HTTP code, cookies, and body.

        Raises:
            NetworkError: If the request fails.


async def add_user(self, add_user_request):

response = hangouts_pb2.AddUserResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>add_user_request, response)<EOL>return response<EOL>

Invite users to join an existing group conversation.


async def create_conversation(self, create_conversation_request):

response = hangouts_pb2.CreateConversationResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>create_conversation_request, response)<EOL>return response<EOL>


async def delete_conversation(self, delete_conversation_request):

response = hangouts_pb2.DeleteConversationResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>delete_conversation_request, response)<EOL>return response<EOL>

Leave a one-to-one conversation.

        One-to-one conversations are "sticky"; they can't actually be deleted.
        This API clears the event history of the specified conversation up to
        ``delete_upper_bound_timestamp``, hiding it if no events remain.


async def easter_egg(self, easter_egg_request):

response = hangouts_pb2.EasterEggResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>easter_egg_request, response)<EOL>return response<EOL>

Send an easter egg event to a conversation.


async def get_conversation(self, get_conversation_request):

response = hangouts_pb2.GetConversationResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>get_conversation_request, response)<EOL>return response<EOL>

Return conversation info and recent events.


async def get_entity_by_id(self, get_entity_by_id_request):

response = hangouts_pb2.GetEntityByIdResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>get_entity_by_id_request, response)<EOL>return response<EOL>

Return one or more user entities.

        Searching by phone number only finds entities when their phone number
        is in your contacts (and not always even then), and can't be used to
        find Google Voice contacts.


response = hangouts_pb2.GetGroupConversationUrlResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>get_group_conversation_url_request,<EOL>response)<EOL>return response<EOL>

Get URL to allow others to join a group conversation.


async def get_self_info(self, get_self_info_request):

response = hangouts_pb2.GetSelfInfoResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>get_self_info_request, response)<EOL>return response<EOL>


async def get_suggested_entities(self, get_suggested_entities_request):

response = hangouts_pb2.GetSuggestedEntitiesResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>get_suggested_entities_request, response)<EOL>return response<EOL>


async def query_presence(self, query_presence_request):

response = hangouts_pb2.QueryPresenceResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>query_presence_request, response)<EOL>return response<EOL>

Return presence status for a list of users.


async def remove_user(self, remove_user_request):

response = hangouts_pb2.RemoveUserResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>remove_user_request, response)<EOL>return response<EOL>

Remove a participant from a group conversation.


async def rename_conversation(self, rename_conversation_request):

response = hangouts_pb2.RenameConversationResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>rename_conversation_request, response)<EOL>return response<EOL>

Rename a conversation.

        Both group and one-to-one conversations may be renamed, but the
        official Hangouts clients have mixed support for one-to-one
        conversations with custom names.


async def search_entities(self, search_entities_request):

response = hangouts_pb2.SearchEntitiesResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>search_entities_request, response)<EOL>return response<EOL>


async def send_chat_message(self, send_chat_message_request):

response = hangouts_pb2.SendChatMessageResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>send_chat_message_request, response)<EOL>return response<EOL>


async def modify_otr_status(self, modify_otr_status_request):

response = hangouts_pb2.ModifyOTRStatusResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>modify_otr_status_request, response)<EOL>return response<EOL>

Enable or disable message history in a conversation.


async def send_offnetwork_invitation(<EOL>self, send_offnetwork_invitation_request<EOL>):

response = hangouts_pb2.SendOffnetworkInvitationResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>send_offnetwork_invitation_request,<EOL>response)<EOL>return response<EOL>

Send an email to invite a non-Google contact to Hangouts.


async def set_active_client(self, set_active_client_request):

response = hangouts_pb2.SetActiveClientResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>set_active_client_request, response)<EOL>return response<EOL>


async def set_conversation_notification_level(<EOL>self, set_conversation_notification_level_request<EOL>):

response = hangouts_pb2.SetConversationNotificationLevelResponse()<EOL>await self._pb_request(<EOL>'<STR_LIT>',<EOL>set_conversation_notification_level_request, response<EOL>)<EOL>return response<EOL>

Set the notification level of a conversation.


async def set_focus(self, set_focus_request):

response = hangouts_pb2.SetFocusResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>set_focus_request, response)<EOL>return response<EOL>


async def set_group_link_sharing_enabled(<EOL>self, set_group_link_sharing_enabled_request<EOL>):

response = hangouts_pb2.SetGroupLinkSharingEnabledResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>set_group_link_sharing_enabled_request,<EOL>response)<EOL>return response<EOL>

Set whether group link sharing is enabled for a conversation.


async def set_presence(self, set_presence_request):

response = hangouts_pb2.SetPresenceResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>set_presence_request, response)<EOL>return response<EOL>


async def set_typing(self, set_typing_request):

response = hangouts_pb2.SetTypingResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>set_typing_request, response)<EOL>return response<EOL>

Set the typing status of a conversation.


async def sync_all_new_events(self, sync_all_new_events_request):

response = hangouts_pb2.SyncAllNewEventsResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>sync_all_new_events_request, response)<EOL>return response<EOL>

List all events occurring at or after a timestamp.


async def sync_recent_conversations(<EOL>self, sync_recent_conversations_request<EOL>):

response = hangouts_pb2.SyncRecentConversationsResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>sync_recent_conversations_request,<EOL>response)<EOL>return response<EOL>

Return info on recent conversations and their events.


async def update_watermark(self, update_watermark_request):

response = hangouts_pb2.UpdateWatermarkResponse()<EOL>await self._pb_request('<STR_LIT>',<EOL>update_watermark_request, response)<EOL>return response<EOL>

Update the watermark (read timestamp) of a conversation.


conv_states, sync_timestamp = await _sync_all_conversations(client)<EOL>required_user_ids = set()<EOL>for conv_state in conv_states:<EOL><INDENT>required_user_ids |= {<EOL>user.UserID(chat_id=part.id.chat_id, gaia_id=part.id.gaia_id)<EOL>for part in conv_state.conversation.participant_data<EOL>}<EOL><DEDENT>required_entities = []<EOL>if required_user_ids:<EOL><INDENT>logger.debug('<STR_LIT>'<EOL>.format(required_user_ids))<EOL>try:<EOL><INDENT>response = await client.get_entity_by_id(<EOL>hangouts_pb2.GetEntityByIdRequest(<EOL>request_header=client.get_request_header(),<EOL>batch_lookup_spec=[<EOL>hangouts_pb2.EntityLookupSpec(<EOL>gaia_id=user_id.gaia_id,<EOL>create_offnetwork_gaia=True,<EOL>)<EOL>for user_id in required_user_ids<EOL>],<EOL>)<EOL>)<EOL>for entity_result in response.entity_result:<EOL><INDENT>required_entities.extend(entity_result.entity)<EOL><DEDENT><DEDENT>except exceptions.NetworkError as e:<EOL><INDENT>logger.warning('<STR_LIT>'.format(e))<EOL><DEDENT><DEDENT>conv_part_list = []<EOL>for conv_state in conv_states:<EOL><INDENT>conv_part_list.extend(conv_state.conversation.participant_data)<EOL><DEDENT>get_self_info_response = await client.get_self_info(<EOL>hangouts_pb2.GetSelfInfoRequest(<EOL>request_header=client.get_request_header(),<EOL>)<EOL>)<EOL>self_entity = get_self_info_response.self_entity<EOL>user_list = user.UserList(client, self_entity, required_entities,<EOL>conv_part_list)<EOL>conversation_list = ConversationList(client, conv_states,<EOL>user_list, sync_timestamp)<EOL>return (user_list, conversation_list)<EOL>

Build :class:`.UserList` and :class:`.ConversationList`.

    This method requests data necessary to build the list of conversations and
    users. Users that are not in the contact list but are participating in a
    conversation will also be retrieved.

    Args:
        client (Client): Connected client.

    Returns:
        (:class:`.UserList`, :class:`.ConversationList`):
            Tuple of built objects.


conv_states = []<EOL>sync_timestamp = None<EOL>request = hangouts_pb2.SyncRecentConversationsRequest(<EOL>request_header=client.get_request_header(),<EOL>max_conversations=CONVERSATIONS_PER_REQUEST,<EOL>max_events_per_conversation=<NUM_LIT:1>,<EOL>sync_filter=[<EOL>hangouts_pb2.SYNC_FILTER_INBOX,<EOL>hangouts_pb2.SYNC_FILTER_ARCHIVED,<EOL>]<EOL>)<EOL>for _ in range(MAX_CONVERSATION_PAGES):<EOL><INDENT>logger.info(<EOL>'<STR_LIT>', request.last_event_timestamp<EOL>)<EOL>response = await client.sync_recent_conversations(request)<EOL>conv_states = list(response.conversation_state) + conv_states<EOL>sync_timestamp = parsers.from_timestamp(<EOL>response.response_header.current_server_time<EOL>)<EOL>if response.continuation_end_timestamp == <NUM_LIT:0>:<EOL><INDENT>logger.info('<STR_LIT>')<EOL>break<EOL><DEDENT>else:<EOL><INDENT>request.last_event_timestamp = response.continuation_end_timestamp<EOL><DEDENT><DEDENT>else:<EOL><INDENT>logger.warning('<STR_LIT>')<EOL><DEDENT>logger.info('<STR_LIT>', len(conv_states))<EOL>return conv_states, sync_timestamp<EOL>

Sync all conversations by making paginated requests.

    Conversations are ordered by ascending sort timestamp.

    Args:
        client (Client): Connected client.

    Raises:
        NetworkError: If the requests fail.

    Returns:
        tuple of list of ``ConversationState`` messages and sync timestamp


return self._conversation.conversation_id.id<EOL>


return [self._user_list.get_user(user.UserID(chat_id=part.id.chat_id,<EOL>gaia_id=part.id.gaia_id))<EOL>for part in self._conversation.participant_data]<EOL>

List of conversation participants (:class:`~hangups.user.User`).


custom_name = self._conversation.name<EOL>return None if custom_name == '<STR_LIT>' else custom_name<EOL>

The conversation's custom name (:class:`str`)

        May be ``None`` if conversation has no custom name.


timestamp = self._conversation.self_conversation_state.sort_timestamp<EOL>if timestamp is None:<EOL><INDENT>timestamp = <NUM_LIT:0><EOL><DEDENT>return parsers.from_timestamp(timestamp)<EOL>

When conversation was last modified (:class:`datetime.datetime`).


return list(self._events)<EOL>

Loaded events sorted oldest to newest.

        (list of :class:`.ConversationEvent`).


return self._watermarks.copy()<EOL>

Participant watermarks.

        (dict of :class:`.UserID`, :class:`datetime.datetime`).


return [conv_event for conv_event in self._events<EOL>if conv_event.timestamp > self.latest_read_timestamp]<EOL>

Loaded events which are unread sorted oldest to newest.

        Some Hangouts clients don't update the read timestamp for certain event
        types, such as membership changes, so this may return more unread
        events than these clients will show. There's also a delay between
        sending a message and the user's own message being considered read.

        (list of :class:`.ConversationEvent`).


return (hangouts_pb2.CONVERSATION_VIEW_ARCHIVED in<EOL>self._conversation.self_conversation_state.view)<EOL>

``True`` if this conversation has been archived.


level = self._conversation.self_conversation_state.notification_level<EOL>return level == hangouts_pb2.NOTIFICATION_LEVEL_QUIET<EOL>

``True`` if notification level for this conversation is quiet.


status = self._conversation.otr_status<EOL>return status == hangouts_pb2.OFF_THE_RECORD_STATUS_OFF_THE_RECORD<EOL>

``True`` if conversation is off the record (history is disabled).


<EOL>if self.get_user(notif.user_id).is_self:<EOL><INDENT>logger.info('<STR_LIT>'<EOL>.format(self.id_, notif.read_timestamp))<EOL>self_conversation_state = (<EOL>self._conversation.self_conversation_state<EOL>)<EOL>self_conversation_state.self_read_state.latest_read_timestamp = (<EOL>parsers.to_timestamp(notif.read_timestamp)<EOL>)<EOL><DEDENT>previous_timestamp = self._watermarks.get(<EOL>notif.user_id,<EOL>datetime.datetime.min.replace(tzinfo=datetime.timezone.utc)<EOL>)<EOL>if notif.read_timestamp > previous_timestamp:<EOL><INDENT>logger.info(('<STR_LIT>' +<EOL>'<STR_LIT>').format(self.id_,<EOL>notif.user_id.chat_id,<EOL>notif.read_timestamp))<EOL>self._watermarks[notif.user_id] = notif.read_timestamp<EOL><DEDENT>


<EOL>new_state = conversation.self_conversation_state<EOL>old_state = self._conversation.self_conversation_state<EOL>self._conversation = conversation<EOL>if not new_state.delivery_medium_option:<EOL><INDENT>new_state.delivery_medium_option.extend(<EOL>old_state.delivery_medium_option<EOL>)<EOL><DEDENT>old_timestamp = old_state.self_read_state.latest_read_timestamp<EOL>new_timestamp = new_state.self_read_state.latest_read_timestamp<EOL>if new_timestamp == <NUM_LIT:0>:<EOL><INDENT>new_state.self_read_state.latest_read_timestamp = old_timestamp<EOL><DEDENT>for new_entry in conversation.read_state:<EOL><INDENT>tstamp = parsers.from_timestamp(new_entry.latest_read_timestamp)<EOL>if tstamp == <NUM_LIT:0>:<EOL><INDENT>continue<EOL><DEDENT>uid = parsers.from_participantid(new_entry.participant_id)<EOL>if uid not in self._watermarks or self._watermarks[uid] < tstamp:<EOL><INDENT>self._watermarks[uid] = tstamp<EOL><DEDENT><DEDENT>

Update the internal state of the conversation.

        This method is used by :class:`.ConversationList` to maintain this
        instance.

        Args:
            conversation: ``Conversation`` message.


cls = conversation_event.ConversationEvent<EOL>if event_.HasField('<STR_LIT>'):<EOL><INDENT>cls = conversation_event.ChatMessageEvent<EOL><DEDENT>elif event_.HasField('<STR_LIT>'):<EOL><INDENT>cls = conversation_event.OTREvent<EOL><DEDENT>elif event_.HasField('<STR_LIT>'):<EOL><INDENT>cls = conversation_event.RenameEvent<EOL><DEDENT>elif event_.HasField('<STR_LIT>'):<EOL><INDENT>cls = conversation_event.MembershipChangeEvent<EOL><DEDENT>elif event_.HasField('<STR_LIT>'):<EOL><INDENT>cls = conversation_event.HangoutEvent<EOL><DEDENT>elif event_.HasField('<STR_LIT>'):<EOL><INDENT>cls = conversation_event.GroupLinkSharingModificationEvent<EOL><DEDENT>return cls(event_)<EOL>

Wrap hangouts_pb2.Event in ConversationEvent subclass.


conv_event = self._wrap_event(event_)<EOL>if conv_event.id_ not in self._events_dict:<EOL><INDENT>self._events.append(conv_event)<EOL>self._events_dict[conv_event.id_] = conv_event<EOL><DEDENT>else:<EOL><INDENT>logger.info('<STR_LIT>',<EOL>self.id_, conv_event.id_)<EOL>return None<EOL><DEDENT>return conv_event<EOL>

Add an event to the conversation.

        This method is used by :class:`.ConversationList` to maintain this
        instance.

        Args:
            event_: ``Event`` message.

        Returns:
            :class:`.ConversationEvent` representing the event.


return self._user_list.get_user(user_id)<EOL>

Get user by its ID.

        Args:
            user_id (~hangups.user.UserID): ID of user to return.

        Raises:
            KeyError: If the user ID is not found.

        Returns:
            :class:`~hangups.user.User` with matching ID.


medium_options = (<EOL>self._conversation.self_conversation_state.delivery_medium_option<EOL>)<EOL>try:<EOL><INDENT>default_medium = medium_options[<NUM_LIT:0>].delivery_medium<EOL><DEDENT>except IndexError:<EOL><INDENT>logger.warning('<STR_LIT>', self.id_)<EOL>default_medium = hangouts_pb2.DeliveryMedium(<EOL>medium_type=hangouts_pb2.DELIVERY_MEDIUM_BABEL<EOL>)<EOL><DEDENT>for medium_option in medium_options:<EOL><INDENT>if medium_option.current_default:<EOL><INDENT>default_medium = medium_option.delivery_medium<EOL><DEDENT><DEDENT>return default_medium<EOL>

Return default DeliveryMedium to use for sending messages.

        Use the first option, or an option that's marked as the current
        default.


Return EventRequestHeader for conversation.


async def send_message(self, segments, image_file=None, image_id=None,<EOL>image_user_id=None):

async with self._send_message_lock:<EOL><INDENT>if image_file:<EOL><INDENT>try:<EOL><INDENT>uploaded_image = await self._client.upload_image(<EOL>image_file, return_uploaded_image=True<EOL>)<EOL><DEDENT>except exceptions.NetworkError as e:<EOL><INDENT>logger.warning('<STR_LIT>'.format(e))<EOL>raise<EOL><DEDENT>image_id = uploaded_image.image_id<EOL><DEDENT>try:<EOL><INDENT>request = hangouts_pb2.SendChatMessageRequest(<EOL>request_header=self._client.get_request_header(),<EOL>event_request_header=self._get_event_request_header(),<EOL>message_content=hangouts_pb2.MessageContent(<EOL>segment=[seg.serialize() for seg in segments],<EOL>),<EOL>)<EOL>if image_id is not None:<EOL><INDENT>request.existing_media.photo.photo_id = image_id<EOL><DEDENT>if image_user_id is not None:<EOL><INDENT>request.existing_media.photo.user_id = image_user_id<EOL>request.existing_media.photo.is_custom_user_id = True<EOL><DEDENT>await self._client.send_chat_message(request)<EOL><DEDENT>except exceptions.NetworkError as e:<EOL><INDENT>logger.warning('<STR_LIT>'.format(e))<EOL>raise<EOL><DEDENT><DEDENT>

Send a message to this conversation.

        A per-conversation lock is acquired to ensure that messages are sent in
        the correct order when this method is called multiple times
        asynchronously.

        Args:
            segments: List of :class:`.ChatMessageSegment` objects to include
                in the message.
            image_file: (optional) File-like object containing an image to be
                attached to the message.
            image_id: (optional) ID of an Picasa photo to be attached to the
                message. If you specify both ``image_file`` and ``image_id``
                together, ``image_file`` takes precedence and ``image_id`` will
                be ignored.
            image_user_id: (optional) Picasa user ID, required only if
                ``image_id`` refers to an image from a different Picasa user,
                such as Google's sticker user.

        Raises:
            .NetworkError: If the message cannot be sent.


Leave this conversation.

        Raises:
            .NetworkError: If conversation cannot be left.


Rename this conversation.

        Hangouts only officially supports renaming group conversations, so
        custom names for one-to-one conversations may or may not appear in all
        first party clients.

        Args:
            name (str): New name.

        Raises:
            .NetworkError: If conversation cannot be renamed.


async def set_notification_level(self, level):

Set the notification level of this conversation.

        Args:
            level: ``NOTIFICATION_LEVEL_QUIET`` to disable notifications, or
                ``NOTIFICATION_LEVEL_RING`` to enable them.

        Raises:
            .NetworkError: If the request fails.


async def set_typing(self, typing=hangouts_pb2.TYPING_TYPE_STARTED):

Set your typing status in this conversation.

        Args:
            typing: (optional) ``TYPING_TYPE_STARTED``, ``TYPING_TYPE_PAUSED``,
                or ``TYPING_TYPE_STOPPED`` to start, pause, or stop typing,
                respectively. Defaults to ``TYPING_TYPE_STARTED``.

        Raises:
            .NetworkError: If typing status cannot be set.


async def update_read_timestamp(self, read_timestamp=None):

if read_timestamp is None:<EOL><INDENT>read_timestamp = (self.events[-<NUM_LIT:1>].timestamp if self.events else<EOL>datetime.datetime.now(datetime.timezone.utc))<EOL><DEDENT>if read_timestamp > self.latest_read_timestamp:<EOL><INDENT>logger.info(<EOL>'<STR_LIT>'<EOL>.format(self.id_, self.latest_read_timestamp, read_timestamp)<EOL>)<EOL>state = self._conversation.self_conversation_state<EOL>state.self_read_state.latest_read_timestamp = (<EOL>parsers.to_timestamp(read_timestamp)<EOL>)<EOL>try:<EOL><INDENT>await self._client.update_watermark(<EOL>hangouts_pb2.UpdateWatermarkRequest(<EOL>request_header=self._client.get_request_header(),<EOL>conversation_id=hangouts_pb2.ConversationId(<EOL>id=self.id_<EOL>),<EOL>last_read_timestamp=parsers.to_timestamp(<EOL>read_timestamp<EOL>),<EOL>)<EOL>)<EOL><DEDENT>except exceptions.NetworkError as e:<EOL><INDENT>logger.warning('<STR_LIT>'.format(e))<EOL>raise<EOL><DEDENT><DEDENT>

Update the timestamp of the latest event which has been read.

        This method will avoid making an API request if it will have no effect.

        Args:
            read_timestamp (datetime.datetime): (optional) Timestamp to set.
                Defaults to the timestamp of the newest event.

        Raises:
            .NetworkError: If the timestamp cannot be updated.


async def get_events(self, event_id=None, max_events=<NUM_LIT:50>):

if event_id is None:<EOL><INDENT>conv_events = self._events[-<NUM_LIT:1> * max_events:]<EOL><DEDENT>else:<EOL><INDENT>conv_event = self.get_event(event_id)<EOL>if self._events[<NUM_LIT:0>].id_ != event_id:<EOL><INDENT>conv_events = self._events[self._events.index(conv_event) + <NUM_LIT:1>:]<EOL><DEDENT>else:<EOL><INDENT>logger.info('<STR_LIT>'<EOL>.format(self.id_, conv_event.timestamp))<EOL>res = await self._client.get_conversation(<EOL>hangouts_pb2.GetConversationRequest(<EOL>request_header=self._client.get_request_header(),<EOL>conversation_spec=hangouts_pb2.ConversationSpec(<EOL>conversation_id=hangouts_pb2.ConversationId(<EOL>id=self.id_<EOL>)<EOL>),<EOL>include_event=True,<EOL>max_events_per_conversation=max_events,<EOL>event_continuation_token=self._event_cont_token<EOL>)<EOL>)<EOL>if res.conversation_state.HasField('<STR_LIT>'):<EOL><INDENT>self.update_conversation(<EOL>res.conversation_state.conversation<EOL>)<EOL><DEDENT>self._event_cont_token = (<EOL>res.conversation_state.event_continuation_token<EOL>)<EOL>conv_events = [self._wrap_event(event) for event<EOL>in res.conversation_state.event]<EOL>logger.info('<STR_LIT>'<EOL>.format(len(conv_events), self.id_))<EOL>for conv_event in reversed(conv_events):<EOL><INDENT>if conv_event.id_ not in self._events_dict:<EOL><INDENT>self._events.insert(<NUM_LIT:0>, conv_event)<EOL>self._events_dict[conv_event.id_] = conv_event<EOL><DEDENT>else:<EOL><INDENT>logger.info(<EOL>'<STR_LIT>',<EOL>self.id_, conv_event.id_<EOL>)<EOL><DEDENT><DEDENT><DEDENT><DEDENT>return conv_events<EOL>

Get events from this conversation.

        Makes a request to load historical events if necessary.

        Args:
            event_id (str): (optional) If provided, return events preceding
                this event, otherwise return the newest events.
            max_events (int): Maximum number of events to return. Defaults to
                50.

        Returns:
            List of :class:`.ConversationEvent` instances, ordered
            newest-first.

        Raises:
            KeyError: If ``event_id`` does not correspond to a known event.
            .NetworkError: If the events could not be requested.


def next_event(self, event_id, prev=False):

i = self.events.index(self._events_dict[event_id])<EOL>if prev and i > <NUM_LIT:0>:<EOL><INDENT>return self.events[i - <NUM_LIT:1>]<EOL><DEDENT>elif not prev and i + <NUM_LIT:1> < len(self.events):<EOL><INDENT>return self.events[i + <NUM_LIT:1>]<EOL><DEDENT>else:<EOL><INDENT>return None<EOL><DEDENT>

Get the event following another event in this conversation.

        Args:
            event_id (str): ID of the event.
            prev (bool): If ``True``, return the previous event rather than the
                next event. Defaults to ``False``.

        Raises:
            KeyError: If no such :class:`.ConversationEvent` is known.

        Returns:
            :class:`.ConversationEvent` or ``None`` if there is no following
            event.


return self._events_dict[event_id]<EOL>

Get an event in this conversation by its ID.

        Args:
            event_id (str): ID of the event.

        Raises:
            KeyError: If no such :class:`.ConversationEvent` is known.

        Returns:
            :class:`.ConversationEvent` with the given ID.


return [conv for conv in self._conv_dict.values()<EOL>if not conv.is_archived or include_archived]<EOL>

Get all the conversations.

        Args:
            include_archived (bool): (optional) Whether to include archived
                conversations. Defaults to ``False``.

        Returns:
            List of all :class:`.Conversation` objects.


return self._conv_dict[conv_id]<EOL>

Get a conversation by its ID.

        Args:
            conv_id (str): ID of conversation to return.

        Raises:
            KeyError: If the conversation ID is not found.

        Returns:
            :class:`.Conversation` with matching ID.


async def leave_conversation(self, conv_id):

logger.info('<STR_LIT>'.format(conv_id))<EOL>await self._conv_dict[conv_id].leave()<EOL>del self._conv_dict[conv_id]<EOL>

Leave a conversation.

        Args:
            conv_id (str): ID of conversation to leave.


def _add_conversation(self, conversation, events=[],<EOL>event_cont_token=None):

<EOL>conv_id = conversation.conversation_id.id<EOL>logger.debug('<STR_LIT>'.format(conv_id))<EOL>conv = Conversation(self._client, self._user_list, conversation,<EOL>events, event_cont_token)<EOL>self._conv_dict[conv_id] = conv<EOL>return conv<EOL>

Add new conversation from hangouts_pb2.Conversation


async def _on_state_update(self, state_update):

<EOL>notification_type = state_update.WhichOneof('<STR_LIT>')<EOL>if state_update.HasField('<STR_LIT>'):<EOL><INDENT>try:<EOL><INDENT>await self._handle_conversation_delta(<EOL>state_update.conversation<EOL>)<EOL><DEDENT>except exceptions.NetworkError:<EOL><INDENT>logger.warning(<EOL>'<STR_LIT>',<EOL>notification_type.replace('<STR_LIT:_>', '<STR_LIT:U+0020>'),<EOL>state_update.conversation.conversation_id.id<EOL>)<EOL>return<EOL><DEDENT><DEDENT>if notification_type == '<STR_LIT>':<EOL><INDENT>await self._handle_set_typing_notification(<EOL>state_update.typing_notification<EOL>)<EOL><DEDENT>elif notification_type == '<STR_LIT>':<EOL><INDENT>await self._handle_watermark_notification(<EOL>state_update.watermark_notification<EOL>)<EOL><DEDENT>elif notification_type == '<STR_LIT>':<EOL><INDENT>await self._on_event(<EOL>state_update.event_notification.event<EOL>)<EOL><DEDENT>

Receive a StateUpdate and fan out to Conversations.

        Args:
            state_update: hangouts_pb2.StateUpdate instance


async def _get_or_fetch_conversation(self, conv_id):

conv = self._conv_dict.get(conv_id, None)<EOL>if conv is None:<EOL><INDENT>logger.info('<STR_LIT>', conv_id)<EOL>res = await self._client.get_conversation(<EOL>hangouts_pb2.GetConversationRequest(<EOL>request_header=self._client.get_request_header(),<EOL>conversation_spec=hangouts_pb2.ConversationSpec(<EOL>conversation_id=hangouts_pb2.ConversationId(<EOL>id=conv_id<EOL>)<EOL>), include_event=False<EOL>)<EOL>)<EOL>conv_state = res.conversation_state<EOL>event_cont_token = None<EOL>if conv_state.HasField('<STR_LIT>'):<EOL><INDENT>event_cont_token = conv_state.event_continuation_token<EOL><DEDENT>return self._add_conversation(conv_state.conversation,<EOL>event_cont_token=event_cont_token)<EOL><DEDENT>else:<EOL><INDENT>return conv<EOL><DEDENT>

Get a cached conversation or fetch a missing conversation.

        Args:
            conv_id: string, conversation identifier

        Raises:
            NetworkError: If the request to fetch the conversation fails.

        Returns:
            :class:`.Conversation` with matching ID.


conv_id = event_.conversation_id.id<EOL>try:<EOL><INDENT>conv = await self._get_or_fetch_conversation(conv_id)<EOL><DEDENT>except exceptions.NetworkError:<EOL><INDENT>logger.warning(<EOL>'<STR_LIT>',<EOL>conv_id<EOL>)<EOL><DEDENT>else:<EOL><INDENT>self._sync_timestamp = parsers.from_timestamp(event_.timestamp)<EOL>conv_event = conv.add_event(event_)<EOL>if conv_event is not None:<EOL><INDENT>await self.on_event.fire(conv_event)<EOL>await conv.on_event.fire(conv_event)<EOL><DEDENT><DEDENT>

Receive a hangouts_pb2.Event and fan out to Conversations.

        Args:
            event_: hangouts_pb2.Event instance


async def _handle_conversation_delta(self, conversation):

conv_id = conversation.conversation_id.id<EOL>conv = self._conv_dict.get(conv_id, None)<EOL>if conv is None:<EOL><INDENT>await self._get_or_fetch_conversation(conv_id)<EOL><DEDENT>else:<EOL><INDENT>conv.update_conversation(conversation)<EOL><DEDENT>

Receive Conversation delta and create or update the conversation.

        Args:
            conversation: hangouts_pb2.Conversation instance

        Raises:
            NetworkError: A request to fetch the complete conversation failed.


async def _handle_set_typing_notification(self, set_typing_notification):

conv_id = set_typing_notification.conversation_id.id<EOL>res = parsers.parse_typing_status_message(set_typing_notification)<EOL>await self.on_typing.fire(res)<EOL>try:<EOL><INDENT>conv = await self._get_or_fetch_conversation(conv_id)<EOL><DEDENT>except exceptions.NetworkError:<EOL><INDENT>logger.warning(<EOL>'<STR_LIT>',<EOL>conv_id<EOL>)<EOL><DEDENT>else:<EOL><INDENT>await conv.on_typing.fire(res)<EOL><DEDENT>

Receive SetTypingNotification and update the conversation.

        Args:
            set_typing_notification: hangouts_pb2.SetTypingNotification
                instance


async def _handle_watermark_notification(self, watermark_notification):

conv_id = watermark_notification.conversation_id.id<EOL>res = parsers.parse_watermark_notification(watermark_notification)<EOL>await self.on_watermark_notification.fire(res)<EOL>try:<EOL><INDENT>conv = await self._get_or_fetch_conversation(conv_id)<EOL><DEDENT>except exceptions.NetworkError:<EOL><INDENT>logger.warning(<EOL>'<STR_LIT>',<EOL>conv_id<EOL>)<EOL><DEDENT>else:<EOL><INDENT>await conv.on_watermark_notification.fire(res)<EOL><DEDENT>

Receive WatermarkNotification and update the conversation.

        Args:
            watermark_notification: hangouts_pb2.WatermarkNotification instance


logger.info('<STR_LIT>'.format(self._sync_timestamp))<EOL>try:<EOL><INDENT>res = await self._client.sync_all_new_events(<EOL>hangouts_pb2.SyncAllNewEventsRequest(<EOL>request_header=self._client.get_request_header(),<EOL>last_sync_timestamp=parsers.to_timestamp(<EOL>self._sync_timestamp<EOL>),<EOL>max_response_size_bytes=<NUM_LIT>,  <EOL>)<EOL>)<EOL><DEDENT>except exceptions.NetworkError as e:<EOL><INDENT>logger.warning('<STR_LIT>'<EOL>.format(e))<EOL><DEDENT>else:<EOL><INDENT>for conv_state in res.conversation_state:<EOL><INDENT>conv_id = conv_state.conversation_id.id<EOL>conv = self._conv_dict.get(conv_id, None)<EOL>if conv is not None:<EOL><INDENT>conv.update_conversation(conv_state.conversation)<EOL>for event_ in conv_state.event:<EOL><INDENT>timestamp = parsers.from_timestamp(event_.timestamp)<EOL>if timestamp > self._sync_timestamp:<EOL><INDENT>await self._on_event(event_)<EOL><DEDENT><DEDENT><DEDENT>else:<EOL><INDENT>self._add_conversation(<EOL>conv_state.conversation,<EOL>conv_state.event,<EOL>conv_state.event_continuation_token<EOL>)<EOL><DEDENT><DEDENT><DEDENT>

Sync conversation state and events that could have been missed.