From 9c2229fa47ebee67414b5d0d3f82facc9bb4623b Mon Sep 17 00:00:00 2001 From: eust-w Date: Wed, 9 Apr 2025 11:13:17 +0800 Subject: [PATCH] code init --- .env | 2 + README.md | 99 ++++++ app.py | 165 +++++++++ app/__init__.py | 1 + app/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 146 bytes .../baidu_image_search.cpython-39.pyc | Bin 0 -> 6090 bytes .../__pycache__/image_utils.cpython-39.pyc | Bin 0 -> 2405 bytes app/api/baidu_image_search.py | 226 ++++++++++++ app/api/image_utils.py | 74 ++++ app/static/css/style.css | 160 +++++++++ app/static/img/placeholder.png | 1 + app/static/js/main.js | 282 +++++++++++++++ app/templates/index.html | 192 ++++++++++ environment.yml | 9 + mock_baidu_api.py | 155 ++++++++ poetry.lock | 334 ++++++++++++++++++ pyproject.toml | 19 + requirements.txt | 5 + run.sh | 8 + test_baidu_api.py | 187 ++++++++++ 20 files changed, 1919 insertions(+) create mode 100644 .env create mode 100644 README.md create mode 100644 app.py create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-39.pyc create mode 100644 app/api/__pycache__/baidu_image_search.cpython-39.pyc create mode 100644 app/api/__pycache__/image_utils.cpython-39.pyc create mode 100644 app/api/baidu_image_search.py create mode 100644 app/api/image_utils.py create mode 100644 app/static/css/style.css create mode 100644 app/static/img/placeholder.png create mode 100644 app/static/js/main.js create mode 100644 app/templates/index.html create mode 100644 environment.yml create mode 100644 mock_baidu_api.py create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 run.sh create mode 100644 test_baidu_api.py diff --git a/.env b/.env new file mode 100644 index 0000000..c4c61a4 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +BAIDU_API_KEY=ALTAKmzKDy1OqhqmepD2OeXqbN +BAIDU_SECRET_KEY=b79c5fbc26344868916ec6e9e2ff65f0 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..67be4b4 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# 相似图片搜索系统 + +这是一个基于百度AI开放平台的相似图片搜索API的封装系统,提供了图片入库、检索、删除和更新等功能,并配有简洁美观的Web界面。 + +## 功能特点 + +- **图片入库**:将图片添加到百度相似图片搜索库中,支持添加名称、ID和标签 +- **图片检索**:上传图片搜索相似的图片,支持标签过滤 +- **图库管理**:更新和删除图库中的图片信息 +- **直观界面**:简洁美观的Web界面,操作便捷 + +## 安装与使用 + +### 环境要求 + +- Python 3.9+ +- Conda +- Poetry + +### 安装步骤 + +1. 克隆或下载本项目到本地 + +2. 使用Conda创建虚拟环境: + ```bash + conda env create -f environment.yml + conda activate imgsearcher + ``` + +3. 使用Poetry安装依赖: + ```bash + poetry install + ``` + +4. 配置API密钥: + 在项目根目录创建`.env`文件,填入百度AI平台的API密钥: + ``` + BAIDU_API_KEY=你的API_KEY + BAIDU_SECRET_KEY=你的SECRET_KEY + ``` + +5. 创建上传目录: + ```bash + mkdir uploads + ``` + +6. 运行应用: + ```bash + poetry run python app.py + ``` + +7. 在浏览器中访问: + ``` + http://localhost:5000 + ``` + +## 使用说明 + +### 图片入库 + +1. 在"图片入库"标签页中,选择要上传的图片 +2. 填写图片名称和ID(用于后续识别) +3. 可选择添加标签(最多2个,用逗号分隔) +4. 点击"上传入库"按钮 + +### 图片搜索 + +1. 在"图片搜索"标签页中,选择要搜索的图片 +2. 可选择添加标签过滤条件 +3. 选择标签逻辑(AND或OR) +4. 点击"搜索"按钮 +5. 查看搜索结果 + +### 图库管理 + +1. 先在"图片搜索"标签页进行搜索 +2. 在搜索结果中,可以对图片进行编辑或删除操作 + +## API说明 + +本系统封装了百度相似图片搜索API的以下功能: + +- 图片入库:`/upload` (POST) +- 图片检索:`/search` (POST) +- 图片删除:`/delete` (POST) +- 图片更新:`/update` (POST) +- 获取Token:`/api/token` (GET) + +## 注意事项 + +- 上传的图片最短边至少50px,最长边最大4096px +- 支持JPG、PNG、BMP格式的图片 +- 每个图片可以添加最多2个标签 +- 检索接口不返回原图,只返回入库时填写的brief信息 +- 图片删除可能会有延时生效(百度API特性) + +## 许可证 + +MIT diff --git a/app.py b/app.py new file mode 100644 index 0000000..7d59e81 --- /dev/null +++ b/app.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import json +import base64 +from flask import Flask, render_template, request, jsonify, redirect, url_for +from flask_cors import CORS +from werkzeug.utils import secure_filename +from app.api.baidu_image_search import BaiduImageSearch +from app.api.image_utils import ImageUtils + +app = Flask(__name__, template_folder='app/templates', static_folder='app/static') +CORS(app) # 启用CORS支持跨域请求 + +# 配置上传文件夹 +UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'uploads') +if not os.path.exists(UPLOAD_FOLDER): + os.makedirs(UPLOAD_FOLDER) +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER + +# 允许的文件扩展名 +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} + +# 初始化百度图像搜索API +image_search_api = BaiduImageSearch() + +def allowed_file(filename): + """检查文件扩展名是否允许""" + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + +@app.route('/') +def index(): + """首页""" + return render_template('index.html') + +@app.route('/upload', methods=['POST']) +def upload_image(): + """上传图片到图库""" + if 'file' not in request.files: + return jsonify({'error': '没有文件上传'}), 400 + + file = request.files['file'] + if file.filename == '': + return jsonify({'error': '没有选择文件'}), 400 + + if not allowed_file(file.filename): + return jsonify({'error': '不支持的文件类型'}), 400 + + # 获取表单数据 + name = request.form.get('name', '') + image_id = request.form.get('id', '') + tags = request.form.get('tags', '') + + # 创建brief信息 + brief = { + 'name': name, + 'id': image_id + } + + # 保存文件 + filename = secure_filename(file.filename) + file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) + file.save(file_path) + + try: + # 调用API添加图片 + result = image_search_api.add_image( + image_path=file_path, + brief=brief, + tags=tags + ) + + # 添加本地存储路径到结果 + result['file_path'] = file_path + + return jsonify(result) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/search', methods=['POST']) +def search_image(): + """搜索相似图片""" + if 'file' not in request.files: + return jsonify({'error': '没有文件上传'}), 400 + + file = request.files['file'] + if file.filename == '': + return jsonify({'error': '没有选择文件'}), 400 + + if not allowed_file(file.filename): + return jsonify({'error': '不支持的文件类型'}), 400 + + # 获取表单数据 + tags = request.form.get('tags', '') + tag_logic = request.form.get('tag_logic', '0') + + # 保存文件 + filename = secure_filename(file.filename) + file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) + file.save(file_path) + + try: + # 调用API搜索图片 + result = image_search_api.search_image( + image_path=file_path, + tags=tags, + tag_logic=tag_logic + ) + + return jsonify(result) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/delete', methods=['POST']) +def delete_image(): + """删除图库中的图片""" + data = request.get_json() + + if not data or 'cont_sign' not in data: + return jsonify({'error': '缺少必要参数'}), 400 + + cont_sign = data['cont_sign'] + + try: + # 调用API删除图片 + result = image_search_api.delete_image(cont_sign=cont_sign) + return jsonify(result) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/update', methods=['POST']) +def update_image(): + """更新图库中的图片信息""" + data = request.get_json() + + if not data or 'cont_sign' not in data: + return jsonify({'error': '缺少必要参数'}), 400 + + cont_sign = data['cont_sign'] + brief = data.get('brief') + tags = data.get('tags') + + try: + # 调用API更新图片 + result = image_search_api.update_image( + cont_sign=cont_sign, + brief=brief, + tags=tags + ) + return jsonify(result) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/token') +def get_token(): + """获取API访问令牌""" + try: + token = image_search_api.get_access_token() + return jsonify({'access_token': token}) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', port=5000) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..2310428 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1 @@ +# 初始化应用包 diff --git a/app/__pycache__/__init__.cpython-39.pyc b/app/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0dee61c117024ec885a1a4078a470b1b830670c1 GIT binary patch literal 146 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 response.json()) + .then(data => { + if (data.error) { + resultDiv.innerHTML = `
${data.error}
`; + } else { + resultDiv.innerHTML = ` +
+
上传成功!
+

图片签名: ${data.cont_sign}

+

日志ID: ${data.log_id}

+
+ `; + // 重置表单 + form.reset(); + document.getElementById('uploadPreview').classList.add('d-none'); + } + }) + .catch(error => { + resultDiv.innerHTML = `
上传失败: ${error.message}
`; + }); + }); + } +} + +// 初始化搜索表单 +function initSearchForm() { + const form = document.getElementById('searchForm'); + + if (form) { + form.addEventListener('submit', function(e) { + e.preventDefault(); + + const formData = new FormData(this); + const resultDiv = document.getElementById('searchResult'); + const resultList = document.getElementById('searchResultList'); + const resultStats = document.getElementById('searchStats'); + const resultCount = document.getElementById('resultCount'); + + resultList.innerHTML = '
正在搜索,请稍候...
'; + resultStats.classList.add('d-none'); + + fetch('/search', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.error) { + resultList.innerHTML = `
${data.error}
`; + } else { + // 显示搜索结果统计 + resultCount.textContent = data.result_num || 0; + resultStats.classList.remove('d-none'); + + // 清空结果列表 + resultList.innerHTML = ''; + + if (data.result && data.result.length > 0) { + // 显示搜索结果 + data.result.forEach(item => { + // 解析brief信息 + let briefInfo = {}; + try { + briefInfo = JSON.parse(item.brief); + } catch (e) { + briefInfo = { name: '未知', id: '未知' }; + } + + const resultItem = document.createElement('div'); + resultItem.className = 'col-md-4 col-sm-6 result-item'; + resultItem.innerHTML = ` +
+
+ ${briefInfo.name || '图片'} +
+
+
${briefInfo.name || '未命名图片'}
+

ID: ${briefInfo.id || '无ID'}

+

相似度: ${(item.score * 100).toFixed(2)}%

+
+ + +
+
+
+ `; + resultList.appendChild(resultItem); + }); + + // 初始化编辑和删除按钮事件 + initResultButtons(); + } else { + resultList.innerHTML = '
没有找到匹配的图片
'; + } + } + }) + .catch(error => { + resultList.innerHTML = `
搜索失败: ${error.message}
`; + }); + }); + } +} + +// 初始化搜索结果中的按钮事件 +function initResultButtons() { + // 更新按钮点击事件 + document.querySelectorAll('.update-btn').forEach(button => { + button.addEventListener('click', function() { + const contSign = this.getAttribute('data-cont-sign'); + const name = this.getAttribute('data-name'); + const id = this.getAttribute('data-id'); + + document.getElementById('updateContSign').value = contSign; + document.getElementById('updateName').value = name; + document.getElementById('updateId').value = id; + }); + }); + + // 删除按钮点击事件 + document.querySelectorAll('.delete-btn').forEach(button => { + button.addEventListener('click', function() { + const contSign = this.getAttribute('data-cont-sign'); + document.getElementById('deleteContSign').value = contSign; + }); + }); +} + +// 初始化更新表单 +function initUpdateForm() { + const updateButton = document.getElementById('updateSubmit'); + + if (updateButton) { + updateButton.addEventListener('click', function() { + const contSign = document.getElementById('updateContSign').value; + const name = document.getElementById('updateName').value; + const id = document.getElementById('updateId').value; + const tags = document.getElementById('updateTags').value; + + // 创建brief信息 + const brief = { + name: name, + id: id + }; + + // 发送更新请求 + fetch('/update', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + cont_sign: contSign, + brief: brief, + tags: tags + }) + }) + .then(response => response.json()) + .then(data => { + // 关闭模态框 + const modal = bootstrap.Modal.getInstance(document.getElementById('updateModal')); + modal.hide(); + + if (data.error) { + alert(`更新失败: ${data.error}`); + } else { + alert('更新成功!'); + // 如果当前在搜索标签页,刷新搜索结果 + if (document.getElementById('search-tab').classList.contains('active')) { + document.getElementById('searchForm').dispatchEvent(new Event('submit')); + } + } + }) + .catch(error => { + alert(`更新失败: ${error.message}`); + }); + }); + } +} + +// 初始化删除表单 +function initDeleteForm() { + const deleteButton = document.getElementById('deleteSubmit'); + + if (deleteButton) { + deleteButton.addEventListener('click', function() { + const contSign = document.getElementById('deleteContSign').value; + + // 发送删除请求 + fetch('/delete', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + cont_sign: contSign + }) + }) + .then(response => response.json()) + .then(data => { + // 关闭模态框 + const modal = bootstrap.Modal.getInstance(document.getElementById('deleteModal')); + modal.hide(); + + if (data.error) { + alert(`删除失败: ${data.error}`); + } else { + alert('删除成功!'); + // 如果当前在搜索标签页,刷新搜索结果 + if (document.getElementById('search-tab').classList.contains('active')) { + document.getElementById('searchForm').dispatchEvent(new Event('submit')); + } + } + }) + .catch(error => { + alert(`删除失败: ${error.message}`); + }); + }); + } +} diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..07580bb --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,192 @@ + + + + + + 相似图片搜索系统 + + + + +
+
+

相似图片搜索系统

+

基于百度AI开放平台的相似图片搜索API

+
+ +
+
+ +
+ +
+
+
+
图片入库
+
+
+ + +
支持JPG、PNG、BMP格式,最短边至少50px,最长边最大4096px
+
+
+ + +
+
+ + +
用于关联至本地图库的标识
+
+
+ + +
最多2个标签,用逗号分隔
+
+
+
+ 预览图 +
+
+ +
+
+
+
+
+ + + + + +
+
+
+
图库管理
+

此功能需要先搜索图片,然后可以对搜索结果进行管理操作。

+
+ 提示:百度相似图片搜索API不提供直接查看所有图库内容的功能,需要通过搜索相似图片来获取图库内容。 +
+
+

请先在"图片搜索"标签页进行搜索,然后可以对搜索结果进行管理。

+
+
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..e8e25d0 --- /dev/null +++ b/environment.yml @@ -0,0 +1,9 @@ +name: imgsearcher +channels: + - defaults + - conda-forge +dependencies: + - python=3.9 + - pip + - pip: + - poetry diff --git a/mock_baidu_api.py b/mock_baidu_api.py new file mode 100644 index 0000000..70553c4 --- /dev/null +++ b/mock_baidu_api.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +百度图像搜索API模拟类 +用于在没有有效API密钥的情况下进行测试 +""" + +class MockBaiduImageSearch: + """模拟百度相似图片搜索API的类""" + + def __init__(self): + """初始化模拟API""" + self.access_token = "mock_access_token_12345678900987654321" + self.images = {} # 存储模拟图片数据 + print("已初始化模拟百度图像搜索API") + + def add_image(self, image_path=None, image_base64=None, url=None, brief=None, tags=None): + """ + 模拟添加图片到图库 + + Args: + image_path: 本地图片路径 + image_base64: 图片的base64编码 + url: 图片URL + brief: 图片摘要信息 + tags: 分类信息 + + Returns: + dict: 模拟API返回的结果 + """ + # 生成一个唯一的图片ID + import uuid + cont_sign = str(uuid.uuid4()) + + # 存储图片信息 + self.images[cont_sign] = { + 'brief': brief, + 'tags': tags, + 'url': url or image_path or "模拟图片路径", + 'add_time': '2025-04-09 11:00:00' + } + + return { + 'log_id': 123456789, + 'cont_sign': cont_sign, + 'error_code': 0, + 'error_msg': 'success' + } + + def search_image(self, image_path=None, image_base64=None, url=None, tags=None, tag_logic=None, pn=0, rn=300): + """ + 模拟搜索相似图片 + + Args: + image_path: 本地图片路径 + image_base64: 图片的base64编码 + url: 图片URL + tags: 分类信息 + tag_logic: 标签逻辑,'AND'或'OR' + pn: 分页起始位置 + rn: 返回结果数量 + + Returns: + dict: 模拟API返回的结果 + """ + # 模拟搜索结果 + result_list = [] + + # 如果有图片,则返回它们作为搜索结果 + for cont_sign, image_info in self.images.items(): + # 如果指定了标签,则进行标签过滤 + if tags: + image_tags = image_info.get('tags', '') + if tag_logic == 'AND': + # 所有标签都必须匹配 + if not all(tag in image_tags for tag in tags.split(',')): + continue + else: + # 任意标签匹配即可 + if not any(tag in image_tags for tag in tags.split(',')): + continue + + # 添加到结果列表 + result_list.append({ + 'cont_sign': cont_sign, + 'score': 0.95, # 模拟相似度分数 + 'brief': image_info.get('brief', '') + }) + + return { + 'log_id': 987654321, + 'result_num': len(result_list), + 'result': result_list, + 'has_more': False, + 'error_code': 0, + 'error_msg': 'success' + } + + def update_image(self, cont_sign, brief=None, tags=None): + """ + 模拟更新图片信息 + + Args: + cont_sign: 图片ID + brief: 新的图片摘要信息 + tags: 新的分类信息 + + Returns: + dict: 模拟API返回的结果 + """ + if cont_sign not in self.images: + return { + 'log_id': 135792468, + 'error_code': 222202, + 'error_msg': '图片不存在' + } + + # 更新图片信息 + if brief: + self.images[cont_sign]['brief'] = brief + if tags: + self.images[cont_sign]['tags'] = tags + + return { + 'log_id': 135792468, + 'error_code': 0, + 'error_msg': 'success' + } + + def delete_image(self, cont_sign): + """ + 模拟删除图片 + + Args: + cont_sign: 图片ID + + Returns: + dict: 模拟API返回的结果 + """ + if cont_sign not in self.images: + return { + 'log_id': 246813579, + 'error_code': 222202, + 'error_msg': '图片不存在' + } + + # 删除图片 + del self.images[cont_sign] + + return { + 'log_id': 246813579, + 'error_code': 0, + 'error_msg': 'success' + } diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..33eb4c0 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,334 @@ +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. + +[[package]] +name = "certifi" +version = "2023.5.7" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, +] + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.5.0" +groups = ["main"] +files = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "flask" +version = "2.0.1" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "Flask-2.0.1-py3-none-any.whl", hash = "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9"}, + {file = "Flask-2.0.1.tar.gz", hash = "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55"}, +] + +[package.dependencies] +click = ">=7.1.2" +itsdangerous = ">=2.0" +Jinja2 = ">=3.0" +Werkzeug = ">=2.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-cors" +version = "3.0.10" +description = "A Flask extension adding a decorator for CORS support" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"}, + {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"}, +] + +[package.dependencies] +Flask = ">=0.9" +Six = "*" + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +groups = ["main"] +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, +] + +[[package]] +name = "pillow" +version = "8.3.1" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "Pillow-8.3.1-1-cp36-cp36m-win_amd64.whl", hash = "sha256:fd7eef578f5b2200d066db1b50c4aa66410786201669fb76d5238b007918fb24"}, + {file = "Pillow-8.3.1-1-cp37-cp37m-win_amd64.whl", hash = "sha256:75e09042a3b39e0ea61ce37e941221313d51a9c26b8e54e12b3ececccb71718a"}, + {file = "Pillow-8.3.1-1-cp38-cp38-win_amd64.whl", hash = "sha256:c0e0550a404c69aab1e04ae89cca3e2a042b56ab043f7f729d984bf73ed2a093"}, + {file = "Pillow-8.3.1-1-cp39-cp39-win_amd64.whl", hash = "sha256:479ab11cbd69612acefa8286481f65c5dece2002ffaa4f9db62682379ca3bb77"}, + {file = "Pillow-8.3.1-1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f156d6ecfc747ee111c167f8faf5f4953761b5e66e91a4e6767e548d0f80129c"}, + {file = "Pillow-8.3.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:196560dba4da7a72c5e7085fccc5938ab4075fd37fe8b5468869724109812edd"}, + {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c9569049d04aaacd690573a0398dbd8e0bf0255684fee512b413c2142ab723"}, + {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c088a000dfdd88c184cc7271bfac8c5b82d9efa8637cd2b68183771e3cf56f04"}, + {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fc214a6b75d2e0ea7745488da7da3c381f41790812988c7a92345978414fad37"}, + {file = "Pillow-8.3.1-cp36-cp36m-win32.whl", hash = "sha256:a17ca41f45cf78c2216ebfab03add7cc350c305c38ff34ef4eef66b7d76c5229"}, + {file = "Pillow-8.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:67b3666b544b953a2777cb3f5a922e991be73ab32635666ee72e05876b8a92de"}, + {file = "Pillow-8.3.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:ff04c373477723430dce2e9d024c708a047d44cf17166bf16e604b379bf0ca14"}, + {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9364c81b252d8348e9cc0cb63e856b8f7c1b340caba6ee7a7a65c968312f7dab"}, + {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2f381932dca2cf775811a008aa3027671ace723b7a38838045b1aee8669fdcf"}, + {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d0da39795049a9afcaadec532e7b669b5ebbb2a9134576ebcc15dd5bdae33cc0"}, + {file = "Pillow-8.3.1-cp37-cp37m-win32.whl", hash = "sha256:2b6dfa068a8b6137da34a4936f5a816aba0ecc967af2feeb32c4393ddd671cba"}, + {file = "Pillow-8.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a4eef1ff2d62676deabf076f963eda4da34b51bc0517c70239fafed1d5b51500"}, + {file = "Pillow-8.3.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:660a87085925c61a0dcc80efb967512ac34dbb256ff7dd2b9b4ee8dbdab58cf4"}, + {file = "Pillow-8.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:15a2808e269a1cf2131930183dcc0419bc77bb73eb54285dde2706ac9939fa8e"}, + {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:969cc558cca859cadf24f890fc009e1bce7d7d0386ba7c0478641a60199adf79"}, + {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ee77c14a0299d0541d26f3d8500bb57e081233e3fa915fa35abd02c51fa7fae"}, + {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c11003197f908878164f0e6da15fce22373ac3fc320cda8c9d16e6bba105b844"}, + {file = "Pillow-8.3.1-cp38-cp38-win32.whl", hash = "sha256:3f08bd8d785204149b5b33e3b5f0ebbfe2190ea58d1a051c578e29e39bfd2367"}, + {file = "Pillow-8.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:70af7d222df0ff81a2da601fab42decb009dc721545ed78549cb96e3a1c5f0c8"}, + {file = "Pillow-8.3.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:37730f6e68bdc6a3f02d2079c34c532330d206429f3cee651aab6b66839a9f0e"}, + {file = "Pillow-8.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bc3c7ef940eeb200ca65bd83005eb3aae8083d47e8fcbf5f0943baa50726856"}, + {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c35d09db702f4185ba22bb33ef1751ad49c266534339a5cebeb5159d364f6f82"}, + {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b2efa07f69dc395d95bb9ef3299f4ca29bcb2157dc615bae0b42c3c20668ffc"}, + {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cc866706d56bd3a7dbf8bac8660c6f6462f2f2b8a49add2ba617bc0c54473d83"}, + {file = "Pillow-8.3.1-cp39-cp39-win32.whl", hash = "sha256:9a211b663cf2314edbdb4cf897beeb5c9ee3810d1d53f0e423f06d6ebbf9cd5d"}, + {file = "Pillow-8.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:c2a5ff58751670292b406b9f06e07ed1446a4b13ffced6b6cab75b857485cbc8"}, + {file = "Pillow-8.3.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c379425c2707078dfb6bfad2430728831d399dc95a7deeb92015eb4c92345eaf"}, + {file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:114f816e4f73f9ec06997b2fde81a92cbf0777c9e8f462005550eed6bae57e63"}, + {file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8960a8a9f4598974e4c2aeb1bff9bdd5db03ee65fd1fce8adf3223721aa2a636"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:147bd9e71fb9dcf08357b4d530b5167941e222a6fd21f869c7911bac40b9994d"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1fd5066cd343b5db88c048d971994e56b296868766e461b82fa4e22498f34d77"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4ebde71785f8bceb39dcd1e7f06bcc5d5c3cf48b9f69ab52636309387b097c8"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c03e24be975e2afe70dfc5da6f187eea0b49a68bb2b69db0f30a61b7031cee4"}, + {file = "Pillow-8.3.1.tar.gz", hash = "sha256:2cac53839bfc5cece8fdbe7f084d5e3ee61e1303cccc86511d351adcb9e2c792"}, +] + +[[package]] +name = "python-dotenv" +version = "0.19.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.5" +groups = ["main"] +files = [ + {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"}, + {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "requests" +version = "2.26.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +groups = ["main"] +files = [ + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton ; sys_platform == \"win32\" and python_version == \"2.7\""] +use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "urllib3" +version = "1.26.16" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +groups = ["main"] +files = [ + {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "werkzeug" +version = "2.2.3" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"}, + {file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog"] + +[metadata] +lock-version = "2.1" +python-versions = "^3.9" +content-hash = "c80c5641fe75055f2a80acf7a46ab694e2c875a0bc58f86bb52205c2dd34e5af" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fda54d1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "imgsearcher" +version = "0.1.0" +description = "基于百度AI开放平台的相似图片搜索系统" +authors = ["Your Name "] +readme = "README.md" +package-mode = false + +[tool.poetry.dependencies] +python = "^3.9" +flask = "^2.0.1" +requests = "^2.26.0" +python-dotenv = "^0.19.0" +pillow = "^8.3.1" +flask-cors = "^3.0.10" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c2b1ee1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +flask==2.0.1 +requests==2.26.0 +python-dotenv==0.19.0 +Pillow==8.3.1 +flask-cors==3.0.10 diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..63b5f93 --- /dev/null +++ b/run.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# 激活conda环境 +eval "$(conda shell.bash hook)" +conda activate imgsearcher + +# 运行应用 +poetry run python app.py diff --git a/test_baidu_api.py b/test_baidu_api.py new file mode 100644 index 0000000..a04b8bb --- /dev/null +++ b/test_baidu_api.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +百度图像搜索API测试脚本 +简单测试API功能,打印结果 +支持实际API和模拟模式 +""" + +import os +import json +import argparse +from dotenv import load_dotenv +from app.api.baidu_image_search import BaiduImageSearch +from app.api.image_utils import ImageUtils + +# 导入模拟类 +from mock_baidu_api import MockBaiduImageSearch + +# 全局变量,用于存储测试过程中的图片ID +test_cont_sign = None + +def parse_arguments(): + """解析命令行参数""" + parser = argparse.ArgumentParser(description='百度图像搜索API测试脚本') + parser.add_argument('--mock', action='store_true', help='使用模拟模式,不连接实际API') + return parser.parse_args() + +def check_api_keys(): + """检查API密钥是否已配置""" + api_key = os.getenv('BAIDU_API_KEY') + secret_key = os.getenv('BAIDU_SECRET_KEY') + + if not api_key or not secret_key: + print("\n\033[91m错误: API密钥未配置\033[0m") + print("\n请在.env文件中添加以下内容:") + print("BAIDU_API_KEY=你的API密钥") + print("BAIDU_SECRET_KEY=你的密钥") + print("\n您可以从百度AI开放平台获取密钥: https://ai.baidu.com/") + print("\n或者使用 --mock 参数运行模拟模式: python test_baidu_api.py --mock") + return False + + print(f"API密钥: {api_key[:4]}...") + print(f"Secret密钥: {secret_key[:4]}...") + return True + +def main(): + # 解析命令行参数 + args = parse_arguments() + + # 加载环境变量 + print("加载环境变量...") + load_dotenv() + + # 判断是否使用模拟模式 + if args.mock: + print("\n\033[93m注意: 使用模拟模式,不连接实际API\033[0m") + api = MockBaiduImageSearch() + + # 测试添加图片 + test_add_image(api) + + # 测试搜索图片 + test_search_image(api) + + # 测试更新图片 + test_update_image(api) + + # 测试删除图片 + test_delete_image(api) + return + + # 如果不是模拟模式,检查API密钥 + if not check_api_keys(): + return + + # 初始化实际API + print("\n初始化百度图像搜索API...") + try: + api = BaiduImageSearch() + if api.access_token: + print(f"获取到的Access Token: {api.access_token[:10]}...(已截断)") + + # 测试添加图片 + test_add_image(api) + + # 测试搜索图片 + test_search_image(api) + + # 测试更新图片 + test_update_image(api) + + # 测试删除图片 + test_delete_image(api) + else: + print("\033[91m错误: 无法获取Access Token\033[0m") + print("\n尝试使用模拟模式: python test_baidu_api.py --mock") + except Exception as e: + print(f"\033[91m错误: {e}\033[0m") + print("\n尝试使用模拟模式: python test_baidu_api.py --mock") + +def test_add_image(api): + """测试添加图片功能""" + print("\n===== 测试添加图片 =====") + + # 使用URL方式添加图片 + test_image_url = "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png" # 百度logo + brief = json.dumps({"name": "测试图片", "id": "test001"}, ensure_ascii=False) + tags = "测试,图片" + + try: + result = api.add_image( + url=test_image_url, + brief=brief, + tags=tags + ) + print("添加图片结果:") + print(json.dumps(result, indent=2, ensure_ascii=False)) + + # 保存contSign用于后续测试 + if result.get("cont_sign"): + global test_cont_sign + test_cont_sign = result.get("cont_sign") + print(f"已保存图片ID: {test_cont_sign} 用于后续测试") + except Exception as e: + print(f"添加图片失败: {e}") + +def test_search_image(api): + """测试搜索图片功能""" + print("\n===== 测试搜索图片 =====") + + # 使用URL方式搜索图片 + test_image_url = "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png" # 百度logo + + try: + result = api.search_image( + url=test_image_url, + tags="测试" + ) + print("搜索图片结果:") + print(f"找到 {len(result.get('result', []))} 个匹配项") + + # 只打印前2个结果 + for i, item in enumerate(result.get("result", [])[:2]): + print(f"结果 {i+1}:") + print(f" 相似度: {item.get('score', 0)}") + print(f" 简介: {item.get('brief', '')}") + except Exception as e: + print(f"搜索图片失败: {e}") + +def test_update_image(api): + """测试更新图片功能""" + print("\n===== 测试更新图片 =====") + + global test_cont_sign + if not test_cont_sign: + print("没有可用的图片ID,跳过更新图片测试") + return + + try: + new_brief = json.dumps({"name": "更新后的测试图片", "id": "test001"}, ensure_ascii=False) + result = api.update_image(test_cont_sign, new_brief) + print("更新图片结果:") + print(json.dumps(result, indent=2, ensure_ascii=False)) + except Exception as e: + print(f"更新图片失败: {e}") + +def test_delete_image(api): + """测试删除图片功能""" + print("\n===== 测试删除图片 =====") + + global test_cont_sign + if not test_cont_sign: + print("没有可用的图片ID,跳过删除图片测试") + return + + try: + result = api.delete_image(test_cont_sign) + print("删除图片结果:") + print(json.dumps(result, indent=2, ensure_ascii=False)) + # 清除已删除的图片ID + test_cont_sign = None + except Exception as e: + print(f"删除图片失败: {e}") + +if __name__ == "__main__": + main()