From 2335afdb3994eb4582a6ef5936ab54d4ea896e1a Mon Sep 17 00:00:00 2001 From: "jun.hu" Date: Wed, 17 Jul 2019 17:55:49 +0800 Subject: [PATCH] 2019.7.17 v1.1.15 edit from issue #239 #240 --- README.rst | 6 +++ docs/change_log.rst | 5 ++ docs/conf.py | 4 +- docs/fish_data.rst | 3 ++ fishbase/__init__.py | 2 +- fishbase/fish_data.py | 117 ++++++++++++++++++++++++++++++++++++++++ fishbase/fish_logger.py | 54 +++++++++++++++---- test/test_data.py | 30 +++++++++++ test/test_logger.py | 57 ++++++++++++++++++++ 9 files changed, 265 insertions(+), 13 deletions(-) create mode 100644 test/test_logger.py diff --git a/README.rst b/README.rst index 4f045ae..ebd2e68 100644 --- a/README.rst +++ b/README.rst @@ -123,6 +123,12 @@ fishbase 能干什么? 最近更新 ========== +2019.7.17 v1.1.15 +------------------ + +- 添加可选参数,定义日志文件格式 `#240 `_ +- 根据银行卡、身份证获取详细信息的方法 `#243 `_ + 2019.6.25 v1.1.14 ------------------ diff --git a/docs/change_log.rst b/docs/change_log.rst index 88c491c..b190af1 100644 --- a/docs/change_log.rst +++ b/docs/change_log.rst @@ -1,5 +1,10 @@ 更新记录 =========================== +2019.7.17 v1.1.15 +--------------------------- +* `#240 `_, logging, edit function :meth:`set_log_file`, optimize; +* `#243 `_, data, add function :meth:`CardBin.get_card_detail`, :meth:`IdCard.get_number_detail` doc and unittest; + 2019.6.25 v1.1.14 --------------------------- * `#238 `_, common, edit function :meth:`RMBConversion.an2cn`, :meth:`RMBConversion.cn2an` optimize; diff --git a/docs/conf.py b/docs/conf.py index ff24057..6054959 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -58,9 +58,9 @@ # built documents. # # The short X.Y version. -version = '1.1.13' +version = '1.1.15' # The full version, including alpha/beta/rc tags. -release = '1.1.13' +release = '1.1.15' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/fish_data.rst b/docs/fish_data.rst index 0392eb4..e11fa1f 100644 --- a/docs/fish_data.rst +++ b/docs/fish_data.rst @@ -6,11 +6,14 @@ fish_data.CardBin.check_bankcard fish_data.CardBin.get_bank_info fish_data.CardBin.get_cardbin_info + fish_data.CardBin.get_card_detail + fish_data.CardBin.get_bank_name_by_code fish_data.IdCard.get_checkcode fish_data.IdCard.check_number fish_data.IdCard.get_zone_info fish_data.IdCard.get_areanote_info fish_data.IdCard.get_province_info + fish_data.IdCard.get_number_detail .. automodule:: fish_data :members: diff --git a/fishbase/__init__.py b/fishbase/__init__.py index 53d7969..7658c1a 100644 --- a/fishbase/__init__.py +++ b/fishbase/__init__.py @@ -27,4 +27,4 @@ from .fish_project import * from .fish_random import * -__version__ = '1.1.14' # type: str +__version__ = '1.1.15' # type: str diff --git a/fishbase/fish_data.py b/fishbase/fish_data.py index 688a664..1a600d2 100644 --- a/fishbase/fish_data.py +++ b/fishbase/fish_data.py @@ -350,6 +350,43 @@ def get_province_info(cls): {}) return values + # 2019.07.17 create by Hu Jun, add in v1.1.15, github issue #243 + @classmethod + def get_number_detail(cls, id_num): + """ + 根据身份证号获取性别、省份、出生年月日信息 + + :param: + * id_num: (string) 要查询的银行卡号 + + :returns: + * flag: (bool) 是否查询成功 + * info: (dict) 性别信息等 + + 举例如下:: + + from fishbase.fish_data import * + + print('--- fish_data get_number_detail demo ---') + print(IdCard.get_id_num_detail('130522198407316471')) + print('---') + + 输出结果:: + + --- fish_data get_number_detail demo --- + (True, {'province': '130000', 'gender': '男', 'birth_date': '19840731'}) + --- + """ + if len(str(id_num)) != 18: + return False, {} + province = id_num[:2] + '0' * 4 + gender = '男' if int(id_num[-2]) % 2 == 1 else '女' + birth_date = id_num[6:14] + + return True, {'province': province, + 'gender': gender, + 'birth_date': birth_date} + # 2019.1.6 create by David Yi, #188 用 class CardBin 方法实现 class CardBin(object): @@ -555,3 +592,83 @@ def get_cardbin_info(cls, bank, card_type): {"bank": bank, "card_type": card_type}) return values + + # 2019.07.17 create by Hu Jun, add in v1.1.15, github issue #243 + @classmethod + @lru_cache() + def get_card_detail(cls, card_num): + """ + 根据银行卡卡号,获取银行名称和银行卡类型 + + :param: + * card_num: (string) 银行卡号 + :returns: + * flag: (bool) 是否查询成功的标识 + * info: (dict) 银行名称和银行卡类型字典 + + 举例如下:: + + from fishbase.fish_data import * + + print('--- fish_data get_card_detail demo ---') + + result = CardBin.get_card_detail('6212836989522229131') + print(result) + + print('---') + + 输出结果:: + + --- fish_data get_card_detail demo --- + + (True, {'bank_name': '中国银行', 'card_type': 'DC'}) + --- + + """ + # 根据 card_bin 以及卡号长度查询 bank_code 和 card_type + # card_bin 的长度从 10 - 3 + for bin_len in list(range(10, 2, -1)): + value = sqlite_query('fish_data.sqlite', + 'select bankcode,cardtype from cn_cardbin where bin=:bin ' + 'and length=:length', + {"bin": card_num[:bin_len], "length": len(card_num)}) + if value: + bank_name = cls.get_bank_name_by_code(value[0][0]) + return True, {'bank_name': bank_name, + 'card_type': value[0][-1]} + return False, {} + + # 2019.07.17 create by Hu Jun, add in v1.1.15, github issue #243 + @classmethod + @lru_cache() + def get_bank_name_by_code(cls, bank_code): + """ + 根据银行代号,获取银行名称 + + :param: + * bank_code: (string) 银行代号 + :returns: + * info: (dict) 银行名称和银行卡类型字典 + + 举例如下:: + + from fishbase.fish_data import * + + print('--- fish_data get_bank_name_by_code demo ---') + + result = CardBin.get_bank_name_by_code('ABC') + print(result) + + print('---') + + 输出结果:: + + --- fish_data get_bank_name_by_code demo --- + 中国农业银行 + --- + + """ + value = sqlite_query('fish_data.sqlite', + 'select bankname from cn_bank where bankcode=:bank_code ', + {"bank_code": bank_code}) + return value[0][0] diff --git a/fishbase/fish_logger.py b/fishbase/fish_logger.py index 4132e31..9301265 100644 --- a/fishbase/fish_logger.py +++ b/fishbase/fish_logger.py @@ -26,17 +26,32 @@ # edit from https://www.jianshu.com/p/d615bf01e37b class SafeFileHandler(FileHandler): - def __init__(self, filename, mode='a', encoding=None, delay=0): + def __init__(self, filename, mode='a', encoding=None, delay=0, + file_name_format='%project_name-%log-%date'): """ Use the specified filename for streamed logging """ if codecs is None: encoding = None FileHandler.__init__(self, filename, mode, encoding, delay) + + # 日志文件路径 + self.file_path = os.path.split(filename)[0] + # 日志文件名称 + file_name = os.path.split(filename)[1] + + temp_file_name = file_name.split('.') + if len(temp_file_name) == 1: + self.project_name = temp_file_name[0] + self.log_suffix = 'log' + else: + self.project_name, self.log_suffix = temp_file_name[0], temp_file_name[1] + self.mode = mode self.encoding = encoding self.suffix = "%Y-%m-%d" self.suffix_time = "" + self.file_name_format = file_name_format def emit(self, record): """ @@ -62,8 +77,11 @@ def check_base_filename(self, record): """ time_tuple = time.localtime() + if self.file_name_format: + pass + if self.suffix_time != time.strftime(self.suffix, time_tuple) or not os.path.exists( - self.baseFilename + '.' + self.suffix_time): + self._get_format_filename()): return 1 else: return 0 @@ -79,21 +97,29 @@ def build_base_filename(self): self.stream = None # remove old suffix - if self.suffix_time != "": - index = self.baseFilename.find("." + self.suffix_time) - if index == -1: - index = self.baseFilename.rfind(".") - self.baseFilename = self.baseFilename[:index] + # if self.suffix_time != "": + # index = self.baseFilename.find("." + self.suffix_time) + # if index == -1: + # index = self.baseFilename.rfind(".") + # self.baseFilename = self.baseFilename[:index] # add new suffix current_time_tuple = time.localtime() self.suffix_time = time.strftime(self.suffix, current_time_tuple) - self.baseFilename = self.baseFilename + "." + self.suffix_time + self.baseFilename = self._get_format_filename() self.mode = 'a' if not self.delay: self.stream = self._open() + def _get_format_filename(self): + split_list = self.file_name_format.split('-') + name_mapping = {'%log': self.log_suffix, + '%project_name': self.project_name, + '%date': self.suffix_time} + new_file_name = '.'.join([name_mapping.get(i) for i in split_list]) + return os.path.join(self.file_path, new_file_name) + # 设置日志记录,按照每天一个文件,记录包括 info 以及以上级别的内容 # 输入: local_file 日志文件名 @@ -101,7 +127,8 @@ def build_base_filename(self): # 2018.2.11 edit, log 相关代码优化简化; #11010 # 2018.2.13 edit, remove thread watch # 2018.4.23 edit,#19023 增加 docstring -def set_log_file(local_file=None): +# 2019.7.16 v1.1.15 #240 edit by Hu Jun +def set_log_file(local_file=None, file_name_format='%project_name-%log-%date'): """ 设置日志记录,按照每天一个文件,记录包括 info 以及以上级别的内容; @@ -109,6 +136,7 @@ def set_log_file(local_file=None): :param: * local_fie: (string) 日志文件名 + * file_name_format: (string) 日志文件名格式 :return: 无 举例如下:: @@ -136,9 +164,15 @@ def set_log_file(local_file=None): if local_file is not None: default_log_file = local_file + support_split = ['%project_name', '%log', '%date'] + + file_format_split = file_name_format.split('-') + if set(file_format_split) != set(support_split): + raise ValueError('file_name_format error, please check and try again!') + # time rotating file handler # _tfh = TimedRotatingFileHandler(default_log_file, when="midnight") - _tfh = SafeFileHandler(filename=default_log_file) + _tfh = SafeFileHandler(filename=default_log_file, file_name_format=file_name_format) _tfh.setLevel(logging.INFO) _tfh.setFormatter(_formatter) diff --git a/test/test_data.py b/test/test_data.py index 60d4a9c..03faf01 100644 --- a/test/test_data.py +++ b/test/test_data.py @@ -153,3 +153,33 @@ def test_get_note_by_province(self): def test_get_province_info(self): values = IdCard.get_province_info() assert len(values) > 0 + + # 2019.07.17 edit by Hu Jun + def test_get_card_detail(self): + values = CardBin.get_card_detail('6212836989522229131') + assert values[0] + assert values[1].get('bank_name') == '中国银行' + assert values[1].get('card_type') == 'DC' + + # 2019.07.17 edit by Hu Jun + def test_get_card_detail_01(self): + values = CardBin.get_card_detail('123762515738129') + assert not values[0] + + # 2019.07.17 edit by Hu Jun + def test_get_bank_name_by_code(self): + result = CardBin.get_bank_name_by_code('ABC') + assert result == '中国农业银行' + + # 2019.07.17 edit by Hu Jun + def test_get_number_detail(self): + values = IdCard.get_number_detail('130522198407316471') + assert values[0] + assert values[1].get('province') == '130000' + assert values[1].get('gender') == '男' + assert values[1].get('birth_date') == '19840731' + + # 2019.07.17 edit by Hu Jun + def test_get_number_detail_01(self): + values = IdCard.get_number_detail('130522198407316') + assert not values[0] diff --git a/test/test_logger.py b/test/test_logger.py new file mode 100644 index 0000000..fb61c4f --- /dev/null +++ b/test/test_logger.py @@ -0,0 +1,57 @@ +# coding=utf-8 +# fish_logging.py 单元测试 +# 2018.7.17 create by Hu Jun + +import os +import time +import shutil +import pytest +from fishbase.fish_logger import set_log_file, logger as log +# 定义当前路径 +current_path = os.path.dirname(os.path.abspath(__file__)) + + +class TestFishLogging(object): + log_filename = '' + suffix = "%Y-%m-%d" + suffix_time = '' + log_path = '' + + def setup_class(self): + # 定义配置文件名 + self.log_path = os.path.join(current_path, 'log_path') + if not os.path.exists(self.log_path): + os.mkdir(self.log_path) + + self.log_filename = os.path.join(self.log_path, 'unittest.log') + + current_time_tuple = time.localtime() + self.suffix_time = time.strftime(self.suffix, current_time_tuple) + + def teardown_class(self): + try: + # 关闭日志文件句柄 + for h in log.handlers: + h.close() + shutil.rmtree(self.log_path) + except Exception as _: + pass + + def test_format1(self): + set_log_file(self.log_filename) + log.info('test_format1') + assert 'unittest.log.{}'.format(self.suffix_time) in os.listdir(self.log_path) + + def test_format2(self): + set_log_file(self.log_filename, file_name_format='%project_name-%date-%log') + log.info('test_format2') + assert 'unittest.{}.log'.format(self.suffix_time) in os.listdir(self.log_path) + + def test_format3(self): + set_log_file(self.log_filename, file_name_format='%date-%project_name-%log') + log.info('test_format3') + assert 'unittest.log.{}'.format(self.suffix_time) in os.listdir(self.log_path) + + def test_format4(self): + with pytest.raises(ValueError): + set_log_file(self.log_filename, file_name_format='%date-%project_name-%log1')