From 813660c00680f5a4fa4c7fea4ffd29b0333a0253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=91=AB=E5=BD=AA?= Date: Thu, 9 Nov 2023 15:01:26 +0800 Subject: [PATCH] feat: retrieve files from Minio for data processing --- assets/data_process.drawio | 109 ++++++ assets/data_process.drawio.png | Bin 0 -> 234109 bytes data-process/.gitignore | 7 + data-process/README.md | 1 + data-process/README.zh.md | 30 ++ .../data_manipulation/common/config.py | 21 ++ .../file_handle/csv_handle.py | 233 ++++++++++++ data-process/data_manipulation/server.py | 137 +++++++ .../service/minio_store_process_service.py | 176 +++++++++ .../transform/text/clean_transform.py | 55 +++ .../transform/text/duplicates_transform.py | 28 ++ .../transform/text/filtration_transform.py | 42 +++ .../transform/text/privacy_transform.py | 53 +++ .../transform/text/support_type.py | 137 +++++++ .../utils/date_time_utils.py | 43 +++ .../data_manipulation/utils/file_utils.py | 69 ++++ .../data_manipulation/utils/json_utils.py | 76 ++++ .../data_manipulation/utils/log_utils.py | 53 +++ .../data_manipulation/utils/minio_utils.py | 31 ++ data-process/docker/base/Dockerfile.base | 43 +++ data-process/docker/base/build_image.sh | 6 + data-process/docker/base/lzma.py | 352 ++++++++++++++++++ data-process/docker/base/requirements.txt | 1 + data-process/requirements.txt | 7 + 24 files changed, 1710 insertions(+) create mode 100644 assets/data_process.drawio create mode 100644 assets/data_process.drawio.png create mode 100644 data-process/.gitignore create mode 100644 data-process/README.zh.md create mode 100644 data-process/data_manipulation/common/config.py create mode 100644 data-process/data_manipulation/file_handle/csv_handle.py create mode 100644 data-process/data_manipulation/server.py create mode 100644 data-process/data_manipulation/service/minio_store_process_service.py create mode 100644 data-process/data_manipulation/transform/text/clean_transform.py create mode 100644 data-process/data_manipulation/transform/text/duplicates_transform.py create mode 100644 data-process/data_manipulation/transform/text/filtration_transform.py create mode 100644 data-process/data_manipulation/transform/text/privacy_transform.py create mode 100644 data-process/data_manipulation/transform/text/support_type.py create mode 100644 data-process/data_manipulation/utils/date_time_utils.py create mode 100644 data-process/data_manipulation/utils/file_utils.py create mode 100644 data-process/data_manipulation/utils/json_utils.py create mode 100644 data-process/data_manipulation/utils/log_utils.py create mode 100644 data-process/data_manipulation/utils/minio_utils.py create mode 100644 data-process/docker/base/Dockerfile.base create mode 100644 data-process/docker/base/build_image.sh create mode 100644 data-process/docker/base/lzma.py create mode 100644 data-process/docker/base/requirements.txt create mode 100644 data-process/requirements.txt diff --git a/assets/data_process.drawio b/assets/data_process.drawio new file mode 100644 index 000000000..df5ea5cb7 --- /dev/null +++ b/assets/data_process.drawio @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/data_process.drawio.png b/assets/data_process.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..7f2bb48cfe27c9278766bb1f5720d1b1c27751de GIT binary patch literal 234109 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⪕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 z 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 He@^`u@=CH@ literal 0 HcmV?d00001 diff --git a/data-process/.gitignore b/data-process/.gitignore new file mode 100644 index 000000000..700634c4f --- /dev/null +++ b/data-process/.gitignore @@ -0,0 +1,7 @@ +# python +__pycache__ +.ipynb_checkpoints + +mock_data + +log \ No newline at end of file diff --git a/data-process/README.md b/data-process/README.md index 46add2464..ab0485585 100644 --- a/data-process/README.md +++ b/data-process/README.md @@ -1 +1,2 @@ # Data Process +The current documentation is only available in Chinese. Please refer to the content in .zh.md for specific details. \ No newline at end of file diff --git a/data-process/README.zh.md b/data-process/README.zh.md new file mode 100644 index 000000000..668906ef2 --- /dev/null +++ b/data-process/README.zh.md @@ -0,0 +1,30 @@ +# Data Process + +## 当前版本主要功能 +Data Process用于做数据处理,通MinIO、数据库、Web API等方式获取数据,数据类型包括一下几种: +- txt +- json +- doc +- html +- excel +- csv +- pdf +- markdown +- ppt + +### 当前文本类型处理 +数据处理的过程包括:异常数据清洗、过滤、去重、去隐私。 + +## 设计 +![设计](../assets/data_process.drawio.png) + +## 本地开发 +### 软件要求 +本地搭建 data-process 环境之前请确保已经安装一下软件: +- Python 3.10.x + +### 环境搭建 +安装 requirements.txt 中的python依赖包 + +### 运行 +python data_manipulation/server.py文件 \ No newline at end of file diff --git a/data-process/data_manipulation/common/config.py b/data-process/data_manipulation/common/config.py new file mode 100644 index 000000000..def88e1aa --- /dev/null +++ b/data-process/data_manipulation/common/config.py @@ -0,0 +1,21 @@ +# Copyright 2023 KubeAGI. +# +# 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. + +import os + +minio_access_key = os.getenv('MINIO_ACCESSKEY', 'minioadmin') +minio_secret_key = os.getenv('MINIO_SECRETKEY', 'minioadmin') +minio_api_url = os.getenv('MINIO_API_URL', '192.168.90.31:9000') +# 如果使用HTTP,将secure设置为False;如果使用HTTPS,将其设置为True +minio_secure = os.getenv('MINIO_SECURE', False) diff --git a/data-process/data_manipulation/file_handle/csv_handle.py b/data-process/data_manipulation/file_handle/csv_handle.py new file mode 100644 index 000000000..dca6d0c90 --- /dev/null +++ b/data-process/data_manipulation/file_handle/csv_handle.py @@ -0,0 +1,233 @@ +# Copyright 2023 KubeAGI. +# +# 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. + +### +# CSV文件处理 +# @author: wangxinbiao +# @date: 2023-11-01 16:43:01 +# modify history +# ==== 2023-11-01 16:43:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### + +import csv +import ulid +import pandas as pd +import os +import logging + +from transform.text import ( + clean_transform, + privacy_transform +) + +from utils import ( + date_time_utils, + file_utils +) + +logger = logging.getLogger('csv_handle') + +### +# 文本数据处理 +# @author: wangxinbiao +# @date: 2023-11-02 14:42:01 +# modify history +# ==== 2023-11-02 14:42:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def text_manipulate(opt={}): + logger.info("csv text manipulate!") + + """ + 数据处理逻辑: + 处理某条数据时,如果某个方式(比如:去除不可见字符)处理失败了,则直接结束,不在处理,整个文件都视作处理失败 + + """ + + try: + file_name = opt['file_name'] + support_type = opt['support_type'] + + csv_file_path = await file_utils.get_temp_file_path() + file_path = csv_file_path + 'original/' + file_name + + # 获取CSV文件的内容 + data = pd.read_csv(file_path) + + logger.info('data') + + clean_text_list = [] + logger.info("start text manipulate!") + text_data = data['prompt'] + + # 数据清洗 + clean_result = await data_clean({ + 'support_type': support_type, + 'file_name': file_name, + 'data': text_data + }) + + if clean_result['status'] != 200: + return clean_result + + text_data = clean_result['data'] + + + # 将清洗后的文件保存为final + new_file_name = await file_utils.get_file_name({ + 'file_name': file_name, + 'handle_name': 'final' + }) + + await save_csv({ + 'file_name': new_file_name, + 'phase_value': 'final', + 'data': text_data + }) + + return { + 'status': 200, + 'message': '', + 'data': '' + } + except Exception as ex: + return { + 'status': 400, + 'message': '', + 'data': '' + } + + +### +# 数据异常清洗 +# @author: wangxinbiao +# @date: 2023-11-08 09:32:01 +# modify history +# ==== 2023-11-08 09:32:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def data_clean(opt={}): + logger.info("csv text data clean start!") + support_type = opt['support_type'] + data = opt['data'] + + # 去除不可见字符 + if 'remove_invisible_characters' in support_type: + clean_data = [] + for item in data: + result = await remove_invisible_characters({ + 'text': item + }) + + if result['status'] != 200: + return { + 'status': 400, + 'message': '去除不可见字符失败', + 'data': '' + } + + clean_data.append(result['data']) + data = clean_data + + # 将文件存为middle + file_name = await file_utils.get_file_name({ + 'file_name': opt['file_name'], + 'handle_name': 'middle' + }) + + await save_csv({ + 'file_name': file_name, + 'phase_value': 'middle', + 'data': data + }) + + logger.info("csv text data clean stop!") + + return { + 'status': 200, + 'message': '', + 'data': data + } + + +### +# 去除不可见字符 +# @author: wangxinbiao +# @date: 2023-11-02 14:42:01 +# modify history +# ==== 2023-11-02 14:42:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def remove_invisible_characters(opt={}): + return await clean_transform.remove_invisible_characters({ + 'text': opt['text'] + }) + +### +# 去除邮箱地址 +# @author: wangxinbiao +# @date: 2023-11-02 14:42:01 +# modify history +# ==== 2023-11-02 14:42:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def remove_email(opt={}): + return await privacy_transform.remove_email({ + 'text': opt['text'] + }) + +### +# 将数据存到CSV中 +# @author: wangxinbiao +# @date: 2023-11-02 14:42:01 +# modify history +# ==== 2023-11-02 14:42:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def save_csv(opt={}): + file_name = opt['file_name'] + phase_value = opt['phase_value'] + data = opt['data'] + + csv_file_path = await file_utils.get_temp_file_path() + + # 如果文件夹不存在,则创建 + directory_path = csv_file_path + phase_value + if not os.path.exists(directory_path): + os.makedirs(directory_path) + + file_path = directory_path + '/' + file_name + + with open(file_path, 'w', newline='') as file: + writer = csv.writer(file) + + writer.writerow(['prompt']) + + for row in data: + writer.writerow([row]) + + return file_path diff --git a/data-process/data_manipulation/server.py b/data-process/data_manipulation/server.py new file mode 100644 index 000000000..0059e6340 --- /dev/null +++ b/data-process/data_manipulation/server.py @@ -0,0 +1,137 @@ +# Copyright 2023 KubeAGI. +# +# 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. + + +### +# 数据处理 后端 +# @author: wangxinbiao +# @date: 2023-10-31 17:34:01 +# modify history +# ==== 2023-10-31 17:34:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### + +from sanic import Sanic +from sanic.response import json, text +from sanic_cors import CORS, cross_origin +from sanic.exceptions import NotFound + +import asyncio +import aiohttp + +import sys + +import logging + + +from service import ( + minio_store_process_service +) + +from transform.text import ( + support_type +) + +from utils import ( + log_utils +) + +### +# 初始化日志配置 +### +log_utils.init_config({ + 'source_type': 'manipulate_server' +}) + + +logger = logging.getLogger('manipulate_server') + +app = Sanic(name='data_manipulate') +CORS(app) + +app.config['REQUEST_MAX_SIZE'] = 1024 * 1024 * 1024 # 1G +app.config['REQUEST_TIMEOUT'] = 60 * 60 * 60 +app.config['RESPONSE_TIMEOUT'] = 60 * 60 * 60 +app.config['KEEP_ALIVE_TIMEOUT'] = 60 * 60 * 60 + +### +# 文本数据处理 +# @author: wangxinbiao +# @date: 2023-11-01 10:44:01 +# modify history +# ==== 2023-11-01 10:44:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +@app.route('text-manipulate', methods=['POST']) +async def text_manipulate(request): + """ + 对文本类数据进行处理 + + Args: + type: 对文本数据需要进行那些处理; + file_path: 文本路径 + + Returns: + + """ + + await asyncio.create_task( + minio_store_process_service.text_manipulate(request) + ) + + return json({ + 'status': 200, + 'message': '', + 'data': '' + }) + +### +# 数据处理支持类型 +# @author: wangxinbiao +# @date: 2023-11-02 14:42:01 +# modify history +# ==== 2023-11-02 14:42:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +@app.route('text-process-type', methods=['POST']) +async def text_process_type(request): + """ + 获取数据处理支持的类型 + + Args: + + + Returns: + json: 支持的类型 + """ + + return json({ + 'status': 200, + 'message': '', + 'data': support_type.support_types + }) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', + port=28888, + access_log=True, + debug=True, + workers=2) \ No newline at end of file diff --git a/data-process/data_manipulation/service/minio_store_process_service.py b/data-process/data_manipulation/service/minio_store_process_service.py new file mode 100644 index 000000000..beba735ba --- /dev/null +++ b/data-process/data_manipulation/service/minio_store_process_service.py @@ -0,0 +1,176 @@ +# Copyright 2023 KubeAGI. +# +# 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. + +### +# MinIO +# @author: wangxinbiao +# @date: 2023-11-01 16:43:01 +# modify history +# ==== 2023-11-01 16:43:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### + +from sanic.response import json, raw +from minio import Minio +from minio.commonconfig import Tags +from minio.error import S3Error +import pandas as pd +import io +import os + +import logging + +from file_handle import ( + csv_handle +) + +from utils import ( + minio_utils, + file_utils +) + +logger = logging.getLogger('minio_store_process_service') + +### +# 文本数据处理 +# @author: wangxinbiao +# @date: 2023-11-01 10:44:01 +# modify history +# ==== 2023-11-01 10:44:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def text_manipulate(request): + + request_json = request.json + bucket_name = request_json['bucket_name'] + support_type = request_json['type'] + folder_prefix = request_json['folder_prefix'] + + # create minio client + minio_client = await minio_utils.create_client() + + # 查询存储桶下的所有对象 + objects = minio_client.list_objects(bucket_name, prefix=folder_prefix) + + # 将文件都下载到本地 + file_names = await download({ + 'minio_client': minio_client, + 'bucket_name': bucket_name, + 'folder_prefix': folder_prefix, + 'objects': objects + }) + + # 文件处理 + for item in file_names: + file_extension = item.split('.')[-1].lower() + if file_extension in ['csv']: + # 处理CSV文件 + result = await csv_handle.text_manipulate({ + 'file_name': item, + 'support_type': support_type + }) + + # 将清洗后的文件上传到MinIO中 + # 上传middle文件夹下的文件,并添加tag + tags = Tags(for_object=True) + tags["phase"] = "middle" + file_path = await file_utils.get_temp_file_path() + await upload_files_to_minio_with_tags( + minio_client, + file_path + 'middle', + bucket_name, + minio_prefix=folder_prefix, + tags=tags + ) + + # 上传final文件夹下的文件,并添加tag + tags["phase"] = "final" + await upload_files_to_minio_with_tags( + minio_client, + file_path + 'final', + bucket_name, + minio_prefix=folder_prefix, + tags=tags + ) + + # 将本地临时文件删除 + for item in file_names: + remove_file_path = await file_utils.get_temp_file_path() + await file_utils.delete_file(remove_file_path + 'original/' + item) + + return json({ + 'status': 200, + 'message': '', + 'data': '' + }) + +### +# 下载MinIO中的文件 +# @author: wangxinbiao +# @date: 2023-11-07 17:42:01 +# modify history +# ==== 2023-11-07 17:42:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def download(opt={}): + objects = opt['objects'] + minio_client = opt['minio_client'] + bucket_name = opt['bucket_name'] + folder_prefix = opt['folder_prefix'] + file_names = [] + for obj in objects: + file_name = obj.object_name[len(folder_prefix):] + + data = minio_client.get_object(bucket_name, obj.object_name) + df = pd.read_csv(data) + + await csv_handle.save_csv({ + 'file_name': file_name, + 'phase_value': 'original', + 'data': df['prompt'] + }) + file_names.append(file_name) + + return file_names + +### +# 文件上传至MinIO,添加Tags +# @author: wangxinbiao +# @date: 2023-11-08 14:02:01 +# modify history +# ==== 2023-11-08 14:02:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def upload_files_to_minio_with_tags(minio_client, local_folder, minio_bucket, minio_prefix="", tags=None): + for root, dirs, files in os.walk(local_folder): + for file in files: + local_file_path = os.path.join(root, file) + minio_object_name = os.path.join(minio_prefix, os.path.relpath(local_file_path, local_folder)) + + try: + minio_client.fput_object(minio_bucket, minio_object_name, local_file_path, tags=tags) + + # 删除本地文件 + await file_utils.delete_file(local_file_path) + except S3Error as e: + logger.error(f"Error uploading {minio_object_name} to {minio_bucket}: {e}") + diff --git a/data-process/data_manipulation/transform/text/clean_transform.py b/data-process/data_manipulation/transform/text/clean_transform.py new file mode 100644 index 000000000..5e50dc3c0 --- /dev/null +++ b/data-process/data_manipulation/transform/text/clean_transform.py @@ -0,0 +1,55 @@ +# Copyright 2023 KubeAGI. +# +# 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. + + +### +# 异常清洗 +# @author: wangxinbiao +# @date: 2023-11-01 10:44:01 +# modify history +# ==== 2023-11-01 10:44:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### + +import re + +### +# 去除不可见字符 +# @author: wangxinbiao +# @date: 2023-11-02 14:42:01 +# modify history +# ==== 2023-11-02 14:42:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def remove_invisible_characters(opt={}): + text = opt['text'] + + try: + clean_text = re.sub(r'[\x00-\x1F\x7F-\x9F\xAD\r\n\t\b\x0B\x1C\x1D\x1E]', '', text) + return { + 'status': 200, + 'message': '', + 'data': clean_text + } + + except Exception as ex: + return { + 'status': 400, + 'message': '去除不可见字符失败:' + str(ex), + 'data': '' + } diff --git a/data-process/data_manipulation/transform/text/duplicates_transform.py b/data-process/data_manipulation/transform/text/duplicates_transform.py new file mode 100644 index 000000000..69962ef96 --- /dev/null +++ b/data-process/data_manipulation/transform/text/duplicates_transform.py @@ -0,0 +1,28 @@ +# Copyright 2023 KubeAGI. +# +# 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. + + +### +# 去重 +# @author: wangxinbiao +# @date: 2023-11-02 14:42:01 +# modify history +# ==== 2023-11-02 14:42:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def remove_duplicates(opt={}): + return opt['text'] + diff --git a/data-process/data_manipulation/transform/text/filtration_transform.py b/data-process/data_manipulation/transform/text/filtration_transform.py new file mode 100644 index 000000000..ba692eff0 --- /dev/null +++ b/data-process/data_manipulation/transform/text/filtration_transform.py @@ -0,0 +1,42 @@ +# Copyright 2023 KubeAGI. +# +# 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. + + +### +# 过滤 +# @author: wangxinbiao +# @date: 2023-11-01 10:44:01 +# modify history +# ==== 2023-11-01 10:44:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### + +import re + +### +# 检查文档的词数目 +# @author: wangxinbiao +# @date: 2023-11-02 14:42:01 +# modify history +# ==== 2023-11-02 14:42:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def word_count(opt={}): + return 49 + + diff --git a/data-process/data_manipulation/transform/text/privacy_transform.py b/data-process/data_manipulation/transform/text/privacy_transform.py new file mode 100644 index 000000000..6b3e6df1d --- /dev/null +++ b/data-process/data_manipulation/transform/text/privacy_transform.py @@ -0,0 +1,53 @@ +# Copyright 2023 KubeAGI. +# +# 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. + + +### +# 去隐私 +# @author: wangxinbiao +# @date: 2023-11-01 10:44:01 +# modify history +# ==== 2023-11-01 10:44:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### + +import re + +### +# 去除邮箱地址 +# @author: wangxinbiao +# @date: 2023-11-02 14:42:01 +# modify history +# ==== 2023-11-02 14:42:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def remove_email(opt={}): + text = opt['text'] + + try: + email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' + + # 将邮箱地址替换为 "PI:EMAIL" + replacement_text = "PI:EMAIL" + + cleaned_text = re.sub(email_pattern, replacement_text, chinese_text) + return clean_text + + except Exception as ex: + return '' + \ No newline at end of file diff --git a/data-process/data_manipulation/transform/text/support_type.py b/data-process/data_manipulation/transform/text/support_type.py new file mode 100644 index 000000000..788aaf0c5 --- /dev/null +++ b/data-process/data_manipulation/transform/text/support_type.py @@ -0,0 +1,137 @@ +# Copyright 2023 KubeAGI. +# +# 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. + + +### +# 数据处理支持的类型 +# @author: wangxinbiao +# @date: 2023-11-02 14:25:01 +# modify history +# ==== 2023-11-02 14:25:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### + +support_types = [ + { + 'name': 'clean', + 'description': '异常清洗', + 'children': [ + { + 'name': 'remove_invisible_characters', + 'zh_name': '移除不可见字符', + 'description': '移除ASCII中的一些不可见字符, 如0-32 和127-160这两个范围' + }, + { + 'name': 'space_standardization', + 'zh_name': '规范化空格', + 'description': '将不同的unicode空格比如u2008, 转成正常的空格' + }, + { + 'name': 'remove_garbled_text', + 'zh_name': '去除乱码', + 'description': '去除乱码和无意义的unicode' + }, + { + 'name': 'traditional_to_simplified', + 'zh_name': '繁体转简体', + 'description': '繁体转简体,如“不經意,妳的笑容”清洗成“不经意,你的笑容”' + }, + { + 'name': 'remove_html_tag', + 'zh_name': '去除网页标识符', + 'description': '移除文档中的html标签, 如,,

等' + }, + { + 'name': 'remove_emojis', + 'zh_name': '去除表情', + 'description': '去除文档中的表情,如‘🐰’, ‘🧑🏼’等' + } + ] + }, + { + 'name': 'filtration', + 'description': '过滤', + 'children': [ + { + 'name': 'word_count', + 'zh_name': '检查文档的词数目', + 'description': '词数目不在指定范围会被过滤掉,如中文[1,1000000]' + }, + { + 'name': 'character_duplication_rate', + 'zh_name': '检查文档的字重复率', + 'description': '如果字重复率太高,意味着文档中重复的字太多,文档会被过滤掉' + }, + { + 'name': 'word_duplication_rate', + 'zh_name': '检查文档的词重复率', + 'description': '如果词重复率太高,意味着文档中重复的词太多,文档会被过滤掉' + }, + { + 'name': 'special_character_rate', + 'zh_name': '检查文档的特殊字符率', + 'description': '如果特殊字符率太高,意味着文档中特殊字符太多,文档会被过滤掉' + }, + { + 'name': 'pornography_violence_word_rate', + 'zh_name': '检查文档的色情暴力词率', + 'description': '如果色情暴力词率太高,文档会被过滤掉' + }, + { + 'name': 'language_probability', + 'zh_name': '检查文档的语言概率', + 'description': '如果语言概率太低,文档会被过滤掉' + }, + { + 'name': 'perplexity', + 'zh_name': '检查文档的困惑度', + 'description': '如果困惑度太高,文档会被过滤掉' + } + ] + }, + { + 'name': 'duplicates', + 'description': '去重', + 'children': [ + { + 'name': 'simhash_operator', + 'zh_name': 'simhash-operator', + 'description': '根据海明距离计算文档相似度, 相似度<=海明距离,认为两个文档相似。(范围:4-6)' + } + ] + }, + { + 'name': 'privacy_erosion', + 'description': '去隐私', + 'children': [ + { + 'name': 'remove_email', + 'zh_name': '去除邮箱', + 'description': '去除email地址' + }, + { + 'name': 'remove_ip_address', + 'zh_name': '去除IP地址', + 'description': '去除IPv4 或者 IPv6 地址' + }, + { + 'name': 'remove_number', + 'zh_name': '去除数字', + 'description': '去除数字和字母数字标识符,如电话号码、信用卡号、十六进制散列等,同时跳过年份和简单数字的实例' + } + ] + } +] diff --git a/data-process/data_manipulation/utils/date_time_utils.py b/data-process/data_manipulation/utils/date_time_utils.py new file mode 100644 index 000000000..3bd3121bb --- /dev/null +++ b/data-process/data_manipulation/utils/date_time_utils.py @@ -0,0 +1,43 @@ +# Copyright 2023 KubeAGI. +# +# 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. + + +import datetime + + +def now_str(): + return f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S.%f}" + +def now_str_for_day(): + return f"{datetime.datetime.now():%Y-%m-%d}" + + +def now_str_for_file_name(): + return f"{datetime.datetime.now():%Y_%m_%d_%H_%M_%S_%f}" + + +def timestamp_to_str(timestamp): + return f"{datetime.datetime.fromtimestamp(timestamp):%Y-%m-%d %H:%M:%S.%f}" + + +def timestamp_to_str_second(timestamp): + return f"{datetime.datetime.fromtimestamp(timestamp):%Y-%m-%d %H:%M:%S}" + + +def chage_datetime_fromat(opt={}): + my_date_time = datetime.datetime.strptime( + opt['date_time'], + opt['from_format']) + + return my_date_time.strftime(opt.get('to_format', '%Y-%m-%d %H:%M:%S')) \ No newline at end of file diff --git a/data-process/data_manipulation/utils/file_utils.py b/data-process/data_manipulation/utils/file_utils.py new file mode 100644 index 000000000..d4c50afce --- /dev/null +++ b/data-process/data_manipulation/utils/file_utils.py @@ -0,0 +1,69 @@ +# Copyright 2023 KubeAGI. +# +# 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. + +### +# 文件工具类 +### + + +import os + +### +# 生成文件名称 +# @author: wangxinbiao +# @date: 2023-11-09 10:14:01 +# modify history +# ==== 2023-11-09 10:14:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def get_file_name(opt={}): + file_name = opt['file_name'] + handle_name = opt['handle_name'] + + file_extension = file_name.split('.')[-1].lower() + file_name_without_extension = file_name.rsplit('.', 1)[0] + + return file_name_without_extension + '_' + handle_name + '.' + file_extension + +### +# 获取临时文件路径 +# @author: wangxinbiao +# @date: 2023-11-09 10:14:01 +# modify history +# ==== 2023-11-09 10:14:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def get_temp_file_path(): + current_directory = os.getcwd() + + csv_file_path = os.path.join(current_directory, 'file_handle/temp_file/') + + return csv_file_path + +### +# 删除文件 +# @author: wangxinbiao +# @date: 2023-11-09 10:14:01 +# modify history +# ==== 2023-11-09 10:14:01 ==== +# author: wangxinbiao +# content: +# 1) 基本功能实现 +### +async def delete_file(file_path): + os.remove(file_path) diff --git a/data-process/data_manipulation/utils/json_utils.py b/data-process/data_manipulation/utils/json_utils.py new file mode 100644 index 000000000..ed97a60ce --- /dev/null +++ b/data-process/data_manipulation/utils/json_utils.py @@ -0,0 +1,76 @@ +# Copyright 2023 KubeAGI. +# +# 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. + + +import ujson +from pathlib import Path + + +def pretty_print(opt={}): + data = opt.get('data', {}) + + print(ujson.dumps(data, + ensure_ascii=False, + escape_forward_slashes=False, + indent=4)) + + +def get_str_empty(opt={}): + json_item = opt['json_item'] + json_key = opt['json_key'] + + if json_item.get(json_key, '') is None: + return '' + + return json_item.get(json_key, '') + + +def write_json_file(opt={}): + file_name = Path(opt['file_name']) + with open(file_name, 'w', encoding = 'utf-8') as outfile: + dump(opt['data'], outfile, opt) + + +def read_json_file(opt={}): + file_name = Path(opt['file_name']) + json_result = None + with open(file_name, 'r', encoding = 'utf-8') as f: + json_result = ujson.load(f) + + return json_result + + + + +def dumps(json_data, opt={}): + indent = opt.get('indent', 2) + ensure_ascii = opt.get('ensure_ascii', False) + escape_forward_slashes = opt.get('escape_forward_slashes', False) + + ujson.dumps(json_data, + indent=indent, + ensure_ascii=ensure_ascii, + escape_forward_slashes=escape_forward_slashes) + + +def dump(json_data, file, opt={}): + indent = opt.get('indent', 2) + ensure_ascii = opt.get('ensure_ascii', False) + escape_forward_slashes = opt.get('escape_forward_slashes', False) + + ujson.dump(json_data, + file, + indent=indent, + ensure_ascii=ensure_ascii, + escape_forward_slashes=escape_forward_slashes) \ No newline at end of file diff --git a/data-process/data_manipulation/utils/log_utils.py b/data-process/data_manipulation/utils/log_utils.py new file mode 100644 index 000000000..4d97b19b2 --- /dev/null +++ b/data-process/data_manipulation/utils/log_utils.py @@ -0,0 +1,53 @@ +# Copyright 2023 KubeAGI. +# +# 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. + + +import logging +from logging.handlers import TimedRotatingFileHandler, RotatingFileHandler +import datetime + +def init_config (opt={}): + source_type = opt['source_type'] + ### + # 配置全局日志配置 + ### + file_handler = TimedRotatingFileHandler( + f'log/{source_type}/{source_type}_{datetime.datetime.now().strftime("%Y-%m-%d")}.log', + when="midnight", + interval=1, + backupCount=30 + ) # 按天生成日志文件,最多保存30天的日志文件 + + file_handler.setLevel(logging.DEBUG) + + # 将error和critical级别的日志单独存放 + error_file_handler = TimedRotatingFileHandler( + f'log/{source_type}/error/{source_type}_{datetime.datetime.now().strftime("%Y-%m-%d")}.err.log', + when="midnight", + interval=1, + backupCount=30 + ) # 按天生成日志文件,最多保存30天的日志文件 + + error_file_handler.suffix = "%Y-%m-%d" # 文件名的时间格式 + error_file_handler.setLevel(logging.ERROR) + + logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s [%(levelname)s] - %(message)s', + handlers=[ + file_handler, + error_file_handler, + logging.StreamHandler() + ] + ) \ No newline at end of file diff --git a/data-process/data_manipulation/utils/minio_utils.py b/data-process/data_manipulation/utils/minio_utils.py new file mode 100644 index 000000000..8dedcc5f8 --- /dev/null +++ b/data-process/data_manipulation/utils/minio_utils.py @@ -0,0 +1,31 @@ +# Copyright 2023 KubeAGI. +# +# 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. + +### +# MinIO +### + +from minio import Minio + +from common import ( + config +) + +async def create_client(): + return Minio( + config.minio_api_url, + access_key=config.minio_access_key, + secret_key=config.minio_secret_key, + secure=config.minio_secure + ) diff --git a/data-process/docker/base/Dockerfile.base b/data-process/docker/base/Dockerfile.base new file mode 100644 index 000000000..ec52b9bef --- /dev/null +++ b/data-process/docker/base/Dockerfile.base @@ -0,0 +1,43 @@ +FROM ubuntu:20.04 + +ENV TIME_ZONE Asia/Shanghai + +RUN cp /etc/apt/sources.list /etc/apt/sources.list.bak && \ +sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \ +sed -i s@/security.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \ +cat /etc/apt/sources.list.bak >> /etc/apt/sources.list && \ +apt-get clean && \ +apt-get update && \ +DEBIAN_FRONTEND="noninteractive" apt-get install -y vim build-essential zlib1g-dev libncurses5-dev python3.10 python3-tk libgdbm-dev libgdbm-compat-dev libnss3-dev libssl-dev libreadline-dev libbz2-dev libgdbm-dev liblzma-dev openssl uuid-dev libffi-dev libsqlite3-dev wget tzdata \ +libatk1.0-0 libatk-bridge2.0-0 libcups2 libdbus-1-3 libxkbcommon0 libatspi2.0-0 libxdamage1 libgbm1 libpango-1.0-0 libcairo2 libasound2 tesseract-ocr libtesseract-dev tesseract-ocr-chi-sim && \ +ln -snf /user/share/zoneinfo/$TIME_ZONE /etc/localtime && echo $TIME_ZONE > /etc/timezone && \ +dpkg-reconfigure -f noninteractive tzdata + +RUN cd /opt/local +wget https://www.python.org/ftp/python/3.10.13/Python-3.10.13.tar.xz + + +RUN cd /opt/local/Python-3.10.13 && \ +./configure --enable-optimizations --enable-loadable-sqlite-extensions && \ +make -j 8 && \ +make altinstall + +ENV LD_LIBRARY_PATH /usr/local/lib:$LD_LIBRARY_PATH +ENV PYTHON_VERSION 3.10 +RUN cd /usr/local/bin && \ +ln -sf python$PYTHON_VERSION python3 && \ +ln -sf pip$PYTHON_VERSION pip3 && \ +echo "/usr/local/lib" > /etc/ld.so.conf.d/local.conf + + +RUN mkdir -p /happy_work_space +WORKDIR /happy_work_space + +VOLUME ["/happy_work_space"] + +RUN python3.10 -m pip install --upgrade pip setuptools + +ADD requirements.txt / +RUN python3.10 -m pip install -U -r /requirements.txt + +ADD lzma.py /usr/local/lib/python3.10/lzma.py \ No newline at end of file diff --git a/data-process/docker/base/build_image.sh b/data-process/docker/base/build_image.sh new file mode 100644 index 000000000..917a245f7 --- /dev/null +++ b/data-process/docker/base/build_image.sh @@ -0,0 +1,6 @@ +set e + +release_image="python:3.10.13" + + +docker build -f ./Dockerfile.base -t ${release_image} --build-arg GIT_VERSION="$gitVersion" . \ No newline at end of file diff --git a/data-process/docker/base/lzma.py b/data-process/docker/base/lzma.py new file mode 100644 index 000000000..acb07805b --- /dev/null +++ b/data-process/docker/base/lzma.py @@ -0,0 +1,352 @@ +"""Interface to the liblzma compression library. + +This module provides a class for reading and writing compressed files, +classes for incremental (de)compression, and convenience functions for +one-shot (de)compression. + +These classes and functions support both the XZ and legacy LZMA +container formats, as well as raw compressed data streams. +""" + +__all__ = [ + "CHECK_NONE", "CHECK_CRC32", "CHECK_CRC64", "CHECK_SHA256", + "CHECK_ID_MAX", "CHECK_UNKNOWN", + "FILTER_LZMA1", "FILTER_LZMA2", "FILTER_DELTA", "FILTER_X86", "FILTER_IA64", + "FILTER_ARM", "FILTER_ARMTHUMB", "FILTER_POWERPC", "FILTER_SPARC", + "FORMAT_AUTO", "FORMAT_XZ", "FORMAT_ALONE", "FORMAT_RAW", + "MF_HC3", "MF_HC4", "MF_BT2", "MF_BT3", "MF_BT4", + "MODE_FAST", "MODE_NORMAL", "PRESET_DEFAULT", "PRESET_EXTREME", + + "LZMACompressor", "LZMADecompressor", "LZMAFile", "LZMAError", + "open", "compress", "decompress", "is_check_supported", +] + +import builtins +import io +import os +try: + from _lzma import * + from _lzma import _encode_filter_properties, _decode_filter_properties +except ImportError: + from backports.lzma import * + from backports.lzma import _encode_filter_properties, _decode_filter_properties + +import _compression + + +_MODE_CLOSED = 0 +_MODE_READ = 1 +# Value 2 no longer used +_MODE_WRITE = 3 + + +class LZMAFile(_compression.BaseStream): + + """A file object providing transparent LZMA (de)compression. + + An LZMAFile can act as a wrapper for an existing file object, or + refer directly to a named file on disk. + + Note that LZMAFile provides a *binary* file interface - data read + is returned as bytes, and data to be written must be given as bytes. + """ + + def __init__(self, filename=None, mode="r", *, + format=None, check=-1, preset=None, filters=None): + """Open an LZMA-compressed file in binary mode. + + filename can be either an actual file name (given as a str, + bytes, or PathLike object), in which case the named file is + opened, or it can be an existing file object to read from or + write to. + + mode can be "r" for reading (default), "w" for (over)writing, + "x" for creating exclusively, or "a" for appending. These can + equivalently be given as "rb", "wb", "xb" and "ab" respectively. + + format specifies the container format to use for the file. + If mode is "r", this defaults to FORMAT_AUTO. Otherwise, the + default is FORMAT_XZ. + + check specifies the integrity check to use. This argument can + only be used when opening a file for writing. For FORMAT_XZ, + the default is CHECK_CRC64. FORMAT_ALONE and FORMAT_RAW do not + support integrity checks - for these formats, check must be + omitted, or be CHECK_NONE. + + When opening a file for reading, the *preset* argument is not + meaningful, and should be omitted. The *filters* argument should + also be omitted, except when format is FORMAT_RAW (in which case + it is required). + + When opening a file for writing, the settings used by the + compressor can be specified either as a preset compression + level (with the *preset* argument), or in detail as a custom + filter chain (with the *filters* argument). For FORMAT_XZ and + FORMAT_ALONE, the default is to use the PRESET_DEFAULT preset + level. For FORMAT_RAW, the caller must always specify a filter + chain; the raw compressor does not support preset compression + levels. + + preset (if provided) should be an integer in the range 0-9, + optionally OR-ed with the constant PRESET_EXTREME. + + filters (if provided) should be a sequence of dicts. Each dict + should have an entry for "id" indicating ID of the filter, plus + additional entries for options to the filter. + """ + self._fp = None + self._closefp = False + self._mode = _MODE_CLOSED + + if mode in ("r", "rb"): + if check != -1: + raise ValueError("Cannot specify an integrity check " + "when opening a file for reading") + if preset is not None: + raise ValueError("Cannot specify a preset compression " + "level when opening a file for reading") + if format is None: + format = FORMAT_AUTO + mode_code = _MODE_READ + elif mode in ("w", "wb", "a", "ab", "x", "xb"): + if format is None: + format = FORMAT_XZ + mode_code = _MODE_WRITE + self._compressor = LZMACompressor(format=format, check=check, + preset=preset, filters=filters) + self._pos = 0 + else: + raise ValueError("Invalid mode: {!r}".format(mode)) + + if isinstance(filename, (str, bytes, os.PathLike)): + if "b" not in mode: + mode += "b" + self._fp = builtins.open(filename, mode) + self._closefp = True + self._mode = mode_code + elif hasattr(filename, "read") or hasattr(filename, "write"): + self._fp = filename + self._mode = mode_code + else: + raise TypeError("filename must be a str, bytes, file or PathLike object") + + if self._mode == _MODE_READ: + raw = _compression.DecompressReader(self._fp, LZMADecompressor, + trailing_error=LZMAError, format=format, filters=filters) + self._buffer = io.BufferedReader(raw) + + def close(self): + """Flush and close the file. + + May be called more than once without error. Once the file is + closed, any other operation on it will raise a ValueError. + """ + if self._mode == _MODE_CLOSED: + return + try: + if self._mode == _MODE_READ: + self._buffer.close() + self._buffer = None + elif self._mode == _MODE_WRITE: + self._fp.write(self._compressor.flush()) + self._compressor = None + finally: + try: + if self._closefp: + self._fp.close() + finally: + self._fp = None + self._closefp = False + self._mode = _MODE_CLOSED + + @property + def closed(self): + """True if this file is closed.""" + return self._mode == _MODE_CLOSED + + def fileno(self): + """Return the file descriptor for the underlying file.""" + self._check_not_closed() + return self._fp.fileno() + + def seekable(self): + """Return whether the file supports seeking.""" + return self.readable() and self._buffer.seekable() + + def readable(self): + """Return whether the file was opened for reading.""" + self._check_not_closed() + return self._mode == _MODE_READ + + def writable(self): + """Return whether the file was opened for writing.""" + self._check_not_closed() + return self._mode == _MODE_WRITE + + def peek(self, size=-1): + """Return buffered data without advancing the file position. + + Always returns at least one byte of data, unless at EOF. + The exact number of bytes returned is unspecified. + """ + self._check_can_read() + # Relies on the undocumented fact that BufferedReader.peek() always + # returns at least one byte (except at EOF) + return self._buffer.peek(size) + + def read(self, size=-1): + """Read up to size uncompressed bytes from the file. + + If size is negative or omitted, read until EOF is reached. + Returns b"" if the file is already at EOF. + """ + self._check_can_read() + return self._buffer.read(size) + + def read1(self, size=-1): + """Read up to size uncompressed bytes, while trying to avoid + making multiple reads from the underlying stream. Reads up to a + buffer's worth of data if size is negative. + + Returns b"" if the file is at EOF. + """ + self._check_can_read() + if size < 0: + size = io.DEFAULT_BUFFER_SIZE + return self._buffer.read1(size) + + def readline(self, size=-1): + """Read a line of uncompressed bytes from the file. + + The terminating newline (if present) is retained. If size is + non-negative, no more than size bytes will be read (in which + case the line may be incomplete). Returns b'' if already at EOF. + """ + self._check_can_read() + return self._buffer.readline(size) + + def write(self, data): + """Write a bytes object to the file. + + Returns the number of uncompressed bytes written, which is + always len(data). Note that due to buffering, the file on disk + may not reflect the data written until close() is called. + """ + self._check_can_write() + compressed = self._compressor.compress(data) + self._fp.write(compressed) + self._pos += len(data) + return len(data) + + def seek(self, offset, whence=io.SEEK_SET): + """Change the file position. + + The new position is specified by offset, relative to the + position indicated by whence. Possible values for whence are: + + 0: start of stream (default): offset must not be negative + 1: current stream position + 2: end of stream; offset must not be positive + + Returns the new file position. + + Note that seeking is emulated, so depending on the parameters, + this operation may be extremely slow. + """ + self._check_can_seek() + return self._buffer.seek(offset, whence) + + def tell(self): + """Return the current file position.""" + self._check_not_closed() + if self._mode == _MODE_READ: + return self._buffer.tell() + return self._pos + + +def open(filename, mode="rb", *, + format=None, check=-1, preset=None, filters=None, + encoding=None, errors=None, newline=None): + """Open an LZMA-compressed file in binary or text mode. + + filename can be either an actual file name (given as a str, bytes, + or PathLike object), in which case the named file is opened, or it + can be an existing file object to read from or write to. + + The mode argument can be "r", "rb" (default), "w", "wb", "x", "xb", + "a", or "ab" for binary mode, or "rt", "wt", "xt", or "at" for text + mode. + + The format, check, preset and filters arguments specify the + compression settings, as for LZMACompressor, LZMADecompressor and + LZMAFile. + + For binary mode, this function is equivalent to the LZMAFile + constructor: LZMAFile(filename, mode, ...). In this case, the + encoding, errors and newline arguments must not be provided. + + For text mode, an LZMAFile object is created, and wrapped in an + io.TextIOWrapper instance with the specified encoding, error + handling behavior, and line ending(s). + + """ + if "t" in mode: + if "b" in mode: + raise ValueError("Invalid mode: %r" % (mode,)) + else: + if encoding is not None: + raise ValueError("Argument 'encoding' not supported in binary mode") + if errors is not None: + raise ValueError("Argument 'errors' not supported in binary mode") + if newline is not None: + raise ValueError("Argument 'newline' not supported in binary mode") + + lz_mode = mode.replace("t", "") + binary_file = LZMAFile(filename, lz_mode, format=format, check=check, + preset=preset, filters=filters) + + if "t" in mode: + return io.TextIOWrapper(binary_file, encoding, errors, newline) + else: + return binary_file + + +def compress(data, format=FORMAT_XZ, check=-1, preset=None, filters=None): + """Compress a block of data. + + Refer to LZMACompressor's docstring for a description of the + optional arguments *format*, *check*, *preset* and *filters*. + + For incremental compression, use an LZMACompressor instead. + """ + comp = LZMACompressor(format, check, preset, filters) + return comp.compress(data) + comp.flush() + + +def decompress(data, format=FORMAT_AUTO, memlimit=None, filters=None): + """Decompress a block of data. + + Refer to LZMADecompressor's docstring for a description of the + optional arguments *format*, *check* and *filters*. + + For incremental decompression, use an LZMADecompressor instead. + """ + results = [] + while True: + decomp = LZMADecompressor(format, memlimit, filters) + try: + res = decomp.decompress(data) + except LZMAError: + if results: + break # Leftover data is not a valid LZMA/XZ stream; ignore it. + else: + raise # Error on the first iteration; bail out. + results.append(res) + if not decomp.eof: + raise LZMAError("Compressed data ended before the " + "end-of-stream marker was reached") + data = decomp.unused_data + if not data: + break + return b"".join(results) \ No newline at end of file diff --git a/data-process/docker/base/requirements.txt b/data-process/docker/base/requirements.txt new file mode 100644 index 000000000..023663fd2 --- /dev/null +++ b/data-process/docker/base/requirements.txt @@ -0,0 +1 @@ +backports.lzma==0.0.14 \ No newline at end of file diff --git a/data-process/requirements.txt b/data-process/requirements.txt new file mode 100644 index 000000000..74a6e3d9d --- /dev/null +++ b/data-process/requirements.txt @@ -0,0 +1,7 @@ +pandas==2.1.2 +numpy==1.26.1 +sanic==23.6.0 +sanic_cors==2.2.0 +aiohttp==3.8.6 +ulid==1.1 +minio==7.1.17 \ No newline at end of file