Vehicle-Anti-Theft-Face-Rec.../venv/Lib/site-packages/gcloud/bigtable/row.py

890 lines
34 KiB
Python
Raw Normal View History

# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""User friendly container for Google Cloud Bigtable Row."""
import struct
import six
from gcloud._helpers import _datetime_from_microseconds
from gcloud._helpers import _microseconds_from_datetime
from gcloud._helpers import _to_bytes
from gcloud.bigtable._generated_v2 import (
data_pb2 as data_v2_pb2)
from gcloud.bigtable._generated_v2 import (
bigtable_pb2 as messages_v2_pb2)
_PACK_I64 = struct.Struct('>q').pack
MAX_MUTATIONS = 100000
"""The maximum number of mutations that a row can accumulate."""
class Row(object):
"""Base representation of a Google Cloud Bigtable Row.
This class has three subclasses corresponding to the three
RPC methods for sending row mutations:
* :class:`DirectRow` for ``MutateRow``
* :class:`ConditionalRow` for ``CheckAndMutateRow``
* :class:`AppendRow` for ``ReadModifyWriteRow``
:type row_key: bytes
:param row_key: The key for the current row.
:type table: :class:`Table <gcloud.bigtable.table.Table>`
:param table: The table that owns the row.
"""
def __init__(self, row_key, table):
self._row_key = _to_bytes(row_key)
self._table = table
class _SetDeleteRow(Row):
"""Row helper for setting or deleting cell values.
Implements helper methods to add mutations to set or delete cell contents:
* :meth:`set_cell`
* :meth:`delete`
* :meth:`delete_cell`
* :meth:`delete_cells`
:type row_key: bytes
:param row_key: The key for the current row.
:type table: :class:`Table <gcloud.bigtable.table.Table>`
:param table: The table that owns the row.
"""
ALL_COLUMNS = object()
"""Sentinel value used to indicate all columns in a column family."""
def _get_mutations(self, state):
"""Gets the list of mutations for a given state.
This method intended to be implemented by subclasses.
``state`` may not need to be used by all subclasses.
:type state: bool
:param state: The state that the mutation should be
applied in.
:raises: :class:`NotImplementedError <exceptions.NotImplementedError>`
always.
"""
raise NotImplementedError
def _set_cell(self, column_family_id, column, value, timestamp=None,
state=None):
"""Helper for :meth:`set_cell`
Adds a mutation to set the value in a specific cell.
``state`` is unused by :class:`DirectRow` but is used by
subclasses.
:type column_family_id: str
:param column_family_id: The column family that contains the column.
Must be of the form
``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
:type column: bytes
:param column: The column within the column family where the cell
is located.
:type value: bytes or :class:`int`
:param value: The value to set in the cell. If an integer is used,
will be interpreted as a 64-bit big-endian signed
integer (8 bytes).
:type timestamp: :class:`datetime.datetime`
:param timestamp: (Optional) The timestamp of the operation.
:type state: bool
:param state: (Optional) The state that is passed along to
:meth:`_get_mutations`.
"""
column = _to_bytes(column)
if isinstance(value, six.integer_types):
value = _PACK_I64(value)
value = _to_bytes(value)
if timestamp is None:
# Use -1 for current Bigtable server time.
timestamp_micros = -1
else:
timestamp_micros = _microseconds_from_datetime(timestamp)
# Truncate to millisecond granularity.
timestamp_micros -= (timestamp_micros % 1000)
mutation_val = data_v2_pb2.Mutation.SetCell(
family_name=column_family_id,
column_qualifier=column,
timestamp_micros=timestamp_micros,
value=value,
)
mutation_pb = data_v2_pb2.Mutation(set_cell=mutation_val)
self._get_mutations(state).append(mutation_pb)
def _delete(self, state=None):
"""Helper for :meth:`delete`
Adds a delete mutation (for the entire row) to the accumulated
mutations.
``state`` is unused by :class:`DirectRow` but is used by
subclasses.
:type state: bool
:param state: (Optional) The state that is passed along to
:meth:`_get_mutations`.
"""
mutation_val = data_v2_pb2.Mutation.DeleteFromRow()
mutation_pb = data_v2_pb2.Mutation(delete_from_row=mutation_val)
self._get_mutations(state).append(mutation_pb)
def _delete_cells(self, column_family_id, columns, time_range=None,
state=None):
"""Helper for :meth:`delete_cell` and :meth:`delete_cells`.
``state`` is unused by :class:`DirectRow` but is used by
subclasses.
:type column_family_id: str
:param column_family_id: The column family that contains the column
or columns with cells being deleted. Must be
of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
:type columns: :class:`list` of :class:`str` /
:func:`unicode <unicode>`, or :class:`object`
:param columns: The columns within the column family that will have
cells deleted. If :attr:`ALL_COLUMNS` is used then
the entire column family will be deleted from the row.
:type time_range: :class:`TimestampRange`
:param time_range: (Optional) The range of time within which cells
should be deleted.
:type state: bool
:param state: (Optional) The state that is passed along to
:meth:`_get_mutations`.
"""
mutations_list = self._get_mutations(state)
if columns is self.ALL_COLUMNS:
mutation_val = data_v2_pb2.Mutation.DeleteFromFamily(
family_name=column_family_id,
)
mutation_pb = data_v2_pb2.Mutation(delete_from_family=mutation_val)
mutations_list.append(mutation_pb)
else:
delete_kwargs = {}
if time_range is not None:
delete_kwargs['time_range'] = time_range.to_pb()
to_append = []
for column in columns:
column = _to_bytes(column)
# time_range will never change if present, but the rest of
# delete_kwargs will
delete_kwargs.update(
family_name=column_family_id,
column_qualifier=column,
)
mutation_val = data_v2_pb2.Mutation.DeleteFromColumn(
**delete_kwargs)
mutation_pb = data_v2_pb2.Mutation(
delete_from_column=mutation_val)
to_append.append(mutation_pb)
# We don't add the mutations until all columns have been
# processed without error.
mutations_list.extend(to_append)
class DirectRow(_SetDeleteRow):
"""Google Cloud Bigtable Row for sending "direct" mutations.
These mutations directly set or delete cell contents:
* :meth:`set_cell`
* :meth:`delete`
* :meth:`delete_cell`
* :meth:`delete_cells`
These methods can be used directly::
>>> row = table.row(b'row-key1')
>>> row.set_cell(u'fam', b'col1', b'cell-val')
>>> row.delete_cell(u'fam', b'col2')
.. note::
A :class:`DirectRow` accumulates mutations locally via the
:meth:`set_cell`, :meth:`delete`, :meth:`delete_cell` and
:meth:`delete_cells` methods. To actually send these mutations to the
Google Cloud Bigtable API, you must call :meth:`commit`.
:type row_key: bytes
:param row_key: The key for the current row.
:type table: :class:`Table <gcloud.bigtable.table.Table>`
:param table: The table that owns the row.
"""
def __init__(self, row_key, table):
super(DirectRow, self).__init__(row_key, table)
self._pb_mutations = []
def _get_mutations(self, state): # pylint: disable=unused-argument
"""Gets the list of mutations for a given state.
``state`` is unused by :class:`DirectRow` but is used by
subclasses.
:type state: bool
:param state: The state that the mutation should be
applied in.
:rtype: list
:returns: The list to add new mutations to (for the current state).
"""
return self._pb_mutations
def set_cell(self, column_family_id, column, value, timestamp=None):
"""Sets a value in this row.
The cell is determined by the ``row_key`` of this :class:`DirectRow`
and the ``column``. The ``column`` must be in an existing
:class:`.ColumnFamily` (as determined by ``column_family_id``).
.. note::
This method adds a mutation to the accumulated mutations on this
row, but does not make an API request. To actually
send an API request (with the mutations) to the Google Cloud
Bigtable API, call :meth:`commit`.
:type column_family_id: str
:param column_family_id: The column family that contains the column.
Must be of the form
``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
:type column: bytes
:param column: The column within the column family where the cell
is located.
:type value: bytes or :class:`int`
:param value: The value to set in the cell. If an integer is used,
will be interpreted as a 64-bit big-endian signed
integer (8 bytes).
:type timestamp: :class:`datetime.datetime`
:param timestamp: (Optional) The timestamp of the operation.
"""
self._set_cell(column_family_id, column, value, timestamp=timestamp,
state=None)
def delete(self):
"""Deletes this row from the table.
.. note::
This method adds a mutation to the accumulated mutations on this
row, but does not make an API request. To actually
send an API request (with the mutations) to the Google Cloud
Bigtable API, call :meth:`commit`.
"""
self._delete(state=None)
def delete_cell(self, column_family_id, column, time_range=None):
"""Deletes cell in this row.
.. note::
This method adds a mutation to the accumulated mutations on this
row, but does not make an API request. To actually
send an API request (with the mutations) to the Google Cloud
Bigtable API, call :meth:`commit`.
:type column_family_id: str
:param column_family_id: The column family that contains the column
or columns with cells being deleted. Must be
of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
:type column: bytes
:param column: The column within the column family that will have a
cell deleted.
:type time_range: :class:`TimestampRange`
:param time_range: (Optional) The range of time within which cells
should be deleted.
"""
self._delete_cells(column_family_id, [column], time_range=time_range,
state=None)
def delete_cells(self, column_family_id, columns, time_range=None):
"""Deletes cells in this row.
.. note::
This method adds a mutation to the accumulated mutations on this
row, but does not make an API request. To actually
send an API request (with the mutations) to the Google Cloud
Bigtable API, call :meth:`commit`.
:type column_family_id: str
:param column_family_id: The column family that contains the column
or columns with cells being deleted. Must be
of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
:type columns: :class:`list` of :class:`str` /
:func:`unicode <unicode>`, or :class:`object`
:param columns: The columns within the column family that will have
cells deleted. If :attr:`ALL_COLUMNS` is used then
the entire column family will be deleted from the row.
:type time_range: :class:`TimestampRange`
:param time_range: (Optional) The range of time within which cells
should be deleted.
"""
self._delete_cells(column_family_id, columns, time_range=time_range,
state=None)
def commit(self):
"""Makes a ``MutateRow`` API request.
If no mutations have been created in the row, no request is made.
Mutations are applied atomically and in order, meaning that earlier
mutations can be masked / negated by later ones. Cells already present
in the row are left unchanged unless explicitly changed by a mutation.
After committing the accumulated mutations, resets the local
mutations to an empty list.
:raises: :class:`ValueError <exceptions.ValueError>` if the number of
mutations exceeds the :data:`MAX_MUTATIONS`.
"""
mutations_list = self._get_mutations(None)
num_mutations = len(mutations_list)
if num_mutations == 0:
return
if num_mutations > MAX_MUTATIONS:
raise ValueError('%d total mutations exceed the maximum allowable '
'%d.' % (num_mutations, MAX_MUTATIONS))
request_pb = messages_v2_pb2.MutateRowRequest(
table_name=self._table.name,
row_key=self._row_key,
mutations=mutations_list,
)
# We expect a `google.protobuf.empty_pb2.Empty`
client = self._table._instance._client
client._data_stub.MutateRow(request_pb, client.timeout_seconds)
self.clear()
def clear(self):
"""Removes all currently accumulated mutations on the current row."""
del self._pb_mutations[:]
class ConditionalRow(_SetDeleteRow):
"""Google Cloud Bigtable Row for sending mutations conditionally.
Each mutation has an associated state: :data:`True` or :data:`False`.
When :meth:`commit`-ed, the mutations for the :data:`True`
state will be applied if the filter matches any cells in
the row, otherwise the :data:`False` state will be applied.
A :class:`ConditionalRow` accumulates mutations in the same way a
:class:`DirectRow` does:
* :meth:`set_cell`
* :meth:`delete`
* :meth:`delete_cell`
* :meth:`delete_cells`
with the only change the extra ``state`` parameter::
>>> row_cond = table.row(b'row-key2', filter_=row_filter)
>>> row_cond.set_cell(u'fam', b'col', b'cell-val', state=True)
>>> row_cond.delete_cell(u'fam', b'col', state=False)
.. note::
As with :class:`DirectRow`, to actually send these mutations to the
Google Cloud Bigtable API, you must call :meth:`commit`.
:type row_key: bytes
:param row_key: The key for the current row.
:type table: :class:`Table <gcloud.bigtable.table.Table>`
:param table: The table that owns the row.
:type filter_: :class:`.RowFilter`
:param filter_: Filter to be used for conditional mutations.
"""
def __init__(self, row_key, table, filter_):
super(ConditionalRow, self).__init__(row_key, table)
self._filter = filter_
self._true_pb_mutations = []
self._false_pb_mutations = []
def _get_mutations(self, state):
"""Gets the list of mutations for a given state.
Over-ridden so that the state can be used in:
* :meth:`set_cell`
* :meth:`delete`
* :meth:`delete_cell`
* :meth:`delete_cells`
:type state: bool
:param state: The state that the mutation should be
applied in.
:rtype: list
:returns: The list to add new mutations to (for the current state).
"""
if state:
return self._true_pb_mutations
else:
return self._false_pb_mutations
def commit(self):
"""Makes a ``CheckAndMutateRow`` API request.
If no mutations have been created in the row, no request is made.
The mutations will be applied conditionally, based on whether the
filter matches any cells in the :class:`ConditionalRow` or not. (Each
method which adds a mutation has a ``state`` parameter for this
purpose.)
Mutations are applied atomically and in order, meaning that earlier
mutations can be masked / negated by later ones. Cells already present
in the row are left unchanged unless explicitly changed by a mutation.
After committing the accumulated mutations, resets the local
mutations.
:rtype: bool
:returns: Flag indicating if the filter was matched (which also
indicates which set of mutations were applied by the server).
:raises: :class:`ValueError <exceptions.ValueError>` if the number of
mutations exceeds the :data:`MAX_MUTATIONS`.
"""
true_mutations = self._get_mutations(state=True)
false_mutations = self._get_mutations(state=False)
num_true_mutations = len(true_mutations)
num_false_mutations = len(false_mutations)
if num_true_mutations == 0 and num_false_mutations == 0:
return
if (num_true_mutations > MAX_MUTATIONS or
num_false_mutations > MAX_MUTATIONS):
raise ValueError(
'Exceed the maximum allowable mutations (%d). Had %s true '
'mutations and %d false mutations.' % (
MAX_MUTATIONS, num_true_mutations, num_false_mutations))
request_pb = messages_v2_pb2.CheckAndMutateRowRequest(
table_name=self._table.name,
row_key=self._row_key,
predicate_filter=self._filter.to_pb(),
true_mutations=true_mutations,
false_mutations=false_mutations,
)
# We expect a `.messages_v2_pb2.CheckAndMutateRowResponse`
client = self._table._instance._client
resp = client._data_stub.CheckAndMutateRow(
request_pb, client.timeout_seconds)
self.clear()
return resp.predicate_matched
# pylint: disable=arguments-differ
def set_cell(self, column_family_id, column, value, timestamp=None,
state=True):
"""Sets a value in this row.
The cell is determined by the ``row_key`` of this
:class:`ConditionalRow` and the ``column``. The ``column`` must be in
an existing :class:`.ColumnFamily` (as determined by
``column_family_id``).
.. note::
This method adds a mutation to the accumulated mutations on this
row, but does not make an API request. To actually
send an API request (with the mutations) to the Google Cloud
Bigtable API, call :meth:`commit`.
:type column_family_id: str
:param column_family_id: The column family that contains the column.
Must be of the form
``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
:type column: bytes
:param column: The column within the column family where the cell
is located.
:type value: bytes or :class:`int`
:param value: The value to set in the cell. If an integer is used,
will be interpreted as a 64-bit big-endian signed
integer (8 bytes).
:type timestamp: :class:`datetime.datetime`
:param timestamp: (Optional) The timestamp of the operation.
:type state: bool
:param state: (Optional) The state that the mutation should be
applied in. Defaults to :data:`True`.
"""
self._set_cell(column_family_id, column, value, timestamp=timestamp,
state=state)
def delete(self, state=True):
"""Deletes this row from the table.
.. note::
This method adds a mutation to the accumulated mutations on this
row, but does not make an API request. To actually
send an API request (with the mutations) to the Google Cloud
Bigtable API, call :meth:`commit`.
:type state: bool
:param state: (Optional) The state that the mutation should be
applied in. Defaults to :data:`True`.
"""
self._delete(state=state)
def delete_cell(self, column_family_id, column, time_range=None,
state=True):
"""Deletes cell in this row.
.. note::
This method adds a mutation to the accumulated mutations on this
row, but does not make an API request. To actually
send an API request (with the mutations) to the Google Cloud
Bigtable API, call :meth:`commit`.
:type column_family_id: str
:param column_family_id: The column family that contains the column
or columns with cells being deleted. Must be
of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
:type column: bytes
:param column: The column within the column family that will have a
cell deleted.
:type time_range: :class:`TimestampRange`
:param time_range: (Optional) The range of time within which cells
should be deleted.
:type state: bool
:param state: (Optional) The state that the mutation should be
applied in. Defaults to :data:`True`.
"""
self._delete_cells(column_family_id, [column], time_range=time_range,
state=state)
def delete_cells(self, column_family_id, columns, time_range=None,
state=True):
"""Deletes cells in this row.
.. note::
This method adds a mutation to the accumulated mutations on this
row, but does not make an API request. To actually
send an API request (with the mutations) to the Google Cloud
Bigtable API, call :meth:`commit`.
:type column_family_id: str
:param column_family_id: The column family that contains the column
or columns with cells being deleted. Must be
of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
:type columns: :class:`list` of :class:`str` /
:func:`unicode <unicode>`, or :class:`object`
:param columns: The columns within the column family that will have
cells deleted. If :attr:`ALL_COLUMNS` is used then the
entire column family will be deleted from the row.
:type time_range: :class:`TimestampRange`
:param time_range: (Optional) The range of time within which cells
should be deleted.
:type state: bool
:param state: (Optional) The state that the mutation should be
applied in. Defaults to :data:`True`.
"""
self._delete_cells(column_family_id, columns, time_range=time_range,
state=state)
# pylint: enable=arguments-differ
def clear(self):
"""Removes all currently accumulated mutations on the current row."""
del self._true_pb_mutations[:]
del self._false_pb_mutations[:]
class AppendRow(Row):
"""Google Cloud Bigtable Row for sending append mutations.
These mutations are intended to augment the value of an existing cell
and uses the methods:
* :meth:`append_cell_value`
* :meth:`increment_cell_value`
The first works by appending bytes and the second by incrementing an
integer (stored in the cell as 8 bytes). In either case, if the
cell is empty, assumes the default empty value (empty string for
bytes or and 0 for integer).
:type row_key: bytes
:param row_key: The key for the current row.
:type table: :class:`Table <gcloud.bigtable.table.Table>`
:param table: The table that owns the row.
"""
def __init__(self, row_key, table):
super(AppendRow, self).__init__(row_key, table)
self._rule_pb_list = []
def clear(self):
"""Removes all currently accumulated modifications on current row."""
del self._rule_pb_list[:]
def append_cell_value(self, column_family_id, column, value):
"""Appends a value to an existing cell.
.. note::
This method adds a read-modify rule protobuf to the accumulated
read-modify rules on this row, but does not make an API
request. To actually send an API request (with the rules) to the
Google Cloud Bigtable API, call :meth:`commit`.
:type column_family_id: str
:param column_family_id: The column family that contains the column.
Must be of the form
``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
:type column: bytes
:param column: The column within the column family where the cell
is located.
:type value: bytes
:param value: The value to append to the existing value in the cell. If
the targeted cell is unset, it will be treated as
containing the empty string.
"""
column = _to_bytes(column)
value = _to_bytes(value)
rule_pb = data_v2_pb2.ReadModifyWriteRule(
family_name=column_family_id,
column_qualifier=column,
append_value=value)
self._rule_pb_list.append(rule_pb)
def increment_cell_value(self, column_family_id, column, int_value):
"""Increments a value in an existing cell.
Assumes the value in the cell is stored as a 64 bit integer
serialized to bytes.
.. note::
This method adds a read-modify rule protobuf to the accumulated
read-modify rules on this row, but does not make an API
request. To actually send an API request (with the rules) to the
Google Cloud Bigtable API, call :meth:`commit`.
:type column_family_id: str
:param column_family_id: The column family that contains the column.
Must be of the form
``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
:type column: bytes
:param column: The column within the column family where the cell
is located.
:type int_value: int
:param int_value: The value to increment the existing value in the cell
by. If the targeted cell is unset, it will be treated
as containing a zero. Otherwise, the targeted cell
must contain an 8-byte value (interpreted as a 64-bit
big-endian signed integer), or the entire request
will fail.
"""
column = _to_bytes(column)
rule_pb = data_v2_pb2.ReadModifyWriteRule(
family_name=column_family_id,
column_qualifier=column,
increment_amount=int_value)
self._rule_pb_list.append(rule_pb)
def commit(self):
"""Makes a ``ReadModifyWriteRow`` API request.
This commits modifications made by :meth:`append_cell_value` and
:meth:`increment_cell_value`. If no modifications were made, makes
no API request and just returns ``{}``.
Modifies a row atomically, reading the latest existing
timestamp / value from the specified columns and writing a new value by
appending / incrementing. The new cell created uses either the current
server time or the highest timestamp of a cell in that column (if it
exceeds the server time).
After committing the accumulated mutations, resets the local mutations.
.. code:: python
>>> append_row.commit()
{
u'col-fam-id': {
b'col-name1': [
(b'cell-val', datetime.datetime(...)),
(b'cell-val-newer', datetime.datetime(...)),
],
b'col-name2': [
(b'altcol-cell-val', datetime.datetime(...)),
],
},
u'col-fam-id2': {
b'col-name3-but-other-fam': [
(b'foo', datetime.datetime(...)),
],
},
}
:rtype: dict
:returns: The new contents of all modified cells. Returned as a
dictionary of column families, each of which holds a
dictionary of columns. Each column contains a list of cells
modified. Each cell is represented with a two-tuple with the
value (in bytes) and the timestamp for the cell.
:raises: :class:`ValueError <exceptions.ValueError>` if the number of
mutations exceeds the :data:`MAX_MUTATIONS`.
"""
num_mutations = len(self._rule_pb_list)
if num_mutations == 0:
return {}
if num_mutations > MAX_MUTATIONS:
raise ValueError('%d total append mutations exceed the maximum '
'allowable %d.' % (num_mutations, MAX_MUTATIONS))
request_pb = messages_v2_pb2.ReadModifyWriteRowRequest(
table_name=self._table.name,
row_key=self._row_key,
rules=self._rule_pb_list,
)
# We expect a `.data_v2_pb2.Row`
client = self._table._instance._client
row_response = client._data_stub.ReadModifyWriteRow(
request_pb, client.timeout_seconds)
# Reset modifications after commit-ing request.
self.clear()
# NOTE: We expect row_response.key == self._row_key but don't check.
return _parse_rmw_row_response(row_response)
def _parse_rmw_row_response(row_response):
"""Parses the response to a ``ReadModifyWriteRow`` request.
:type row_response: :class:`.data_v2_pb2.Row`
:param row_response: The response row (with only modified cells) from a
``ReadModifyWriteRow`` request.
:rtype: dict
:returns: The new contents of all modified cells. Returned as a
dictionary of column families, each of which holds a
dictionary of columns. Each column contains a list of cells
modified. Each cell is represented with a two-tuple with the
value (in bytes) and the timestamp for the cell. For example:
.. code:: python
{
u'col-fam-id': {
b'col-name1': [
(b'cell-val', datetime.datetime(...)),
(b'cell-val-newer', datetime.datetime(...)),
],
b'col-name2': [
(b'altcol-cell-val', datetime.datetime(...)),
],
},
u'col-fam-id2': {
b'col-name3-but-other-fam': [
(b'foo', datetime.datetime(...)),
],
},
}
"""
result = {}
for column_family in row_response.row.families:
column_family_id, curr_family = _parse_family_pb(column_family)
result[column_family_id] = curr_family
return result
def _parse_family_pb(family_pb):
"""Parses a Family protobuf into a dictionary.
:type family_pb: :class:`._generated_v2.data_pb2.Family`
:param family_pb: A protobuf
:rtype: tuple
:returns: A string and dictionary. The string is the name of the
column family and the dictionary has column names (within the
family) as keys and cell lists as values. Each cell is
represented with a two-tuple with the value (in bytes) and the
timestamp for the cell. For example:
.. code:: python
{
b'col-name1': [
(b'cell-val', datetime.datetime(...)),
(b'cell-val-newer', datetime.datetime(...)),
],
b'col-name2': [
(b'altcol-cell-val', datetime.datetime(...)),
],
}
"""
result = {}
for column in family_pb.columns:
result[column.qualifier] = cells = []
for cell in column.cells:
val_pair = (
cell.value,
_datetime_from_microseconds(cell.timestamp_micros),
)
cells.append(val_pair)
return family_pb.name, result