veadk-python 0.2.27__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- veadk/__init__.py +37 -0
- veadk/a2a/__init__.py +13 -0
- veadk/a2a/agent_card.py +45 -0
- veadk/a2a/remote_ve_agent.py +390 -0
- veadk/a2a/utils/__init__.py +13 -0
- veadk/a2a/utils/agent_to_a2a.py +170 -0
- veadk/a2a/ve_a2a_server.py +93 -0
- veadk/a2a/ve_agent_executor.py +78 -0
- veadk/a2a/ve_middlewares.py +313 -0
- veadk/a2a/ve_task_store.py +37 -0
- veadk/agent.py +402 -0
- veadk/agent_builder.py +93 -0
- veadk/agents/loop_agent.py +68 -0
- veadk/agents/parallel_agent.py +72 -0
- veadk/agents/sequential_agent.py +64 -0
- veadk/auth/__init__.py +13 -0
- veadk/auth/base_auth.py +22 -0
- veadk/auth/ve_credential_service.py +203 -0
- veadk/auth/veauth/__init__.py +13 -0
- veadk/auth/veauth/apmplus_veauth.py +58 -0
- veadk/auth/veauth/ark_veauth.py +75 -0
- veadk/auth/veauth/base_veauth.py +50 -0
- veadk/auth/veauth/cozeloop_veauth.py +13 -0
- veadk/auth/veauth/opensearch_veauth.py +75 -0
- veadk/auth/veauth/postgresql_veauth.py +75 -0
- veadk/auth/veauth/prompt_pilot_veauth.py +60 -0
- veadk/auth/veauth/speech_veauth.py +54 -0
- veadk/auth/veauth/utils.py +69 -0
- veadk/auth/veauth/vesearch_veauth.py +62 -0
- veadk/auth/veauth/viking_mem0_veauth.py +91 -0
- veadk/cli/__init__.py +13 -0
- veadk/cli/cli.py +58 -0
- veadk/cli/cli_clean.py +87 -0
- veadk/cli/cli_create.py +163 -0
- veadk/cli/cli_deploy.py +233 -0
- veadk/cli/cli_eval.py +215 -0
- veadk/cli/cli_init.py +214 -0
- veadk/cli/cli_kb.py +110 -0
- veadk/cli/cli_pipeline.py +285 -0
- veadk/cli/cli_prompt.py +86 -0
- veadk/cli/cli_update.py +106 -0
- veadk/cli/cli_uploadevalset.py +139 -0
- veadk/cli/cli_web.py +143 -0
- veadk/cloud/__init__.py +13 -0
- veadk/cloud/cloud_agent_engine.py +485 -0
- veadk/cloud/cloud_app.py +475 -0
- veadk/config.py +115 -0
- veadk/configs/__init__.py +13 -0
- veadk/configs/auth_configs.py +133 -0
- veadk/configs/database_configs.py +132 -0
- veadk/configs/model_configs.py +78 -0
- veadk/configs/tool_configs.py +54 -0
- veadk/configs/tracing_configs.py +110 -0
- veadk/consts.py +74 -0
- veadk/evaluation/__init__.py +17 -0
- veadk/evaluation/adk_evaluator/__init__.py +17 -0
- veadk/evaluation/adk_evaluator/adk_evaluator.py +302 -0
- veadk/evaluation/base_evaluator.py +642 -0
- veadk/evaluation/deepeval_evaluator/__init__.py +17 -0
- veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +339 -0
- veadk/evaluation/eval_set_file_loader.py +48 -0
- veadk/evaluation/eval_set_recorder.py +146 -0
- veadk/evaluation/types.py +65 -0
- veadk/evaluation/utils/prometheus.py +196 -0
- veadk/integrations/__init__.py +13 -0
- veadk/integrations/ve_apig/__init__.py +13 -0
- veadk/integrations/ve_apig/ve_apig.py +349 -0
- veadk/integrations/ve_apig/ve_apig_utils.py +332 -0
- veadk/integrations/ve_code_pipeline/__init__.py +13 -0
- veadk/integrations/ve_code_pipeline/ve_code_pipeline.py +431 -0
- veadk/integrations/ve_cozeloop/__init__.py +13 -0
- veadk/integrations/ve_cozeloop/ve_cozeloop.py +96 -0
- veadk/integrations/ve_cr/__init__.py +13 -0
- veadk/integrations/ve_cr/ve_cr.py +220 -0
- veadk/integrations/ve_faas/__init__.py +13 -0
- veadk/integrations/ve_faas/template/cookiecutter.json +15 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/config.yaml.example +6 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/deploy.py +106 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/__init__.py +13 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/agent.py +25 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/app.py +202 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/requirements.txt +3 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/run.sh +49 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name }}/__init__.py +14 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name }}/agent.py +27 -0
- veadk/integrations/ve_faas/ve_faas.py +754 -0
- veadk/integrations/ve_faas/ve_faas_utils.py +408 -0
- veadk/integrations/ve_faas/web_template/cookiecutter.json +20 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example +2 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py +44 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile +23 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py +123 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py +46 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py +36 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt +4 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css +368 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/js/admin.js +0 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html +24 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html +53 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html +45 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html +29 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html +14 -0
- veadk/integrations/ve_identity/__init__.py +110 -0
- veadk/integrations/ve_identity/auth_config.py +261 -0
- veadk/integrations/ve_identity/auth_mixins.py +650 -0
- veadk/integrations/ve_identity/auth_processor.py +385 -0
- veadk/integrations/ve_identity/function_tool.py +158 -0
- veadk/integrations/ve_identity/identity_client.py +864 -0
- veadk/integrations/ve_identity/mcp_tool.py +181 -0
- veadk/integrations/ve_identity/mcp_toolset.py +431 -0
- veadk/integrations/ve_identity/models.py +228 -0
- veadk/integrations/ve_identity/token_manager.py +188 -0
- veadk/integrations/ve_identity/utils.py +151 -0
- veadk/integrations/ve_prompt_pilot/__init__.py +13 -0
- veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +85 -0
- veadk/integrations/ve_tls/__init__.py +13 -0
- veadk/integrations/ve_tls/utils.py +116 -0
- veadk/integrations/ve_tls/ve_tls.py +212 -0
- veadk/integrations/ve_tos/ve_tos.py +710 -0
- veadk/integrations/ve_viking_db_memory/__init__.py +13 -0
- veadk/integrations/ve_viking_db_memory/ve_viking_db_memory.py +308 -0
- veadk/knowledgebase/__init__.py +17 -0
- veadk/knowledgebase/backends/__init__.py +13 -0
- veadk/knowledgebase/backends/base_backend.py +72 -0
- veadk/knowledgebase/backends/in_memory_backend.py +91 -0
- veadk/knowledgebase/backends/opensearch_backend.py +162 -0
- veadk/knowledgebase/backends/redis_backend.py +172 -0
- veadk/knowledgebase/backends/utils.py +92 -0
- veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +608 -0
- veadk/knowledgebase/entry.py +25 -0
- veadk/knowledgebase/knowledgebase.py +307 -0
- veadk/memory/__init__.py +35 -0
- veadk/memory/long_term_memory.py +365 -0
- veadk/memory/long_term_memory_backends/__init__.py +13 -0
- veadk/memory/long_term_memory_backends/base_backend.py +35 -0
- veadk/memory/long_term_memory_backends/in_memory_backend.py +67 -0
- veadk/memory/long_term_memory_backends/mem0_backend.py +155 -0
- veadk/memory/long_term_memory_backends/opensearch_backend.py +124 -0
- veadk/memory/long_term_memory_backends/redis_backend.py +140 -0
- veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +189 -0
- veadk/memory/short_term_memory.py +252 -0
- veadk/memory/short_term_memory_backends/__init__.py +13 -0
- veadk/memory/short_term_memory_backends/base_backend.py +31 -0
- veadk/memory/short_term_memory_backends/mysql_backend.py +49 -0
- veadk/memory/short_term_memory_backends/postgresql_backend.py +49 -0
- veadk/memory/short_term_memory_backends/sqlite_backend.py +55 -0
- veadk/memory/short_term_memory_processor.py +100 -0
- veadk/processors/__init__.py +26 -0
- veadk/processors/base_run_processor.py +120 -0
- veadk/prompts/__init__.py +13 -0
- veadk/prompts/agent_default_prompt.py +30 -0
- veadk/prompts/prompt_evaluator.py +20 -0
- veadk/prompts/prompt_memory_processor.py +55 -0
- veadk/prompts/prompt_optimization.py +150 -0
- veadk/runner.py +732 -0
- veadk/tools/__init__.py +13 -0
- veadk/tools/builtin_tools/__init__.py +13 -0
- veadk/tools/builtin_tools/agent_authorization.py +94 -0
- veadk/tools/builtin_tools/generate_image.py +23 -0
- veadk/tools/builtin_tools/image_edit.py +300 -0
- veadk/tools/builtin_tools/image_generate.py +446 -0
- veadk/tools/builtin_tools/lark.py +67 -0
- veadk/tools/builtin_tools/las.py +24 -0
- veadk/tools/builtin_tools/link_reader.py +66 -0
- veadk/tools/builtin_tools/llm_shield.py +381 -0
- veadk/tools/builtin_tools/load_knowledgebase.py +97 -0
- veadk/tools/builtin_tools/mcp_router.py +29 -0
- veadk/tools/builtin_tools/run_code.py +113 -0
- veadk/tools/builtin_tools/tts.py +253 -0
- veadk/tools/builtin_tools/vesearch.py +49 -0
- veadk/tools/builtin_tools/video_generate.py +363 -0
- veadk/tools/builtin_tools/web_scraper.py +76 -0
- veadk/tools/builtin_tools/web_search.py +83 -0
- veadk/tools/demo_tools.py +58 -0
- veadk/tools/load_knowledgebase_tool.py +149 -0
- veadk/tools/sandbox/__init__.py +13 -0
- veadk/tools/sandbox/browser_sandbox.py +37 -0
- veadk/tools/sandbox/code_sandbox.py +40 -0
- veadk/tools/sandbox/computer_sandbox.py +34 -0
- veadk/tracing/__init__.py +13 -0
- veadk/tracing/base_tracer.py +58 -0
- veadk/tracing/telemetry/__init__.py +13 -0
- veadk/tracing/telemetry/attributes/attributes.py +29 -0
- veadk/tracing/telemetry/attributes/extractors/common_attributes_extractors.py +180 -0
- veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +858 -0
- veadk/tracing/telemetry/attributes/extractors/tool_attributes_extractors.py +152 -0
- veadk/tracing/telemetry/attributes/extractors/types.py +164 -0
- veadk/tracing/telemetry/exporters/__init__.py +13 -0
- veadk/tracing/telemetry/exporters/apmplus_exporter.py +558 -0
- veadk/tracing/telemetry/exporters/base_exporter.py +39 -0
- veadk/tracing/telemetry/exporters/cozeloop_exporter.py +129 -0
- veadk/tracing/telemetry/exporters/inmemory_exporter.py +248 -0
- veadk/tracing/telemetry/exporters/tls_exporter.py +139 -0
- veadk/tracing/telemetry/opentelemetry_tracer.py +320 -0
- veadk/tracing/telemetry/telemetry.py +411 -0
- veadk/types.py +47 -0
- veadk/utils/__init__.py +13 -0
- veadk/utils/audio_manager.py +95 -0
- veadk/utils/auth.py +294 -0
- veadk/utils/logger.py +59 -0
- veadk/utils/mcp_utils.py +44 -0
- veadk/utils/misc.py +184 -0
- veadk/utils/patches.py +101 -0
- veadk/utils/volcengine_sign.py +205 -0
- veadk/version.py +15 -0
- veadk_python-0.2.27.dist-info/METADATA +373 -0
- veadk_python-0.2.27.dist-info/RECORD +218 -0
- veadk_python-0.2.27.dist-info/WHEEL +5 -0
- veadk_python-0.2.27.dist-info/entry_points.txt +2 -0
- veadk_python-0.2.27.dist-info/licenses/LICENSE +201 -0
- veadk_python-0.2.27.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# This file is partly adapted from https://github.com/volcengine/mcp-server/blob/main/server/mcp_server_vefaas_function/src/mcp_server_vefaas_function/vefaas_server.py
|
|
16
|
+
|
|
17
|
+
import base64
|
|
18
|
+
import datetime
|
|
19
|
+
import hashlib
|
|
20
|
+
import hmac
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import zipfile
|
|
26
|
+
from io import BytesIO
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Tuple
|
|
29
|
+
from urllib.parse import quote
|
|
30
|
+
|
|
31
|
+
import requests
|
|
32
|
+
from volcenginesdkcore.rest import ApiException
|
|
33
|
+
|
|
34
|
+
Service = "apig"
|
|
35
|
+
Version = "2021-03-03"
|
|
36
|
+
Region = "cn-beijing"
|
|
37
|
+
Host = "iam.volcengineapi.com"
|
|
38
|
+
ContentType = "application/x-www-form-urlencoded"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def ensure_executable_permissions(folder_path: str):
|
|
42
|
+
for root, _, files in os.walk(folder_path):
|
|
43
|
+
for fname in files:
|
|
44
|
+
full_path = os.path.join(root, fname)
|
|
45
|
+
if fname.endswith(".sh") or fname in ("run.sh",):
|
|
46
|
+
os.chmod(full_path, 0o755)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def python_zip_implementation(folder_path: str) -> bytes:
|
|
50
|
+
"""Pure Python zip implementation with permissions support"""
|
|
51
|
+
buffer = BytesIO()
|
|
52
|
+
|
|
53
|
+
with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
|
|
54
|
+
for root, dirs, files in os.walk(folder_path):
|
|
55
|
+
for file in files:
|
|
56
|
+
file_path = os.path.join(root, file)
|
|
57
|
+
arcname = os.path.relpath(file_path, folder_path)
|
|
58
|
+
|
|
59
|
+
# Skip excluded paths and binary/cache files
|
|
60
|
+
if any(
|
|
61
|
+
excl in arcname for excl in [".git", ".venv", "__pycache__", ".pyc"]
|
|
62
|
+
):
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
st = os.stat(file_path)
|
|
67
|
+
dt = datetime.datetime.fromtimestamp(st.st_mtime)
|
|
68
|
+
date_time = (
|
|
69
|
+
dt.year,
|
|
70
|
+
dt.month,
|
|
71
|
+
dt.day,
|
|
72
|
+
dt.hour,
|
|
73
|
+
dt.minute,
|
|
74
|
+
dt.second,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
info = zipfile.ZipInfo(arcname)
|
|
78
|
+
info.external_attr = 0o755 << 16 # rwxr-xr-x
|
|
79
|
+
info.date_time = date_time
|
|
80
|
+
|
|
81
|
+
with open(file_path, "rb") as f:
|
|
82
|
+
zipf.writestr(info, f.read())
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"Warning: Skipping file {arcname} due to error: {str(e)}")
|
|
85
|
+
|
|
86
|
+
print(f"Your .zip project size: {buffer.tell() / 1024 / 1024:.2f} MB")
|
|
87
|
+
return buffer.getvalue()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def zip_and_encode_folder(folder_path: str) -> Tuple[bytes, int, Exception]:
|
|
91
|
+
"""
|
|
92
|
+
Zips a folder with system zip command (if available) or falls back to Python implementation.
|
|
93
|
+
Returns (zip_data, size_in_bytes, error) tuple.
|
|
94
|
+
"""
|
|
95
|
+
# Check for system zip first
|
|
96
|
+
if not shutil.which("zip"):
|
|
97
|
+
print("System zip command not found, using Python implementation")
|
|
98
|
+
try:
|
|
99
|
+
data = python_zip_implementation(folder_path)
|
|
100
|
+
return data, len(data), None
|
|
101
|
+
except Exception as e:
|
|
102
|
+
return None, 0, e
|
|
103
|
+
|
|
104
|
+
# print(f"Zipping folder: {folder_path}")
|
|
105
|
+
try:
|
|
106
|
+
ensure_executable_permissions(folder_path)
|
|
107
|
+
# Create zip process with explicit arguments
|
|
108
|
+
proc = subprocess.Popen(
|
|
109
|
+
[
|
|
110
|
+
"zip",
|
|
111
|
+
"-r",
|
|
112
|
+
"-q",
|
|
113
|
+
"-",
|
|
114
|
+
".",
|
|
115
|
+
"-x",
|
|
116
|
+
"*.git*",
|
|
117
|
+
"-x",
|
|
118
|
+
"*.venv*",
|
|
119
|
+
"-x",
|
|
120
|
+
"*__pycache__*",
|
|
121
|
+
"-x",
|
|
122
|
+
"*.pyc",
|
|
123
|
+
],
|
|
124
|
+
cwd=folder_path,
|
|
125
|
+
stdout=subprocess.PIPE,
|
|
126
|
+
stderr=subprocess.PIPE,
|
|
127
|
+
bufsize=1024 * 8, # 8KB buffer
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Collect output with proper error handling
|
|
131
|
+
try:
|
|
132
|
+
stdout, stderr = proc.communicate(timeout=30)
|
|
133
|
+
if proc.returncode != 0:
|
|
134
|
+
print(f"Zip error: {stderr.decode()}")
|
|
135
|
+
data = python_zip_implementation(folder_path)
|
|
136
|
+
return data, len(data), None
|
|
137
|
+
|
|
138
|
+
if stdout:
|
|
139
|
+
size = len(stdout)
|
|
140
|
+
# print(f"Zip finished, size: {size / 1024 / 1024:.2f} MB")
|
|
141
|
+
return stdout, size, None
|
|
142
|
+
else:
|
|
143
|
+
print("No data from zip command, falling back to Python implementation")
|
|
144
|
+
data = python_zip_implementation(folder_path)
|
|
145
|
+
return data, len(data), None
|
|
146
|
+
|
|
147
|
+
except subprocess.TimeoutExpired:
|
|
148
|
+
proc.kill()
|
|
149
|
+
proc.wait(timeout=5) # Give it 5 seconds to cleanup
|
|
150
|
+
print("Zip process timed out, falling back to Python implementation")
|
|
151
|
+
try:
|
|
152
|
+
data = python_zip_implementation(folder_path)
|
|
153
|
+
return data, len(data), None
|
|
154
|
+
except Exception as e:
|
|
155
|
+
return None, 0, e
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
print(f"System zip error: {str(e)}")
|
|
159
|
+
try:
|
|
160
|
+
data = python_zip_implementation(folder_path)
|
|
161
|
+
return data, len(data), None
|
|
162
|
+
except Exception as e2:
|
|
163
|
+
return None, 0, e2
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def get_project_path() -> str:
|
|
167
|
+
"""Pack the whole project into a zip file."""
|
|
168
|
+
proj_dir = Path(__file__).parent.parent.parent
|
|
169
|
+
return proj_dir
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def encoding():
|
|
173
|
+
with open("/root/test_data/test_zip.zip", "rb") as zip_file:
|
|
174
|
+
zip_binary = zip_file.read()
|
|
175
|
+
zip_base64 = base64.b64encode(zip_binary).decode("utf-8")
|
|
176
|
+
return zip_base64
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def hmac_sha256(key: bytes, content: str):
|
|
180
|
+
return hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def hash_sha256(content: str):
|
|
184
|
+
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def norm_query(params):
|
|
188
|
+
query = ""
|
|
189
|
+
for key in sorted(params.keys()):
|
|
190
|
+
if isinstance(params[key], list):
|
|
191
|
+
for k in params[key]:
|
|
192
|
+
query = (
|
|
193
|
+
query + quote(key, safe="-_.~") + "=" + quote(k, safe="-_.~") + "&"
|
|
194
|
+
)
|
|
195
|
+
else:
|
|
196
|
+
query = (
|
|
197
|
+
query
|
|
198
|
+
+ quote(key, safe="-_.~")
|
|
199
|
+
+ "="
|
|
200
|
+
+ quote(params[key], safe="-_.~")
|
|
201
|
+
+ "&"
|
|
202
|
+
)
|
|
203
|
+
query = query[:-1]
|
|
204
|
+
return query.replace("+", "%20")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def request(method, date, query, header, ak, sk, token, action, body):
|
|
208
|
+
credential = {
|
|
209
|
+
"access_key_id": ak,
|
|
210
|
+
"secret_access_key": sk,
|
|
211
|
+
"service": Service,
|
|
212
|
+
"region": Region,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if token is not None:
|
|
216
|
+
credential["session_token"] = token
|
|
217
|
+
|
|
218
|
+
if action in [
|
|
219
|
+
"CodeUploadCallback",
|
|
220
|
+
"CreateDependencyInstallTask",
|
|
221
|
+
"GetReleaseStatus",
|
|
222
|
+
"GetDependencyInstallTaskStatus",
|
|
223
|
+
]:
|
|
224
|
+
credential["service"] = "vefaas"
|
|
225
|
+
|
|
226
|
+
content_type = ContentType
|
|
227
|
+
version = Version
|
|
228
|
+
if method == "POST":
|
|
229
|
+
content_type = "application/json"
|
|
230
|
+
|
|
231
|
+
if action == "CreateRoute" or action == "ListRoutes":
|
|
232
|
+
version = "2022-11-12"
|
|
233
|
+
|
|
234
|
+
request_param = {
|
|
235
|
+
"body": body,
|
|
236
|
+
"host": Host,
|
|
237
|
+
"path": "/",
|
|
238
|
+
"method": method,
|
|
239
|
+
"content_type": content_type,
|
|
240
|
+
"date": date,
|
|
241
|
+
"query": {"Action": action, "Version": version, **query},
|
|
242
|
+
}
|
|
243
|
+
if body is None:
|
|
244
|
+
request_param["body"] = ""
|
|
245
|
+
|
|
246
|
+
x_date = request_param["date"].strftime("%Y%m%dT%H%M%SZ")
|
|
247
|
+
short_x_date = x_date[:8]
|
|
248
|
+
x_content_sha256 = hash_sha256(request_param["body"])
|
|
249
|
+
sign_result = {
|
|
250
|
+
"Host": request_param["host"],
|
|
251
|
+
"X-Content-Sha256": x_content_sha256,
|
|
252
|
+
"X-Date": x_date,
|
|
253
|
+
"Content-Type": request_param["content_type"],
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
signed_headers_str = ";".join(
|
|
257
|
+
["content-type", "host", "x-content-sha256", "x-date"]
|
|
258
|
+
)
|
|
259
|
+
# signed_headers_str = signed_headers_str + ";x-security-token"
|
|
260
|
+
canonical_request_str = "\n".join(
|
|
261
|
+
[
|
|
262
|
+
request_param["method"].upper(),
|
|
263
|
+
request_param["path"],
|
|
264
|
+
norm_query(request_param["query"]),
|
|
265
|
+
"\n".join(
|
|
266
|
+
[
|
|
267
|
+
"content-type:" + request_param["content_type"],
|
|
268
|
+
"host:" + request_param["host"],
|
|
269
|
+
"x-content-sha256:" + x_content_sha256,
|
|
270
|
+
"x-date:" + x_date,
|
|
271
|
+
]
|
|
272
|
+
),
|
|
273
|
+
"",
|
|
274
|
+
signed_headers_str,
|
|
275
|
+
x_content_sha256,
|
|
276
|
+
]
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
hashed_canonical_request = hash_sha256(canonical_request_str)
|
|
280
|
+
|
|
281
|
+
credential_scope = "/".join(
|
|
282
|
+
[short_x_date, credential["region"], credential["service"], "request"]
|
|
283
|
+
)
|
|
284
|
+
string_to_sign = "\n".join(
|
|
285
|
+
["HMAC-SHA256", x_date, credential_scope, hashed_canonical_request]
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
k_date = hmac_sha256(credential["secret_access_key"].encode("utf-8"), short_x_date)
|
|
289
|
+
k_region = hmac_sha256(k_date, credential["region"])
|
|
290
|
+
k_service = hmac_sha256(k_region, credential["service"])
|
|
291
|
+
k_signing = hmac_sha256(k_service, "request")
|
|
292
|
+
signature = hmac_sha256(k_signing, string_to_sign).hex()
|
|
293
|
+
|
|
294
|
+
sign_result["Authorization"] = (
|
|
295
|
+
"HMAC-SHA256 Credential={}, SignedHeaders={}, Signature={}".format(
|
|
296
|
+
credential["access_key_id"] + "/" + credential_scope,
|
|
297
|
+
signed_headers_str,
|
|
298
|
+
signature,
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
header = {**header, **sign_result}
|
|
302
|
+
header = {**header, **{"X-Security-Token": token}}
|
|
303
|
+
r = requests.request(
|
|
304
|
+
method=method,
|
|
305
|
+
url="https://{}{}".format(request_param["host"], request_param["path"]),
|
|
306
|
+
headers=header,
|
|
307
|
+
params=request_param["query"],
|
|
308
|
+
data=request_param["body"],
|
|
309
|
+
)
|
|
310
|
+
return r.json()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def signed_request(ak: str, sk: str, target: str, body: dict):
|
|
314
|
+
now = datetime.datetime.utcnow()
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
response_body = request(
|
|
318
|
+
"POST",
|
|
319
|
+
now,
|
|
320
|
+
{},
|
|
321
|
+
{},
|
|
322
|
+
ak,
|
|
323
|
+
sk,
|
|
324
|
+
"",
|
|
325
|
+
target,
|
|
326
|
+
json.dumps(body),
|
|
327
|
+
)
|
|
328
|
+
return response_body
|
|
329
|
+
except Exception as e:
|
|
330
|
+
error_message = f"Error creating upstream: {str(e)}"
|
|
331
|
+
raise ValueError(error_message)
|
|
332
|
+
except ApiException as e:
|
|
333
|
+
print("Exception when calling API: %s\n" % e)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def create_api_gateway_trigger(
|
|
337
|
+
ak, sk, function_id: str, api_gateway_id: str, service_id: str, region: str = None
|
|
338
|
+
):
|
|
339
|
+
token = ""
|
|
340
|
+
|
|
341
|
+
now = datetime.datetime.utcnow()
|
|
342
|
+
|
|
343
|
+
body = {
|
|
344
|
+
"Name": f"{function_id}-trigger",
|
|
345
|
+
"GatewayId": api_gateway_id,
|
|
346
|
+
"SourceType": "VeFaas",
|
|
347
|
+
"UpstreamSpec": {"VeFaas": {"FunctionId": function_id}},
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
response_body = request(
|
|
352
|
+
"POST", now, {}, {}, ak, sk, token, "CreateUpstream", json.dumps(body)
|
|
353
|
+
)
|
|
354
|
+
# Print the full response for debugging
|
|
355
|
+
# print(f"Response: {json.dumps(response_body)}")
|
|
356
|
+
# Check if response contains an error
|
|
357
|
+
if "Error" in response_body or (
|
|
358
|
+
"ResponseMetadata" in response_body
|
|
359
|
+
and "Error" in response_body["ResponseMetadata"]
|
|
360
|
+
):
|
|
361
|
+
error_info = response_body.get("Error") or response_body[
|
|
362
|
+
"ResponseMetadata"
|
|
363
|
+
].get("Error")
|
|
364
|
+
error_message = f"API Error: {error_info.get('Message', 'Unknown error')}"
|
|
365
|
+
raise ValueError(error_message)
|
|
366
|
+
|
|
367
|
+
# Check if Result exists in the response
|
|
368
|
+
if "Result" not in response_body:
|
|
369
|
+
raise ValueError(f"API call did not return a Result field: {response_body}")
|
|
370
|
+
|
|
371
|
+
upstream_id = response_body["Result"]["Id"]
|
|
372
|
+
except Exception as e:
|
|
373
|
+
error_message = f"Error creating upstream: {str(e)}"
|
|
374
|
+
raise ValueError(error_message)
|
|
375
|
+
|
|
376
|
+
body = {
|
|
377
|
+
"Name": "router1",
|
|
378
|
+
"UpstreamList": [{"Type": "VeFaas", "UpstreamId": upstream_id, "Weight": 100}],
|
|
379
|
+
"ServiceId": service_id,
|
|
380
|
+
"MatchRule": {
|
|
381
|
+
"Method": ["POST", "GET", "PUT", "DELETE", "HEAD", "OPTIONS"],
|
|
382
|
+
"Path": {"MatchType": "Prefix", "MatchContent": "/"},
|
|
383
|
+
},
|
|
384
|
+
"AdvancedSetting": {
|
|
385
|
+
"TimeoutSetting": {"Enable": False, "Timeout": 30},
|
|
386
|
+
"CorsPolicySetting": {"Enable": False},
|
|
387
|
+
},
|
|
388
|
+
}
|
|
389
|
+
try:
|
|
390
|
+
response_body = request(
|
|
391
|
+
"POST", now, {}, {}, ak, sk, token, "CreateRoute", json.dumps(body)
|
|
392
|
+
)
|
|
393
|
+
except Exception as e:
|
|
394
|
+
error_message = f"Error creating route: {str(e)}"
|
|
395
|
+
raise ValueError(error_message)
|
|
396
|
+
return response_body
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def list_routes(ak, sk, upstream_id: str):
|
|
400
|
+
now = datetime.datetime.utcnow()
|
|
401
|
+
token = ""
|
|
402
|
+
|
|
403
|
+
body = {"UpstreamId": upstream_id}
|
|
404
|
+
|
|
405
|
+
response_body = request(
|
|
406
|
+
"POST", now, {}, {}, ak, sk, token, "ListRoutes", json.dumps(body)
|
|
407
|
+
)
|
|
408
|
+
return response_body
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"local_dir_name": "veadk_vefaas_web_proj",
|
|
3
|
+
"app_name": "simple-blog",
|
|
4
|
+
"vefaas_application_name": "simple-blog",
|
|
5
|
+
"veapig_instance_name": "",
|
|
6
|
+
"veapig_service_name": "",
|
|
7
|
+
"veapig_upstream_name": "",
|
|
8
|
+
"use_adk_web": false,
|
|
9
|
+
"auth_method": "none",
|
|
10
|
+
"veidentity_user_pool_name": "",
|
|
11
|
+
"veidentity_client_name": "",
|
|
12
|
+
"veadk_version": "",
|
|
13
|
+
"_copy_without_render": [
|
|
14
|
+
"*.html",
|
|
15
|
+
"*.css",
|
|
16
|
+
"*.js",
|
|
17
|
+
"static/**/*",
|
|
18
|
+
"templates/**/*"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from veadk.cloud.cloud_app import CloudApp
|
|
16
|
+
|
|
17
|
+
def main() -> None:
|
|
18
|
+
cloud_app = CloudApp(vefaas_application_name="{{cookiecutter.vefaas_application_name}}")
|
|
19
|
+
cloud_app.delete_self()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
main()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from veadk.cloud.cloud_agent_engine import CloudAgentEngine
|
|
19
|
+
|
|
20
|
+
async def main():
|
|
21
|
+
engine = CloudAgentEngine()
|
|
22
|
+
|
|
23
|
+
cloud_app = engine.deploy(
|
|
24
|
+
path=str(Path(__file__).parent / "src"),
|
|
25
|
+
application_name="{{cookiecutter.vefaas_application_name}}",
|
|
26
|
+
gateway_name="{{cookiecutter.veapig_instance_name}}",
|
|
27
|
+
gateway_service_name="{{cookiecutter.veapig_service_name}}",
|
|
28
|
+
gateway_upstream_name="{{cookiecutter.veapig_upstream_name}}",
|
|
29
|
+
use_adk_web={{cookiecutter.use_adk_web}},
|
|
30
|
+
auth_method="{{cookiecutter.auth_method}}",
|
|
31
|
+
identity_user_pool_name="{{cookiecutter.veidentity_user_pool_name}}",
|
|
32
|
+
identity_client_name="{{cookiecutter.veidentity_client_name}}",
|
|
33
|
+
local_test=False, # Set to True for local testing before deploy to VeFaaS
|
|
34
|
+
)
|
|
35
|
+
print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}")
|
|
36
|
+
|
|
37
|
+
if {{cookiecutter.use_adk_web}}:
|
|
38
|
+
print(f"Web is running at: {cloud_app.vefaas_endpoint}")
|
|
39
|
+
else:
|
|
40
|
+
print(f"Web template does not support use_adk_web=False")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 使用官方Python镜像作为基础镜像
|
|
2
|
+
FROM python:3.9-slim
|
|
3
|
+
|
|
4
|
+
# 设置工作目录
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
# 复制依赖文件并安装依赖
|
|
8
|
+
COPY requirements.txt .
|
|
9
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
10
|
+
|
|
11
|
+
# 复制项目文件
|
|
12
|
+
COPY . .
|
|
13
|
+
|
|
14
|
+
# 设置环境变量
|
|
15
|
+
ENV FLASK_APP=app.py
|
|
16
|
+
ENV FLASK_ENV=production
|
|
17
|
+
ENV PYTHONUNBUFFERED=1
|
|
18
|
+
|
|
19
|
+
# 暴露端口
|
|
20
|
+
EXPOSE 8000
|
|
21
|
+
|
|
22
|
+
# 启动命令
|
|
23
|
+
CMD ["bash", "run.sh"]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from flask import Flask, render_template, request, redirect, url_for, flash, session
|
|
16
|
+
from models import db, Post, User
|
|
17
|
+
from werkzeug.security import generate_password_hash, check_password_hash
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
app = Flask(__name__)
|
|
21
|
+
app.config['SECRET_KEY'] = 'your-secret-key-here'
|
|
22
|
+
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
|
|
23
|
+
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
24
|
+
app.instance_path = os.path.join("/tmp", "flask_instance")
|
|
25
|
+
os.makedirs(app.instance_path, exist_ok=True)
|
|
26
|
+
|
|
27
|
+
db.init_app(app)
|
|
28
|
+
|
|
29
|
+
# 前台首页
|
|
30
|
+
@app.route('/')
|
|
31
|
+
def index():
|
|
32
|
+
page = request.args.get('page', 1, type=int)
|
|
33
|
+
posts = Post.query.order_by(Post.created_at.desc()).paginate(
|
|
34
|
+
page=page, per_page=5, error_out=False)
|
|
35
|
+
return render_template('index.html', posts=posts)
|
|
36
|
+
|
|
37
|
+
# 文章详情页
|
|
38
|
+
@app.route('/post/<int:post_id>')
|
|
39
|
+
def post_detail(post_id):
|
|
40
|
+
post = Post.query.get_or_404(post_id)
|
|
41
|
+
return render_template('post.html', post=post)
|
|
42
|
+
|
|
43
|
+
# 后台登录页
|
|
44
|
+
@app.route('/admin/login', methods=['GET', 'POST'])
|
|
45
|
+
def admin_login():
|
|
46
|
+
if request.method == 'POST':
|
|
47
|
+
username = request.form['username']
|
|
48
|
+
password = request.form['password']
|
|
49
|
+
|
|
50
|
+
user = User.query.filter_by(username=username).first()
|
|
51
|
+
|
|
52
|
+
if user and check_password_hash(user.password, password):
|
|
53
|
+
session['admin_logged_in'] = True
|
|
54
|
+
return redirect(url_for('admin_dashboard'))
|
|
55
|
+
else:
|
|
56
|
+
flash('用户名或密码错误')
|
|
57
|
+
|
|
58
|
+
return render_template('admin/login.html')
|
|
59
|
+
|
|
60
|
+
# 后台登出
|
|
61
|
+
@app.route('/admin/logout')
|
|
62
|
+
def admin_logout():
|
|
63
|
+
session.pop('admin_logged_in', None)
|
|
64
|
+
return redirect(url_for('admin_login'))
|
|
65
|
+
|
|
66
|
+
# 后台管理面板
|
|
67
|
+
@app.route('/admin/dashboard')
|
|
68
|
+
def admin_dashboard():
|
|
69
|
+
if not session.get('admin_logged_in'):
|
|
70
|
+
return redirect(url_for('admin_login'))
|
|
71
|
+
|
|
72
|
+
post_count = Post.query.count()
|
|
73
|
+
return render_template('admin/dashboard.html', post_count=post_count)
|
|
74
|
+
|
|
75
|
+
# 文章管理
|
|
76
|
+
@app.route('/admin/posts')
|
|
77
|
+
def admin_posts():
|
|
78
|
+
if not session.get('admin_logged_in'):
|
|
79
|
+
return redirect(url_for('admin_login'))
|
|
80
|
+
|
|
81
|
+
page = request.args.get('page', 1, type=int)
|
|
82
|
+
posts = Post.query.order_by(Post.created_at.desc()).paginate(
|
|
83
|
+
page=page, per_page=10, error_out=False)
|
|
84
|
+
return render_template('admin/posts.html', posts=posts)
|
|
85
|
+
|
|
86
|
+
# 创建/编辑文章
|
|
87
|
+
@app.route('/admin/post', methods=['GET', 'POST'])
|
|
88
|
+
@app.route('/admin/post/<int:post_id>', methods=['GET', 'POST'])
|
|
89
|
+
def admin_edit_post(post_id=None):
|
|
90
|
+
if not session.get('admin_logged_in'):
|
|
91
|
+
return redirect(url_for('admin_login'))
|
|
92
|
+
|
|
93
|
+
if post_id:
|
|
94
|
+
post = Post.query.get_or_404(post_id)
|
|
95
|
+
else:
|
|
96
|
+
post = Post()
|
|
97
|
+
|
|
98
|
+
if request.method == 'POST':
|
|
99
|
+
post.title = request.form['title']
|
|
100
|
+
post.content = request.form['content']
|
|
101
|
+
|
|
102
|
+
if post_id is None:
|
|
103
|
+
db.session.add(post)
|
|
104
|
+
db.session.commit()
|
|
105
|
+
flash('文章保存成功')
|
|
106
|
+
return redirect(url_for('admin_posts'))
|
|
107
|
+
|
|
108
|
+
return render_template('admin/edit_post.html', post=post)
|
|
109
|
+
|
|
110
|
+
# 删除文章
|
|
111
|
+
@app.route('/admin/post/delete/<int:post_id>', methods=['POST'])
|
|
112
|
+
def admin_delete_post(post_id):
|
|
113
|
+
if not session.get('admin_logged_in'):
|
|
114
|
+
return redirect(url_for('admin_login'))
|
|
115
|
+
|
|
116
|
+
post = Post.query.get_or_404(post_id)
|
|
117
|
+
db.session.delete(post)
|
|
118
|
+
db.session.commit()
|
|
119
|
+
flash('文章删除成功')
|
|
120
|
+
return redirect(url_for('admin_posts'))
|
|
121
|
+
|
|
122
|
+
if __name__ == '__main__':
|
|
123
|
+
app.run(debug=True)
|