python基于happybase库实现hbase工具类封装

说明:对Python操作hbase的常用操作做一个统一的封装,同时hbase的rowkey(行键)常用的存储方式是hash散列方式存储,故该工具类扩展了常用MD5加密的方式,如需要其他方式的存储请自行扩展。

Python3连接hbase问题汇总_MY测试之道的博客-CSDN博客

"""
    hbase相关的操作
"""

import happybase
import hashlib

def md5_encrypt(text):
    """对字符串MD5加密,取前32位"""
    md5 = hashlib.md5()
    md5.update(text.encode('utf-8'))
    encrypted_text = md5.hexdigest()
    # 取前32位字符
    encrypted_text_32 = encrypted_text[:32]
    return encrypted_text_32

class HbaseUtils:

    # 初始化table对象
    __init_table = None
    def __init__(self, bootstrap_servers=[], protocol='binary', transport='buffered', table_name=None):
        """
        :function: 初始化Hbase连接
        :param bootstrap_servers: Hbase服务器集群地址和Thrift服务端口, 默认端口:9090 ,eg:192.168.172.121:9090
        :param protocol: 连接协议,默认为 'binary'
        :param transport: 传输方式,默认为 'buffered'
        :param table_name: 初始化连接的表,默认为 None
        """
        self.connection_status = False
        self.connection_fail_info = {}
        if not isinstance(bootstrap_servers, list):
            raise ValueError("参数bootstrap_servers必须是list类型!")
        for addr in bootstrap_servers:
            host = addr.split(":")[0]
            port = 9090
            if ":" in addr:
                port = int(addr.split(":")[1])
            try:
                self.conn = happybase.Connection(host=host, port=port, protocol=protocol, transport=transport,timeout=10000)
                if self.is_hbase_master:
                    self.connection_status = True
                    if table_name:
                        self.__init_table = self.conn.table(table_name)
                    print(f"初始化Hbase主节点连接成功,主节点信息:{addr}")
                    break
            except Exception as e:
                self.connection_fail_info[f"{addr}"] = e
        if not self.connection_status:
            raise ValueError(f"初始化连接Hbase失败!,失败原因如下:\n {self.connection_fail_info}")

    @property
    def is_hbase_master(self):
        """判断连接的Hbase是否为主节点地址"""
        status = self.conn.is_table_enabled('hbase:meta')
        if status:
            return True
        else:
            return False

    def __get_table_object(self, table_name=None):
        """根据table_name获取表连接对象"""
        if table_name:
            self.__init_table = self.conn.table(table_name)
            table = self.__init_table
        elif self.__init_table:
            table = self.__init_table
        else:
           raise ValueError("参数table_name不能为空!")
        return table

    def __code_values(self, data, code_type="decode"):
        """
        :function: 对列表、元祖、字典类型的数据结构里面的bytes类型数据编码成utf-8 或 将str编码成bytes
        :param data: 需要操作的数据
        :param code_type: 默认:decode bytes->utf-8  encode;utf-8->bytes
        :return:
        """
        if not data and data !=0:
            log.error("参数data不可为空!")
            raise ValueError("参数data不可为空!")
        if isinstance(data, list) or isinstance(data, tuple):
            return [self.__code_values(item, code_type) for item in data]
        elif isinstance(data, dict):
            if code_type == "encode":
                return {key.encode(): self.__code_values(value, code_type) for key, value in data.items()}
            return {key.decode('utf-8'): self.__code_values(value, code_type) for key, value in data.items()}
        else:
            try:
                if code_type == "encode":
                    return str(data).encode()
                return data.decode('utf-8')
            except (AttributeError, UnicodeDecodeError):
                return data


    def __validate_input_type(self, data, data_type):
        """校验输入参数类型"""
        if type(data_type) is type:
            if not isinstance(data, data_type):
                raise ValueError(f"参数{data}的类型必须是{data_type}!")
        elif type(data_type) is list or type(data_type) is tuple:
            if type(data) not in data_type:
                raise ValueError(f"参数{data}的类型必须是{data_type}!")
        else:
            raise ValueError("参数data_type的类型错误!")

    def get_tables(self):
        """获取全部表信息"""
        tables_byte = self.conn.tables()
        return self.__code_values(tables_byte)


    def get_table_datas(self, table_name=None, limit=10, filter=None):
        """
        :function: 获取指定表的数据,默认前10条数据
        :param table_name: 表名 【初始化连接时指定了表名,可缺省】
        :param limit: 显示的数据条数
        :param filter: 过滤器, eg:"SingleColumnValueFilter('tag', 'user_tag_value', =, 'binary:1',true, true)"
        【注:最后两个参数true,表示返回的结果为实际预期过滤的结果;如果没有这两个参数,则不存在列user_tag_value的数据也会返回】
        :return:
        """
        data_list = []
        table = self.__get_table_object(table_name)
        if filter and limit != 10:
            data = table.scan(filter=filter, limit=limit)
        elif filter:
            data = table.scan(filter=filter)
        else:
            data = table.scan(limit=limit)
        for key, value in data:
            data_list.append([self.__code_values(key), self.__code_values(value)])
        return data_list


    def get_rows_datas(self, rowkeys, table_name=None, colums=[], rowkey_format=None):
        """
        :fuction: 根据指定条件获取指定数据集
        :param rowkeys: 行号
        :param table_name: 表名 【初始化连接时指定了表名,可缺省】
        :param colums: 列簇:列名
        :param rowkey_format: rowkey的格式,如果为:None 保持原有格式;如果为:MD5 则先对rowkey进行MD5加密
        :return:
        """
        table = self.__get_table_object(table_name)
        self.__validate_input_type(rowkeys, list)
        self.__validate_input_type(colums, list)
        if rowkey_format == "MD5":
            rowkeys = [md5_encrypt(rowkey) for rowkey in rowkeys]
        datas = table.rows(rows=rowkeys, columns=colums) if colums else table.rows(rows=rowkeys)
        if datas:
            return self.__code_values(datas)

    def upsert_one_data(self, rowkey, data, table_name=None, rowkey_format=None):
        """
        :fuction: 对指定表插入或更新单条数据 【注:如果添加的数据不存在则插入,存在则更新】
        :param rowkey: 行号
        :param data: 插入的列数据
        eg:表只有一个列簇的情况                  data = {
                                                        b'column1': b'value7',
                                                        b'column2': b'value8',
                                                     }
        eg:表存在多个列簇的情况,必须指定列簇      data = {
                                                        b'cf1:column1': b'value7',
                                                        b'cf1:column2': b'value8',
                                                    }
        :param table_name: 表名 【初始化连接时指定了表名,可缺省】
        :param rowkey_format: rowkey的格式,如果为:None 保持原有格式;如果为:MD5 则先对rowkey进行MD5加密
        :return:
        """
        tmp_dict = {}
        origin_rowkey_value = rowkey
        table = self.__get_table_object(table_name)
        self.__validate_input_type(data, dict)
        rowkey = rowkey.decode('utf-8') if isinstance(rowkey, bytes) else rowkey
        rowkey = md5_encrypt(rowkey) if rowkey_format == "MD5" else rowkey
        column_family_nums = len(table._column_family_names())

        for key, value in data.items():
            self.__validate_input_type(key, bytes)
            self.__validate_input_type(value, bytes)
            key_str = key.decode('utf-8')
            if column_family_nums > 1 and ":" not in key_str:
                raise ValueError("添加的数据没有带上列簇!")
            elif column_family_nums == 1 and ":" not in key_str:
                column_family_name = table._column_family_names()[0].decode('utf-8')
                key = f"{column_family_name}:{key_str}".encode()
                tmp_dict[key] = value

        data = tmp_dict if tmp_dict else data

        try:
            table.put(row=rowkey, data=data)
            if self.get_rows_datas(rowkeys=[f"{rowkey}"], table_name=table_name):
                print(f"rowkey={origin_rowkey_value}的数据更新成功!")
            else:
                print(f"rowkey={origin_rowkey_value}的数据添加成功!")
            return True
        except Exception as e:
            print(f"数据添加失败!失败原因:{e}")
            return False

    def upsert_batch_datas(self, datas, table_name=None, rowkey_format=None):
        """
        :function: 批量数据插入或批量数据更新
        :param datas: 数据集
        eg: key可为str或bytes类型  data = {
                                            b'row_key1': {b'cf1:column1': b'value1', b'cf2:column2': b'value2'},
                                            b'row_key2': {b'cf1:column1': b'value3', b'cf2:column2': b'value4'},
                                            # 添加更多的行和列族、列名与值
                                        }
        :param table_name: 表名 【初始化连接时指定了表名,可缺省】
        :param rowkey_format: rowkey的格式,如果为:None 保持原有格式;如果为:MD5 则先对rowkey进行MD5加密
        :return:
        """
        if isinstance(datas, dict):
            for rowkey, column_data in datas.items():
                self.__validate_input_type(column_data, dict)
                for key, value in column_data.items():
                    self.__validate_input_type(key, bytes)
                    self.__validate_input_type(value, bytes)
                self.upsert_one_data(rowkey=rowkey, data=column_data, table_name=table_name, rowkey_format=rowkey_format)
        else:
            raise ValueError("参数datas的数据类型必须是dict!")

    def delete_table_datas(self, rowkeys, columns=None, table_name=None, rowkey_format=None):
        """
        :function: 删除表数据(单条/批量)
        :param rowkeys: 行号集合 list类型
        :param columns: 列名集合 list or tuple类型
        :param table_name: 表名
        :param rowkey_format:  rowkey的格式,如果为:None 保持原有格式;如果为:MD5 则先对rowkey进行MD5加密
        :return:
        """
        table = self.__get_table_object(table_name)
        self.__validate_input_type(rowkeys, list)
        if rowkey_format == "MD5":
            rowkeys = [md5_encrypt(rowkey) for rowkey in rowkeys]
        try:

            if columns:
                self.__validate_input_type(columns, (list, tuple))
                for rowkey in rowkeys:
                    table.delete(rowkey, columns)
            else:
                for rowkey in rowkeys:
                    table.delete(rowkey)
            print("删除数据成功!")
            return True
        except Exception as e:
            print(f"删除数据失败, 失败的原因是;{e}")
            return False


    def delete_by_filter(self, filter, table_name=None):
        """
        :function: 根据过滤器删除指定表数据
        :param filter: 过滤器表达式 【注:使用列过滤器,表达式最后注意添加 true,true 两个参数,目的是避免误删其他数据】
        eg;"SingleColumnValueFilter('f1', 'column1', = , 'binary:value1_006',true, true)"
        :param table_name: 表名
        :return:
        """
        self.__validate_input_type(filter, str)
        datas = self.get_table_datas(table_name=table_name, filter=filter)
        rowkeys = [data[0] for data in datas]
        return self.delete_table_datas(rowkeys=rowkeys)


    def close(self):
        """关闭Hbase连接"""
        self.conn.close()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值